diff --git a/androbd/src/main/AndroidManifest.xml b/androbd/src/main/AndroidManifest.xml
index e75ede8d..ef023452 100644
--- a/androbd/src/main/AndroidManifest.xml
+++ b/androbd/src/main/AndroidManifest.xml
@@ -11,8 +11,11 @@
-
-
+
+
+
@@ -26,6 +29,8 @@
+
+
+ android:exported="false"
+ android:foregroundServiceType="connectedDevice" />
\ No newline at end of file
diff --git a/androbd/src/main/java/com/fr3ts0n/ecu/gui/androbd/BleDeviceListActivity.java b/androbd/src/main/java/com/fr3ts0n/ecu/gui/androbd/BleDeviceListActivity.java
index 7b8956d0..e4d92552 100644
--- a/androbd/src/main/java/com/fr3ts0n/ecu/gui/androbd/BleDeviceListActivity.java
+++ b/androbd/src/main/java/com/fr3ts0n/ecu/gui/androbd/BleDeviceListActivity.java
@@ -11,6 +11,7 @@ import android.os.Build;
import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat;
+import java.util.ArrayList;
import java.util.List;
@RequiresApi (api = Build.VERSION_CODES.LOLLIPOP)
@@ -22,11 +23,16 @@ public class BleDeviceListActivity
@SuppressLint("InlinedApi")
@Override
protected void startDeviceScan() {
+ List missingPermissions = new ArrayList<>();
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
- ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.BLUETOOTH_SCAN}, 1);
+ missingPermissions.add(Manifest.permission.BLUETOOTH_SCAN);
}
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
- ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.BLUETOOTH_CONNECT}, 1);
+ missingPermissions.add(Manifest.permission.BLUETOOTH_CONNECT);
+ }
+ if (!missingPermissions.isEmpty()) {
+ ActivityCompat.requestPermissions(this, missingPermissions.toArray(new String[0]), 1);
+ return;
}
leScanner = mBtAdapter.getBluetoothLeScanner();
leScanner.startScan(scanCallback);
diff --git a/androbd/src/main/java/com/fr3ts0n/ecu/gui/androbd/BtDeviceListActivity.java b/androbd/src/main/java/com/fr3ts0n/ecu/gui/androbd/BtDeviceListActivity.java
index 06be8df6..3c83b726 100644
--- a/androbd/src/main/java/com/fr3ts0n/ecu/gui/androbd/BtDeviceListActivity.java
+++ b/androbd/src/main/java/com/fr3ts0n/ecu/gui/androbd/BtDeviceListActivity.java
@@ -41,6 +41,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
+import java.util.ArrayList;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -71,11 +72,15 @@ public class BtDeviceListActivity extends Activity
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ List missingPermissions = new ArrayList<>();
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
- ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.BLUETOOTH_SCAN}, 1);
+ missingPermissions.add(Manifest.permission.BLUETOOTH_SCAN);
}
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
- ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.BLUETOOTH_CONNECT}, 1);
+ missingPermissions.add(Manifest.permission.BLUETOOTH_CONNECT);
+ }
+ if (!missingPermissions.isEmpty()) {
+ ActivityCompat.requestPermissions(this, missingPermissions.toArray(new String[0]), 1);
}
}
diff --git a/androbd/src/main/java/com/fr3ts0n/ecu/gui/androbd/ChartActivity.java b/androbd/src/main/java/com/fr3ts0n/ecu/gui/androbd/ChartActivity.java
index ec909adf..b75112a7 100644
--- a/androbd/src/main/java/com/fr3ts0n/ecu/gui/androbd/ChartActivity.java
+++ b/androbd/src/main/java/com/fr3ts0n/ecu/gui/androbd/ChartActivity.java
@@ -18,11 +18,14 @@
package com.fr3ts0n.ecu.gui.androbd;
+import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.app.Activity;
+import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.Paint.Align;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -34,6 +37,9 @@ import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ListAdapter;
+import androidx.annotation.NonNull;
+import androidx.core.app.ActivityCompat;
+
import com.fr3ts0n.ecu.EcuDataPv;
import com.fr3ts0n.ecu.prot.obd.ObdProt;
@@ -70,6 +76,9 @@ public class ChartActivity extends Activity
*/
public static final String POSITIONS = "POSITIONS";
+ /** Permission request code for WRITE_EXTERNAL_STORAGE (screenshot, API 23-28) */
+ private static final int REQUEST_WRITE_STORAGE = 1;
+
/** Map to uniquely collect PID numbers */
private final TreeSet pidNumbers = new TreeSet<>();
@@ -248,7 +257,16 @@ public class ChartActivity extends Activity
if (itemId == R.id.share) {
new ExportTask(this).execute(sensorData);
} else if (itemId == R.id.snapshot) {
- Screenshot.takeScreenShot(this, getWindow().peekDecorView());
+ // WRITE_EXTERNAL_STORAGE is required on API 23-28 for the screenshot
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
+ && ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ REQUEST_WRITE_STORAGE);
+ } else {
+ Screenshot.takeScreenShot(this, getWindow().peekDecorView());
+ }
}
return super.onOptionsItemSelected(item);
}
@@ -272,6 +290,22 @@ public class ChartActivity extends Activity
super.onDestroy();
}
+ /**
+ * Take screenshot once WRITE_EXTERNAL_STORAGE permission is granted (API 23-28).
+ */
+ @Override
+ public void onRequestPermissionsResult(int requestCode,
+ @NonNull String[] permissions,
+ @NonNull int[] grantResults)
+ {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (requestCode == REQUEST_WRITE_STORAGE
+ && grantResults.length > 0
+ && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ Screenshot.takeScreenShot(this, getWindow().peekDecorView());
+ }
+ }
+
private final Timer refreshTimer = new Timer();
/**
diff --git a/androbd/src/main/java/com/fr3ts0n/ecu/gui/androbd/MainActivity.java b/androbd/src/main/java/com/fr3ts0n/ecu/gui/androbd/MainActivity.java
index 1342d878..180309df 100644
--- a/androbd/src/main/java/com/fr3ts0n/ecu/gui/androbd/MainActivity.java
+++ b/androbd/src/main/java/com/fr3ts0n/ecu/gui/androbd/MainActivity.java
@@ -84,6 +84,7 @@ import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
@@ -163,6 +164,8 @@ public class MainActivity extends PluginManager
private static final int REQUEST_SETTINGS = 5;
private static final int REQUEST_CONNECT_DEVICE_USB = 6;
private static final int REQUEST_GRAPH_DISPLAY_DONE = 7;
+ private static final int REQUEST_BT_PERMISSIONS = 8;
+ private static final int REQUEST_NOTIFICATIONS = 9;
/**
* app exit parameters
*/
@@ -641,6 +644,13 @@ public class MainActivity extends PluginManager
CommService.medium = CommService.MEDIUM.USB;
}
+ // Request POST_NOTIFICATIONS permission on API 33+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.POST_NOTIFICATIONS}, REQUEST_NOTIFICATIONS);
+ }
+ }
+
initSelectedMode();
// Bind to background OBD service for continuous monitoring
@@ -666,22 +676,28 @@ public class MainActivity extends PluginManager
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
log.fine("Adapter: " + mBluetoothAdapter);
- if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
- log.warning("permission.BLUETOOTH_SCAN missing");
- ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.BLUETOOTH_SCAN}, 1);
+ // Collect missing BT/location permissions and request them all at once
+ List missingPermissions = new ArrayList<>();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ // API 31+: new Bluetooth permissions replace location for scanning
+ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
+ log.warning("permission.BLUETOOTH_SCAN missing");
+ missingPermissions.add(Manifest.permission.BLUETOOTH_SCAN);
+ }
+ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
+ log.warning("permission.BLUETOOTH_CONNECT missing");
+ missingPermissions.add(Manifest.permission.BLUETOOTH_CONNECT);
+ }
+ } else {
+ // API 23-30: location permission required for BT/BLE scan
+ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ log.warning("permission.ACCESS_FINE_LOCATION missing");
+ missingPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
+ }
}
- if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
- log.warning("permission.ACCESS_FINE_LOCATION missing");
- ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1);
- }
- if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
- log.warning("permission.ACCESS_FINE_LOCATION missing");
- ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
- }
-
- if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
- log.warning("permission.BLUETOOTH_CONNECT missing");
- ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.BLUETOOTH_CONNECT}, 1);
+ if (!missingPermissions.isEmpty()) {
+ ActivityCompat.requestPermissions(this, missingPermissions.toArray(new String[0]), REQUEST_BT_PERMISSIONS);
+ return;
}
// If BT is not on, request that it be enabled.
@@ -732,6 +748,30 @@ public class MainActivity extends PluginManager
super.onStart();
}
+ /**
+ * Handle permission request results.
+ * Retries BT/BLE mode initialisation once the required permissions are granted.
+ */
+ @Override
+ public void onRequestPermissionsResult(int requestCode,
+ @androidx.annotation.NonNull String[] permissions,
+ @androidx.annotation.NonNull int[] grantResults)
+ {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (requestCode == REQUEST_BT_PERMISSIONS) {
+ boolean allGranted = true;
+ for (int result : grantResults) {
+ if (result != PackageManager.PERMISSION_GRANTED) {
+ allGranted = false;
+ break;
+ }
+ }
+ if (allGranted) {
+ initSelectedMode();
+ }
+ }
+ }
+
@Override protected void onPause()
{
super.onPause();