Yesterday my Nexus 5 receive the update from Android MNC to version 6.0 - Marshmallow.
Since then, the action to scan the networks available in the device stop receiving the list, in this case the result list have a size of 0, even with 10+ Wifi networks listed in the Wifi system settings.
The code for this is the usual: Register the SCAN_RESULTS_AVAILABLE_ACTION and wait for the event in the Receiver, like this:
// Register the Receiver in some part os fragment...
getActivity().registerReceiver(wifiListener, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
WifiManager wifiManager = (WifiManager) getActivity().getSystemService(Context.WIFI_SERVICE);
wifiManager.startScan();
// Inside the receiver:
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
List<ScanResult> results = wifiManager.getScanResults();
// the result.size() is 0 after update to Android v6.0, same code working in older devices.
I searched in the changes of the API topic about this, but I didn' see any breaking changes for this functionality.
Did anyone notice this? Is something new in the API or just a isolated case?
As of Android 6.0, permission behaviour has changed to runtime. To use a feature that requires a permission, one should check first if the permission is granted previously. Using checkSelfPermission(permissionString) method a result is returned, wither ther permission is PERMISSION_GRANTED or PERMISSION_DENIED.
If permission isn't granted or it is first time, a request for permission should be made. Giving a user an option to grant or deny.
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED){
requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
PERMISSIONS_REQUEST_CODE_ACCESS_COARSE_LOCATION);
//After this point you wait for callback in onRequestPermissionsResult(int, String[], int[]) overriden method
}else{
getScanningResults();
//do something, permission was previously granted; or legacy device
}
If your code is running on device prior to M, you proceed with your code, permission was granted using legacy method.
Once requested for permission, dialog will be shown to user. His/her response will be delivered as:
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
if (requestCode == PERMISSIONS_REQUEST_CODE_ACCESS_COARSE_LOCATION
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Do something with granted permission
mWifiListener.getScanningResults();
}
}
After that, you can check if the Location Services is ON, using LocationServices.SettingsApi and request the user to enable if this options is disabled. This is possible with Play Services LocationSettingsStatusCodes.RESOLUTION_REQUIRED callback.
I find related issue in AOSP issue tracker issue 185370 WifiManager#getScanResults() returns an empty array list if GPS is turned off.
The problem mentions from #1, The mobile must open location service to get wifi list of mobile.
And From #18, The Android project member claims that the development team has fixed the issue that you have reported and it will be available in a future build.
The APP is in targetSdkVersion 23, just follow above solution to check runtime permission. Enforcing to enable location services problem will fix in Android future release.
This won't work unless you have the GPS turned on. Weird, but it's the only way I got the list of wifi's :-(.
Edit
So, the problem seems to be with the new permission handling. You have to ask for permission before proceeding to the wifi code. Here is an example:
// call this method only if you are on 6.0 and up, otherwise call doGetWifi()
private void getWifi() {
if (checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, 0x12345);
} else {
doGetWifi(); // the actual wifi scanning
}
}
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == 0x12345) {
for (int grantResult : grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) {
return;
}
}
getWifi();
}
}
This check must be done in an Activity.
The original sample code is available here, modified according to the problem discussed in this topic.
Original
According to the linked changes of the API, your application must have one of the location permissions. Quote:
WifiManager.getScanResults(): Your app must have ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION permission.
Also note that there is a new boolean key received by your BroadcastReceiver on SCAN_RESULTS_AVAILABLE_ACTION action: EXTRA_RESULTS_UPDATED. This shows if the scan is complete and you can access the results by calling wifiManager.getScanResults().
In addition to the given responses; you can also use checkSelfPermission of ContextCompat to allow for backwards compatibility with lower Android versions:
if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSIONS_REQUEST_CODE_ACCESS_FINE_LOCATION);
// Get the result in onRequestPermissionsResult(int, String[], int[])
} else {
// Permission was granted, do your stuff here
}
Related
How can I get instant transitions updates from geofence, when the app is running in the background?
Android background location limitations are preventing that from happening. Is there any way around it?
In Android 8: Apps in the background can only retrieve the user's location a few times per hour.
Before Android 10: Location permission is a single resource, and the application can be used everywhere with only one authorization (foreground and background).
In Android 10: The background location becomes an independent resource. In addition to the foreground request, the application must explicitly request this permission.
In Android 11: It is not possible to request background location permission at the same time as others, and the application must request it separately. In addition, requesting this permission will not prompt the user immediately like other permissions, but will take the user to the Settings page so that the user can update the permission level.
Before Android 10
Location permission only needs to be requested once, and apps in the foreground and background can be used. The user has only 2 options: authorize or not authorize.
#TargetApi(28)
fun Context.checkLocationPermissionAPI28(locationRequestCode : Int) {
if (!checkSinglePermission(Manifest.permission.ACCESS_FINE_LOCATION) ||
!checkSinglePermission(Manifest.permission.ACCESS_COARSE_LOCATION)) {
val permList = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
requestPermissions(permList, locationRequestCode)
}
}
private fun Context.checkSinglePermission(permission: String) : Boolean {
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
}
Android 10
In this version, ACCESS_BACKGROUND_LOCATION is added, you can request this permission to obtain both the foreground and background permissions, like the following:
#TargetApi(29)
private fun Context.checkLocationPermissionAPI29(locationRequestCode : Int) {
if (checkSinglePermission(Manifest.permission.ACCESS_FINE_LOCATION) &&
checkSinglePermission(Manifest.permission.ACCESS_COARSE_LOCATION) &&
checkSinglePermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) return
val permList = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION)
requestPermissions(permList, locationRequestCode)
}
private fun Context.checkSinglePermission(permission: String) : Boolean {
return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
}
Similarly, if the foreground permission (ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION) is requested, the Android operating system will automatically add the background permission (ACCESS_BACKGROUND_LOCATION) to the request. It is similar to the declaration of , whether ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION is declared, ACCESS_BACKGROUND_LOCATION will be added during the installation process.
The user now has three options: background (anytime), foreground (only during APP use), and reject.
Android 11
In addition to the above, developers also need to add some other steps.
There are two scenarios here. The first one is when only requesting permission from the front desk. In this case, we usually use ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION. However, the pop-up window for requesting authorization is slightly different from the previous one. In Android 11, Google added an option Only this time.
Please note that even if ACCESS_BACKGROUND_LOCATION is added to the list of permissions to request, the system will ignore it.
The second situation is that the application also needs background permission. For this, you must prepare your own dialog box and use a clear message to explain the use of the background location.
When the user agrees, he will be directed to the application settings page, where he can choose the permission level he wants to grant.
#TargetApi(30)
private fun Context.checkBackgroundLocationPermissionAPI30(backgroundLocationRequestCode: Int) {
if (checkSinglePermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) return
AlertDialog.Builder(this)
.setTitle(R.string.background_location_permission_title)
.setMessage(R.string.background_location_permission_message)
.setPositiveButton(R.string.yes) { _,_ ->
// this request will take user to Application's Setting page
requestPermissions(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION), backgroundLocationRequestCode)
}
.setNegativeButton(R.string.no) { dialog,_ ->
dialog.dismiss()
}
.create()
.show()
}
In Android 11, we have 4 permission levels for location information.
When the user selects Allow all the time, the APP has the permission to use location information in the background.
I am targeting API 24+ for my Android application so I need to request permissions at runtime. I have the request permissions code as shown below. It does not pull up a dialog that asks permissions to write to external storage. According to both my emulator and my phone, I already have write permissions to external storage. This is confirmed by going into the application settings on my phone where it shows the app with permission to read and write to my SD card. However, when I run the application I cannot delete any files from my SD card. File.canWrite() returns false and File.setWritable(true) also returns false. The permissions for READ and WRITE external storage both exist in my manifest file.
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
requestPermissions(permissions, MY_PERMISSIONS_WRITE_EXTERNAL_STORAGE);
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_WRITE_EXTERNAL_STORAGE: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
configureSettings();
listFilesForFolder(folder);
checkMatches();
deleteDuplicates();
textViewStatus.append("\nCompleted!");
writeLogFile();
progressBar.setVisibility(View.GONE);
} else {
break;
}
return;
}
}
}
When the code above is run, it evaluates the permission as granted and carries out the rest of the operations, but it cannot delete the files.
My app requires permissions in order to run. If users deny permissions on the initial run it will close. If they run the app a second time and deny permissions again, the third time they attempt to run the app and it requests permissions, there will also appear a radio button in the dialog with the option "Don't ask again". If the user clicks that then the app is going to close, and the next time they run it permissions will not be asked, resulting in an endless loop hole. The user will be opening the app and it will be crashing forever, unless they uninstall and reinstall the app. How do we solve this problem? can we code so that the radio button never appears no matter how many times users deny permissions? is there an other non obvious way i am missing? i can tell this will be a challenge to crack...
public void requestPermissions() {
//Requesting permissions.
ActivityCompat.requestPermissions(this,new String[] {
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.RECORD_AUDIO
}, REQUEST_PERMISSION_CODE);
}
public boolean checkPermissionFromDenice() {
//Checking whether permissions have been granted.
int write_external_storage_result = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
int record_audio_result = ContextCompat.checkSelfPermission(this,Manifest.permission.RECORD_AUDIO);
return write_external_storage_result == PackageManager.PERMISSION_GRANTED && record_audio_result == PackageManager.PERMISSION_GRANTED;
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
//Returns user's input regarding the requested permissions.
if (requestCode == REQUEST_PERMISSION_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show();
finish(); //Killing the activity in case user accepts permissions so they set in.
} else {
Toast.makeText(this, "Permission not granted", Toast.LENGTH_SHORT).show();
finishAffinity(); //Terminating the application in case user denies permissions.
}
}
}
You should harden your app against users that are unwilling to grant permissions. You should warn them that the app will not work and if the permissions are not granted later, give the users instructions how they can reset their permission settings and otherwise refuse to run the rest of the app.
Note that this is a drastic measure and you should only do so if the permission is absolutely required for functionality of your app. If there are some features that are still usable without this particular permission, then you should still allow users to do them.
In my app, I need to known if there is any Google account or any Samsung account.
Up to Android 7 it was easy to get this information with something like:
Account[] accounts = AccountManager.get(getContext())
.getAccountsByType("com.google")
But with the event of Oreo this does not work anymore.
EDIT: see official information on this subject:
In Android 8.0 (API level 26), apps can no longer get access to user accounts unless the authenticator owns the accounts or the user grants that access. The GET_ACCOUNTS permission is no longer sufficient. To be granted access to an account, apps should either use AccountManager.newChooseAccountIntent() or an authenticator-specific method. After getting access to accounts, an app can can call AccountManager.getAccounts() to access them.
Android 8.0 deprecates LOGIN_ACCOUNTS_CHANGED_ACTION. Apps should instead use addOnAccountsUpdatedListener() to get updates about accounts during runtime.
For information about new APIs and methods added for account access and discoverability, see Account Access and Discoverability in the New APIs section of this document
I spent half a day to find a solution to my need, without success.
I've found information claiming that now the only way to access to accounts is to use AccountPicker like this:
AccountPicker.newChooseAccountIntent(null, null, new String[]{"com.google"},true, null, null, null, null);
But this does respond to my problem. To be clear I only need to know if an account exists for a certain type (Google, Samsung...) I do not need to know how much if so and do not need accounts information.
Using "android.permission.READ_CONTACTS" permission, and
Account[] accounts = AccountManager.get(getContext())
.getAccountsByType("com.google")
working again in android Oreo
As you already said, there's no way to read other accounts if the user didn't give you the permission to do so. The permission now is provided not only with the run-time permission but even with the account picker, i.e. an account is visible to your app only if the user selected the account after you called the account picker. This new restriction is exactly to avoid what you are trying to do: read all user accounts. There's no solution to your problem, the only thing you can do is to present the picker to the user and let him select all the accounts, not the best user experience however.
Edit: starting from Google Play Services 11.6 there's now a new method requestGoogleAccountsAccess() to get all Google accounts.
To get the installed google accounts on a device running Oreo+ (8+) with this code
Account[] accounts = AccountManager.get(getContext()).getAccountsByType("com.google")
You need to first call
https://developers.google.com/android/reference/com/google/android/gms/auth/GoogleAuthUtil.html#requestGoogleAccountsAccess(android.content.Context)
Please add the following dependency first
com.google.android.gms:play-services-auth:16.0.0
The call requestGoogleAccountsAccess() throws an exception which you can cast (after checking) to UserRecoverableAuthException and get an intent from it to start with startActivityForResult
Here is some example code, working on Android Oreo
// call this on a background thread!
private void requestGoogleAccountAccess() throws Exception
{
googleAccountAccessGranted = GoogleAuthUtil.requestGoogleAccountsAccess(this);
Log.i(TAG, "googleAccountAccessGranted: " + googleAccountAccessGranted);
}
// exception handler after calling method above
private void handleAuthResult(Throwable e)
{
if (e instanceof UserRecoverableAuthException)
{
UserRecoverableAuthException authException = (UserRecoverableAuthException) e;
startActivityForResult(authException.getIntent(), AUTH_PERMISSION_REQUEST);
}
else
{
Log.e(TAG, "Cannot request Google Account Access", e);
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == AUTH_PERMISSION_REQUEST)
{
Log.i(TAG, "Google Auth Permission Result");
if (resultCode == Activity.RESULT_CANCELED)
{
Log.w(TAG, "User Cancelled Play Services Auth Request.")
}
else if (resultCode == Activity.RESULT_OK)
{
Log.d(TAG, "User accepted Play Services Auth Request.");
// call the following line again on a background thread. the call now returns a boolean instead of throwing an exception
// googleAccountAccessGranted = GoogleAuthUtil.requestGoogleAccountsAccess(this);
}
}
}
It's a bit strange why Google decided themselves for this "architecture". Why not return a Task, etc.
But this is how you get it working.
Of course this code needs proper exception handling which I left out for readability.
I have added uses-permission including WRITE_EXTERNAL_STORAGE,MOUNT_UNMOUNT_FILESYSTEMS,READ_EXTERNAL_STORAGE to AndroidManifest.xml.
When I tried to run my application in Nexus5 (Android 6.0),it threw a exception as below:
java.io.IOException: open failed: EACCES (Permission denied)
And I tried another Android phone(Android 5.1),everything was OK.Here's the code:
private File createImageFile() throws IOException {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(imageFileName, ".jpg", storageDir);
currentPhotoPath = image.getAbsolutePath();
return image;
}
Does Android 6.0 have difference about permission?
Android added new permission model for Android 6.0 (Marshmallow).
http://www.captechconsulting.com/blogs/runtime-permissions-best-practices-and-how-to-gracefully-handle-permission-removal
So you have to check Runtime Permission :
What Are Runtime Permissions?
With Android 6.0 Marshmallow, Google introduced a new permission model that allows users to better understand why an application may be requesting specific permissions. Rather than the user blindly accepting all permissions at install time, the user is now prompted to accept permissions as they become necessary during application use.
When to Implement the New Model?
it doesn’t require full support until you choose to target version 23 in your application. If you are targeting version 22 or below, your application will request all permissions at install time just as it would on any device running an OS below Marshmallow.
This information is taken from here :
Please check How to implement from this link :
http://www.captechconsulting.com/blogs/runtime-permissions-best-practices-and-how-to-gracefully-handle-permission-removal
In Android 6(Marshmallow), even though the user accepted all your permissions at install time, they can later decide to take some of those permissions away from you.
Fast solution but not recommended: maybe if you change your targetSdkVersion in the gradle to 22, the problem will be solved.
How To Implement?(Best Practices)
First determine if the user’s device is a Marshmallow device or not:
private boolean shouldAskPermission(){
return(Build.VERSION.SDK_INT>Build.VERSION_CODES.LOLLIPOP_MR1);
}
If shouldAskPermission() return true, ask for permission you need:
String[] perms = {"android.permission.WRITE_EXTERNAL_STORAGE"};
int permsRequestCode = 200;
requestPermissions(perms, permsRequestCode);
The method requestPermissions(String[] permissions, int requestCode); is a public method found inside of the Android Activity class.
You will receive the results of your request in the method onRequestPermissionResult as shown below:
#Override
public void onRequestPermissionsResult(int permsRequestCode, String[] permissions, int[] grantResults){
switch(permsRequestCode){
case 200:
boolean writeAccepted = grantResults[0]==PackageManager.PERMISSION_GRANTED;
break;
}
}
After receiving the results, you will need to handle them appropriately.
Suggested Permissions Flow:
More Info:
A user with a Marshmallow device will now have the ability to revoke dangerous permissions via the application settings
Android defines some permissions as “dangerous” and some permissions as “normal.” Both are required in your application’s manifest but only dangerous permissions require a runtime request.
If you have chosen not to implement the new permissions model(runtime request), the revocation of permissions can cause unwanted user experiences and in some cases application crashes.
The table below lists all the current dangerous permissions and their respective groups:
If the user accepts one permission in a group/category they accept the entire group!
Source:http://www.captechconsulting.com
Using Dexter Library:
You can use Dexter. Android library that simplifies the process of requesting permissions at runtime.
You can also use ActivityCompat.requestPermissions for backwards compatible.
example:
private static final int REQUEST_CODE = 0x11;
String[] permissions = {"android.permission.WRITE_EXTERNAL_STORAGE"};
ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE); // without sdk version check
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// save file
} else {
Toast.makeText(getApplicationContext(), "PERMISSION_DENIED", Toast.LENGTH_SHORT).show();
}
}
}
From API-23 you need to declare the permission in activity even if you have already declared in manifest.
// Storage Permissions variables
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
//persmission method.
public static void verifyStoragePermissions(Activity activity) {
// Check if we have read or write permission
int writePermission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
int readPermission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE);
if (writePermission != PackageManager.PERMISSION_GRANTED || readPermission != PackageManager.PERMISSION_GRANTED) {
// We don't have permission so prompt the user
ActivityCompat.requestPermissions(
activity,
PERMISSIONS_STORAGE,
REQUEST_EXTERNAL_STORAGE
);
}
}
To use simply call verifyStoragePermissions(this); in onCreate.
That should do it hopefully.
If you are lazy, just downgrade targetSdkVersion to 22 (before lollipop)
This worked for me.
Go to Settings -> Applications -> YourApp -> Provide permissions to Storage, Contacts etc.
Google has a new feature on Android Q: filtered view for external storage. A quick fix for that is to add this code in the AndroidManifest.xml file:
<manifest ... >
<!-- This attribute is "false" by default on apps targeting Android Q. -->
<application android:requestLegacyExternalStorage="true" ... >
...
</application>
</manifest>
You can read more about it here: https://developer.android.com/training/data-storage/compatibility
For me, my phone connected as USB as MTP was the issue even after doing everything stated here. Switched it to "Charging Only" worked for me.
I met the same trouble.
Maybe it is caused by your phone security mechanism. You may go to 'settings' to authorize write/read permissions.