Bluetooth LE Scan fails in the background - permissions - android

The following code works great on my Nexus 9 running Android 5.1.1 (Build LMY48M), but won't work on a Nexus 9 running Android 6.0 (Build MPA44l)
List<ScanFilter> filters = new ArrayList<ScanFilter>();
ScanSettings settings = (new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)).build();
ScanFilter.Builder builder = new ScanFilter.Builder();
builder.setManufacturerData((int) 0x0118, new byte[]{(byte) 0xbe, (byte) 0xac}, new byte[]{(byte) 0xff, (byte)0xff});
ScanFilter scanFilter = builder.build();
filters.add(scanFilter);
mBluetoothLeScanner.startScan(filters, settings, new ScanCallback() {
...
});
On Android 5.x, the above code yields a callback when a manufacturer advertisement matching the scan filter is seen. (See example Logcat output below.) On the Nexus 9 with MPA44l, no callbacks are received. If you comment out the scan filter, callbacks are received successfully on the Nexus 9.
09-22 00:07:28.050 1748-1796/org.altbeacon.beaconreference D/BluetoothLeScanner﹕ onScanResult() - ScanResult{mDevice=00:07:80:03:89:8C, mScanRecord=ScanRecord [mAdvertiseFlags=6, mServiceUuids=null, mManufacturerSpecificData={280=[-66, -84, 47, 35, 68, 84, -49, 109, 74, 15, -83, -14, -12, -111, 27, -87, -1, -90, 0, 1, 0, 1, -66, 0]}, mServiceData={}, mTxPowerLevel=-2147483648, mDeviceName=null], mRssi=-64, mTimestampNanos=61272522487278}
Has anybody seen ScanFilters work on Android M?

The problem was not the scan filter, but background permissions.
Android 10-11:
In order to detect BLE devices in the background, you must have several permissions in the manifest. Place the following in your AndroidManifest.xml:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.ACCESS_BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Then add code like follows to your Activity to dynamically request these permissions from the user:
private static final int PERMISSION_REQUEST_FINE_LOCATION = 1;
private static final int PERMISSION_REQUEST_BACKGROUND_LOCATION = 2;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (this.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
if (this.checkSelfPermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
if (this.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("This app needs background location access");
builder.setMessage("Please grant location access so this app can detect beacons in the background.");
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
#TargetApi(23)
#Override
public void onDismiss(DialogInterface dialog) {
requestPermissions(new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION},
PERMISSION_REQUEST_BACKGROUND_LOCATION);
}
});
builder.show();
}
else {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Functionality limited");
builder.setMessage("Since background location access has not been granted, this app will not be able to discover beacons in the background. Please go to Settings -> Applications -> Permissions and grant background location access to this app.");
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
#Override
public void onDismiss(DialogInterface dialog) {
}
});
builder.show();
}
}
} else {
if (!this.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION},
PERMISSION_REQUEST_FINE_LOCATION);
}
else {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Functionality limited");
builder.setMessage("Since location access has not been granted, this app will not be able to discover beacons. Please go to Settings -> Applications -> Permissions and grant location access to this app.");
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
#Override
public void onDismiss(DialogInterface dialog) {
}
});
builder.show();
}
}
}
}
When you prompt the user for location permission, the OS dialog will give them the option to downgrade that permission request to "Allow Only While Using the App" vs. "Allow All the Time". If the user chooses the first option, you will not get detections in the background, even if everything else above is set up.
On Android 11, things get more complex still, as the OS offers yet another option of "Only this time" for the permission request. If your app targets SDK 30 (Android 11), it won't even offer the user the option for "Allow All the Time", and the user will have to go to Settings as a separate step to turn on all the time access. See here for more details on the way this works on Android 11.
For a broader discussion of the evolution of permissions prompting, see my blog post here.
Before Android 10:
Starting with Android M, Bluetooth LE scanning in the background is blocked unless the app has one of the following two permissions:
android.permission.ACCESS_COARSE_LOCATION
android.permission.ACCESS_FINE_LOCATION
The app I was testing did not request either of these permissions, so it did not work in the background (the only time the scan filter was active) on Android M. Adding the first one solved the problem.
I realized this was the problem because I saw the following line in Logcat:
09-22 22:35:20.152 5158 5254 E BluetoothUtils: Permission denial: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to get scan results
See here for details: https://code.google.com/p/android-developer-preview/issues/detail?id=2964

