From dae77ebcaf9541f486ec56084bb1d381313ebbac Mon Sep 17 00:00:00 2001 From: john Date: Tue, 2 Dec 2025 12:43:54 +0100 Subject: [PATCH] first startup now contains permissions (fills ui immediately), added 'all subscriptions' option (set default) for set subscriptions option to log both subscription, made compatible to dynamically switch to single subscription: TODO: signal strenght logging still compatible for only one subscription; neighbour cells unidentifiable and should be sanitized with mcc/mnc like netmonster core (can still be done manually due to logging is ordered e.g. for 20416 and nieghbour cells enabled then following cells without mcc/mnc are logically its neighbours untill we see another cell with mcc/mnc --- .../DataProvider/DataProvider.java | 70 ++-- .../OpenMobileNetworkToolkit/GlobalVars.java | 4 + .../MainActivity.java | 346 ++++++++++++------ .../SettingPreferences/SettingsFragment.java | 4 + .../util/DirectExecutor.java | 18 + app/src/main/res/raw/config.json | 162 ++++---- app/src/main/res/xml/preference_main.xml | 2 +- docs/config.json | 162 ++++---- docs/preferences.md | 94 ++--- 9 files changed, 500 insertions(+), 362 deletions(-) create mode 100644 app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/util/DirectExecutor.java diff --git a/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/DataProvider/DataProvider.java b/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/DataProvider/DataProvider.java index de6f1f9..9c4e652 100644 --- a/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/DataProvider/DataProvider.java +++ b/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/DataProvider/DataProvider.java @@ -86,7 +86,6 @@ */ public class DataProvider extends TelephonyCallback implements LocationListener, TelephonyCallback.CellInfoListener, TelephonyCallback.PhysicalChannelConfigListener, TelephonyCallback.SignalStrengthsListener { private static final String TAG = "DataProvider"; - private final Context ct; private final boolean permission_phone_state; private final DeviceInformation di = new DeviceInformation(); private final BatteryInformation bi = new BatteryInformation(); @@ -107,7 +106,9 @@ public class DataProvider extends TelephonyCallback implements LocationListener, // Time stamp, should be updated on each update of internal data caches private long ts = System.currentTimeMillis(); - @SuppressLint("ObsoleteSdkInt") + private final Context ct; + + @SuppressLint("MissingPermission") public DataProvider(Context context) { GlobalVars gv = GlobalVars.getInstance(); ct = context; @@ -120,31 +121,23 @@ public DataProvider(Context context) { sm = (SubscriptionManager) ct.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); } - // We need location permission otherwise logging is useless - if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { - lm = (LocationManager) ct.getSystemService(Context.LOCATION_SERVICE); - if (lm.isLocationEnabled()) { - Log.d(TAG, "Location Provider " + lm.getProviders(true)); - li = new LocationInformation(); // empty LocationInformation to be filled by callback - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - lm.requestLocationUpdates(LocationManager.FUSED_PROVIDER, 0, 0, this); - Location loc = lm.getLastKnownLocation(LocationManager.FUSED_PROVIDER); - if (loc != null) { - onLocationChanged(Objects.requireNonNull(lm.getLastKnownLocation(LocationManager.FUSED_PROVIDER))); - } - } else { - lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this); - onLocationChanged(Objects.requireNonNull(lm.getLastKnownLocation(LocationManager.GPS_PROVIDER))); - } - } else { - Log.d(TAG, "GPS is disabled"); - // todo use same popup as in main activity + lm = (LocationManager) ct.getSystemService(Context.LOCATION_SERVICE); + if (lm.isLocationEnabled()) { + Log.d(TAG, "Location Provider " + lm.getProviders(true)); + li = new LocationInformation(); // empty LocationInformation to be filled by callback + + lm.requestLocationUpdates(LocationManager.FUSED_PROVIDER, 0, 0, this); + Location loc = lm.getLastKnownLocation(LocationManager.FUSED_PROVIDER); + if (loc != null) { + onLocationChanged(Objects.requireNonNull(lm.getLastKnownLocation(LocationManager.FUSED_PROVIDER))); } + } else { - Log.d(TAG, "No Location Permissions"); - // todo we need to handle this in more details as we can't do logging without it + Log.d(TAG, "GPS is disabled"); + // todo use same popup as in main activity } + locationCallback = new LocationCallback() { @Override public void onLocationResult(@NonNull LocationResult locationResult) { @@ -154,17 +147,14 @@ public void onLocationResult(@NonNull LocationResult locationResult) { startLocationUpdates(); registerWiFiCallback(); - - // initialize internal state - refreshAll(); } /** - * Update the current Telephony Manager reference e.g. after subscription change - * @param tm new Telephony Manager reference + * Update the current Telephony Manager reference e.g. after subscription change via GlobalVars */ - public void setTm(TelephonyManager tm) { - this.tm = tm; + public void syncTelephonyManager() { + GlobalVars gv = GlobalVars.getInstance(); + this.tm = gv.getTm(); } /** @@ -329,8 +319,6 @@ public List getNetworkInterfaceInformationPoints() { * * @param list is the list of currently visible cells. */ - @SuppressLint("ObsoleteSdkInt") - @Override public void onCellInfoChanged(@NonNull List list) { updateTimestamp(); long ts_ = ts; @@ -448,7 +436,7 @@ public List getCellInformationPoint() { public List getAllCellInfo() { List cellInfo; if (ActivityCompat.checkSelfPermission(ct, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Access Fine Location permission missing"); + Log.e(TAG, "Access Fine Location permission missing"); cellInfo = new ArrayList<>(); } else { cellInfo = tm.getAllCellInfo(); @@ -781,21 +769,12 @@ public String getIMSI() { * * @return List of SubscriptionIno */ - @SuppressLint("ObsoleteSdkInt") public List getSubscriptions() { - List subscriptions = new ArrayList<>(); ArrayList activeSubscriptions = new ArrayList<>(); - - if (Build.VERSION.SDK_INT >= 30) { - subscriptions.addAll(sm.getCompleteActiveSubscriptionInfoList()); - } else { - if (ActivityCompat.checkSelfPermission(ct, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { - List subscriptions_ = sm.getActiveSubscriptionInfoList(); - if (subscriptions_ != null) { - subscriptions.addAll(subscriptions_); - } - } + if (ActivityCompat.checkSelfPermission(ct, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { + return activeSubscriptions; } + List subscriptions = new ArrayList<>(sm.getCompleteActiveSubscriptionInfoList()); for (SubscriptionInfo info : Objects.requireNonNull(subscriptions)) { if (tm.getSimState(info.getSimSlotIndex()) == TelephonyManager.SIM_STATE_READY) { activeSubscriptions.add(info); @@ -814,7 +793,6 @@ public void refreshAll() { refreshNetworkInformation(); refreshBatteryInfo(); refreshNetworkInterfaceInformation(); - onCellInfoChanged(getAllCellInfo()); SignalStrength ss = tm.getSignalStrength(); // if the phone is not connected and we missed the update on tis we clear our internal cache diff --git a/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/GlobalVars.java b/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/GlobalVars.java index 3da4987..8241431 100644 --- a/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/GlobalVars.java +++ b/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/GlobalVars.java @@ -40,6 +40,10 @@ public class GlobalVars { public final int[] WCDMA_ECNO_Threasholds = {-24,-14,-6,1}; public final int[] WCDMA_RSCP_Threasholds = {-115,-105,-95,-85}; + // Predefined values for "select_subscription" preference + public static int MULTIPLE_SUBSCRIPTIONS_SELECTED_ID = 99999; + public static int INVALID_SUBSCRIPTION_ID = -1; + public static final String INFLUX_WRITE_STATUS = "influxdb_write_status"; private static GlobalVars instance; ImageView log_status; diff --git a/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/MainActivity.java b/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/MainActivity.java index 62f31f8..a94c217 100644 --- a/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/MainActivity.java +++ b/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/MainActivity.java @@ -8,6 +8,9 @@ package de.fraunhofer.fokus.OpenMobileNetworkToolkit; +import static de.fraunhofer.fokus.OpenMobileNetworkToolkit.GlobalVars.INVALID_SUBSCRIPTION_ID; +import static de.fraunhofer.fokus.OpenMobileNetworkToolkit.GlobalVars.MULTIPLE_SUBSCRIPTIONS_SELECTED_ID; + import android.Manifest; import android.annotation.SuppressLint; import android.app.AlertDialog; @@ -37,7 +40,10 @@ import android.view.Menu; import android.view.MenuItem; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; +import androidx.annotation.RequiresPermission; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.view.menu.MenuBuilder; import androidx.appcompat.widget.Toolbar; @@ -51,9 +57,16 @@ import java.security.MessageDigest; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; -import java.util.concurrent.Executors; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.Stream; import de.fraunhofer.fokus.OpenMobileNetworkToolkit.DataProvider.DataProvider; import de.fraunhofer.fokus.OpenMobileNetworkToolkit.DataProvider.NetworkCallback; @@ -61,10 +74,12 @@ import de.fraunhofer.fokus.OpenMobileNetworkToolkit.Preferences.SPType; import de.fraunhofer.fokus.OpenMobileNetworkToolkit.Preferences.SharedPreferencesGrouper; import de.fraunhofer.fokus.OpenMobileNetworkToolkit.WorkProfile.WorkProfileActivity; +import de.fraunhofer.fokus.OpenMobileNetworkToolkit.util.DirectExecutor; public class MainActivity extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { private static final String TAG = "MainActivity"; public TelephonyManager tm; + private Map telephonyManagers; public PackageManager pm; public DataProvider dp; public SharedPreferencesGrouper spg; @@ -76,33 +91,74 @@ public class MainActivity extends AppCompatActivity implements PreferenceFragmen NavController navController; private Handler requestCellInfoUpdateHandler; private GlobalVars gv; + + private static final String[] BASE_PERMISSIONS = new String[]{ + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_WIFI_STATE, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + }; + + private String[] finalRequiredPermissions; + + private final ActivityResultLauncher requestMultiplePermissionsLauncher = + registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), permissionsMap -> { + + boolean allGranted = true; + + for (String permission : finalRequiredPermissions) { + Boolean isGranted = permissionsMap.get(permission); + if (isGranted == null || !isGranted) { + allGranted = false; + } + } + if (allGranted) { + init(); + } + }); + + + private Context context; + /** - * Runnable to handle Cell Info Updates + * Checks if all permissions in the provided array are currently granted. + * @param permissions The array of permissions to check. + * @return True if all permissions are granted, false otherwise. */ - private final Runnable requestCellInfoUpdate = new Runnable() { - @SuppressLint("MissingPermission") // we check them already in the Main activity - @Override - public void run() { - if (gv.isPermission_fine_location()) { - tm.requestCellInfoUpdate(Executors.newSingleThreadExecutor(), new TelephonyManager.CellInfoCallback() { - @Override - public void onCellInfo(@NonNull List list) { - dp.onCellInfoChanged(list); - } - }); + private boolean hasAllPermissions(String[] permissions) { + for (String permission : permissions) { + if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { + return false; } - requestCellInfoUpdateHandler.postDelayed(this, Integer.parseInt(spg.getSharedPreference(SPType.LOGGING).getString("logging_interval", "1000"))); } - }; - private Context context; + return true; + } + + private void buildRequiredPermissionsList() { + List permissions = new ArrayList<>(Arrays.asList(BASE_PERMISSIONS)); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + permissions.add(Manifest.permission.POST_NOTIFICATIONS); + } + + // Convert the List back to an array + finalRequiredPermissions = permissions.toArray(new String[0]); + } @SuppressLint("ObsoleteSdkInt") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // check and request permissions from the user - requestPermission(); + buildRequiredPermissionsList(); + if (hasAllPermissions(finalRequiredPermissions)) { + init(); + } else { + requestMultiplePermissionsLauncher.launch(finalRequiredPermissions); + } + } + void init() { // initialize variables we need later on context = getApplicationContext(); gv = GlobalVars.getInstance(); @@ -148,35 +204,9 @@ protected void onCreate(Bundle savedInstanceState) { tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); gv.setTm(tm); dp = new DataProvider(this); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !dp.getSubscriptions().isEmpty()) { - // make sure the subscription in the app settings exists in the current subscription list. - // if it is not in the subscription list change it to the first one of the current list - boolean valid_subscription = false; - String pref_subscription_str = spg.getSharedPreference(SPType.MAIN).getString("select_subscription","99999"); - for (SubscriptionInfo info : dp.getSubscriptions()) { - if (Integer.parseInt(pref_subscription_str) == info.getSubscriptionId()) { - valid_subscription = true; - break; - } - } - if (!valid_subscription) { - spg.getSharedPreference(SPType.MAIN).edit().putString("select_subscription", String.valueOf(dp.getSubscriptions().iterator().next().getSubscriptionId())).apply(); - } - // switch the telephony manager to a new one according to the app settings - tm = tm.createForSubscriptionId(Integer.parseInt(spg.getSharedPreference(SPType.MAIN).getString("select_subscription", "0"))); - - // update reference to tm - gv.setTm(tm); - dp.setTm(tm); - } - - gv.setSm((SubscriptionManager) getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)); - cp = tm.hasCarrierPrivileges(); - gv.setCarrier_permissions(cp); - if (cp) { - gv.setCcm((CarrierConfigManager) getSystemService(Context.CARRIER_CONFIG_SERVICE)); - } - } //todo this will go very wrong on android devices without telephony api, maybe show warning and exit? + updateTelephonyManagers(); + } + //todo this will go very wrong on android devices without telephony api, maybe show warning and exit? gv.set_dp(dp); @@ -197,7 +227,6 @@ protected void onCreate(Bundle savedInstanceState) { .show(); } } - initHandlerAndHandlerThread(); loggingServiceIntent = new Intent(this, LoggingService.class); @@ -273,13 +302,94 @@ protected void onCreate(Bundle savedInstanceState) { if (target != null && target.equals("PingFragment")) { navController.navigate(R.id.ping_fragment); } + getAppSignature(); + gv.setGit_hash(getString(R.string.git_hash)); + dp.refreshAll(); + } + private int getSelectedSubId() { + return Integer.parseInt(spg.getSharedPreference(SPType.MAIN).getString( + "select_subscription", + String.valueOf(GlobalVars.MULTIPLE_SUBSCRIPTIONS_SELECTED_ID) + )); + } + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + private void handleMultipleSubscription(List subscriptions) { + SubscriptionManager sm = (SubscriptionManager) getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); + for (SubscriptionInfo info : subscriptions) { + int subId = info.getSubscriptionId(); + telephonyManagers.put(subId, tm.createForSubscriptionId(subId)); + } + for (int i = 0; i <= 1; i++) { + // tm still required for carrier screen, reboot modem, special numbers for dial. + // Made it compatible to try and use first subscription in the list. + SubscriptionInfo subInfo = sm.getActiveSubscriptionInfoForSimSlotIndex(i); + if (subInfo == null) { + continue; + } + int subId = subInfo.getSubscriptionId(); + if (subId != INVALID_SUBSCRIPTION_ID) { + tm = tm.createForSubscriptionId(subId); + break; + } + } + } + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + private void handleSingleSubscription(int selectedSubId, List subscriptions) { + boolean isValid = subscriptions.stream() + .anyMatch(info -> info.getSubscriptionId() == selectedSubId); + // backup mechanism to find first subscription from the list if selected subId is not valid + if (!isValid) { + subscriptions.stream() + .findFirst() // Get the first element as an Optional + .ifPresent(firstInfo -> { + int fallbackId = firstInfo.getSubscriptionId(); + tm = tm.createForSubscriptionId(fallbackId); + telephonyManagers.put(fallbackId, tm.createForSubscriptionId(fallbackId)); + spg.getSharedPreference(SPType.MAIN).edit() + .putString("select_subscription", String.valueOf(fallbackId)) + .apply(); + } + ); + } else { + tm = tm.createForSubscriptionId(selectedSubId); + telephonyManagers.put(selectedSubId, tm.createForSubscriptionId(selectedSubId)); + } + } - getAppSignature(); - gv.setGit_hash(getString(R.string.git_hash)); + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + private void updateTelephonyManagers() { + List subscriptions = dp.getSubscriptions(); + telephonyManagers = new HashMap<>(); + + if (subscriptions.isEmpty()) return; + + // make sure the subscription in the app settings exists in the current subscription list. + // if it is not in the subscription list change it to the first one of the current list + int selectedSubId = getSelectedSubId(); + if(selectedSubId == MULTIPLE_SUBSCRIPTIONS_SELECTED_ID) { + handleMultipleSubscription(subscriptions); + } else { + handleSingleSubscription(selectedSubId, subscriptions); + } + // update reference to tm + gv.setTm(tm); + dp.syncTelephonyManager(); + SubscriptionManager sm = (SubscriptionManager) getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); + gv.setSm(sm); + + setCarrierConfig(); + } + + private void setCarrierConfig() { + cp = tm.hasCarrierPrivileges(); + gv.setCarrier_permissions(cp); + if (cp) { + gv.setCcm((CarrierConfigManager) getSystemService(Context.CARRIER_CONFIG_SERVICE)); + } } /** @@ -323,64 +433,7 @@ public static String toHexString(byte[] bytes) { return hexString.toString(); } - /** - * Check and request permission the app needs to access APIs and so on - */ - private void requestPermission() { - List permissions = new ArrayList<>(); - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Requesting READ_PHONE_STATE Permission"); - permissions.add(Manifest.permission.READ_PHONE_STATE); - } else { - Log.d(TAG, "Got READ_PHONE_STATE Permission"); - } - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Requesting COARSE_LOCATION Permission"); - permissions.add(Manifest.permission.ACCESS_COARSE_LOCATION); - } else { - Log.d(TAG, "Got COARSE_LOCATION_LOCATION Permission"); - } - - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Requesting FINE_LOCATION Permission"); - permissions.add(Manifest.permission.ACCESS_FINE_LOCATION); - } else { - Log.d(TAG, "Got FINE_LOCATION Permission"); - } - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_WIFI_STATE) != PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Requesting WIFI_STATE Permission"); - permissions.add(Manifest.permission.ACCESS_WIFI_STATE); - } else { - Log.d(TAG, "Got WIFI_STATE Permission"); - } - - // on android 13 an newer we need to ask for permission to show the notification - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Requesting POST_NOTIFICATIONS Permission"); - permissions.add(Manifest.permission.POST_NOTIFICATIONS); - } else { - Log.d(TAG, "Got POST_NOTIFICATIONS Permission"); - } - } - - // we can only request background after fine location. If that has failed on the first try we need to check again - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) { - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) { - Log.d(TAG, "Requesting ACCESS_BACKGROUND_LOCATION Permission"); - ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 3); - } else { - Log.d(TAG, "Got ACCESS_BACKGROUND_LOCATION Permission"); - } - } - - - if (!permissions.isEmpty()) { - String[] perms = permissions.toArray(new String[0]); - ActivityCompat.requestPermissions(this, perms, 1337); - } - } /** * @param i The request code passed in by the callback @@ -544,6 +597,87 @@ public boolean onPreferenceStartFragment(@NonNull PreferenceFragmentCompat calle return true; } + @SuppressLint("MissingPermission") // we check them already in the Main activity + private void syncTelephonyManagers() { + int selectedSubId = getSelectedSubId(); + Set currentSubIds = telephonyManagers.keySet(); + int totalManagers = currentSubIds.size(); + boolean needsUpdate = (selectedSubId != MULTIPLE_SUBSCRIPTIONS_SELECTED_ID && totalManagers > 1) || + (totalManagers == 1 && currentSubIds.stream().anyMatch(sub -> sub != selectedSubId)); + + if (needsUpdate) { + updateTelephonyManagers(); + } + } + + @SuppressLint("MissingPermission") // we check them already in the Main activity + private void sendCellInfo(Map> cellInfoBySubId) { + List mergedList = cellInfoBySubId.keySet().stream() + .flatMap(subId -> { + List subList = cellInfoBySubId.get(subId); + return subList != null ? subList.stream() : Stream.empty(); + }) + .collect(Collectors.toList()); +// // Post the final list to the main thread using a lambda + new Handler(context.getMainLooper()).post(() -> dp.onCellInfoChanged(mergedList)); + } + + @SuppressLint("MissingPermission") // we check them already in the Main activity + private void requestCellInfoUpdateFromManagers() { + final Set subIds = telephonyManagers.keySet(); + final int totalManagers = subIds.size(); + + // Concurrent map to safely collect results from multiple threads/callbacks + final Map> cellInfoBySubId = new ConcurrentHashMap<>(); + final AtomicInteger completedRequests = new AtomicInteger(0); + + for (int subId : subIds) { + + TelephonyManager telephonyManager = telephonyManagers.get(subId); + if (telephonyManager == null) { + throw new IllegalStateException("No TelephonyManager found for subId: " + subId); + } + + telephonyManager.requestCellInfoUpdate(new DirectExecutor(), new TelephonyManager.CellInfoCallback() { + @Override + public void onCellInfo(@NonNull List list) { + cellInfoBySubId.put(subId, list); + + if (completedRequests.incrementAndGet() == totalManagers) { + sendCellInfo(cellInfoBySubId); + } + } + }); + } + } + + /** + * Runnable to handle Cell Info Updates + */ + private final Runnable requestCellInfoUpdate = new Runnable() { + @SuppressLint("MissingPermission") // we check them already in the Main activity + @Override + public void run() { + if (!gv.isPermission_fine_location()) { + scheduleUpdate(); + return; + } + syncTelephonyManagers(); + + if (!telephonyManagers.isEmpty()) { + requestCellInfoUpdateFromManagers(); + } + + scheduleUpdate(); + } + + + private void scheduleUpdate() { + requestCellInfoUpdateHandler.postDelayed(this, Integer.parseInt(spg.getSharedPreference(SPType.LOGGING).getString("logging_interval", "1000"))); + } + + }; + private void initHandlerAndHandlerThread() { HandlerThread requestCellInfoUpdateHandlerThread = new HandlerThread("RequestCellInfoUpdateHandlerThread"); requestCellInfoUpdateHandlerThread.start(); diff --git a/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/SettingPreferences/SettingsFragment.java b/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/SettingPreferences/SettingsFragment.java index 0e49edc..646ac91 100644 --- a/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/SettingPreferences/SettingsFragment.java +++ b/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/SettingPreferences/SettingsFragment.java @@ -56,6 +56,10 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { entries.add(info.getDisplayName().toString()); entryValues.add(String.valueOf(info.getSubscriptionId())); } + if(subscriptions.size() > 1) { + entries.add("All subscriptions"); + entryValues.add("99999"); + } CharSequence[] entries_char = entries.toArray(new CharSequence[0]); CharSequence[] entryValues_char = entryValues.toArray(new CharSequence[0]); sub_select.setEntries(entries_char); diff --git a/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/util/DirectExecutor.java b/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/util/DirectExecutor.java new file mode 100644 index 0000000..bd551de --- /dev/null +++ b/app/src/main/java/de/fraunhofer/fokus/OpenMobileNetworkToolkit/util/DirectExecutor.java @@ -0,0 +1,18 @@ +package de.fraunhofer.fokus.OpenMobileNetworkToolkit.util; + +import java.util.concurrent.Executor; + +/** + * An Executor that executes submitted tasks immediately on the calling thread. + */ +public class DirectExecutor implements Executor { + + /** + * Executes the given command (Runnable) immediately on the current thread. + * @param command The Runnable to execute. + */ + @Override + public void execute(Runnable command) { + command.run(); + } +} \ No newline at end of file diff --git a/app/src/main/res/raw/config.json b/app/src/main/res/raw/config.json index 1763930..d97d118 100644 --- a/app/src/main/res/raw/config.json +++ b/app/src/main/res/raw/config.json @@ -129,86 +129,6 @@ } ] }, - { - "setting": "main", - "categories": [ - { - "name": "home_screen_settings", - "preferences": [ - { - "key": "show_neighbour_cells", - "value": null, - "type": "boolean" - } - ] - }, - { - "name": "notification_settings", - "preferences": [ - { - "key": "enable_radio_notification", - "value": null, - "type": "boolean" - } - ] - }, - { - "name": "app_settings", - "preferences": [ - { - "key": "device_name", - "value": null, - "type": "string" - }, - { - "key": "select_subscription", - "value": null, - "type": "string" - } - ] - } - ] - }, - { - "setting": "mqtt", - "categories": [ - { - "name": "mqtt_service", - "preferences": [ - { - "key": "enable_mqtt", - "value": null, - "type": "boolean" - }, - { - "key": "enable_mqtt_on_boot", - "value": null, - "type": "boolean" - } - ] - }, - { - "name": "mqtt_credentials", - "preferences": [ - { - "key": "mqtt_host", - "value": null, - "type": "string" - }, - { - "key": "mqtt_client_username", - "value": null, - "type": "string" - }, - { - "key": "mqtt_client_password", - "value": null, - "type": "string" - } - ] - } - ] - }, { "setting": "mobile_network", "categories": [ @@ -509,11 +429,91 @@ } ] }, + { + "setting": "mqtt", + "categories": [ + { + "name": "mqtt_service", + "preferences": [ + { + "key": "enable_mqtt", + "value": null, + "type": "boolean" + }, + { + "key": "enable_mqtt_on_boot", + "value": null, + "type": "boolean" + } + ] + }, + { + "name": "mqtt_credentials", + "preferences": [ + { + "key": "mqtt_host", + "value": null, + "type": "string" + }, + { + "key": "mqtt_client_username", + "value": null, + "type": "string" + }, + { + "key": "mqtt_client_password", + "value": null, + "type": "string" + } + ] + } + ] + }, + { + "setting": "main", + "categories": [ + { + "name": "home_screen_settings", + "preferences": [ + { + "key": "show_neighbour_cells", + "value": null, + "type": "boolean" + } + ] + }, + { + "name": "notification_settings", + "preferences": [ + { + "key": "enable_radio_notification", + "value": null, + "type": "boolean" + } + ] + }, + { + "name": "app_settings", + "preferences": [ + { + "key": "device_name", + "value": null, + "type": "string" + }, + { + "key": "select_subscription", + "value": null, + "type": "string" + } + ] + } + ] + }, { "metadata": { "version": "0.7", "code": 7, - "gitHash": "377044e" + "gitHash": "4690531" } } ] \ No newline at end of file diff --git a/app/src/main/res/xml/preference_main.xml b/app/src/main/res/xml/preference_main.xml index 79aae8c..71c590f 100644 --- a/app/src/main/res/xml/preference_main.xml +++ b/app/src/main/res/xml/preference_main.xml @@ -64,7 +64,7 @@