diff --git a/.gitignore b/.gitignore index 15c0991..d425b7b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,6 @@ gen/ .gradle/ build/ /*/build/ -gradlew* -gradle/wrapper/gradle-wrapper.jar # Local configuration file (sdk path, etc) local.properties @@ -24,4 +22,10 @@ proguard/ .idea *.iml +#vim +.*swp +.*swo +.tags + + captures/ diff --git a/.travis.yml b/.travis.yml index 0615af9..0fb8260 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,26 @@ language: android java: oraclejdk8 -cache: false + +before_cache: + - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock + - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ + +cache: + directories: + - $HOME/.gradle/caches/ + - $HOME/.gradle/wrapper/ + - $HOME/.android/build-cache + android: - componentes: + components: - tools - platform-tools - - tools - - - build-tools-27.0.3 - - android-27 + - build-tools-29.0.3 + - android-29 - extra-m2-repository - - sys-img-armeabi-v7a-android-27 + - sys-img-armeabi-v7a-android-28 -before_script: - - mkdir "$ANDROID_HOME/licenses" || true - - echo -e "\nd56f5187479451eabf01fb78af6dfcb131a6481e" > "$ANDROID_HOME/licenses/android-sdk-license" - - touch empty.file - - gradle wrapper --gradle-version 4.1 -b empty.file - - rm empty.file script: - ./gradlew assembleDebug + - ./gradlew test diff --git a/README.md b/README.md index 4641d81..8cf6903 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,8 @@ Exodus Privacy is an Android application. Exodus Privacy application let you know what trackers are embedded in apps installed on your smartphone. It let you also know the permissions required by any apps on your smartphone. It helps you to take your privacy back! + + +Get it on F-Droid + +Get it on Google Play diff --git a/app/build.gradle b/app/build.gradle index c9a3073..c17274b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,14 +1,14 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 27 - buildToolsVersion "27.0.3" + compileSdkVersion 29 + buildToolsVersion "29.0.3" defaultConfig { applicationId "org.eu.exodus_privacy.exodusprivacy" - minSdkVersion 16 - targetSdkVersion 27 - versionCode 4 - versionName "1.0.4" + minSdkVersion 17 + targetSdkVersion 29 + versionCode 10 + versionName "2.1.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { @@ -24,19 +24,20 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } - dataBinding { - enabled = true + buildFeatures { + dataBinding = true } } dependencies { - compile fileTree(include: ['*.jar'], dir: 'libs') - androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + implementation fileTree(include: ['*.jar'], dir: 'libs') + androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) - compile 'com.android.support:appcompat-v7:27.1.0' - compile 'com.android.support.constraint:constraint-layout:1.0.2' - compile 'com.android.support:design:27.1.0' - compile 'com.android.support:recyclerview-v7:27.1.0' - testCompile 'junit:junit:4.12' + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + 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' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 905ac9c..de911e1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -11,16 +12,17 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme" + tools:ignore="GoogleAppIndexingWarning" + android:largeHeap="true" > - - \ No newline at end of file + diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/MainActivity.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/MainActivity.java index 71cb5d2..764cf0f 100644 --- a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/MainActivity.java +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/MainActivity.java @@ -18,52 +18,83 @@ package org.eu.exodus_privacy.exodusprivacy; -import android.support.v4.app.FragmentManager; -import android.app.SearchManager; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.content.Context; -import android.databinding.DataBindingUtil; -import android.support.design.widget.Snackbar; -import android.support.v4.app.FragmentTransaction; -import android.support.v7.app.AppCompatActivity; +import android.content.Intent; +import android.net.Uri; import android.os.Bundle; -import android.support.v7.widget.SearchView; +import android.provider.Settings; import android.view.Menu; +import android.view.MenuItem; import android.view.MenuInflater; import android.view.inputmethod.InputMethodManager; +import android.widget.SearchView; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.databinding.DataBindingUtil; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; + +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.AppListFragment; +import org.eu.exodus_privacy.exodusprivacy.fragments.HomeFragment; 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 java.util.ArrayList; +import java.util.List; public class MainActivity extends AppCompatActivity { - AppListFragment appList; - ReportFragment report; - SearchView searchView; - private Menu mMenu; + private List fragments; + private SearchView searchView; + private Menu toolbarMenu; + private MenuItem settingsMenuItem; + private String packageName; + private MainBinding binding; + private ApplicationListAdapter.OnAppClickListener onAppClickListener; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - final MainBinding mainBinding = DataBindingUtil.setContentView(this,R.layout.main); + binding = DataBindingUtil.setContentView(this,R.layout.main); + final MainBinding mainBinding = binding; + getSupportActionBar().setTitle(R.string.app_title); + fragments = new ArrayList<>(); NetworkListener networkListener = new NetworkListener() { @Override public void onSuccess() { runOnUiThread(() -> { - appList.updateComplete(); - if(report != null) - report.updateComplete(); - + for(Updatable updatable : fragments){ + if(updatable instanceof ReportFragment) { + ApplicationViewModel model = ((ReportFragment) updatable).getModel(); + 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.trackers = DatabaseManager.getInstance(MainActivity.this).getTrackers(model.report.trackers); + } + updatable.onUpdateComplete(); + } }); } @Override public void onError(String error) { runOnUiThread(() -> { - appList.updateComplete(); + for(Updatable updatable : fragments){ + updatable.onUpdateComplete(); + } Snackbar bar = Snackbar.make(mainBinding.fragmentContainer,error,Snackbar.LENGTH_LONG); bar.show(); }); @@ -75,45 +106,72 @@ public class MainActivity extends AppCompatActivity { } }; - ApplicationListAdapter.OnAppClickListener onAppClickListener = packageInfo -> { - - report = ReportFragment.newInstance(getPackageManager(),packageInfo); + TrackerListAdapter.OnTrackerClickListener onTrackerClickListener = id -> { + TrackerFragment tracker = TrackerFragment.newInstance(id); + tracker.setOnAppClickListener(onAppClickListener); + 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,report) + .replace(R.id.fragment_container,tracker) .addToBackStack(null) .commit(); - searchView.clearFocus(); - if (mMenu != null) - (mMenu.findItem(R.id.action_filter)).collapseActionView(); - InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); - assert imm != null; - imm.hideSoftInputFromWindow(mainBinding.fragmentContainer.getWindowToken(), 0); - }; - appList = AppListFragment.newInstance(networkListener,onAppClickListener); + onAppClickListener = vm -> { + try { + PackageManager pm = getPackageManager(); + PackageInfo packageInfo = pm.getPackageInfo(vm.packageName, PackageManager.GET_PERMISSIONS); + + 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) + .addToBackStack(null) + .commit(); + + packageName = packageInfo.packageName; + + searchView.clearFocus(); + if (toolbarMenu != null) + (toolbarMenu.findItem(R.id.action_filter)).collapseActionView(); + InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); + assert imm != null; + imm.hideSoftInputFromWindow(mainBinding.fragmentContainer.getWindowToken(), 0); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + }; + + 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,appList) + transaction.replace(R.id.fragment_container,home) .commit(); + home.startRefresh(); } @Override public void onBackPressed() { if (getSupportFragmentManager().getBackStackEntryCount() == 0) finish(); - else + else { getSupportFragmentManager().popBackStack(); + fragments.remove(fragments.size()-1); + } } @Override public boolean onCreateOptionsMenu(Menu menu) { - mMenu = menu; + toolbarMenu = menu; MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.main, menu); searchView = (SearchView) menu.findItem(R.id.action_filter).getActionView(); @@ -121,16 +179,41 @@ public class MainActivity extends AppCompatActivity { searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { - appList.filter(query); + HomeFragment home = (HomeFragment) fragments.get(0); + home.filter(query); return true; } @Override public boolean onQueryTextChange(String newText) { - appList.filter(newText); + HomeFragment home = (HomeFragment) fragments.get(0); + home.filter(newText); return true; } }); + + settingsMenuItem = menu.findItem(R.id.action_settings); + Updatable fragment = fragments.get(fragments.size()-1); + if (fragment instanceof ReportFragment) + settingsMenuItem.setVisible(true); + else + settingsMenuItem.setVisible(false); return true; } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if(item.getItemId() == R.id.action_settings) { + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + 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); + bar.show(); + } + return true; + } + return false; + } } diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/ReportViewModel.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/ReportViewModel.java new file mode 100644 index 0000000..5241ea1 --- /dev/null +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/ReportViewModel.java @@ -0,0 +1,178 @@ +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; + +import org.eu.exodus_privacy.exodusprivacy.objects.Permission; +import org.eu.exodus_privacy.exodusprivacy.objects.ReportDisplay; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; + + +public class ReportViewModel extends BaseObservable { + private ReportDisplay reportDisplay; + + public void setReportDisplay(ReportDisplay report){ + this.reportDisplay = report; + notifyChange(); + } + + @Bindable + public String getName() { + return reportDisplay.displayName; + } + + @Bindable + public Drawable getLogo() { + return reportDisplay.logo; + } + + + public int getPermissionNumber() { + return reportDisplay.permissions != null ? reportDisplay.permissions.size() : 0; + } + + @Bindable + public String getPermissionNumberStr() { + return String.valueOf(getPermissionNumber()); + } + + @Bindable + public int getPermissionColor() { + return getColor(getPermissionNumber()); + } + + @Bindable + public boolean getPermissionVisibility() { + return reportDisplay.permissions != null; + } + + @Bindable + public boolean getHasPermissionDangerous() { + for(Permission perm : reportDisplay.permissions) { + if(perm.dangerous) + return true; + } + return false; + } + + public int getTrackerNumber() { + return reportDisplay.trackers != null ? reportDisplay.trackers.size() : 0; + } + + @Bindable + public String getTrackerNumberStr() { + return String.valueOf(getTrackerNumber()); + } + + + @Bindable + public boolean getTrackerVisibility() { + return reportDisplay.trackers != null; + } + + @Bindable + public int getTrackerColor() { + return getColor(getTrackerNumber()); + } + + public String getCreator(Context context) { + 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)); + creator += " (" + download + ")"; + } + return creator; + } + + @Bindable + public boolean getCreatorVisibility() { + return reportDisplay.creator != null && !reportDisplay.creator.isEmpty(); + } + + @Bindable + public String getInstalledVersion() { + return reportDisplay.versionName != null ? reportDisplay.versionName : String.valueOf(reportDisplay.versionCode); + } + + @Bindable + public String getReportVersion() { + 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) { + return String.valueOf(reportDisplay.report.versionCode); + } + } + return ""; + } + + @Bindable + public boolean getReportVersionVisibility() { + return !getReportVersion().isEmpty(); + } + + @Bindable + public boolean getReportVisibility() { + return reportDisplay.report != null; + } + + public String getReportDate(Context context) { + String reportDate = ""; + 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())+"."; + 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 ""; + } + + public String getCodePermissionInfo(Context context) { + if(reportDisplay.permissions != null && reportDisplay.permissions.size() > 0) + return context.getString(R.string.code_permission_found); + else if(reportDisplay.permissions != null) + return context.getString(R.string.code_permission_not_found); + else + return ""; + } + + + private int getColor(int number) { + if (number == 0) + return R.drawable.square_green; + else if(number < 5) + return R.drawable.square_light_yellow; + else + return R.drawable.square_light_red; + } + + @Bindable + public String getSource() { + return reportDisplay.source; + } + + public String getViewOnStore() { + return reportDisplay.viewOnStore; + } + + + +} diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/Utils.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/Utils.java new file mode 100644 index 0000000..b7da7f2 --- /dev/null +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/Utils.java @@ -0,0 +1,224 @@ +package org.eu.exodus_privacy.exodusprivacy; + +import android.annotation.SuppressLint; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.Signature; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Utils { + + @SuppressLint("PackageManagerGetSignatures") + public static String getCertificateSHA1Fingerprint(PackageManager pm, String packageName) { + int flags = PackageManager.GET_SIGNATURES; + PackageInfo packageInfo = null; + try { + packageInfo = pm.getPackageInfo(packageName, flags); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + assert packageInfo != null; + Signature[] signatures = packageInfo.signatures; + + StringBuilder builder = new StringBuilder(); + builder.append(packageName); + + + for(Signature signature: signatures) { + InputStream input = new ByteArrayInputStream(signature.toByteArray()); + CertificateFactory cf = null; + try { + cf = CertificateFactory.getInstance("X509"); + } catch (CertificateException e) { + e.printStackTrace(); + } + + try { + assert cf != null; + X509Certificate c = (X509Certificate) cf.generateCertificate(input); + try { + MessageDigest md = MessageDigest.getInstance("SHA1"); + byte[] publicKey = md.digest(c.getEncoded()); + builder.append(' '); + builder.append(byte2HexFormatted(publicKey).toUpperCase()); + } catch (NoSuchAlgorithmException e1) { + e1.printStackTrace(); + } + } catch (CertificateException e) { + e.printStackTrace(); + } + } + + String hexString = null; + try { + MessageDigest md = MessageDigest.getInstance("SHA1"); + byte[] publicKey = md.digest(builder.toString().getBytes()); + hexString = byte2HexFormatted(publicKey); + } catch (NoSuchAlgorithmException e1) { + e1.printStackTrace(); + } + assert hexString != null; + return hexString.toUpperCase(); + } + + private static String byte2HexFormatted(byte[] arr) { + StringBuilder str = new StringBuilder(arr.length * 2); + for (byte anArr : arr) { + String h = Integer.toHexString(anArr); + int l = h.length(); + if (l == 1) h = "0" + h; + if (l > 2) h = h.substring(l - 2, l); + str.append(h.toUpperCase()); + } + return str.toString(); + } + + /* + Simple and not complete markdownToHtml converter + */ + public static String markdownToHtml(String markdown) { + StringBuilder builder = new StringBuilder(); + String[] lines = markdown.split("\r\n"); + ArrayList listStarter = new ArrayList<>(); + ArrayList formatStarter = new ArrayList<>(); + ArrayList closeTags = new ArrayList<>(); + for(String line : lines) { + if (line.matches("^#{1,5} .*")) { + int nb = line.indexOf(" "); + String hx = ""; + String endhx = ""; + builder.append(hx); + closeTags.add(endhx); + 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); + } else { + Pattern pattern = Pattern.compile("^( *[+\\-*] )"); + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + starter = matcher.group(1); + listStarter.add(starter); + builder.append("
    \n"); + + } + } + builder.append("
  • "); + int beginIndex = line.indexOf(starter)+starter.length(); + line = line.substring(beginIndex); + closeTags.add("
  • "); + } else { + while(!listStarter.isEmpty()) { + listStarter.remove(listStarter.size() - 1); + builder.append("
\n"); + } + builder.append("

"); + closeTags.add("

"); + } + while(!line.isEmpty()){ + Pattern pattern = Pattern.compile("^\\[(.+?)(?=\\]\\()\\]\\((http.+?)(?=\\))\\)"); + //Pattern pattern = Pattern.compile("^\\[(.*)\\]\\((http.*)\\)"); + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + builder.append(""); + builder.append(matcher.group(1)); + builder.append(""); + line = line.substring(line.indexOf(")")+1); + continue; + } + pattern = Pattern.compile("^(http.*)"); + matcher = pattern.matcher(line); + if (matcher.find()) { + builder.append(""); + builder.append(matcher.group(1)); + builder.append(""); + line = line.substring(matcher.group(1).length()); + continue; + } + pattern = Pattern.compile("^[*_]{2}(.+)[*_]{2}"); + matcher = pattern.matcher(line); + if (matcher.find()) { + if(line.startsWith("*")) { + line = line.replaceFirst("\\*\\*", ""); + formatStarter.add("**"); + } + else { + line = line.replaceFirst("__", ""); + formatStarter.add("__"); + } + continue; + } + pattern = Pattern.compile("^[*_]{1}(.+)"); + matcher = pattern.matcher(line); + if (matcher.find()) { + if(line.startsWith("*")) { + line = line.replaceFirst("\\*", ""); + formatStarter.add("*"); + } + else { + line = line.replaceFirst("_", ""); + formatStarter.add("_"); + } + continue; + } + if(formatStarter.size() > 0) { + String checkFormat; + if(line.contains(" ")) + checkFormat = line.substring(0,line.indexOf(" ")); + else + checkFormat = line; + String lastFormat = formatStarter.get(formatStarter.size()-1); + if (checkFormat.contains(lastFormat)) { + if(lastFormat.length()==2) { + if (lastFormat.contains("*")) + line = line.replaceFirst("\\*\\*", ""); + else + line = line.replaceFirst("__", ""); + } else { + if (lastFormat.contains("*")) + line = line.replaceFirst("\\*", ""); + else + line = line.replaceFirst("_", ""); + } + formatStarter.remove(formatStarter.size()-1); + continue; + } + } + + if(line.contains(" ")) { + builder.append(line.substring(0, line.indexOf(" ") + 1)); + line = line.substring(line.indexOf(" ") + 1); + } else { + builder.append(line); + line = ""; + } + } + //close all unclosed tags starting at the end + while(!closeTags.isEmpty()) { + builder.append(closeTags.remove(closeTags.size()-1)); + } + builder.append("\n"); + + } + while(!listStarter.isEmpty()) { + listStarter.remove(listStarter.size() - 1); + builder.append("\n"); + } + return builder.toString(); + } +} diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/ApplicationListAdapter.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/ApplicationListAdapter.java index 6655aef..5148655 100644 --- a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/ApplicationListAdapter.java +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/ApplicationListAdapter.java @@ -19,19 +19,20 @@ package org.eu.exodus_privacy.exodusprivacy.adapters; import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.databinding.DataBindingUtil; -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.databinding.DataBindingUtil; +import androidx.recyclerview.widget.RecyclerView; + import org.eu.exodus_privacy.exodusprivacy.R; import org.eu.exodus_privacy.exodusprivacy.databinding.AppItemBinding; -import org.eu.exodus_privacy.exodusprivacy.manager.DatabaseManager; +import org.eu.exodus_privacy.exodusprivacy.fragments.AppListFragment; 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; @@ -41,49 +42,36 @@ import java.util.regex.Pattern; public class ApplicationListAdapter extends RecyclerView.Adapter { - private List packages; - private PackageManager packageManager; + private List applicationViewModels; private OnAppClickListener onAppClickListener; - private static final String gStore = "com.android.vending"; - private String filter = ""; + private Object filter = ""; + private AppListFragment.Type filterType = AppListFragment.Type.NAME; private final int HIDDEN_APP = 0; private final int DISPLAYED_APP = 1; + private int displayedApp = 0; - - private Comparator alphaPackageComparator = new Comparator() { + private Comparator alphaPackageComparator = new Comparator() { @Override - public int compare(PackageInfo pack1, PackageInfo pack2) { - String pkg1 = packageManager.getApplicationLabel(pack1.applicationInfo).toString(); - String pkg2 = packageManager.getApplicationLabel(pack2.applicationInfo).toString(); - return pkg1.compareToIgnoreCase(pkg2); + 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(PackageManager manager, OnAppClickListener listener) { + public ApplicationListAdapter(Context context, OnAppClickListener listener) { + applicationViewModels = new ArrayList<>(); onAppClickListener = listener; - setPackageManager(manager); - } - - private void setInstalledPackages(List installedPackages) { - packages = installedPackages; - applyStoreFilter(); - Collections.sort(packages, alphaPackageComparator); - notifyDataSetChanged(); - } - - private void applyStoreFilter() { - List toRemove = new ArrayList<>(); - for (PackageInfo pkg : packages) { - if (!gStore.equals(packageManager.getInstallerPackageName(pkg.packageName))) { - toRemove.add(pkg); - } - } - packages.removeAll(toRemove); } @Override public int getItemViewType(int position){ - return (Pattern.compile(Pattern.quote(filter.trim()), Pattern.CASE_INSENSITIVE).matcher(packageManager.getApplicationLabel(packages.get(position).applicationInfo)).find())?DISPLAYED_APP:HIDDEN_APP; + return applicationViewModels.get(position).isVisible ? DISPLAYED_APP : HIDDEN_APP; } @NonNull @@ -100,12 +88,13 @@ public class ApplicationListAdapter extends RecyclerView.Adapter { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { if( viewHolder.getItemViewType() == DISPLAYED_APP) { final ApplicationListViewHolder holder = (ApplicationListViewHolder) viewHolder; - holder.setData(packages.get(position)); + ApplicationViewModel vm = applicationViewModels.get(position); + holder.setViewModel(vm); //noinspection Convert2Lambda holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - onAppClickListener.onAppClick(holder.packageInfo); + onAppClickListener.onAppClick(vm); } }); }else { @@ -115,30 +104,30 @@ public class ApplicationListAdapter extends RecyclerView.Adapter { } } - @Override public int getItemCount() { - return packages.size(); + return applicationViewModels.size(); } - public void setPackageManager(PackageManager manager) { - packageManager = manager; - if(packageManager != null) { - List installedPackages = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS); - setInstalledPackages(installedPackages); - } + public void displayAppList(List applications) { + applicationViewModels = applications; + Collections.sort(applicationViewModels, alphaPackageComparator); + filter(filterType,filter); } - class ApplicationEmptyViewHolder extends RecyclerView.ViewHolder{ + public int getDisplayedApps() { + return displayedApp; + } + + static class ApplicationEmptyViewHolder extends RecyclerView.ViewHolder{ ApplicationEmptyViewHolder(View itemView) { super(itemView); } } + static class ApplicationListViewHolder extends RecyclerView.ViewHolder { - class ApplicationListViewHolder extends RecyclerView.ViewHolder { - - PackageInfo packageInfo; + ApplicationViewModel viewModel; AppItemBinding appItemBinding; ApplicationListViewHolder(AppItemBinding binding) { @@ -146,8 +135,8 @@ public class ApplicationListAdapter extends RecyclerView.Adapter { appItemBinding = binding; } - public void setData(PackageInfo data) { - packageInfo = data; + void setViewModel(ApplicationViewModel vm) { + viewModel = vm; Context context = appItemBinding.getRoot().getContext(); @@ -155,49 +144,90 @@ public class ApplicationListAdapter extends RecyclerView.Adapter { appItemBinding.otherVersion.setVisibility(View.GONE); appItemBinding.analysed.setVisibility(View.GONE); appItemBinding.appTrackerNb.setVisibility(View.VISIBLE); + appItemBinding.appTracker.setVisibility(View.VISIBLE); + String versionName = viewModel.versionName; + long versionCode = viewModel.versionCode; - String packageName = packageInfo.packageName; - String versionName = packageInfo.versionName; + appItemBinding.appLogo.setImageDrawable(viewModel.icon); - //get logo - try { - appItemBinding.appLogo.setImageDrawable(packageManager.getApplicationIcon(packageName)); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } - //get name - appItemBinding.appName.setText(packageManager.getApplicationLabel(packageInfo.applicationInfo)); + appItemBinding.appName.setText(viewModel.label); + appItemBinding.source.setText(context.getString(R.string.source,viewModel.source)); - //get permissions - if(packageInfo.requestedPermissions != null) { - appItemBinding.appPermissionNb.setText(context.getString(R.string.permissions) + " " + String.valueOf(data.requestedPermissions.length)); - } else { - appItemBinding.appPermissionNb.setText(context.getString(R.string.permissions) + " " + String.valueOf(0)); - } - //get reports - Report report = DatabaseManager.getInstance(context).getReportFor(packageName, versionName); + long size = viewModel.requestedPermissions != null ? viewModel.requestedPermissions.length : 0; + appItemBinding.appPermissionNb.setText(String.valueOf(size)); + if(size == 0) + appItemBinding.appPermissionNb.setBackgroundResource(R.drawable.square_green); + else if (size < 5) + appItemBinding.appPermissionNb.setBackgroundResource(R.drawable.square_light_yellow); + else + appItemBinding.appPermissionNb.setBackgroundResource(R.drawable.square_light_red); + + Report report = viewModel.report; if(report != null) { - Set trackers = DatabaseManager.getInstance(context).getTrackers(report.trackers); - appItemBinding.appTrackerNb.setText(context.getString(R.string.trackers) + " " + trackers.size()); - if(!report.version.equals(data.versionName)) { + Set trackers = viewModel.trackers; + + size = trackers.size(); + appItemBinding.appTrackerNb.setText(String.valueOf(size)); + 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); + 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)); + appItemBinding.otherVersion.setText(string); appItemBinding.otherVersion.setVisibility(View.VISIBLE); } - } else { appItemBinding.appTrackerNb.setVisibility(View.GONE); + appItemBinding.appTracker.setVisibility(View.GONE); appItemBinding.analysed.setVisibility(View.VISIBLE); } } } public interface OnAppClickListener { - void onAppClick(PackageInfo packageInfo); + 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; - public void filter(String text) { - filter = text; + 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(); } } diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/ApplicationViewModel.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/ApplicationViewModel.java new file mode 100644 index 0000000..dbe8c2f --- /dev/null +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/ApplicationViewModel.java @@ -0,0 +1,29 @@ +package org.eu.exodus_privacy.exodusprivacy.adapters; + +import android.graphics.drawable.Drawable; + +import androidx.annotation.Nullable; + +import org.eu.exodus_privacy.exodusprivacy.objects.Report; +import org.eu.exodus_privacy.exodusprivacy.objects.Tracker; + +import java.util.Set; + +/** + * This class holds the data needed to display an application cell in the RecyclerView + */ +public class ApplicationViewModel { + public String packageName; + public String versionName; + public int versionCode; + public String[] requestedPermissions; + public @Nullable + Report report; + public Set trackers; + public @Nullable + Drawable icon; + public CharSequence label; + public String installerPackageName; + public boolean isVisible; + public String source; +} diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/PermissionListAdapter.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/PermissionListAdapter.java index 6798182..fb0f884 100644 --- a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/PermissionListAdapter.java +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/PermissionListAdapter.java @@ -1,12 +1,13 @@ package org.eu.exodus_privacy.exodusprivacy.adapters; -import android.databinding.DataBindingUtil; -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.databinding.DataBindingUtil; +import androidx.recyclerview.widget.RecyclerView; + import org.eu.exodus_privacy.exodusprivacy.R; import org.eu.exodus_privacy.exodusprivacy.databinding.PermissionItemBinding; import org.eu.exodus_privacy.exodusprivacy.objects.Permission; @@ -39,7 +40,7 @@ public class PermissionListAdapter extends RecyclerView.Adapter { if( permission.description != null && permission.description.trim().length() > 0) { @@ -74,6 +88,9 @@ public class PermissionListAdapter extends RecyclerView.Adapter 0 ) permissionItemBinding.arrow.setText("▶"); else - permissionItemBinding.arrow.setText("■"); + permissionItemBinding.arrow.setText(""); permissionItemBinding.permissionDescription.setVisibility(View.GONE); } diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/TrackerListAdapter.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/TrackerListAdapter.java index 0cf7c13..103ea08 100644 --- a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/TrackerListAdapter.java +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/TrackerListAdapter.java @@ -1,26 +1,35 @@ package org.eu.exodus_privacy.exodusprivacy.adapters; -import android.databinding.DataBindingUtil; -import android.databinding.ViewDataBinding; -import android.support.annotation.NonNull; -import android.support.v7.widget.RecyclerView; +import android.content.Intent; +import android.net.Uri; import android.view.LayoutInflater; import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.databinding.DataBindingUtil; +import androidx.databinding.ViewDataBinding; +import androidx.recyclerview.widget.RecyclerView; + import org.eu.exodus_privacy.exodusprivacy.R; import org.eu.exodus_privacy.exodusprivacy.databinding.TrackerItemBinding; 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; -public class TrackerListAdapter extends android.support.v7.widget.RecyclerView.Adapter{ +public class TrackerListAdapter extends RecyclerView.Adapter{ - private Set trackersList; + private List trackersList; + private OnTrackerClickListener trackerClickListener; private int layout; - public TrackerListAdapter(Set trackerList, int resource) { + public TrackerListAdapter(Set trackerList, int resource, OnTrackerClickListener listener) { setTrackers(trackerList); layout = resource; + trackerClickListener = listener; } @NonNull @@ -35,7 +44,7 @@ public class TrackerListAdapter extends android.support.v7.widget.RecyclerView.A if(trackersList == null || trackersList.size() == 0) holder.setupData(null); else - holder.setupData((Tracker) trackersList.toArray()[position]); + holder.setupData(trackersList.get(position)); } @Override @@ -46,8 +55,13 @@ public class TrackerListAdapter extends android.support.v7.widget.RecyclerView.A return trackersList.size(); } + private Comparator alphaTrackerComparator = (track1, track2) -> track1.name.compareToIgnoreCase(track2.name); + public void setTrackers(Set trackers) { - trackersList = trackers; + if(trackers != null) { + trackersList = new ArrayList<>(trackers); + Collections.sort(trackersList, alphaTrackerComparator); + } } class TrackerListViewHolder extends RecyclerView.ViewHolder { @@ -62,13 +76,21 @@ public class TrackerListAdapter extends android.support.v7.widget.RecyclerView.A void setupData(Tracker tracker) { if(viewDataBinding instanceof TrackerItemBinding) { TrackerItemBinding binding = (TrackerItemBinding) viewDataBinding; - if(tracker != null) - binding.trackerName.setText(tracker.name); + if(tracker != null) { + binding.trackerName.setText(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); + } } diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/AppListFragment.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/AppListFragment.java index 7780ab4..0b9acde 100644 --- a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/AppListFragment.java +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/AppListFragment.java @@ -1,178 +1,104 @@ -/* - * Copyright (C) 2018 Anthony Chomienne, anthony@mob-dev.fr - * - * 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. - */ - package org.eu.exodus_privacy.exodusprivacy.fragments; -import android.app.Activity; import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.databinding.DataBindingUtil; import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v7.widget.LinearLayoutManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.databinding.DataBindingUtil; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; + import org.eu.exodus_privacy.exodusprivacy.R; import org.eu.exodus_privacy.exodusprivacy.adapters.ApplicationListAdapter; +import org.eu.exodus_privacy.exodusprivacy.adapters.ApplicationViewModel; import org.eu.exodus_privacy.exodusprivacy.databinding.ApplistBinding; -import org.eu.exodus_privacy.exodusprivacy.listener.NetworkListener; -import org.eu.exodus_privacy.exodusprivacy.manager.NetworkManager; import java.util.ArrayList; import java.util.List; public class AppListFragment extends Fragment { - - private PackageManager packageManager; - private NetworkListener networkListener; - private ApplicationListAdapter.OnAppClickListener onAppClickListener; - private boolean startupRefresh; private ApplistBinding applistBinding; + private List applications; private ApplicationListAdapter adapter; - - public static AppListFragment newInstance(NetworkListener networkListener, ApplicationListAdapter.OnAppClickListener appClickListener) { - AppListFragment fragment = new AppListFragment(); - fragment.setNetworkListener(networkListener); - fragment.setOnAppClickListener(appClickListener); - fragment.startupRefresh = true; - return fragment; - } + private ApplicationListAdapter.OnAppClickListener onAppClickListener; + private Type filterType = Type.NAME; + private Object filterObject = ""; + private boolean scrollbarEnabled = true; @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - applistBinding = DataBindingUtil.inflate(inflater,R.layout.applist,container,false); + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + //create binding + 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)); + applistBinding.appList.setVerticalScrollBarEnabled(scrollbarEnabled); + adapter = new ApplicationListAdapter(context, onAppClickListener); + adapter.displayAppList(applications); + adapter.filter(filterType,filterObject); + applistBinding.appList.setAdapter(adapter); return applistBinding.getRoot(); } - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - if(applistBinding == null) - return; - Context context = applistBinding.getRoot().getContext(); - applistBinding.swipeRefresh.setOnRefreshListener(() -> startRefresh()); - if (packageManager == null) - packageManager = context.getPackageManager(); - - applistBinding.appList.setLayoutManager(new LinearLayoutManager(context)); - if (packageManager != null) { - if(startupRefresh) { - startRefresh(); - startupRefresh = false; - } - applistBinding.noPackageManager.setVisibility(View.GONE); - applistBinding.noAppFound.setVisibility(View.GONE); - adapter = new ApplicationListAdapter(packageManager, onAppClickListener); - if(adapter.getItemCount() == 0) { - applistBinding.noAppFound.setVisibility(View.VISIBLE); - } else { - applistBinding.appList.setAdapter(adapter); - } - } else { - applistBinding.noPackageManager.setVisibility(View.VISIBLE); - } + public void setOnAppClickListener(ApplicationListAdapter.OnAppClickListener listener){ + onAppClickListener = listener; } - public void startRefresh(){ - if(applistBinding == null) - return; - applistBinding.layoutProgress.setVisibility(View.VISIBLE); - applistBinding.swipeRefresh.setRefreshing(true); - List packageInstalled = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS); - ArrayList packageList = new ArrayList<>(); - for(PackageInfo pkgInfo : packageInstalled) - packageList.add(pkgInfo.packageName); - - NetworkManager.getInstance().getReports(applistBinding.getRoot().getContext(),networkListener,packageList); + public void setApplications(List applicationList){ + applications = applicationList; + if(adapter != null) + adapter.displayAppList(applications); } - public void updateComplete() { - if(applistBinding != null) { - applistBinding.layoutProgress.setVisibility(View.GONE); - applistBinding.swipeRefresh.setRefreshing(false); - if(packageManager != null && applistBinding.appList.getAdapter() != null) { - ((ApplicationListAdapter) applistBinding.appList.getAdapter()).setPackageManager(packageManager); - } - } + public void setFilter(Type type, Object filter){ + filterType = type; + filterObject = filter; + if(adapter != null) + adapter.filter(type,filterObject); } - public void setNetworkListener(NetworkListener listener) { - this.networkListener = new NetworkListener() { - @Override - public void onSuccess() { - listener.onSuccess(); - } - - @Override - public void onError(String error) { - listener.onError(error); - } - - public void onProgress(int resourceId, int progress, int maxProgress) { - updateProgress(resourceId, progress, maxProgress); - } - }; + public void disableScrollBar() { + scrollbarEnabled = false; + if (applistBinding != null) + applistBinding.appList.setVerticalScrollBarEnabled(false); } - private void updateProgress(int resourceId, int progress, int maxProgress) { - Activity activity = getActivity(); - if(activity == null) - return; - activity.runOnUiThread(() -> { - if (applistBinding == null) - return; - if(maxProgress > 0) - applistBinding.statusProgress.setText(getString(resourceId)+" "+progress+"/"+maxProgress); - else - applistBinding.statusProgress.setText(getString(resourceId)); - applistBinding.progress.setMax(maxProgress); - applistBinding.progress.setProgress(progress); - }); - + public void enableScrollBar() { + scrollbarEnabled = true; + if (applistBinding != null) + applistBinding.appList.setVerticalScrollBarEnabled(true); } - public void setOnAppClickListener(ApplicationListAdapter.OnAppClickListener onAppClickListener) { - this.onAppClickListener = onAppClickListener; + public int getTotalApps() { + if (adapter == null) + return 0; + return adapter.getItemCount(); } - @Override - public void onAttach(Context context) { - super.onAttach(context); - packageManager = context.getPackageManager(); + public int getDisplayedApps() { + if (adapter == null) + return 0; + return adapter.getDisplayedApps(); } - @Override - public void onDetach() { - super.onDetach(); - packageManager = null; + public enum Type { + NAME, + TRACKER } - public void filter(String filter){ - adapter.filter(filter); + public enum Order { + NAME_ASC, + NAME_DSC, + TRACKER_ASC, + TRACKER_DSC, + PERMISSIONS_ASC, + PERMISSIONS_DSC } } diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/ComputeAppListTask.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/ComputeAppListTask.java new file mode 100644 index 0000000..f966419 --- /dev/null +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/ComputeAppListTask.java @@ -0,0 +1,138 @@ +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> { + + interface Listener { + void onAppsComputed(List apps); + } + + private static final String gStore = "com.android.vending"; + private static final String fdroid = "ord.fdroid.fdroid"; + + private WeakReference packageManagerRef; + private WeakReference databaseManagerRef; + private WeakReference listenerRef; + + ComputeAppListTask(WeakReference packageManagerRef, + WeakReference databaseManagerRef, + WeakReference listenerRef) { + this.packageManagerRef = packageManagerRef; + this.databaseManagerRef = databaseManagerRef; + this.listenerRef = listenerRef; + } + + protected List doInBackground(Void... params) { + PackageManager packageManager = packageManagerRef.get(); + DatabaseManager databaseManager = databaseManagerRef.get(); + + List vms = new ArrayList<>(); + if(packageManager != null && databaseManager != null) { + List installedPackages = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS); + vms = applyStoreFilter(installedPackages, databaseManager, packageManager); + convertPackagesToViewModels(vms, databaseManager, packageManager); + } + return vms; + } + + @Override + protected void onPostExecute(List vms) { + Listener listener = listenerRef.get(); + + if(listener != null) { + listener.onAppsComputed(vms); + } + } + + private void convertPackagesToViewModels(List 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 applyStoreFilter(List packageInfos, + DatabaseManager databaseManager, + PackageManager packageManager) { + List 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 sources = databaseManager.getSources(packageName); + for(Map.Entry 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; + } + +} diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/HomeFragment.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/HomeFragment.java new file mode 100644 index 0000000..1ccec15 --- /dev/null +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/HomeFragment.java @@ -0,0 +1,183 @@ +package org.eu.exodus_privacy.exodusprivacy.fragments; + +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.view.LayoutInflater; +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; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; + +import org.eu.exodus_privacy.exodusprivacy.R; +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 java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +public class HomeFragment extends Fragment implements ComputeAppListTask.Listener, Updatable { + + private @Nullable + PackageManager packageManager; + private NetworkListener networkListener; + private ApplicationListAdapter.OnAppClickListener onAppClickListener; + private boolean startupRefresh = true; + private HomeBinding homeBinding; + private List applications; + private AppListFragment appListFragment; + private boolean startRefreshAsked; + private boolean refreshInProgress; + + private int lastResource=0; + private int lastProgress=0; + private int lastMaxProgress=0; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + if(applications == null) + applications = new ArrayList<>(); + 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.commit(); + Context context = homeBinding.getRoot().getContext(); + packageManager = context.getPackageManager(); + homeBinding.swipeRefresh.setOnRefreshListener(this::startRefresh); + if(packageManager != null) { + homeBinding.noPackageManager.setVisibility(View.GONE); + onAppsComputed(applications); + if(applications.isEmpty()) + displayAppListAsync(); + if(startRefreshAsked) + startRefresh(); + else if (refreshInProgress) { + homeBinding.layoutProgress.setVisibility(View.VISIBLE); + homeBinding.swipeRefresh.setRefreshing(true); + updateProgress(lastResource,lastProgress,lastMaxProgress); + } + } else { + homeBinding.noPackageManager.setVisibility(View.VISIBLE); + } + return homeBinding.getRoot(); + } + + public void startRefresh(){ + if(packageManager != null) { + refreshInProgress = true; + homeBinding.layoutProgress.setVisibility(View.VISIBLE); + homeBinding.swipeRefresh.setRefreshing(true); + List packageInstalled = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS); + ArrayList packageList = new ArrayList<>(); + for (PackageInfo pkgInfo : packageInstalled) + packageList.add(pkgInfo.packageName); + + NetworkManager.getInstance().getReports(homeBinding.getRoot().getContext(), networkListener, packageList); + startRefreshAsked = false; + } else { + startRefreshAsked = true; + } + } + + @Override + public void onUpdateComplete() { + refreshInProgress = false; + homeBinding.layoutProgress.setVisibility(View.GONE); + homeBinding.swipeRefresh.setRefreshing(false); + displayAppListAsync(); + } + + public void setNetworkListener(NetworkListener listener) { + this.networkListener = new NetworkListener() { + @Override + public void onSuccess() { + listener.onSuccess(); + } + + @Override + public void onError(String error) { + listener.onError(error); + } + + public void onProgress(int resourceId, int progress, int maxProgress) { + updateProgress(resourceId, progress, maxProgress); + } + }; + } + + private void updateProgress(int resourceId, int progress, int maxProgress) { + lastResource = resourceId; + lastProgress = progress; + lastMaxProgress = maxProgress; + if(lastResource == 0) + return; + Activity activity = getActivity(); + if(activity == null) + return; + activity.runOnUiThread(() -> { + if (homeBinding == null) + return; + if(maxProgress > 0) + homeBinding.statusProgress.setText(activity.getString(resourceId)+" "+progress+"/"+maxProgress);//fixme + else + homeBinding.statusProgress.setText(activity.getString(resourceId)); + homeBinding.progress.setMax(maxProgress); + homeBinding.progress.setProgress(progress); + }); + + } + + public void setOnAppClickListener(ApplicationListAdapter.OnAppClickListener onAppClickListener) { + this.onAppClickListener = onAppClickListener; + if(appListFragment != null) + appListFragment.setOnAppClickListener(onAppClickListener); + } + + public void filter(String filter){ + appListFragment.setFilter(AppListFragment.Type.NAME,filter); + } + + private void displayAppListAsync() { + 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(); + } + + @Override + public void onAppsComputed(List apps) { + this.applications = apps; + homeBinding.retrieveApp.setVisibility(View.GONE); + homeBinding.logo.setVisibility(View.GONE); + homeBinding.noAppFound.setVisibility(apps.isEmpty() ? View.VISIBLE : View.GONE); + appListFragment.setApplications(apps); + if(!apps.isEmpty()) { + if(startupRefresh) { + startRefresh(); + startupRefresh = false; + } + } + } +} diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/ReportFragment.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/ReportFragment.java index 7fa9671..501fb1b 100644 --- a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/ReportFragment.java +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/ReportFragment.java @@ -20,171 +20,154 @@ package org.eu.exodus_privacy.exodusprivacy.fragments; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.PermissionInfo; -import android.databinding.DataBindingUtil; +import android.net.Uri; import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v7.widget.LinearLayoutManager; +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.databinding.DataBindingUtil; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; + import org.eu.exodus_privacy.exodusprivacy.R; +import org.eu.exodus_privacy.exodusprivacy.ReportViewModel; +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.manager.DatabaseManager; -import org.eu.exodus_privacy.exodusprivacy.objects.Permission; -import org.eu.exodus_privacy.exodusprivacy.objects.Report; -import org.eu.exodus_privacy.exodusprivacy.objects.Tracker; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -public class ReportFragment extends Fragment { +import org.eu.exodus_privacy.exodusprivacy.objects.ReportDisplay; +public class ReportFragment extends Fragment implements Updatable { private PackageManager packageManager; - private PackageInfo packageInfo; + private PackageInfo packageInfo = null; private ReportBinding reportBinding; + private TrackerListAdapter.OnTrackerClickListener trackerClickListener; + private ApplicationViewModel model; - public static ReportFragment newInstance(PackageManager packageManager, PackageInfo packageInfo) { + public static ReportFragment newInstance(PackageManager packageManager,ApplicationViewModel model, PackageInfo packageInfo, TrackerListAdapter.OnTrackerClickListener trackerClickListener) { ReportFragment fragment = new ReportFragment(); fragment.setPackageManager(packageManager); fragment.setPackageInfo(packageInfo); + fragment.setApplicationViewModel(model); + fragment.setOnTrackerClickListener(trackerClickListener); return fragment; } + private void setApplicationViewModel(ApplicationViewModel model) { + this.model = model; + } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if(savedInstanceState != null && packageInfo == null) { + packageInfo = savedInstanceState.getParcelable("PackageInfo"); + } setHasOptionsMenu(true); } + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putParcelable("PackageInfo", packageInfo); + super.onSaveInstanceState(outState); + } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { reportBinding = DataBindingUtil.inflate(inflater,R.layout.report,container,false); - updateComplete(); + onUpdateComplete(); return reportBinding.getRoot(); } - public void updateComplete() { + @Override + public void onUpdateComplete() { + if(model != null) + onUpdateComplete(model); + } + + public void onUpdateComplete(ApplicationViewModel model) { Context context = reportBinding.getRoot().getContext(); - String packageName = packageInfo.packageName; - String versionName = packageInfo.versionName; - //setup logo - try { - reportBinding.logo.setImageDrawable(packageManager.getApplicationIcon(packageName)); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } + ReportDisplay reportDisplay = ReportDisplay.buildReportDisplay(context,model,packageManager,packageInfo); + ReportViewModel viewModel = new ReportViewModel(); + viewModel.setReportDisplay(reportDisplay); + reportBinding.setReportInfo(viewModel); - //setup name - reportBinding.name.setText(packageManager.getApplicationLabel(packageInfo.applicationInfo)); - - //setup permissions number - String permissions_text; - if (packageInfo.requestedPermissions != null && packageInfo.requestedPermissions.length > 0) - permissions_text = context.getString(R.string.permissions) + " " + String.valueOf(packageInfo.requestedPermissions.length); - else - permissions_text = context.getString(R.string.permissions); - - reportBinding.permissionsTitle.setText(permissions_text); - - //setup permissions list - List requestedPermissions = null; - if (packageInfo.requestedPermissions != null && packageInfo.requestedPermissions.length > 0) { - requestedPermissions = new ArrayList<>(); - for(int i = 0; i < packageInfo.requestedPermissions.length; i++) { - Permission permission = new Permission(); - permission.fullName = packageInfo.requestedPermissions[i]; - try { - PermissionInfo permissionInfo = packageManager.getPermissionInfo(permission.fullName,PackageManager.GET_META_DATA); - if(permissionInfo.loadDescription(packageManager) != null) - permission.description = permissionInfo.loadDescription(packageManager).toString(); - if(permissionInfo.loadLabel(packageManager) != null) - permission.name = permissionInfo.loadLabel(packageManager).toString(); - requestedPermissions.add(permission); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } - } - } reportBinding.permissions.setLayoutManager(new LinearLayoutManager(context)); - PermissionListAdapter permissionAdapter = new PermissionListAdapter(requestedPermissions); + PermissionListAdapter permissionAdapter = new PermissionListAdapter(reportDisplay.permissions); reportBinding.permissions.setNestedScrollingEnabled(false); reportBinding.permissions.setAdapter(permissionAdapter); - reportBinding.analysed.setVisibility(View.GONE); - reportBinding.trackerLayout.setVisibility(View.VISIBLE); - - //get trackers - Report report = DatabaseManager.getInstance(context).getReportFor(packageName,versionName); - Set trackers = null; - if(report != null) { - trackers = DatabaseManager.getInstance(context).getTrackers(report.trackers); - } else { - reportBinding.analysed.setVisibility(View.VISIBLE); - reportBinding.trackerLayout.setVisibility(View.GONE); - } - //setup trackers report - String trackers_text; - if(trackers != null && trackers.size() > 0) - trackers_text = context.getString(R.string.trackers)+" "+String.valueOf(trackers.size()); - else - trackers_text = context.getString(R.string.trackers); - reportBinding.trackersTitle.setText(trackers_text); - //setup trackers lists reportBinding.trackers.setLayoutManager(new LinearLayoutManager(context)); - TrackerListAdapter trackerAdapter = new TrackerListAdapter(trackers,R.layout.tracker_item); + TrackerListAdapter trackerAdapter = new TrackerListAdapter(reportDisplay.trackers,R.layout.tracker_item, trackerClickListener); reportBinding.trackers.setNestedScrollingEnabled(false); reportBinding.trackers.setAdapter(trackerAdapter); - //setup creator - if(report != null) - reportBinding.creator.setText(DatabaseManager.getInstance(context).getCreator(report.appId)); - else - reportBinding.creator.setVisibility(View.GONE); + reportBinding.reportDate.setText(viewModel.getReportDate(context)); + reportBinding.creatorValue.setText(viewModel.getCreator(context)); + reportBinding.codeSignature.setText(viewModel.getCodeSignatureInfo(context)); + reportBinding.codePermission.setText(viewModel.getCodePermissionInfo(context)); - //setup installed - String installed_str = context.getString(R.string.installed) +" "+ versionName; - reportBinding.installedVersion.setText(installed_str); + reportBinding.trackerExplanation.setText(getText(R.string.tracker_infos)); + reportBinding.trackerExplanation.setMovementMethod(LinkMovementMethod.getInstance()); + reportBinding.trackerExplanation.setClickable(true); - //setup reportversion - reportBinding.reportVersion.setVisibility(View.VISIBLE); - if(report != null && !report.version.equals(versionName)) { - String report_str = context.getString(R.string.report_version)+" "+report.version; - reportBinding.reportVersion.setText(report_str); + reportBinding.permissionExplanationDangerous.setText(getText(R.string.permission_infos_dangerous)); + reportBinding.permissionExplanationDangerous.setMovementMethod(LinkMovementMethod.getInstance()); + reportBinding.permissionExplanationDangerous.setClickable(true); + + reportBinding.permissionExplanation.setText(getText(R.string.permission_infos)); + reportBinding.permissionExplanation.setMovementMethod(LinkMovementMethod.getInstance()); + reportBinding.permissionExplanation.setClickable(true); + + 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)); + else + intent.setData(Uri.parse("https://f-droid.org/packages/"+reportDisplay.packageName)); + startActivity(intent); + }); + + 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 + "/")); + startActivity(intent); + }); } - else - reportBinding.reportVersion.setVisibility(View.GONE); - - //setup report url - if(report != null) - reportBinding.reportUrl.setText("https://reports.exodus-privacy.eu.org/reports/"+report.id+"/"); } - public void setPackageManager(PackageManager packageManager) { + private void setPackageManager(PackageManager packageManager) { this.packageManager = packageManager; } - public void setPackageInfo(PackageInfo packageInfo) { + private void setPackageInfo(PackageInfo packageInfo) { this.packageInfo = packageInfo; } + private void setOnTrackerClickListener(TrackerListAdapter.OnTrackerClickListener listener) { + trackerClickListener = listener; + } + @Override public void onPrepareOptionsMenu(Menu menu) { MenuItem item = menu.findItem(R.id.action_filter); item.setVisible(false); } + + public ApplicationViewModel getModel() { + return model; + } } diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/TrackerFragment.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/TrackerFragment.java new file mode 100644 index 0000000..db87ce4 --- /dev/null +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/TrackerFragment.java @@ -0,0 +1,155 @@ +package org.eu.exodus_privacy.exodusprivacy.fragments; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +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.Nullable; +import androidx.databinding.DataBindingUtil; +import androidx.fragment.app.Fragment; +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.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 { + + private TrackerBinding trackerBinding; + private long trackerId; + private PackageManager packageManager; + private List applications; + private AppListFragment appListFragment; + private ApplicationListAdapter.OnAppClickListener onAppClickListener; + + public static TrackerFragment newInstance(long trackerId) { + TrackerFragment fragment = new TrackerFragment(); + fragment.setTrackerId(trackerId); + return fragment; + } + + private void setTrackerId(long id) { + trackerId = id; + } + + @Override + public void onUpdateComplete() { + Context context = trackerBinding.getRoot().getContext(); + Tracker tracker = DatabaseManager.getInstance(context).getTracker(trackerId); + trackerBinding.name.setText(tracker.name); + trackerBinding.codeDetection.setText(tracker.codeSignature); + trackerBinding.networkDetection.setText(tracker.networkSignature); + trackerBinding.description.setText(Html.fromHtml(Utils.markdownToHtml(tracker.description))); + trackerBinding.description.setMovementMethod(LinkMovementMethod.getInstance()); + trackerBinding.description.setClickable(true); + trackerBinding.trackerUrl.setOnClickListener(v -> { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(tracker.website)); + startActivity(intent); + }); + displayAppListAsync(); + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public View onCreateView(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.disableScrollBar(); + appListFragment.setOnAppClickListener(onAppClickListener); + FragmentManager manager = getChildFragmentManager(); + FragmentTransaction transaction = manager.beginTransaction(); + transaction.replace(R.id.applications,appListFragment); + transaction.commit(); + Context context = trackerBinding.getRoot().getContext(); + packageManager = context.getPackageManager(); + onUpdateComplete(); + return trackerBinding.getRoot(); + } + + + @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); + + } + + private void displayAppListAsync() { + trackerBinding.noAppFound.setVisibility(View.GONE); + trackerBinding.trackerPresence.setVisibility(View.GONE); + //todo + trackerBinding.trackerPresenceNb.setVisibility(View.GONE); + //todo + trackerBinding.trackerPresenceTitle.setVisibility(View.GONE); + if (applications.isEmpty()) { + trackerBinding.retrieveApp.setVisibility(View.VISIBLE); + } + + new ComputeAppListTask( + new WeakReference<>(packageManager), + new WeakReference<>(DatabaseManager.getInstance(getActivity())), + new WeakReference<>(this) + ).execute(); + } + + @Override + public void onAppsComputed(List apps) { + this.applications = apps; + trackerBinding.retrieveApp.setVisibility(View.GONE); + trackerBinding.noAppFound.setVisibility(apps.isEmpty() ? View.VISIBLE : View.GONE); + trackerBinding.trackerPresence.setVisibility(View.VISIBLE); + trackerBinding.trackerPresenceNb.setVisibility(View.VISIBLE); + trackerBinding.trackerPresenceTitle.setVisibility(View.VISIBLE); + appListFragment.setApplications(apps); + int total = appListFragment.getTotalApps(); + int displayedApps = appListFragment.getDisplayedApps(); + int percent = displayedApps*100/total; + if(percent >=50) + trackerBinding.trackerPresenceNb.setBackgroundResource(R.drawable.square_red); + else if(percent >=33) + trackerBinding.trackerPresenceNb.setBackgroundResource(R.drawable.square_dark_orange); + else if(percent >=20) + trackerBinding.trackerPresenceNb.setBackgroundResource(R.drawable.square_yellow); + else + trackerBinding.trackerPresenceNb.setBackgroundResource(R.drawable.square_light_blue); + + trackerBinding.trackerPresenceNb.setText(percent+"%"); + Context context = trackerBinding.getRoot().getContext(); + String presence = context.getResources().getString(R.string.tracker_presence,displayedApps); + trackerBinding.trackerPresence.setText(presence); + trackerBinding.trackerPresenceTitle.setText(R.string.tracker_presence_in); + } + + public void setOnAppClickListener(ApplicationListAdapter.OnAppClickListener listener) { + onAppClickListener = listener; + } +} diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/Updatable.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/Updatable.java new file mode 100644 index 0000000..b95aaa2 --- /dev/null +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/Updatable.java @@ -0,0 +1,5 @@ +package org.eu.exodus_privacy.exodusprivacy.fragments; + +public interface Updatable { + void onUpdateComplete(); +} diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/manager/DatabaseManager.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/manager/DatabaseManager.java index c42026d..5b815a2 100644 --- a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/manager/DatabaseManager.java +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/manager/DatabaseManager.java @@ -29,8 +29,10 @@ import org.eu.exodus_privacy.exodusprivacy.objects.Report; import org.eu.exodus_privacy.exodusprivacy.objects.Tracker; import java.util.Calendar; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; public class DatabaseManager extends SQLiteOpenHelper { @@ -44,14 +46,14 @@ public class DatabaseManager extends SQLiteOpenHelper { public static DatabaseManager getInstance(Context context) { if(instance == null) - instance = new DatabaseManager(context,"Exodus.db",null,1); + instance = new DatabaseManager(context,"Exodus.db",null,3); return instance; } @Override public void onCreate(SQLiteDatabase db) { - db.execSQL("Create Table if not exists applications (id INTEGER primary key autoincrement, package TEXT, name TEXT, creator TEXT);"); - db.execSQL("Create Table if not exists reports (id INTEGER primary key, creation INTEGER, updateat INTEGER, downloads TEXT, version TEXT, version_code INTEGER, app_id INTEGER, foreign key(app_id) references applications(id));"); + db.execSQL("Create Table if not exists applications (id INTEGER primary key autoincrement, package TEXT, name TEXT, creator TEXT, sources TEXT);"); + db.execSQL("Create Table if not exists reports (id INTEGER primary key, creation INTEGER, updateat INTEGER, downloads TEXT, version TEXT, version_code INTEGER, app_id INTEGER, source TEXT, foreign key(app_id) references applications(id));"); db.execSQL("Create Table if not exists trackers (id INTEGER primary key, name TEXT, creation_date INTEGER, code_signature TEXT, network_signature TEXT, website TEXT, description TEXT);"); db.execSQL("Create Table if not exists trackers_reports (id INTEGER primary key autoincrement, tracker_id INTEGER, report_id INTEGER, foreign key(tracker_id) references trackers(id), foreign key(report_id) references reports(id));"); @@ -59,7 +61,36 @@ public class DatabaseManager extends SQLiteOpenHelper { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - // do nothing + if(oldVersion <= 1) { + db.execSQL("Alter Table applications add column auid TEXT"); + } + if (oldVersion <= 2) { + try { + db.beginTransaction(); + db.execSQL("Alter Table reports add column source TEXT"); + 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()){ + 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); + } + cursor.close(); + db.execSQL("Drop Table old_apps"); + db.setTransactionSuccessful(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + db.endTransaction(); + } + + } } private boolean existReport(SQLiteDatabase db, long reportId) { @@ -119,6 +150,7 @@ public class DatabaseManager extends SQLiteOpenHelper { values.put("package", application.packageName); values.put("name",application.name); values.put("creator",application.creator); + values.put("sources",buildSourcesStr(application.sources)); if(!existApplication(db, application.packageName)) { db.insert("applications", null, values); @@ -134,8 +166,8 @@ public class DatabaseManager extends SQLiteOpenHelper { Cursor cursor = db.query("applications",columns,where,whereArgs,null,null,null); if(cursor.moveToFirst()) { application.id = cursor.getLong(0); - cursor.close(); } + cursor.close(); for(Report report : application.reports) { insertOrUpdateReport(db,report,application.id); @@ -151,6 +183,7 @@ public class DatabaseManager extends SQLiteOpenHelper { 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); @@ -180,36 +213,91 @@ public class DatabaseManager extends SQLiteOpenHelper { db.insert("trackers_reports",null,values); } - public Report getReportFor(String packageName, String version) { + public Report getReportFor(String packageName, String version, String source) { + SQLiteDatabase db = getReadableDatabase(); String[] columns = {"id"}; String where = "package = ?"; String[] whereArgs = {packageName}; - Cursor cursor = getReadableDatabase().query("applications",columns,where,whereArgs,null,null,null); + 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 = ?"; - whereArgs = new String[2]; + where = "app_id = ? and version = ? and source = ?"; + whereArgs = new String[3]; whereArgs[0] = String.valueOf(appId); whereArgs[1] = version; + whereArgs[2] = source; String order = "id ASC"; - cursor = getReadableDatabase().query("reports",columns,where,whereArgs,null,null,order); + cursor = db.query("reports",columns,where,whereArgs,null,null,order); long reportId; 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 = ?"; - whereArgs = new String[1]; + where = "app_id = ? and source = ?"; + whereArgs = new String[2]; whereArgs[0] = String.valueOf(appId); + whereArgs[1] = source; order = "creation DESC"; //search a recent reports - cursor = getReadableDatabase().query("reports",columns,where,whereArgs,null,null,order); + cursor = db.query("reports",columns,where,whereArgs,null,null,order); if(cursor.moveToFirst()) { reportId = cursor.getLong(0); + cursor.close(); } else { + cursor.close(); + return null; + } + } + return getReport(reportId); + + } else { + cursor.close(); + return null; + } + } + + public Report getReportFor(String packageName, long version, String source) { + SQLiteDatabase db = getReadableDatabase(); + String[] columns = {"id"}; + String where = "package = ?"; + String[] whereArgs = {packageName}; + 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 = ?"; + whereArgs = new String[3]; + whereArgs[0] = String.valueOf(appId); + whereArgs[1] = String.valueOf(version); + whereArgs[2] = source; + String order = "id ASC"; + cursor = db.query("reports",columns,where,whereArgs,null,null,order); + long reportId; + 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; + order = "creation DESC"; + //search a recent reports + cursor = db.query("reports",columns,where,whereArgs,null,null,order); + if(cursor.moveToFirst()) { + reportId = cursor.getLong(0); + cursor.close(); + } else { + cursor.close(); return null; } } @@ -222,12 +310,15 @@ public class DatabaseManager extends SQLiteOpenHelper { } private Report getReport(long reportId) { + SQLiteDatabase db = getReadableDatabase(); String where = "id = ?"; String[] whereArgs = {String.valueOf(reportId)}; - Cursor cursor = getReadableDatabase().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; + } Report report = new Report(); int col = 0; @@ -235,19 +326,23 @@ public class DatabaseManager extends SQLiteOpenHelper { long creation = cursor.getLong(col++); report.creationDate = Calendar.getInstance(); report.creationDate.setTimeInMillis(creation); + 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.downloads = cursor.getString(col++); report.version = cursor.getString(col++); report.versionCode = cursor.getLong(col++); - report.appId = cursor.getLong(col); + report.appId = cursor.getLong(col++); + report.source = cursor.getString(col); + cursor.close(); report.trackers = new HashSet<>(); where = "report_id = ?"; String[] columns = {"tracker_id"}; String order = "tracker_id DESC"; - cursor = getReadableDatabase().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)); @@ -270,10 +365,11 @@ public class DatabaseManager extends SQLiteOpenHelper { return creator; } - private Tracker getTracker(long trackerId) { + public Tracker getTracker(long trackerId) { + SQLiteDatabase db = getReadableDatabase(); String where = "id = ?"; String[] whereArgs = {String.valueOf(trackerId)}; - Cursor cursor = getReadableDatabase().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()) { @@ -309,4 +405,40 @@ public class DatabaseManager extends SQLiteOpenHelper { insertOrUpdateTracker(db,tracker); } } + + public Map 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()) + { + sourcesStr = cursor.getString(0); + } + cursor.close(); + return extractSources(sourcesStr); + } + + private String buildSourcesStr(Map sources) { + StringBuilder sourceStr = new StringBuilder(); + for(Map.Entry entry : sources.entrySet()) { + sourceStr.append(entry.getKey()).append(":").append(entry.getValue()).append("|"); + } + return sourceStr.toString(); + } + + private Map extractSources(String sourcesStr) { + Map sources = new HashMap<>(); + String[] sourceList = sourcesStr.split("\\|"); + for(String sourceItem : sourceList){ + if(!sourceItem.isEmpty()) { + String[] data = sourceItem.split(":"); + if(data.length == 2) + sources.put(data[0], data[1]); + } + } + + return sources; + } } diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/manager/NetworkManager.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/manager/NetworkManager.java index 64c800e..aeb7086 100644 --- a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/manager/NetworkManager.java +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/manager/NetworkManager.java @@ -22,12 +22,6 @@ import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Bundle; - -import org.eu.exodus_privacy.exodusprivacy.listener.NetworkListener; -import org.eu.exodus_privacy.exodusprivacy.R; -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; @@ -42,14 +36,27 @@ 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.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; + /* Singleton that handle all network connection */ @@ -195,8 +202,14 @@ public class NetworkManager { 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(); + } } - DatabaseManager.getInstance(mes.context).insertOrUpdateTrackers(trackersList); + if(!trackersList.isEmpty()) + DatabaseManager.getInstance(mes.context).insertOrUpdateTrackers(trackersList); + trackersList.clear(); } catch (JSONException e) { mes.listener.onError(mes.context.getString(R.string.json_error)); } @@ -207,7 +220,7 @@ public class NetworkManager { mes.listener.onProgress(R.string.get_reports_connection,0,0); URL url; try { - url = new URL(apiUrl+"applications"); + url = new URL(apiUrl+"applications?option=short"); } catch (Exception e){ e.printStackTrace(); return; @@ -218,45 +231,64 @@ public class NetworkManager { if(object != null) { - List handles = new ArrayList<>(); + Map> handles = new HashMap<>(); + ArrayList packages = mes.args.getStringArrayList("packages"); + if (packages == null) + return; + try { JSONArray applications = object.getJSONArray("applications"); + + //manage handles map (handle,UAID) for(int i = 0; i sources = handles.get(handle); + if(sources == null) + sources = new HashMap<>(); + + sources.put(source,auid); + if (packages.contains(handle)) + 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()); + int alea = rand.nextInt(120) % 10 + 11; + 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<>()); + packages.add(handle); + } + //shuffle the list + Collections.shuffle(packages); + object.remove("applications"); + } catch (JSONException e) { + e.printStackTrace(); mes.listener.onError(mes.context.getString(R.string.json_error)); } - getReports(mes,handles); + object = null; + getReports(mes,handles,packages); } mes.listener.onSuccess(); } - private void getReports(Message mes, List handles) { - ArrayList packages = mes.args.getStringArrayList("packages"); - if(packages == null) - return; - - packages.retainAll(handles); - - // Add some random packages to avoid tracking - Random rand = new Random(Thread.currentThread().getId()); - int alea = rand.nextInt(120) % 10 + 11; - for(int i = 0 ; i < alea; i++) { - int val = rand.nextInt(handles.size()); - packages.add(handles.get(val)); - } - - + private void getReports(Message mes, Map> handles, ArrayList 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)); + getReport(mes,packages.get(i),handles.get(packages.get(i))); } } - private void getReport(Message mes, String handle) { + private void getReport(Message mes, String handle, Map sources) { URL url; try { url = new URL(apiUrl+"search/"+handle); @@ -272,6 +304,7 @@ public class NetworkManager { ArrayList packages = mes.args.getStringArrayList("packages"); if(packages != null && packages.contains(handle)) { Application app = parseApplication(application, handle); + app.sources = sources; DatabaseManager.getInstance(mes.context).insertOrUpdateApplication(app); } } catch (JSONException e) { @@ -285,6 +318,7 @@ public class NetworkManager { application.packageName = packageName; application.creator = object.getString("creator"); application.name = object.getString("name"); + //parse Report application.reports = new HashSet<>(); JSONArray reports = object.getJSONArray("reports"); @@ -300,6 +334,7 @@ public class NetworkManager { report.id = object.getLong("id"); report.downloads = object.getString("downloads"); report.version = object.getString("version"); + report.source = object.getString("source"); 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()); @@ -307,10 +342,12 @@ public class NetworkManager { 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); 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); } catch (ParseException e) { e.printStackTrace(); } diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/Application.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/Application.java index 1c65173..1dd2fcf 100644 --- a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/Application.java +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/Application.java @@ -18,6 +18,8 @@ package org.eu.exodus_privacy.exodusprivacy.objects; +import java.util.Map; +import java.util.Objects; import java.util.Set; public class Application { @@ -26,6 +28,7 @@ public class Application { public String name; public String creator; public Set reports; + public Map sources; @Override public boolean equals(Object o) { @@ -35,13 +38,13 @@ public class Application { Application that = (Application) o; if (id != that.id) return false; - return packageName.equals(that.packageName); + return packageName != null ? packageName.equals(that.packageName) : that.packageName == null; } @Override public int hashCode() { int result = (int) (id ^ (id >>> 32)); - result = 31 * result + packageName.hashCode(); + result = 31 * result + (packageName != null ? packageName.hashCode() : 0); return result; } } diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/Permission.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/Permission.java index bdb5f87..8b9b8fc 100644 --- a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/Permission.java +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/Permission.java @@ -1,8 +1,12 @@ package org.eu.exodus_privacy.exodusprivacy.objects; +import android.graphics.drawable.Drawable; + public class Permission { public String name; public String fullName; public String description; + public Drawable icon; + public boolean dangerous; public boolean expanded = false; } diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/Report.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/Report.java index 1960db9..0f311b8 100644 --- a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/Report.java +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/Report.java @@ -30,6 +30,7 @@ public class Report { public long versionCode; public Set trackers; public long appId; + public String source; @Override public boolean equals(Object o) { diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/ReportDisplay.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/ReportDisplay.java new file mode 100644 index 0000000..7f7d52c --- /dev/null +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/ReportDisplay.java @@ -0,0 +1,87 @@ +package org.eu.exodus_privacy.exodusprivacy.objects; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageInfo; +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; +import org.eu.exodus_privacy.exodusprivacy.manager.DatabaseManager; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class ReportDisplay { + public Report report; + public String packageName; + public String versionName; + public String displayName; + public String creator; + public long versionCode; + public Drawable logo; + public List permissions; + public Set trackers; + public String source; + public String viewOnStore; + + + private ReportDisplay(){ + + } + + public static ReportDisplay buildReportDisplay(Context context, ApplicationViewModel model, PackageManager manager, PackageInfo info) { + ReportDisplay reportDisplay = new ReportDisplay(); + reportDisplay.packageName = model.packageName; + reportDisplay.versionName = model.versionName; + reportDisplay.versionCode = model.versionCode; + 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.trackers = model.trackers; + + if (reportDisplay.report != null) + reportDisplay.creator = DatabaseManager.getInstance(context).getCreator(reportDisplay.report.appId); + + List requestedPermissions= new ArrayList<>(); + if (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) + permission.description = permissionInfo.loadDescription(manager).toString(); + 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 + permission.dangerous = true; + + if (permissionInfo.group != null) { + PermissionGroupInfo permissionGroupInfo = manager.getPermissionGroupInfo(permissionInfo.group, PackageManager.GET_META_DATA); + if(permissionGroupInfo.loadIcon(manager) != null) + permission.icon = permissionGroupInfo.loadIcon(manager); + } + + } catch (PackageManager.NameNotFoundException e) { + e.getLocalizedMessage(); + } + requestedPermissions.add(permission); + } + } + reportDisplay.permissions = requestedPermissions; + + reportDisplay.logo = model.icon; + + return reportDisplay; + } +} diff --git a/app/src/main/res/drawable-hdpi/ic_search.png b/app/src/main/res/drawable-hdpi/ic_search.png deleted file mode 100644 index c53e331..0000000 Binary files a/app/src/main/res/drawable-hdpi/ic_search.png and /dev/null differ diff --git a/app/src/main/res/drawable-ldpi/ic_search.png b/app/src/main/res/drawable-ldpi/ic_search.png deleted file mode 100644 index d190496..0000000 Binary files a/app/src/main/res/drawable-ldpi/ic_search.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_search.png b/app/src/main/res/drawable-mdpi/ic_search.png deleted file mode 100644 index 5feb851..0000000 Binary files a/app/src/main/res/drawable-mdpi/ic_search.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_search.png b/app/src/main/res/drawable-xhdpi/ic_search.png deleted file mode 100644 index 3b17455..0000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_search.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_search.png b/app/src/main/res/drawable-xxhdpi/ic_search.png deleted file mode 100644 index c635b4a..0000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_search.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_search.png b/app/src/main/res/drawable-xxxhdpi/ic_search.png deleted file mode 100644 index 88f973d..0000000 Binary files a/app/src/main/res/drawable-xxxhdpi/ic_search.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_danger.xml b/app/src/main/res/drawable/ic_danger.xml new file mode 100644 index 0000000..7fe6430 --- /dev/null +++ b/app/src/main/res/drawable/ic_danger.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/drawable/ic_logo_purple.xml b/app/src/main/res/drawable/ic_logo_purple.xml new file mode 100644 index 0000000..58fcb9a --- /dev/null +++ b/app/src/main/res/drawable/ic_logo_purple.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 0000000..6a5ca80 --- /dev/null +++ b/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 0000000..ed3ac36 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/square_dark_orange.xml b/app/src/main/res/drawable/square_dark_orange.xml new file mode 100644 index 0000000..f5f9b9b --- /dev/null +++ b/app/src/main/res/drawable/square_dark_orange.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/drawable/square_green.xml b/app/src/main/res/drawable/square_green.xml new file mode 100644 index 0000000..fca10e9 --- /dev/null +++ b/app/src/main/res/drawable/square_green.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/drawable/square_light_blue.xml b/app/src/main/res/drawable/square_light_blue.xml new file mode 100644 index 0000000..7b6cbb8 --- /dev/null +++ b/app/src/main/res/drawable/square_light_blue.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/drawable/square_light_red.xml b/app/src/main/res/drawable/square_light_red.xml new file mode 100644 index 0000000..0ec94f0 --- /dev/null +++ b/app/src/main/res/drawable/square_light_red.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/drawable/square_light_yellow.xml b/app/src/main/res/drawable/square_light_yellow.xml new file mode 100644 index 0000000..9d20e63 --- /dev/null +++ b/app/src/main/res/drawable/square_light_yellow.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/drawable/square_purple.xml b/app/src/main/res/drawable/square_purple.xml new file mode 100644 index 0000000..d1a7658 --- /dev/null +++ b/app/src/main/res/drawable/square_purple.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/square_red.xml b/app/src/main/res/drawable/square_red.xml new file mode 100644 index 0000000..8bede2d --- /dev/null +++ b/app/src/main/res/drawable/square_red.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/drawable/square_yellow.xml b/app/src/main/res/drawable/square_yellow.xml new file mode 100644 index 0000000..55beb5f --- /dev/null +++ b/app/src/main/res/drawable/square_yellow.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/layout/app_item.xml b/app/src/main/res/layout/app_item.xml index dab2818..fdf259d 100644 --- a/app/src/main/res/layout/app_item.xml +++ b/app/src/main/res/layout/app_item.xml @@ -10,6 +10,8 @@ - - + + + + - + android:layout_height="wrap_content" + android:layout_marginTop="5dp" + > + + + + - - - - - - - - - - - - - - - - - - + android:scrollbars="vertical"/> \ No newline at end of file diff --git a/app/src/main/res/layout/home.xml b/app/src/main/res/layout/home.xml new file mode 100644 index 0000000..0bc7f0b --- /dev/null +++ b/app/src/main/res/layout/home.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml index 6619d50..33f8b63 100644 --- a/app/src/main/res/layout/main.xml +++ b/app/src/main/res/layout/main.xml @@ -8,12 +8,8 @@ > - + - + + android:layout_height="wrap_content" + > + + + + + + - - + + android:layout_height="wrap_content" + android:layout_marginStart="66dp" + android:layout_marginEnd="20dp" + android:textStyle="italic" + /> \ No newline at end of file diff --git a/app/src/main/res/layout/report.xml b/app/src/main/res/layout/report.xml index 6ee4300..4d71498 100644 --- a/app/src/main/res/layout/report.xml +++ b/app/src/main/res/layout/report.xml @@ -1,130 +1,444 @@ - - + + + + + - - + android:layout_height="match_parent"> + android:layout_marginTop="20dp" + android:padding="10dp" + android:src="@{reportInfo.logo}" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent"/> - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + android:autoLink="web" + android:text="@string/view_on_exodus" + android:visibility="@{reportInfo.reportVisibility ? View.VISIBLE : View.GONE}" + app:layout_constraintEnd_toEndOf="parent" + android:textAlignment="textEnd" + app:layout_constraintTop_toBottomOf="@id/report_date" + android:layout_marginTop="5dp" + android:layout_marginStart="20dp" + android:layout_marginEnd="20dp" + android:textSize="16sp" + android:textColor="@color/colorPurple"/> - + android:layout_height="wrap_content" + android:autoLink="web" + android:text="@{reportInfo.viewOnStore}" + android:textAlignment="textEnd" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/report_url" + android:layout_marginTop="5dp" + android:layout_marginStart="20dp" + android:layout_marginEnd="20dp" + android:textSize="16sp" + android:textColor="@color/colorPurple"/> - + - - + - + + - + android:layout_height="wrap_content" + android:visibility="@{reportInfo.trackerVisibility ? View.VISIBLE : View.GONE}" + android:layout_marginTop="5dp" + android:layout_marginBottom="5dp" + android:layout_marginStart="20dp" + android:layout_marginEnd="20dp" + app:layout_constraintTop_toBottomOf="@id/code_signature" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + /> - - - - - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/tracker.xml b/app/src/main/res/layout/tracker.xml new file mode 100644 index 0000000..b7b24dd --- /dev/null +++ b/app/src/main/res/layout/tracker.xml @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/tracker_item.xml b/app/src/main/res/layout/tracker_item.xml index b711715..1c6e24c 100644 --- a/app/src/main/res/layout/tracker_item.xml +++ b/app/src/main/res/layout/tracker_item.xml @@ -3,25 +3,11 @@ xmlns:android="http://schemas.android.com/apk/res/android" > - - - - + \ No newline at end of file diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml index 2846266..b05145e 100644 --- a/app/src/main/res/menu/main.xml +++ b/app/src/main/res/menu/main.xml @@ -5,6 +5,11 @@ android:id="@+id/action_filter" android:icon="@drawable/ic_search" android:title="@string/menu_action_filter" - app:actionViewClass="android.support.v7.widget.SearchView" + app:actionViewClass="android.widget.SearchView" app:showAsAction="ifRoom|collapseActionView" /> - \ No newline at end of file + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..20b4aae --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_background.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_background.xml new file mode 100644 index 0000000..9228a9e --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_background.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_foreground.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_foreground.xml new file mode 100644 index 0000000..8d11962 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_foreground.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..20b4aae --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000..65d3116 --- /dev/null +++ b/app/src/main/res/values-ar/strings.xml @@ -0,0 +1,52 @@ + + + شعار التطبيق + المتعقِّبون: + التصريحات: + المتعقِّبون + التصريحات + الشبكة غير متوفرة + لا يوجد اتصال بشبكة الانترنت + خطأ JSON + النسخة المثبتة: + النسخة المُجرَّبة: + لم نعثر على أي متعقِّب قمنا بتجريبه + هذا التطبيق لا يتطلب أية تصريحات + %1$s%2$sليس هناك أي تقرير عن النسخة المثبتة، إنّ البيانات المعروضة مستمدة مِن نسخة أخرى قديمة لنفس التطبيق + لم يتم بعد فحص هذا التطبيق بواسطة Exodus Privacy. + يبدو أنّ نظام جهازك لا يسمح بالوصول إلى قائمة التطبيقات المثبة. + يبدو أنه ليس لديك أي تطبيقات مثبتة من المصدر الذي نقوم باختباره (متجر Google Play أو F-Droid). + جلب التطبيقات: في انتظار الاتصال بالخادم + جارٍ جلب التطبيقات + فحص التطبيقات: + فحص المتعقّبون: + جلب المتعقّبين: في انتظار الاتصال بالخادم + جارٍ جلب المُتعقّبين + ليس لدى هذا التطبيق إعدادات + شعار تطبيق %1 + أنشأه + تم إنشاء التقرير في + وتحديثه في + رؤيته على εxodus ← + رؤيته على متجر غوغل للتطبيقات ← + رؤيته على F-Droid ← + تنزيلات + لقد وجدنا توقيع الرمز البرمجي للمتعقبين التاليين في التطبيق: + لم نجد توقيع الرمز البرمجي لأي متتبع نعرفه في التطبيق. + لقد وجدنا التصريحات التالية في التطبيق: + هذا التطبيق لا يطلب صلاحيات. + المتعقب هو جزء من برنامج يهدف إلى جمع البيانات عنك أو عن استخداماتك. تعرف على المزيد… + التصريحات هي إجراءات يمكن أن يقوم بها التطبيق على هاتفك. اعرف المزيد… + تشير الأيقونة ! إلى درجات \'خطير\' أو \'خاص\' حسب تصنيف درجات الأمان لدى غوغل. + صفحة الويب للمتعقب ← + قواعد الكشف + قاعدة كشف الرمز: + القاعدة الشبكية للإكتشاف: + موجود في %d من تطبيقاتك + موجود في: + يبدو أن هذا المتعقب غير موجود في تطبيقاتك + المصدر: %s + + عامل التصفية + إعدادات التطبيق + diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml new file mode 100644 index 0000000..89e4102 --- /dev/null +++ b/app/src/main/res/values-ca/strings.xml @@ -0,0 +1,52 @@ + + + Logo de l\'aplicació + Rastrejadors: + Permisos: + Rastrejadors + Permisos + Xarxa no disponible + No hi ha connexió a Internet + Error JSON + Versió instal·lada: + Versió que s\'ha provat: + L\'aplicació podria contenir rastrejadors que encara no ho coneixem. + Aquesta aplicació no requereix cap permís. + No hi ha cap informe per a la versió instal·lada (%1$s), la informació mostrada es basa en un altre versió (%2$s) + Aquesta aplicació no ha estat analitzada encara per Exodus Privacy. + Sembla que el sistema no permet l’accés a la llista d’aplicacions instal·lades. + Sembla que no teniu cap aplicació instal·lada des de la font que provem (Google Play Store o F-Droid). + S\'estan obtenint les aplicacions: s\'està esperant la connexió del servidor + S\'estan obtenint les aplicacions + S\'estan processant les aplicacions: + S\'estan processant els rastrejadors: + S\'estan obtenint els rastrejadors: s\'està esperant la connexió del servidor + S\'estan obtenint els rastrejadors + Aquesta aplicació no té configuració + Logo de l\'aplicació %1 + Creat per + S\'ha creat aquest informe el + i actualitzat el + Vegueu al εxodus ➤ + Vegueu al Google Play ➤ + Vegeu a F-Droid ➤ + baixades + Hem trobat la signatura del codi dels següents rastrejadors a l\'aplicació: + No hem trobat cap signatura de codi de cap rastrejador que coneguem a l\'aplicació. + Hem trobat els permisos següents a l\'aplicació: + Aquesta aplicació no requereix cap permís. + Un rastrejador és una peça de programari destinat a recollir dades sobre tu o el teu comportament. Més informació… + Els permisos són accions que l\'aplicació pot fer al telèfon. Més informació… + La icona ! indica nivell «perillós» o «especial» segons els nivells de protecció de Google. + Pàgina web del rastrejador ➤ + Regles de detecció + Regla de detecció de codi: + Regla de detecció de xarxa: + Present en %d de les vostres aplicacions + Present en: + Aquest rastrejador no sembla estar present en les vostres aplicacions + Font: %s + + Filtre + Paràmetres de l\'aplicació + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..152e0a5 --- /dev/null +++ b/app/src/main/res/values-de/strings.xml @@ -0,0 +1,52 @@ + + + App Logo + Tracker: + Berechtigungen: + Tracker + Berechtigungen + Netzwerk nicht verfügbar + Keine Verbindung + JSON Fehler + Installierte Version: + Getestete Version: + Diese Anwendung könnte Tracker, die uns unbekannt sind, enthalten. + Diese App braucht keine Berechtigungen + Es gibt keinen Bericht für die installierte Version (%1$s), die gezeigte Informationen basieren auf einer anderen Version (%2$s) + Diese App wurde von Exodus Privacy noch nicht analysiert. + Ihr System scheint den Zugriff auf die Appliste nicht zu erlauben. + Es scheint, dass keine App aus den Quellen, die wir prüfen (Google Play Store und F-Droid), installiert ist. + App erhalten: Warte auf Antwort des Servers + App erhalten + Untersuchung der App: + Untersuchung der Tracker: + Tracker erhalten: Warte auf Antwort des Servers + Tracker erhalten + Diese App hat keine Einstellungen + %1 Anwendungslogo + Erstellt von + Bericht erstellt am + und zuletzt aktualisiert am + Siehe auf εxodus ➤ + Siehe auf Google Play ➤ + Siehe auf F-Droid ➤ + downloads + Wir haben die Code-Signatur der folgenden Tracker in der Anwendung gefunden: + Wir haben keine Code-Signatur irgendeines Trackers in der App gefunden. + Wir haben die folgenden Berechtigungen in der Anwendung gefunden: + Diese App erfordert keine Berechtigungen. + Ein Tracker ist eine Software, die Daten über Sie oder Ihre Nutzung erfassen soll. Erfahre mehr… + Berechtigungen sind Aktionen, die die Anwendung auf Ihrem Telefon ausführen kann. Mehr erfahren… + Das Symbol ! zeigt eine \'Gefährliche\' oder \'Spezial\' Stufe nach Googles Schutzstufen an. + Tracker-Webseite ➤ + Erkennungsregeln + Code-Erkennungsregel: + Netzwerkerkennungsregel: + Vorhanden in %d der installierten Apps + Vorhanden in: + Dieser Tracker scheint in den installierten Apps nicht vorhanden zu sein + Quelle: %s + + Filter + Anwendungs-Einstellungen + diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml new file mode 100644 index 0000000..b7d5b8c --- /dev/null +++ b/app/src/main/res/values-el/strings.xml @@ -0,0 +1,52 @@ + + + Λογότυπο Εφαρμογής + Trackers: + Άδειες: + Trackers + Άδειες + Το δίκτυο δεν είναι διαθέσιμο + Χωρίς σύνδεση στο Διαδίκτυο + Σφάλμα JSON + Εγκατεστημένη Έκδοση: + Δοκιμαστική έκδοση: + Η εφαρμογή μπορεί να περιέχει tracker(s) που δεν γνωρίζουμε ακόμα. + Αυτή η εφαρμογή δεν απαιτεί δικαιώματα. + Δεν υπάρχει αναφορά για την εγκατεστημένη έκδοση (%1$s), οι πληροφορίες που εμφανίζονται βασίζονται σε άλλη (%2$s) έκδοση + Αυτή η εφαρμογή δεν έχει αναλυθεί ακόμη από το Exodus Privacy. + Φαίνεται ότι το σύστημα σας δεν επιτρέπει την πρόσβαση στη λίστα των εγκατεστημένων εφαρμογών. + Φαίνεται ότι δεν έχετε εγκαταστήσει εφαρμογές, από την πηγές που δοκιμάζουμε (Google Play store ή F-Droid). + Λήψη αναφοράς εφαρμογών: Αναμονή για σύνδεση διακομιστή + Λήψη αναφοράς εφαρμογών + Επεξεργασία Εφαρμογών: + Επεξεργασία των Trackers: + Λήψη αναφοράς των trackers: Αναμονή για σύνδεση διακομιστή + Λήψη αναφοράς των trackers + Αυτή η εφαρμογή δεν έχει ρυθμίσεις + %1 Λογότυπο Εφαρμογής + Δημιουργήθηκε από + Η αναφορά δημιουργήθηκε στις + και ενημερώθηκε στις + Δείτε στο εxodus ➤ + Δείτε στο Google Play ➤ + Δείτε στο F-Droid ➤ + λήψεις + Εχουμε βρει υπογραφές κώδικα των παρακάτω trackers στην εφαρμογή: + Δεν έχουμε βρει υπογραφή κώδικα σε κάποιο tracker που γνωρίζουμε στην εφαρμογή. + Βρήκαμε τα ακόλουθα δικαιώματα στην εφαρμογή: + Αυτή η εφαρμογή δεν απαιτεί δικαιώματα. + Το tracker είναι ένα λογισμικό που προορίζεται για να συλλέγει δεδομένα για εσάς, ή για τις ενέργειες που κάνετε. Μάθετε περισσότερα… + Οι άδειες, είναι οι ενέργειες που μπορεί εκτελέσει μια εφαρμογή στο τηλέφωνό σας. Μάθετε περισσότερα… + Το εικονίδιο ! υποδεικνύει ένα «Επικίνδυνο» ή «Ειδικό» επίπεδο, σύμφωνα με τα Επίπεδα προστασίας της Google. + Ιστοσελίδα του Tracker ➤ + Κανόνες Ανίχνευσης + Κανόνας ανίχνευσης κώδικα: + Κανόνας ανίχνευσης δικτύου: + Παρών σε %d από τις εφαρμογές σας + Παρών σε: + Αυτό το tracker φαίνεται να μην υπάρχει στις εφαρμογές σας + Πηγή: %s + + Φίλτρο + Ρυθμίσεις Εφαρμογής + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 776ff53..6844f99 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,26 +1,52 @@ - Exodus Privacy Application Logo - Pisteurs : - Autorisations : + Pisteurs : + Autorisations : + Pisteurs + Autorisations Réseau indisponible Aucune connexion Internet Erreur JSON Version installée : Version testée : - Aucun pisteur testé n\'a été trouvé - Aucune autorisation n\'est demandée par cette application - Il n\'y a pas de rapport pour la version installée, les informations affichées sont basées sur une autre version (vraisemblablement plus ancienne) + L’application pourrait contenir un ou plusieurs pisteurs que nous n’avons pas encore identifiés. + Cette application ne demande aucun autorisation. + Il n\'y a pas de rapport pour la version installée (%1$s), les informations affichées sont basées sur une autre version (%2$s) Cette application n\'a pas encore été analysée par Exodus Privacy. Votre système semble ne pas donner accès aux applications installées. - Vous semblez n\'avoir aucune application installée par la source que nous recherchons (Google Play). + Vous semblez n\'avoir aucune application installée par la source que nous recherchons (Google Play ou F-Droid). Récupération des applications : en attente de connexion au serveur Récupération des applications Traitement des applications : Traitement des pisteurs : Récupération des pisteurs : en attente de connexion au serveur Récupération des pisteurs + Cette app n\'a pas de paramêtre + Logo de l\'application %1 + Crée par + Rapport créé le + et mis à jour le + Voir sur εxodus ➤ + Voir sur Google Play ➤ + Voir sur F-Droid ➤ + téléchargements + Nous avons trouvé la signature des pisteurs suivants dans cette application : + Nous n’avons pas trouvé la signature de pisteurs que nous connaissons dans l’application. + Nous avons trouvé les permissions suivantes dans cette application: + Cette application ne demande aucune autorisation. + Un pisteur est une partie du logiciel dédiée à la collecte de données sur vous et vos usages. En savoir plus… + Les permissions sont les actions que l\'application peut effectuer sur votre téléphone. En savoir plus… + L\'icône ! indique un niveau \'Dangereux\' ou \'Spécial\' d\'après les niveaux de protection de Google. + Page web du pisteur ➤ + Règles de détection + Règle de détection(code): + Règle de détection(réseau): + Présent dans %d de vos applications + Présent dans: + Ce pisteur ne semble pas être présent dans vos applications + Source: %s Filtrer + Paramètres de l\'application diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml new file mode 100644 index 0000000..f92b715 --- /dev/null +++ b/app/src/main/res/values-it/strings.xml @@ -0,0 +1,52 @@ + + + Logo applicazione + Tracciatori: + Autorizzazioni: + Tracciatori + Autorizzazioni + Rete non disponibile + Nessuna connessione internet + Errore JSON + Versione installata: + Versione testata: + Nessun tracciatore testato trovato + Nessuna autorizzazione richiesta dall\'applicazione + Non ci sono report per la versione installata (%1$s), le informazioni mostrate sono basate su un\'altra (%2$s) versione + Questa app non è ancora stata analizzata da Exodus Privacy. + Sembra che il tuo sistema non permetta l\'accesso all\'elenco di app installate. + Sembra che tu non abbia nessuna app installata dalla fonte che testiamo (Google Play store o F-Droid). + Rilevazione applicazioni: in attesa della connessione al server + Rilevazione applicazioni + Elaborazione applicazioni: + Elaborazione tracciatori: + Rilevazione tracciatori: in attesa della connessione al server + Rilevazione tracker + Questa app non ha impostazioni + %1 Logo dell\'applicazione + Creato da + Report creato il + ae aggiornato il + Vedi su εxodus ➤ + Vedi su Google Play ➤ + Vedi su F-Droid ➤ + scaricamenti + Abbiamo trovato i codici di firma dei seguenti tracker nell\'applicazione: + Non abbiamo trovato il codice di firma di alcun tracker che conosciamo nell\'applicazione. + Abbiamo trovato i permessi seguenti nell\'applicazione: + Questa applicazione non richiede alcun permesso. + Un tracker è un software destinato a raccogliere dati su di te o sui tuoi utilizzi. Per saperne di più… + Le autorizzazioni sono azioni che l\'applicazione può fare sul tuo telefono. Per saperne di più… + L\'icona ! indica un livello \'Pericoloso\' o \'Speciale\' in base ai livelli di protezione di Google. + Pagina web del tracker ➤ + Regole di rilevamento + Regola di rilevamento (codice): + Regola di rilevamento (rete): + Presente in %d applicazioni + Presente in: + Questo tracker non sembra essere presente nelle tue applicazioni + Fonte: %s + + Filtra + Impostazioni dell\'applicazione + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000..78134ee --- /dev/null +++ b/app/src/main/res/values-ru/strings.xml @@ -0,0 +1,52 @@ + + + Логотип приложения + Трекеры: + Разрешения: + трекеров + разрешений + Сеть недоступна + Нет подключения к интернету + Ошибка JSON + Установленная версия:  + Изученная версия:  + Не найдено ни одного известного трекера + Это приложение не запрашивает никаких разрешений + Данные по установленной версии (%1$s) отсутствуют, показана информация на основе изученной (%2$s) версии + Это приложение ещё не было изучено Exodus Privacy. + Похоже, ваша система не разрешает доступ к списку установленных приложений. + Похоже, у вас нет приложений, установленных из источника, который мы тестируем (магазин Google Play или F-Droid). + Получение приложений: ожидание подключения к серверу + Получение приложений + Обработка приложений: + Обработка трекеров: + Получение трекеров: ожидание подключения к серверу + Получение трекеров + Это приложение не имеет настроек + Логотип приложения %1 + Создано + Отчёт создан + и обновлено + εxodus ➤ + Google Play ➤ + F-Droid ➤ + загрузок + В приложении обнаружены сигнатуры следующих трекеров: + В приложении не обнаружено сигнатур ни одного известного трекера. + В приложении обнаружены следующее разрешения: + Приложение не требует никаких разрешений. + Трекер — это часть приложения, предназначенная для сбора данных о вас или ваших действиях. Подробнее… + Разрешения — это действия, которые приложение сможет выполнять на вашем устройстве. Подробнее… + Значок ! обозначает уровень угрозы «Опасность» или «Особый» в соответствии с уровнем защиты Google. + Сайт трекера ➤ + Определяющие правила + Правило обнаружения кода: + Правило определения сети: + Присутствует в %d приложениях + Присутствует в: + Этот трекер не присутствует в ваших приложениях + Источник: %s + + Фильтр + Настройки приложения + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index d313a7c..695a7ac 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,4 +3,19 @@ #684971 #3d2b43 #684971 + + #6fc384 + #e46772 + #ffdb66 + + #17a2b8 + #ffc70f + #ff8c00 + #e61718 + + #684971 + #343A40 + #6C757D + #FFFFFF + #E83E8C diff --git a/app/src/main/res/values/exodus.xml b/app/src/main/res/values/exodus.xml index 7d11892..c41d051 100644 --- a/app/src/main/res/values/exodus.xml +++ b/app/src/main/res/values/exodus.xml @@ -1,4 +1,6 @@ 9c6106a229bc5f34b5802e5861bcb87d1626617d + εxodus + Exodus \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9e2a02e..7db2975 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,26 +1,53 @@ - Exodus Privacy Application Logo - Trackers: - Permissions: + Trackers: + Permissions: + Trackers + Permissions Network Unavailable No Internet Connection JSON Error Installed Version: Tested Version: - No tested trackers were found - No permissions are requested by this application - There\'s no report for the installed version, informations displayed are based on another (presumably older) version + The application could contain tracker(s) we do not know yet. + This application doesn\'t require any permissions. + There\'s no report for the installed version (%1$s), the information displayed is based on another (%2$s) version This app hasn\'t been analysed by Exodus Privacy yet. - Your system seems doesn\'t provide access to installed apps. - You seems not having any apps installed by source we search for (Google Play). + It appears that your system doesn\'t allow access to the list of installed apps. + It appears that you don\'t have any apps installed from the source we test (Google Play store or F-Droid). Getting applications: Waiting for server connection Getting applications Processing Applications: Processing Trackers: Getting trackers: Waiting for server connection Getting trackers + This app doesn\'t have settings + %1 Application Logo + Created By + Report created on + and updated on + See on εxodus ➤ + See on Google Play ➤ + See on F-Droid ➤ + downloads + We have found code signature of the following trackers in the application: + We have not found code signature of any tracker we know in the application. + We have found the following permissions in the application: + This application doesn\'t require any permissions. + A tracker is a piece of software meant to collect data about you or your usages. Learn more… + Permissions are actions the application can do on your phone. Learn more… + The icon ! indicates a \'Dangerous\' or \'Special\' level according to Google\'s protection levels. + Tracker web page ➤ + Detection Rules + Code detection rule: + Network detection rule: + Present in %d of your applications + Present in: + This tracker seems not be present in your applications + Source: %s Filter + Application Settings + diff --git a/build.gradle b/build.gradle index 4b0df51..54dcbea 100644 --- a/build.gradle +++ b/build.gradle @@ -6,9 +6,10 @@ buildscript { maven { url "https://maven.google.com" } + google() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:4.0.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/fastlane/metadata/android/ar/full_description.txt b/fastlane/metadata/android/ar/full_description.txt new file mode 100644 index 0000000..4f34966 --- /dev/null +++ b/fastlane/metadata/android/ar/full_description.txt @@ -0,0 +1,7 @@ +يساعدك Exodus Privacy على معرفة أي مِن المتعقبين والتصريحات المدمجة في التطبيقات المثبتة على جهازك. + +يقوم التطبيق بتحميل التقارير من Exodus Privacy (https://exodus-privacy.eu.org/) ويعرضها عليك تطبيقًا تلو الآخر + +بإمكان التطبيق اكتشاف التطبيقات المثبتة من متجر Google Play فقط. + +الرمز المصدري: https://github.com/Exodus-Privacy/exodus-android-app/ diff --git a/fastlane/metadata/android/ar/short_description.txt b/fastlane/metadata/android/ar/short_description.txt new file mode 100644 index 0000000..1b99bb9 --- /dev/null +++ b/fastlane/metadata/android/ar/short_description.txt @@ -0,0 +1 @@ +يُظهِر المتعقّبِين والتصريحات التي تطلبها التطبيقات المثبتة الأخرى diff --git a/fastlane/metadata/android/ca/full_description.txt b/fastlane/metadata/android/ca/full_description.txt new file mode 100644 index 0000000..b0bf538 --- /dev/null +++ b/fastlane/metadata/android/ca/full_description.txt @@ -0,0 +1,7 @@ +Exodus Privacy us ajuda a saber quins rastrejadors i permisos estan incrustats en les aplicacions instal·lades al vostre dispositiu. + +The app downloads reports from Exodus Privacy (https://exodus-privacy.eu.org/) and shows them to you app by app + +L'aplicació només pot detectar les aplicacions instal·lades des de la botiga Google Play. + +Codi font: https://github.com/Exodus-Privacy/exodus-android-app/ diff --git a/fastlane/metadata/android/ca/short_description.txt b/fastlane/metadata/android/ca/short_description.txt new file mode 100644 index 0000000..9660e51 --- /dev/null +++ b/fastlane/metadata/android/ca/short_description.txt @@ -0,0 +1 @@ +Mostra els rastrejadors i els permisos de les aplicacions instal·lades diff --git a/fastlane/metadata/android/de-De/full_description.txt b/fastlane/metadata/android/de-De/full_description.txt new file mode 100644 index 0000000..f99557c --- /dev/null +++ b/fastlane/metadata/android/de-De/full_description.txt @@ -0,0 +1,7 @@ +Exodus Privacy hilft Ihnen zu wissen, welche Tracker und Berechtigungen in Apps eingebettet sind, die auf Ihrem Gerät installiert sind. + +Die App lädt Berichte von Exodus Privacy (https://exodus-privacy.eu.org/) herunter und zeigt sie für jede App + +Die App kann nur installierte Apps aus dem Google Play Store erkennen. + +Quellcode: https://github.com/Exodus-Privacy/exodus-android-app/ diff --git a/fastlane/metadata/android/de-De/short_description.txt b/fastlane/metadata/android/de-De/short_description.txt new file mode 100644 index 0000000..86bb116 --- /dev/null +++ b/fastlane/metadata/android/de-De/short_description.txt @@ -0,0 +1 @@ +Tracker und Berechtigungen anderer installierter Apps anzeigen diff --git a/fastlane/metadata/android/el/full_description.txt b/fastlane/metadata/android/el/full_description.txt new file mode 100644 index 0000000..dcd3f2a --- /dev/null +++ b/fastlane/metadata/android/el/full_description.txt @@ -0,0 +1,7 @@ +Το Exodus Privacy σάς βοηθά να γνωρίζετε ποια trackers και ποια δικαιώματα, βρίσκονται μέσα στις εφαρμογές που είναι εγκατεστημένες στην συσκευή σας. + +Η εφαρμογή κατεβάζει αναφορές από το Exodus Privacy (https://exodus-privacy.eu.org/) και τις εμφανίζει σε κάθε μία απο τις εφαρμογές σας + +Η εφαρμογή μπορεί να εντοπίσει μόνο εφαρμογές που έχουν εγκατασταθεί από το Google Play store. + +Πηγαίος Κώδικας: https://github.com/Exodus-Privacy/exodus-android-app/ diff --git a/fastlane/metadata/android/el/short_description.txt b/fastlane/metadata/android/el/short_description.txt new file mode 100644 index 0000000..ce04f98 --- /dev/null +++ b/fastlane/metadata/android/el/short_description.txt @@ -0,0 +1 @@ +Εμφάνιση ιχνηλατών και αδειών από άλλες εγκατεστημένες εφαρμογές diff --git a/fastlane/metadata/android/en-US/changelogs/7.txt b/fastlane/metadata/android/en-US/changelogs/7.txt new file mode 100644 index 0000000..9c51440 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/7.txt @@ -0,0 +1,4 @@ +- speedup navigation (thanks to borsini) +- add shortcut to apps settings +- add version identifier when app version doesn't match (thanks to tdelmas) +- bug fix diff --git a/fastlane/metadata/android/en-US/changelogs/8.txt b/fastlane/metadata/android/en-US/changelogs/8.txt new file mode 100644 index 0000000..9c22f63 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/8.txt @@ -0,0 +1,2 @@ +- rewrite of report page +- bug fix diff --git a/fastlane/metadata/android/en-US/changelogs/9.txt b/fastlane/metadata/android/en-US/changelogs/9.txt new file mode 100644 index 0000000..7e02cca --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/9.txt @@ -0,0 +1,4 @@ +- Add Trackers Page +- Add F-Droid detection Support +- bug fix +- New Translations (Italian, Russian, Greek, Arabic, German, and Catalan) diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt new file mode 100644 index 0000000..3d32d23 --- /dev/null +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -0,0 +1,7 @@ +Exodus Privacy helps you to know which trackers and permissions are embedded in apps installed on your device. + +The app downloads reports from Exodus Privacy (https://exodus-privacy.eu.org/) and shows them to you app by app + +The app can only detect apps installed from the Google Play store. + +Source Code: https://github.com/Exodus-Privacy/exodus-android-app/ diff --git a/fastlane/metadata/android/en-US/images/featureGraphic.png b/fastlane/metadata/android/en-US/images/featureGraphic.png new file mode 100644 index 0000000..868f86f Binary files /dev/null and b/fastlane/metadata/android/en-US/images/featureGraphic.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/app_list.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/app_list.png new file mode 100644 index 0000000..7969943 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/app_list.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/report1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/report1.png new file mode 100644 index 0000000..49a39f9 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/report1.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/report2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/report2.png new file mode 100644 index 0000000..8f2f35d Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/report2.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/report2_2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/report2_2.png new file mode 100644 index 0000000..81835db Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/report2_2.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/tracker1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/tracker1.png new file mode 100644 index 0000000..f188cc2 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/tracker1.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/tracker2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/tracker2.png new file mode 100644 index 0000000..cbd060c Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/tracker2.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/tracker2_2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/tracker2_2.png new file mode 100644 index 0000000..9263a54 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/tracker2_2.png differ diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt new file mode 100644 index 0000000..85aced7 --- /dev/null +++ b/fastlane/metadata/android/en-US/short_description.txt @@ -0,0 +1 @@ +Show trackers and permissions from other installed apps diff --git a/fastlane/metadata/android/fr-FR/changelogs/7.txt b/fastlane/metadata/android/fr-FR/changelogs/7.txt new file mode 100644 index 0000000..d120d55 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/7.txt @@ -0,0 +1,4 @@ +- Amélioration de la vitesse de navigation (grâce à borsini) +- Ajout d'un raccourci vers les paramètres des applications +- Ajout du numéro de version lorsque la version de l'application ne correspond pas (grâce à tdelmas) +- Correction de bug diff --git a/fastlane/metadata/android/fr-FR/changelogs/8.txt b/fastlane/metadata/android/fr-FR/changelogs/8.txt new file mode 100644 index 0000000..bc33ab9 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/8.txt @@ -0,0 +1,2 @@ +- Refonte de la page des rapports +- Correction de bug diff --git a/fastlane/metadata/android/fr-FR/changelogs/9.txt b/fastlane/metadata/android/fr-FR/changelogs/9.txt new file mode 100644 index 0000000..dd1dbb0 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/9.txt @@ -0,0 +1,4 @@ +- Ajout de la page des pisteurs +- Ajout de la détection pour F-Droid +- Correction de bug +- Nouvelles Traductions (Italien, Russe, Grec , Arabe, Allemand et Catalan) diff --git a/fastlane/metadata/android/fr-FR/full_description.txt b/fastlane/metadata/android/fr-FR/full_description.txt new file mode 100644 index 0000000..345ea81 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/full_description.txt @@ -0,0 +1,7 @@ +Exodus Privacy vous aide à savoir quels sont les pisteurs et permissions embarqués dans les applications installées sur votre smartphone. + +L'application télécharge les rapports d'Exodus Privacy (https://exodus-privacy.eu.org/) et vous les montre application par application. + +L'application ne peut détecter que les applications installées depuis le Google Play Store. + +Code source: https://github.com/Exodus-Privacy/exodus-android-app/ diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/app_list.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/app_list.png new file mode 100644 index 0000000..ec03436 Binary files /dev/null and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/app_list.png differ diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/report1.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/report1.png new file mode 100644 index 0000000..79b04eb Binary files /dev/null and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/report1.png differ diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/report2.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/report2.png new file mode 100644 index 0000000..8983e8c Binary files /dev/null and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/report2.png differ diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/report2_2.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/report2_2.png new file mode 100644 index 0000000..d09f28b Binary files /dev/null and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/report2_2.png differ diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/tracker1.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/tracker1.png new file mode 100644 index 0000000..e543caf Binary files /dev/null and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/tracker1.png differ diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/tracker2.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/tracker2.png new file mode 100644 index 0000000..061dfb4 Binary files /dev/null and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/tracker2.png differ diff --git a/fastlane/metadata/android/fr-FR/images/phoneScreenshots/tracker2_2.png b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/tracker2_2.png new file mode 100644 index 0000000..a4958b9 Binary files /dev/null and b/fastlane/metadata/android/fr-FR/images/phoneScreenshots/tracker2_2.png differ diff --git a/fastlane/metadata/android/fr-FR/short_description.txt b/fastlane/metadata/android/fr-FR/short_description.txt new file mode 100644 index 0000000..3a6c587 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/short_description.txt @@ -0,0 +1 @@ +Montre les pisteurs et permissions des applications installées diff --git a/fastlane/metadata/android/it/full_description.txt b/fastlane/metadata/android/it/full_description.txt new file mode 100644 index 0000000..4862a76 --- /dev/null +++ b/fastlane/metadata/android/it/full_description.txt @@ -0,0 +1,7 @@ +Exodus Privacy ti aiuta a sapere quali tracker e permessi sono incorporati nelle applicazioni installate sul tuo smartphone. + +L'app scarica i report da Exodus Privacy (https://exodus-privacy.eu.org/) e li mostra all'app per app + +L'applicazione può rilevare solo le applicazioni installate dal Google Play store. + +Codice sorgente: https://github.com/Exodus-Privacy/exodus-android-app/ diff --git a/fastlane/metadata/android/it/short_description.txt b/fastlane/metadata/android/it/short_description.txt new file mode 100644 index 0000000..6c7ebd1 --- /dev/null +++ b/fastlane/metadata/android/it/short_description.txt @@ -0,0 +1 @@ +Mostra tracker e permessi da altre applicazioni installate diff --git a/fastlane/metadata/android/ru-RU/full_description.txt b/fastlane/metadata/android/ru-RU/full_description.txt new file mode 100644 index 0000000..bdeecfc --- /dev/null +++ b/fastlane/metadata/android/ru-RU/full_description.txt @@ -0,0 +1,7 @@ +Exodus Privacy поможет узнать, какие трекеры и разрешения встроены в приложения, установленные на вашем устройстве. + +Оно загружает отчёты из базы Exodus Privacy (https://exodus-privacy.eu.org/) и показывает их для каждого приложения. + +Проанализированы могут быть только приложения, установленные из Google Play. + +Исходный код: https://github.com/Exodus-Privacy/exodus-android-app/ diff --git a/fastlane/metadata/android/ru-RU/short_description.txt b/fastlane/metadata/android/ru-RU/short_description.txt new file mode 100644 index 0000000..d94b1be --- /dev/null +++ b/fastlane/metadata/android/ru-RU/short_description.txt @@ -0,0 +1 @@ +Показывает трекеры и разрешения установленных приложений diff --git a/gradle.properties b/gradle.properties index aac7c9b..0566c22 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,3 +15,10 @@ org.gradle.jvmargs=-Xmx1536m # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..457aad0 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4f8b8b4..bf0f15b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,6 @@ -#Sun Dec 03 16:44:03 CET 2017 +#Tue Jul 07 01:06:44 CEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip -distributionSha256Sum=5c07b3bac2209fbc98fb1fdf6fd831f72429cdf8c503807404eae03d8c8099e5 +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..af6708f --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..0f8d593 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega