fix: adapt all permissions declarations and runtime requests for full SDK range

- AndroidManifest.xml: add maxSdkVersion=30 to location permissions (not
  needed on API 31+ with BLUETOOTH_SCAN neverForLocation), add
  FOREGROUND_SERVICE_CONNECTED_DEVICE permission (required API 34+), add
  foregroundServiceType=connectedDevice to ObdBackgroundService

- MainActivity: replace 4 individual requestPermissions() calls (only last
  dialog was shown) with a single batched call keyed on SDK version
  (BLUETOOTH_SCAN+CONNECT on API 31+, ACCESS_FINE_LOCATION on API 23-30);
  add POST_NOTIFICATIONS runtime request on API 33+; add
  onRequestPermissionsResult() to retry BT init after grant

- BtDeviceListActivity: batch BLUETOOTH_SCAN+BLUETOOTH_CONNECT into one
  requestPermissions() call

- BleDeviceListActivity: same batching fix; guard startScan() behind
  permission check with early return

- ChartActivity: add WRITE_EXTERNAL_STORAGE runtime request before
  screenshot on API 23-28; add onRequestPermissionsResult() to take
  screenshot after permission is granted

Agent-Logs-Url: https://github.com/fr3ts0n/AndrOBD/sessions/6000e1a5-7568-4451-9a6a-42692a940107

Co-authored-by: fr3ts0n <2822578+fr3ts0n@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-29 05:13:30 +00:00
committed by GitHub
parent 2a7da60254
commit bfbbffde94
5 changed files with 114 additions and 23 deletions

View File

@@ -11,8 +11,11 @@
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- location only needed for BT/BLE scan on API 23-30; replaced by BLUETOOTH_SCAN on API 31+ -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="30" />
<!-- to keep Bluetooth measurement running even when screen is inactive -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
@@ -26,6 +29,8 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- For foreground service -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Required for foreground service of type connectedDevice (API 34+) -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<application
android:allowBackup="true"
@@ -103,7 +108,8 @@
<service
android:name=".ObdBackgroundService"
android:enabled="true"
android:exported="false" />
android:exported="false"
android:foregroundServiceType="connectedDevice" />
</application>
</manifest>

View File

@@ -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<String> 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);

View File

@@ -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<String> 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);
}
}

View File

@@ -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<Integer> 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();
/**

View File

@@ -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<String> 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();