I had a similar problem with an app connecting to bluetooth. Not LE ScanFilter, but it was a permissions issue just like the OP had.
Root cause is that starting with SDK 23, you need to prompt the user for permissions at runtime using Activity's requestPermissions() method.
Here's what worked for me:
Add one of the following two lines to AndroidManifest.xml, inside the root node:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
In your Activity, before attempting to connect to bluetooth, call Activity's requestPermissions() method, which opens a system dialog to prompt the user for the permission. The permissions dialog opens in a different thread, so be sure to wait for the result before trying to connect to bluetooth.
Override Activity's onRequestPermissionsResult() to handle the result. This method will really only need to do something if the user refused to grant the permission, to tell the user that the app can't do the bluetooth activity.
This blog post has some example code that uses AlertDialogs to tell the user what's going on. It is a good starting point but has some shortcomings:
It doesn't handle waiting for the requestPermissions() thread to finish
The AlertDialog wrapping the call to requestPermissions() seems extraneous to me. A bare call to requestPermissions() is sufficient.

Add location permission along with BLE
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Copy Paste this method to request and grant location permission
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS: {
Map<String, Integer> perms = new HashMap<String, Integer>();
// Initial
perms.put(Manifest.permission.ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED);
// Fill with results
for (int i = 0; i < permissions.length; i++)
perms.put(permissions[i], grantResults[i]);
// Check for ACCESS_FINE_LOCATION
if (perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
) {
// All Permissions Granted
// Permission Denied
Toast.makeText(ScanningActivity.this, "All Permission GRANTED !! Thank You :)", Toast.LENGTH_SHORT)
.show();
} else {
// Permission Denied
Toast.makeText(ScanningActivity.this, "One or More Permissions are DENIED Exiting App :(", Toast.LENGTH_SHORT)
.show();
finish();
}
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
#TargetApi(Build.VERSION_CODES.M)
private void stuffMarshMallow() {
List<String> permissionsNeeded = new ArrayList<String>();
final List<String> permissionsList = new ArrayList<String>();
if (!addPermission(permissionsList, Manifest.permission.ACCESS_FINE_LOCATION))
permissionsNeeded.add("Show Location");
if (permissionsList.size() > 0) {
if (permissionsNeeded.size() > 0) {
// Need Rationale
String message = "App need access to " + permissionsNeeded.get(0);
for (int i = 1; i < permissionsNeeded.size(); i++)
message = message + ", " + permissionsNeeded.get(i);
showMessageOKCancel(message,
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
}
});
return;
}
requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
return;
}
Toast.makeText(ScanningActivity.this, "No new Permission Required- Launching App .You are Awesome!!", Toast.LENGTH_SHORT)
.show();
}
private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(ScanningActivity.this)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", null)
.create()
.show();
}
#TargetApi(Build.VERSION_CODES.M)
private boolean addPermission(List<String> permissionsList, String permission) {
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
permissionsList.add(permission);
// Check for Rationale Option
if (!shouldShowRequestPermissionRationale(permission))
return false;
}
return true;
}
And then in onCreate check for permission
if (Build.VERSION.SDK_INT >= 23) {
// Marshmallow+ Permission APIs
stuffMarshMallow();
}
Hope it save your time.

If your app targets Android Q, it's not enough with only coarse location, you need to use fine location, otherwise you will get this error:
E/BluetoothUtils: Permission denial: Need ACCESS_FINE_LOCATION permission to get scan results
See https://developer.android.com/preview/privacy/camera-connectivity#fine-location-telephony-wifi-bt for the official source.

Related

How to re-request BLUETOOTH_SCAN and BLUETOOTH_CONNECT permissions for Android 12 after a user denial?

I have a problem re-requesting the permissions required to scan and connect to bluetooth devices when targeting SDK 31 (Android 12).
I call this method inside my main activity's onCreate():
public void requestBluetoothPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if ((this.checkSelfPermission(Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED)
|| (this.checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED)) {
Log.w(getClass().getName(), "requestBluetoothPermissions() BLUETOOTH_SCAN AND BLUETOOTH_CONNECT permissions needed => requesting them...");
this.requestPermissions(new String[]{
Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT
}, MyActivity.REQUEST_BLUETOOTH_PERMISSIONS);
}
}
}
It's works fine the first time it is called i.e. an Android pop-up is displayed to the user, prompting him to grant the permissions.
But if he refuses to grant the permissions, next time onCreate() is called, the pop-up will not be displayed, which means the user remains unable to grant the permissions.
Any idea why and how to fix this ?
It appears Android 12 blocks requesting the same permission after user denied it once only.
Therefore, I ended up using ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.BLUETOOTH_SCAN) to determine wether the permission can be requested or not, in which case a snackbar message is displayed explaining why is the permission needed, with a button opening the app settings where permission can be granted.
Here is a sample of the code:
public void requestBluetoothPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if ((this.checkSelfPermission(Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED)
|| (this.checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED)) {
if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(),
Manifest.permission.BLUETOOTH_SCAN)) {
// display permission rationale in snackbar message
} else {
Log.w(getClass().getName(), "requestBluetoothPermissions() BLUETOOTH_SCAN AND BLUETOOTH_CONNECT permissions needed => requesting them...");
this.requestPermissions(new String[]{
Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT
}, MyActivity.REQUEST_BLUETOOTH_PERMISSIONS);
}
}
}
}

