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 bf4045d..ced7510 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 @@ -53,6 +53,7 @@ import org.eu.exodus_privacy.exodusprivacy.adapters.TrackerListAdapter; import org.eu.exodus_privacy.exodusprivacy.databinding.MainBinding; import org.eu.exodus_privacy.exodusprivacy.fragments.ComputeAppList; import org.eu.exodus_privacy.exodusprivacy.fragments.HomeFragment; +import org.eu.exodus_privacy.exodusprivacy.fragments.MyTrackersFragment; import org.eu.exodus_privacy.exodusprivacy.fragments.ReportFragment; import org.eu.exodus_privacy.exodusprivacy.fragments.TrackerFragment; import org.eu.exodus_privacy.exodusprivacy.fragments.Updatable; @@ -332,7 +333,7 @@ public class MainActivity extends AppCompatActivity { public Fragment getItem(final int position) { switch (position) { case 1: - return new HomeFragment(); + return new MyTrackersFragment(); default: return home; } 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 index 6b82edd..182e208 100644 --- a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/Utils.java +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/Utils.java @@ -16,8 +16,14 @@ import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.Date; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -281,4 +287,17 @@ public class Utils { } return builder.toString(); } + + + public static Map sortByValue(Map map) { + List> list = new LinkedList<>(map.entrySet()); + Collections.sort(list, (Comparator) (o1, o2) -> ((Comparable) ((Map.Entry) (o1)).getValue()).compareTo(((Map.Entry) (o2)).getValue())); + + Map result = new LinkedHashMap<>(); + for (Map.Entry entry : list) { + result.put(entry.getKey(), entry.getValue()); + } + + return result; + } } diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/MyTrackersListAdapter.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/MyTrackersListAdapter.java new file mode 100644 index 0000000..6936a94 --- /dev/null +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/adapters/MyTrackersListAdapter.java @@ -0,0 +1,103 @@ +package org.eu.exodus_privacy.exodusprivacy.adapters; +/* + * Copyright (C) 2020 Thomas Schneider + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import org.eu.exodus_privacy.exodusprivacy.R; +import org.eu.exodus_privacy.exodusprivacy.databinding.MyTrackerItemBinding; +import org.eu.exodus_privacy.exodusprivacy.objects.MyTracker; +import org.eu.exodus_privacy.exodusprivacy.objects.Tracker; + +import java.util.ArrayList; +import java.util.List; + + +public class MyTrackersListAdapter extends RecyclerView.Adapter { + + private final TrackerClickListener trackerClickListener; + private final List myTrackers; + private List trackersList; + + public MyTrackersListAdapter(List mTrackers, TrackerClickListener listener) { + myTrackers = mTrackers; + setTrackers(mTrackers); + trackerClickListener = listener; + + } + + @NonNull + @Override + public MyTrackersListAdapter.TrackerListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + MyTrackerItemBinding itemBinding = MyTrackerItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new TrackerListViewHolder(itemBinding); + } + + @Override + public void onBindViewHolder(@NonNull MyTrackersListAdapter.TrackerListViewHolder holder, int position) { + Tracker tracker = trackersList.get(position); + if (tracker != null) { + holder.viewDataBinding.trackerName.setText(tracker.name); + holder.viewDataBinding.trackerCount.setText(String.valueOf(countOccurences(tracker.codeSignature))); + holder.viewDataBinding.getRoot().setOnClickListener(v -> trackerClickListener.onTrackerClick(tracker.id)); + } else + holder.viewDataBinding.trackerName.setText(R.string.no_trackers); + } + + @Override + public int getItemCount() { + return trackersList.size(); + } + + public void setTrackers(List myTrackers) { + trackersList = new ArrayList<>(); + if (myTrackers != null) { + for (MyTracker myTracker : myTrackers) { + trackersList.add(myTracker.tracker); + } + } + } + + private int countOccurences(String signature) { + if (myTrackers == null) return 0; + for (MyTracker myTracker : myTrackers) { + if (myTracker.signature.compareTo(signature) == 0) return myTracker.number; + } + return 0; + } + + public interface TrackerClickListener { + void onTrackerClick(long trackerId); + } + + static class TrackerListViewHolder extends RecyclerView.ViewHolder { + + MyTrackerItemBinding viewDataBinding; + + TrackerListViewHolder(MyTrackerItemBinding dataBinding) { + super(dataBinding.getRoot()); + viewDataBinding = dataBinding; + } + + } +} + 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 deleted file mode 100644 index 7a83752..0000000 --- a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/ComputeAppListTask.java +++ /dev/null @@ -1,173 +0,0 @@ -package org.eu.exodus_privacy.exodusprivacy.fragments; - -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.AsyncTask; - -import org.eu.exodus_privacy.exodusprivacy.Utils; -import org.eu.exodus_privacy.exodusprivacy.adapters.ApplicationViewModel; -import org.eu.exodus_privacy.exodusprivacy.manager.DatabaseManager; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -public class ComputeAppListTask extends AsyncTask> { - - private static final String gStore = "com.android.vending"; - private static final String fdroid = "ord.fdroid.fdroid"; - private final WeakReference packageManagerRef; - private final WeakReference databaseManagerRef; - private final WeakReference listenerRef; - - order userOrderChoice; - - ComputeAppListTask(WeakReference packageManagerRef, - WeakReference databaseManagerRef, - WeakReference listenerRef, order orderChoice) { - this.packageManagerRef = packageManagerRef; - this.databaseManagerRef = databaseManagerRef; - this.listenerRef = listenerRef; - userOrderChoice = orderChoice; - } - - 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); - } - //Reordering should done here - if (userOrderChoice == null) { - userOrderChoice = order.DEFAULT; - } - vms = order(vms, userOrderChoice); - return vms; - } - - private List order(List vms, order orderChoice) { - if (orderChoice == order.LESS_TRACKERS) { - Collections.sort(vms, (obj1, obj2) -> Integer.compare(obj1.requestedPermissions != null ? obj1.requestedPermissions.length : 0, obj2.requestedPermissions != null ? obj2.requestedPermissions.length : 0)); - Collections.sort(vms, (obj1, obj2) -> Integer.compare(obj1.trackers != null ? obj1.trackers.size() : 0, obj2.trackers != null ? obj2.trackers.size() : 0)); - } else if (orderChoice == order.MOST_TRACKERS) { - Collections.sort(vms, (obj1, obj2) -> Integer.compare(obj2.requestedPermissions != null ? obj2.requestedPermissions.length : 0, obj1.requestedPermissions != null ? obj1.requestedPermissions.length : 0)); - Collections.sort(vms, (obj1, obj2) -> Integer.compare(obj2.trackers != null ? obj2.trackers.size() : 0, obj1.trackers != null ? obj1.trackers.size() : 0)); - } else if (orderChoice == order.LESS_PERMISSIONS) { - Collections.sort(vms, (obj1, obj2) -> Integer.compare(obj1.trackers != null ? obj1.trackers.size() : 0, obj2.trackers != null ? obj2.trackers.size() : 0)); - Collections.sort(vms, (obj1, obj2) -> Integer.compare(obj1.requestedPermissions != null ? obj1.requestedPermissions.length : 0, obj2.requestedPermissions != null ? obj2.requestedPermissions.length : 0)); - } else if (orderChoice == order.MOST_PERMISSIONS) { - Collections.sort(vms, (obj1, obj2) -> Integer.compare(obj2.trackers != null ? obj2.trackers.size() : 0, obj1.trackers != null ? obj1.trackers.size() : 0)); - Collections.sort(vms, (obj1, obj2) -> Integer.compare(obj2.requestedPermissions != null ? obj2.requestedPermissions.length : 0, obj1.requestedPermissions != null ? obj1.requestedPermissions.length : 0)); - } else { - Collections.sort(vms, (obj1, obj2) -> String.valueOf(obj1.label).compareToIgnoreCase(String.valueOf(obj2.label))); - } - 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; - } - - public enum order { - DEFAULT, - MOST_TRACKERS, - LESS_TRACKERS, - MOST_PERMISSIONS, - LESS_PERMISSIONS, - } - - interface Listener { - void onAppsComputed(List apps); - } - -} diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/MyTrackersFragment.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/MyTrackersFragment.java new file mode 100644 index 0000000..e4582a9 --- /dev/null +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/fragments/MyTrackersFragment.java @@ -0,0 +1,140 @@ +package org.eu.exodus_privacy.exodusprivacy.fragments; +/* + * Copyright (C) 2020 Thomas Schneider + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; + +import org.eu.exodus_privacy.exodusprivacy.R; +import org.eu.exodus_privacy.exodusprivacy.adapters.MyTrackersListAdapter; +import org.eu.exodus_privacy.exodusprivacy.databinding.MyTrackersBinding; +import org.eu.exodus_privacy.exodusprivacy.manager.DatabaseManager; +import org.eu.exodus_privacy.exodusprivacy.objects.MyTracker; +import org.eu.exodus_privacy.exodusprivacy.objects.Report; +import org.eu.exodus_privacy.exodusprivacy.objects.Tracker; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class MyTrackersFragment extends Fragment implements MyTrackersListAdapter.TrackerClickListener { + + private Context context; + private MyTrackersBinding trackerBinding; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + context = getContext(); + trackerBinding = MyTrackersBinding.inflate(LayoutInflater.from(context)); + return trackerBinding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + Context context = trackerBinding.getRoot().getContext(); + trackerBinding.loader.setVisibility(View.VISIBLE); + trackerBinding.trackers.setVisibility(View.GONE); + new Thread(() -> { + DatabaseManager databaseManager = DatabaseManager.getInstance(getActivity()); + PackageManager packageManager = context.getPackageManager(); + List packageInstalled = packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS); + List myTrackers = new ArrayList<>(); + List added = new ArrayList<>(); + for (PackageInfo pkgInfo : packageInstalled) { + Report report; + if (pkgInfo.versionName != null) + report = databaseManager.getReportFor(pkgInfo.packageName, pkgInfo.versionName, null); + else { + report = databaseManager.getReportFor(pkgInfo.packageName, pkgInfo.versionCode, null); + } + if (report != null) { + Set trackersApp = databaseManager.getTrackers(report.trackers); + for (Tracker tracker : trackersApp) { + if (added.contains(tracker.codeSignature)) { + for (MyTracker myTracker : myTrackers) { + if (myTracker.signature.compareTo(tracker.codeSignature) == 0) { + myTracker.number += 1; + } + } + } else { + MyTracker myTracker = new MyTracker(); + myTracker.signature = tracker.codeSignature; + myTracker.number = 1; + myTracker.tracker = tracker; + myTrackers.add(myTracker); + added.add(myTracker.signature); + } + } + } + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> { + Collections.sort(myTrackers, (obj1, obj2) -> Integer.compare(obj2.number, obj1.number)); + MyTrackersListAdapter myTrackersListAdapter = new MyTrackersListAdapter(myTrackers, MyTrackersFragment.this); + trackerBinding.trackers.setAdapter(myTrackersListAdapter); + trackerBinding.trackers.setLayoutManager(new LinearLayoutManager(context)); + trackerBinding.trackers.setVisibility(View.VISIBLE); + trackerBinding.loader.setVisibility(View.GONE); + }; + mainHandler.post(myRunnable); + + + }).start(); + + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + this.context = context; + } + + + @Override + public void onPrepareOptionsMenu(Menu menu) { + menu.findItem(R.id.action_filter).setVisible(false); + menu.findItem(R.id.action_settings).setVisible(false); + menu.findItem(R.id.action_filter_options).setVisible(false); + } + + + @Override + public void onTrackerClick(long trackerId) { + + } +} 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 b86e52d..b6bfff1 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 @@ -221,11 +221,18 @@ public class DatabaseManager extends SQLiteOpenHelper { if (cursor.moveToFirst()) { long appId = cursor.getLong(0); cursor.close(); - where = "app_id = ? and version = ? and source = ?"; - whereArgs = new String[3]; - whereArgs[0] = String.valueOf(appId); - whereArgs[1] = version; - whereArgs[2] = source; + if (source != null) { + where = "app_id = ? and version = ? and source = ?"; + whereArgs = new String[3]; + whereArgs[0] = String.valueOf(appId); + whereArgs[1] = version; + whereArgs[2] = source; + } else { + where = "app_id = ? and version = ?"; + whereArgs = new String[2]; + whereArgs[0] = String.valueOf(appId); + whereArgs[1] = version; + } String order = "id ASC"; cursor = db.query("reports", columns, where, whereArgs, null, null, order); long reportId; @@ -234,13 +241,20 @@ public class DatabaseManager extends SQLiteOpenHelper { cursor.close(); } else { cursor.close(); + columns = new String[2]; columns[0] = "id"; columns[1] = "creation"; - where = "app_id = ? and source = ?"; - whereArgs = new String[2]; - whereArgs[0] = String.valueOf(appId); - whereArgs[1] = source; + if (source != null) { + where = "app_id = ? and source = ?"; + whereArgs = new String[2]; + whereArgs[0] = String.valueOf(appId); + whereArgs[1] = source; + } else { + where = "app_id = ?"; + whereArgs = new String[1]; + whereArgs[0] = String.valueOf(appId); + } order = "creation DESC"; //search a recent reports cursor = db.query("reports", columns, where, whereArgs, null, null, order); diff --git a/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/MyTracker.java b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/MyTracker.java new file mode 100644 index 0000000..87c3f8f --- /dev/null +++ b/app/src/main/java/org/eu/exodus_privacy/exodusprivacy/objects/MyTracker.java @@ -0,0 +1,8 @@ +package org.eu.exodus_privacy.exodusprivacy.objects; + +public class MyTracker { + + public String signature; + public Tracker tracker; + public int number; +} diff --git a/app/src/main/res/drawable/ic_baseline_navigate_next_24.xml b/app/src/main/res/drawable/ic_baseline_navigate_next_24.xml new file mode 100644 index 0000000..f8eaa40 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_navigate_next_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/my_tracker_item.xml b/app/src/main/res/layout/my_tracker_item.xml new file mode 100644 index 0000000..88f22d7 --- /dev/null +++ b/app/src/main/res/layout/my_tracker_item.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/my_trackers.xml b/app/src/main/res/layout/my_trackers.xml new file mode 100644 index 0000000..f54510b --- /dev/null +++ b/app/src/main/res/layout/my_trackers.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/tracker.xml b/app/src/main/res/layout/tracker.xml index b7b24dd..2c3cc50 100644 --- a/app/src/main/res/layout/tracker.xml +++ b/app/src/main/res/layout/tracker.xml @@ -6,6 +6,7 @@