diff options
| author | cyberta <cyberta@riseup.net> | 2026-04-06 00:42:22 +0200 |
|---|---|---|
| committer | cyberta <cyberta@riseup.net> | 2026-04-15 23:56:05 +0000 |
| commit | ba4e1500eb9813c2e88eea1d23caa432a21c019c (patch) | |
| tree | bfe6f4d7b221104c4723c6cabc2b52dbfe202e3b | |
| parent | 428a0bf3c4048e97b6fb96277672d1ddcad045fc (diff) | |
update excluded apps list on launch and dynamically after an application gets removed while the app is running. This fixes a wrong counter of excluded apps in the UI
5 files changed, 172 insertions, 69 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/AppQueryReceiver.java b/app/src/main/java/se/leap/bitmaskclient/base/AppQueryReceiver.java new file mode 100644 index 00000000..d5788345 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/AppQueryReceiver.java @@ -0,0 +1,36 @@ +package se.leap.bitmaskclient.base; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import se.leap.bitmaskclient.base.utils.ApplicationInfoManager; + +public class AppQueryReceiver extends BroadcastReceiver { + + private static final String TAG = AppQueryReceiver.class.getSimpleName(); + + @Override + public void onReceive(Context context, Intent intent) { + if (context == null || intent == null || + intent.getAction() == null || intent.getData() == null || + !intent.getAction().equals(Intent.ACTION_PACKAGE_REMOVED) || + intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + return; + } + + String packageName = intent.getData().getEncodedSchemeSpecificPart(); + if (packageName != null) { + ApplicationInfoManager appManager = new ApplicationInfoManager(context); + appManager.onApplicationRemoved(packageName); + } + } + + public static void register(Context context) { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addDataScheme("package"); + context.registerReceiver(new AppQueryReceiver(), filter); + } +}
\ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java b/app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java index cbe954db..ae4d7e08 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java @@ -45,6 +45,7 @@ import java.security.Security; import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.appUpdate.DownloadBroadcastReceiver; import se.leap.bitmaskclient.base.models.ProviderObservable; +import se.leap.bitmaskclient.base.utils.ApplicationInfoManager; import se.leap.bitmaskclient.base.utils.ClientTransportPluginProvider; import se.leap.bitmaskclient.base.utils.PRNGFixes; import se.leap.bitmaskclient.base.utils.PreferenceHelper; @@ -103,6 +104,12 @@ public class BitmaskApp extends MultiDexApplication implements DefaultLifecycleO intentFilter.addCategory(CATEGORY_DEFAULT); LocalBroadcastManager.getInstance(this.getApplicationContext()).registerReceiver(downloadBroadcastReceiver, intentFilter); } + AppQueryReceiver.register(this); + new Thread(() -> { + ApplicationInfoManager applicationInfoManager = new ApplicationInfoManager(BitmaskApp.this); + applicationInfoManager.updateExcludedApps(); + }).start(); + } @Override diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/ExcludeAppsFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/ExcludeAppsFragment.java index 07608211..57217a4f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/ExcludeAppsFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/ExcludeAppsFragment.java @@ -7,11 +7,9 @@ package se.leap.bitmaskclient.base.fragments; import static se.leap.bitmaskclient.R.string.exclude_apps_fragment_title; -import android.Manifest; import android.app.Activity; import android.content.Context; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; @@ -31,14 +29,13 @@ import android.widget.SearchView; import androidx.appcompat.widget.AppCompatTextView; import androidx.fragment.app.Fragment; -import java.util.Collections; -import java.util.List; import java.util.Locale; import java.util.Set; import java.util.Vector; import de.blinkt.openvpn.VpnProfile; import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.base.utils.ApplicationInfoManager; import se.leap.bitmaskclient.base.utils.PreferenceHelper; import se.leap.bitmaskclient.base.utils.ViewHelper; import se.leap.bitmaskclient.base.views.SimpleCheckBox; @@ -113,8 +110,8 @@ public class ExcludeAppsFragment extends Fragment implements AdapterView.OnItemC class PackageAdapter extends BaseAdapter implements Filterable { private Vector<ApplicationInfo> mPackages; private final LayoutInflater mInflater; - private final PackageManager mPm; - private ItemFilter mFilter = new ItemFilter(); + private final ApplicationInfoManager mAppInfoManager; + private final ItemFilter mFilter = new ItemFilter(); private Vector<ApplicationInfo> mFilteredData; @@ -132,7 +129,7 @@ public class ExcludeAppsFragment extends Fragment implements AdapterView.OnItemC for (int i = 0; i < count; i++) { ApplicationInfo pInfo = mPackages.get(i); - CharSequence appName = pInfo.loadLabel(mPm); + CharSequence appName = mAppInfoManager.loadLabel(pInfo); if (TextUtils.isEmpty(appName)) appName = pInfo.packageName; @@ -156,12 +153,10 @@ public class ExcludeAppsFragment extends Fragment implements AdapterView.OnItemC mFilteredData = (Vector<ApplicationInfo>) results.values; notifyDataSetChanged(); } - } - PackageAdapter(Context c, VpnProfile vp) { - mPm = c.getPackageManager(); + mAppInfoManager = new ApplicationInfoManager(c); mProfile = vp; mInflater = LayoutInflater.from(c); @@ -170,40 +165,10 @@ public class ExcludeAppsFragment extends Fragment implements AdapterView.OnItemC } private void populateList(Activity c) { - List<ApplicationInfo> installedPackages = mPm.getInstalledApplications(PackageManager.GET_META_DATA); - - // Remove apps not using Internet - - int androidSystemUid = 0; - ApplicationInfo system = null; - Vector<ApplicationInfo> apps = new Vector<ApplicationInfo>(); - - try { - system = mPm.getApplicationInfo("android", PackageManager.GET_META_DATA); - androidSystemUid = system.uid; - apps.add(system); - } catch (PackageManager.NameNotFoundException e) { - } - - - for (ApplicationInfo app : installedPackages) { - - if (mPm.checkPermission(Manifest.permission.INTERNET, app.packageName) == PackageManager.PERMISSION_GRANTED && - app.uid != androidSystemUid) { - - apps.add(app); - } - } - - Collections.sort(apps, new ApplicationInfo.DisplayNameComparator(mPm)); + Vector<ApplicationInfo> apps = mAppInfoManager.getApplicationInfos(); mPackages = apps; mFilteredData = apps; - c.runOnUiThread(new Runnable() { - @Override - public void run() { - notifyDataSetChanged(); - } - }); + c.runOnUiThread(this::notifyDataSetChanged); } @Override @@ -229,12 +194,12 @@ public class ExcludeAppsFragment extends Fragment implements AdapterView.OnItemC final ApplicationInfo mInfo = mFilteredData.get(position); - CharSequence appName = mInfo.loadLabel(mPm); + CharSequence appName = mAppInfoManager.loadLabel(mInfo); if (TextUtils.isEmpty(appName)) appName = mInfo.packageName; viewHolder.appName.setText(appName); - viewHolder.appIcon.setImageDrawable(mInfo.loadIcon(mPm)); + viewHolder.appIcon.setImageDrawable(mAppInfoManager.loadDrawable(mInfo)); viewHolder.checkBox.setTag(mInfo.packageName); viewHolder.checkBox.setOnCheckedChangeListener(ExcludeAppsFragment.this); viewHolder.checkBox.setChecked(apps.contains(mInfo.packageName)); @@ -272,32 +237,34 @@ public class ExcludeAppsFragment extends Fragment implements AdapterView.OnItemC public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.allowed_apps, menu); SearchView searchView = (SearchView) menu.findItem( R.id.app_search_widget ).getActionView(); - searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String query) { - mListView.setFilterText(query); - mListView.setTextFilterEnabled(true); - return true; - } - - @Override - public boolean onQueryTextChange(String newText) { - mListView.setFilterText(newText); - if (TextUtils.isEmpty(newText)) - mListView.setTextFilterEnabled(false); - else + if (searchView != null) { + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + mListView.setFilterText(query); mListView.setTextFilterEnabled(true); + return true; + } - return true; - } - }); - searchView.setOnCloseListener(() -> { - mListView.clearTextFilter(); - mListAdapter.getFilter().filter(""); - return false; - }); - - tintSearchViewChild(searchView); + @Override + public boolean onQueryTextChange(String newText) { + mListView.setFilterText(newText); + if (TextUtils.isEmpty(newText)) + mListView.setTextFilterEnabled(false); + else + mListView.setTextFilterEnabled(true); + + return true; + } + }); + searchView.setOnCloseListener(() -> { + mListView.clearTextFilter(); + mListAdapter.getFilter().filter(""); + return false; + }); + + tintSearchViewChild(searchView); + } super.onCreateOptionsMenu(menu, inflater); } @@ -332,4 +299,10 @@ public class ExcludeAppsFragment extends Fragment implements AdapterView.OnItemC return v; } + @Override + public void onDestroyView() { + mListView.setOnItemClickListener(null); + mListAdapter = null; + super.onDestroyView(); + } } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java index d4d72812..c8d60b90 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java @@ -4,6 +4,7 @@ import static android.view.View.GONE; import static android.view.View.VISIBLE; import static se.leap.bitmaskclient.R.string.advanced_settings; import static se.leap.bitmaskclient.base.fragments.CensorshipCircumventionFragment.TUNNELING_AUTOMATICALLY; +import static se.leap.bitmaskclient.base.models.Constants.EXCLUDED_APPS; import static se.leap.bitmaskclient.base.models.Constants.GATEWAY_PINNING; import static se.leap.bitmaskclient.base.models.Constants.PREFER_UDP; import static se.leap.bitmaskclient.base.models.Constants.USE_BRIDGES; @@ -409,8 +410,9 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh initFirewallEntry(getView()); } else if (key.equals(GATEWAY_PINNING)) { initGatewayPinningEntry(rootView); + } else if (key.equals(EXCLUDED_APPS)) { + initExcludeAppsEntry(rootView); } - if (key.equals(USE_OBFUSCATION_PINNING) || key.equals(USE_BRIDGES)) { initObfuscationPinningEntry(rootView); } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/ApplicationInfoManager.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/ApplicationInfoManager.java new file mode 100644 index 00000000..1cf688fa --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/ApplicationInfoManager.java @@ -0,0 +1,85 @@ +package se.leap.bitmaskclient.base.utils; + +import android.Manifest; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.util.Log; + +import androidx.annotation.WorkerThread; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.Vector; +import java.util.function.Predicate; + +public class ApplicationInfoManager { + + public final static String TAG = ApplicationInfoManager.class.getSimpleName(); + final PackageManager packageManager; + public ApplicationInfoManager(Context context) { + packageManager = context.getPackageManager(); + } + + @WorkerThread + public Vector<ApplicationInfo> getApplicationInfos() { + List<ApplicationInfo> installedPackages = packageManager.getInstalledApplications(PackageManager.GET_META_DATA); + + // Remove apps not using Internet + + int androidSystemUid = 0; + ApplicationInfo system = null; + Vector<ApplicationInfo> apps = new Vector<>(); + + try { + system = packageManager.getApplicationInfo("android", PackageManager.GET_META_DATA); + androidSystemUid = system.uid; + apps.add(system); + } catch (PackageManager.NameNotFoundException e) { + } + + + for (ApplicationInfo app : installedPackages) { + + if (packageManager.checkPermission(Manifest.permission.INTERNET, app.packageName) == PackageManager.PERMISSION_GRANTED && + app.uid != androidSystemUid) { + + apps.add(app); + } + } + + Collections.sort(apps, new ApplicationInfo.DisplayNameComparator(packageManager)); + return apps; + } + + public CharSequence loadLabel(ApplicationInfo appInfo) { + return appInfo.loadLabel(packageManager); + } + + public Drawable loadDrawable(ApplicationInfo appInfo) { + return appInfo.loadIcon(packageManager); + } + + public void onApplicationRemoved(String appPackage) { + Set<String> excludedApps = PreferenceHelper.getExcludedApps(); + Log.d(TAG, "remove appPackage: " + appPackage); + excludedApps.remove(appPackage); + PreferenceHelper.setExcludedApps(excludedApps); + } + + public void updateExcludedApps() { + Set<String> excludedApps = PreferenceHelper.getExcludedApps(); + Log.d(TAG, "update excluded apps before: " + excludedApps.toString()); + List<ApplicationInfo> appInfos = getApplicationInfos(); + excludedApps.removeIf(s -> + appInfos.stream().noneMatch( + applicationInfo -> s.equals(applicationInfo.packageName) + ) + ); + Log.d(TAG, "update excluded apps after: " + excludedApps.toString()); + PreferenceHelper.setExcludedApps(excludedApps); + } +} |