Why is my runtime dangerous permission auto denied?

I am trying to use the "new" way of requesting external storage write permission.
But the request is automatically denied and no window pops up asking the user for permission.
Virtual Device: Pixel 2 API 30
SDK 30
I did add the permissions into the manifest file
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.lab6">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE "/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
WRITE_EXTERNAL_STORAGE is a dangerous permission from my knowledge, so it has to be requested at runtime.
into my gradle dependencies I added these:
implementation 'androidx.activity:activity:1.2.0'
implementation 'androidx.fragment:fragment:1.3.0'
The activity looks like this:
public class MainActivity extends AppCompatActivity {
private ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
if (isGranted) {
Log.d("mihai", "Permission is granted now.");
} else {
Log.d("mihai", "Permission refused ");
}
});
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button downloadBtn = findViewById(R.id.downloadButton);
Button loadBtn = findViewById(R.id.loadButton);
downloadBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.d("mihai", "requesting permission");
if (ContextCompat.checkSelfPermission(
getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
PackageManager.PERMISSION_GRANTED) {
Log.d("mihai", "permission already granted");
} else {
Log.d("mihai", "launching");
requestPermissionLauncher.launch(
Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
}
});
}
}
And these are the logs I get. As you can see, the request is denied. No question pops up on the phone.
2022-04-28 19:55:15.351 7499-7499/com.example.lab6 D/mihai: requesting permission
2022-04-28 19:55:15.353 7499-7499/com.example.lab6 D/mihai: launching
2022-04-28 19:55:15.592 7499-7499/com.example.lab6 D/mihai: Permission refused
Because you have denied the permission multiple times.
From the documentation:
Starting in Android 11 (API level 30), if the user taps Deny for a specific permission more than once during your app's lifetime of installation on a device, the user doesn't see the system permissions dialog if your app requests that permission again. The user's action implies "don't ask again." On previous versions, users would see the system permissions dialog each time your app requested a permission, unless the user had previously selected a "don't ask again" checkbox or option.
You will need to redirect the user to the app's setting detail to grant the permission manually.
To open an app specific app's detail setting screen:
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);

Permissions - How do I request and/or change them in phones running MIUI during runtime?

Apparently the MIUI OS has already implemented its own Permissions system before Marshmallow does. I'm currently testing a video recording app for the Xiaomi Mi 4i, which uses a MIUI based on API 21 [Android 5.0.2], and it needs the Record Audio permission which is not granted by default by MIUI's Permissions Manager.
So far the way I've managed to alter the permissions is by accessing the Permissions Manager window for the app on clicking the OK button in the AlertDialog:
isMIUI = MIUIUtils.isMIUI();
if(isMIUI)
{
AlertDialog.Builder adb = new AlertDialog.Builder(this);
adb.setMessage("If you intend to use the video recording feature, please enable the 'Record Audio' permission in the settings menu. You will be redirected there if you press OK.")
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS");
intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
intent.putExtra("extra_pkgname", "com.picmix.mobile");
startActivity(intent);
}
})
.setNegativeButton("CANCEL", null)
.create();
adb.show();
}
But this isn't good enough for me. I need to check if the Record Audio permission is already checked in the MIUI Permissions Manager in order to run this only once.
How do I check for the permissions granted or to be notified in the MIUI Permissions Manager programmatically?
private boolean resourceCanBeAccessed() {
boolean response = true;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if(ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
== PackageManager.PERMISSION_DENIED ) {
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.RECORD_AUDIO}, 1);
response = false;
}
}
return response;
}
You just need to call this method before accessing the resource. This method will return true if the permission is granted. It the permission is not granted then it will grant the permission

Pressing allows after requesting permission for ACCESS_COARSE_LOCATION doesn't turn on location - nougat

Lastly I ran into problem that, I can't scan for beacons because of lack of ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION.
I tried to fix it by using code found here, but it actually help me partially.
When this view appears
I click allow. After that I doesn't get this java.lang.SecurityException: Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to get scan results error anymore, but I can't still see my beacons and when I open settings view the location is turned off like on picture bellow.
When I turn on location by finger everything works ok, so I can see my beacons and app works as it should. And here is the question is these some kind of bug or I missed something to turn on location from code behind after access to device location is turned on?
For developing I use Nexus 5x with android 7.1.1.
EDITED:
Code is copied from tutorial linked above, the fragment with button which starts beacon scanner:
public void onBleClicked(View view)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
if (this.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("This app needs location access");
builder.setMessage("Please grant location access so this app can detect beacons.");
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
#Override
public void onDismiss(DialogInterface dialog) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_COARSE_LOCATION);
}
}
});
builder.show();
}
BleManager bleManager = new BleManager(this);
bleManager.tryToTurnOnBle();
}
Fragment of manifest where permissions are declared:
<!-- app only for phones -->
<uses-feature android:name="android.hardware.telephony"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
The bluetooth permissions are apparently in library.
What I found right now is fact that there is similar question to mine here.
But this solution with redirecting user to location option screen is not seems to be clean one for me.
Location can be determined by two ways:
1.Using NETWORK_PROVIDER
2.Using GPS_PROVIDER
NETWORK_PROVIDER: It determines the location of the users using cell towers,wifi access points. It is commonly used for determining location inside the rooms or buildings. Here the GPS coordinates are not able to be obtained.
You can specify either
<uses-permission android:name=”android.permission.ACCESS_COARSE_LOCATION” />
or
<uses-permission android:name=”android.permission.ACCESS_FINE_LOCATION” />
in order to get location using the NETWORK_PROVIDER.
GPS_PROVIDER:
It determines the location of the users using satellites. For this, the GPS coordinates are obtained and used for positioning. The GPS receiver in the smartphone receives the signals from satellites. These signals are processed and precise locations are determined.It works better in outdoors – direct sky/satellite views and communication occurs.
You need specify the permission
<uses-permission android:name=”android.permission.ACCESS_FINE_LOCATION” />
in order to use location from GPS_PROVIDER.
Fine locations:
It gives better and accurate locations. So, that I recommend you to use this to get your beacon locations. It gives permission for using both GPS_PROVIDER and NETWORK_PROVIDER or GPS_PROVIDER only for determining the position.
Coarse locations:
It provides less accurate locations.It gives permission for using NETWORK_PROVIDER only for determining the position.
Now, come to the implementation.
- Declare the above said two permissions in the AnroidManifest.xml file:
- In the java part, do the following:
Request the permission if it not granted yet:
private void requestPermission(Activity activity) {
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.CALL_PHONE}, MainActivity.PERMISSION_REQUEST_CODE_LOCATION);
}
When the above method is called, a dialog asking permission will appear. On selecting Allow or Deny, the below callback gets triggered.
In the onRequestPermissionsResult
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String permissions[], int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case PERMISSION_REQUEST_CODE_LOCATION:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
boolean isGpsProviderEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
boolean isNetworkProviderEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
//Location permission is given. Check if the providers are available and start location updates.
if (isGpsProviderEnabled && isNetworkProviderEnabled) {
startLocationUpdates();
} else {
Log.d(TAG, "GPS and Network providers are disabled");
}
} else if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_DENIED) {
boolean should = ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION);
if (should) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, MainActivity.PERMISSION_REQUEST_CODE_LOCATION);
} else {
promptSettings();
}
}
}
}
In the promptSettings() method, let the user to enable location from the Settings screen.
private void promptSettings() {
AlertDialog.Builder builder;
builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle(getResources().getString(R.string.unable_to_find_location));
builder.setMessage(getResources().getString(R.string.message_denied_location_permission));
builder.setCancelable(false);
builder.setPositiveButton(getResources().getString(R.string.go_to_settings), (dialog, which) -> {
dialog.dismiss();
builder = null;
if (!checkPermission(MainActivity.this)) {
goToSettings();
}
});
builder.show();
}
In the check permissions method:
public boolean checkPermission(Context context) {
int result = ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION);
return result == PackageManager.PERMISSION_GRANTED;
}
The goToSettings() allows the user to go to Settings screen:
private void goToSettings() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivityForResult(intent, 1);
}
Note: You need to give the below permissions in the manifest to scan the beacons. I hope you are doing that, if not please do it.
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
As of Android Marshmallow (6.0), Location must be turned on in settings for apps to scan for Bluetooth LE devices including beacons. This requirement is in addition to the requirement that apps get dynamic permissions. You can see code below to query for location services being turned on and to prompt the user to turn it on if needed.
private void verifyLocationServices() {
final LocationManager manager = (LocationManager) getSystemService(this.LOCATION_SERVICE);
if (!manager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("This app requires that location services be enabled. Please enable location in settings.")
.setCancelable(false)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(final DialogInterface dialog, final int id) {
startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
}
});
final AlertDialog alert = builder.create();
alert.show();
}
}

SecurityException thrown when calling WifiManager startScan

I'm using a PendingIntent launched by AlarmManager (with setRepeating) to start wifi scans (using IntentService) every few minutes.
On most devices and in most cases, there is no problem with that.
However, on several devices I get the following error (Couldn't reproduce the error on any test device. This is a crash log from a user's device):
java.lang.RuntimeException: Unable to start service com.myapp.android.service.MyService#44a9701 with Intent { act=com.myapp.android.ACTION_PERFORM_WIFI_SCAN flg=0x4 cmp=com.myapp/com.mayapp.android.service.MyService (has extras) }: java.lang.SecurityException: Permission Denial: broadcast from android asks to run as user -1 but is calling from user 0; this requires android.permission.INTERACT_ACROSS_USERS_FULL or android.permission.INTERACT_ACROSS_USERS
at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3021)
at android.app.ActivityThread.-wrap17(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1443)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5415)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:725)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:615)
Caused by: java.lang.SecurityException: Permission Denial: broadcast from android asks to run as user -1 but is calling from user 0; this requires android.permission.INTERACT_ACROSS_USERS_FULL or android.permission.INTERACT_ACROSS_USERS
at android.os.Parcel.readException(Parcel.java:1599)
at android.os.Parcel.readException(Parcel.java:1552)
at android.net.wifi.IWifiManager$Stub$Proxy.startScan(IWifiManager.java:1045)
at android.net.wifi.WifiManager.startScan(WifiManager.java:1088)
...
I'm creating the PendingIntent from my app so I see no reason for the SecurityException thrown from WifiManager (Especially since this happens rarely).
The IntentService launched from the PendingIntent code is as follows:
mContext.registerReceiver(mWifiScanReceiver, new IntentFilter(
WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
boolean ok = mWifiManager.startScan();
Any ideas on what might be causing this?
This is happening because of the new app permissions for android m.
See the comment above the source code of wifimanager's getScanResults() for api 23-
/**
* Return the results of the latest access point scan.
* #return the list of access points found in the most recent scan. An app must hold
* {#link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
* {#link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission
* in order to get valid results.
*/
public List<ScanResult> getScanResults() {
try {
return mService.getScanResults(mContext.getOpPackageName());
} catch (RemoteException e) {
return null;
}
}
Hence, you will have to ask the user for permissions on runtime. Put these permissions in your manifest-
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
From api 23 onwards you require a permission to access user location to use it. I suggest you use a permissions check based on the api level and start intent only if the permissions have been granted.
Something like this-
if (Build.VERSION.SDK_INT >= 23) {
int hasReadLocationPermission = checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);
if (hasReadLocationPermission != PackageManager.PERMISSION_GRANTED) {
if (!ActivityCompat.shouldShowRequestPermissionRationale(HomeActivity.this, Manifest.permission.ACCESS_FINE_LOCATION)) {
showMessageOKCancel("You need to allow access to GPS",
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(HomeActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, GPS_ENABLE_REQUEST);
}
});
return;
}
ActivityCompat.requestPermissions(HomeActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, GPS_ENABLE_REQUEST);
return;
}
if (locationManager != null && !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
gotoGPSEnableScreen();
} else {
//Permissions granted and gps is on
launchService(true);
}
}
Further to check results-
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case GPS_ENABLE_REQUEST:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
gotoGPSEnableScreen();
}
} else {
launchService(false);
}
default:
return;
}
}
UPDATE:
android.permission.INTERACT_ACROSS_USERS_FULL is a signature level permission.
Just add this android:protectionLevel="signature" in your manifest .
For more details you can check this
http://developer.android.com/guide/topics/manifest/permission-element.html
<permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" android:protectionLevel="signature"/>
If you're going to override
onCreate()
in your
IntentService,
then make sure you call
super.onCreate()
in it. That seems to quite likely be your problem.
Your issue is you are calling from different user and asking to run on different user and that requires android.permission.INTERACT_ACROSS_USERS_FULL and that is signature level permission. Just add this android:protectionLevel="signature" in your manifest .
For more details you can check this
http://developer.android.com/guide/topics/manifest/permission-element.html
<permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" android:protectionLevel="signature"/>

Categories

Resources