Can't load images from external storage in M preview - android

I have an app that stores it's images on the external storage. This have been working great up until I tried the new M preview.
I use picasso (I even tried Ion) to load the images and I get the images with the "file:///mnt/sdcard/appname/image1.jpg" URI.
I don't get any errors at all, but I'm guessing that M has changed the permissions to read from external storage. I have tried googling but I come up empty.
Writing the images to the external storage works just as normal by the way.

Although siris_cac answer was very well explained it wasn't what caused my problem.
Turns out I can't use "file:///mnt/sdcard/appname/image1.jpg" to load the file from external storage in M.
I guess it might have something to to with http://www.androidpolice.com/2015/05/28/android-m-feature-spotlight-external-storage-can-be-adopted-as-true-internal-storage-or-accessed-normally-with-no-additional-apps/
I guess I was wrong from the begining not using the Enviroment.getExternalStorageDirectory() when loading images, only when saving them.
So now to load images I use:
"file:///" + Environment.getExternalStorageDirectory().getPath() + "/appname/image1.jpg";

You could try checking if the permission is granted by :
checkCallingOrSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
And try requesting permissions on Runtime like this using requestPermissions().
int EXT_PERMISSION = 1;
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 0);
And,
Handle the Permission Granted result
#Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case EXT_PERMISSION: {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//permission granted
} else {
//permission denied
}
break;
}
}
}

Related

Detect when a screenshot was taken without requiring storage permission on Android

I want to know when a screenshot has been taken of our application, but I do not care about the metadata, nor do I want a reference to the actual image.
The current implementation I'm looking to improve declares a custom ContentObserver, which subscribes to MediaStore.Images.Media.EXTERNAL_CONTENT_URI
mContentResolver.registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, this)
This enables the Observer to be notified when content has changed in this URI.
#Override
synchronized public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
if (uri.toString().startsWith(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString())) {
try {
checkPermissionAndProcess(uri);
} catch (Exception e) {
Timber.w(e, "onChange error : %s", e.toString());
}
} else {
Timber.v("[Finish] not EXTERNAL_CONTENT_URI ");
}
}
At this point, we need to check if we have Manifest.permission.READ_EXTERNAL_STORAGE and if not, request it. If the user grants us this permission, we can verify if the URI change is a screenshot with a series of checks. If we don't have permission, a SecurityException is thrown.
private boolean matchPath(String path) {
return (path.toLowerCase().contains("screenshots/") && !path.contains(FILE_POSTFIX));
}
My understanding of a ContentResolver observing MediaStore.Images.Media.EXTERNAL_CONTENT_URI means that any changes external to the application's storage can invoke the onChange callback.
Is there any way around this without asking the user for storage access which feels icky?
On Android 7.1.1 and Android 13 devices, I have tried using the camera while the app is in the background to trigger the onChange of the ContentResolver, and I only see it invoked when I take a screenshot. Assuming just observing changes in MediaStore.Images.Media.EXTERNAL_CONTENT_URI is naive, as other apps storing images could also result in changes here.

Managing AndroidPermissionRequest and MediaPermissionRequest using Geckoview Android

I am currently having a problem on managing requests using geckoview. Android built in webview is not an option for me because the website I want to open is not compatible with chrome. It can be opened only using mozilla so geckoview is my alternative.
The problem I have is granting permission on using the microphone and recording audio. Because the website I am trying to open in geckoview records audio (voice Collection).
I'am new to android and geckoview thats why the guide I use is this project https://searchfox.org/mozilla-central/source/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
I was able to show the request permission and accept it but it seems my application doesn't store the permission result. Currently I am trying my program to this website https://www.onlinemictest.com
This is my PermissionDelegate
private class ExamplePermissionDelegate implements GeckoSession.PermissionDelegate {
public int androidPermissionRequestCode = 1;
#Override
public void onAndroidPermissionsRequest(GeckoSession session, String[] permissions, Callback callback)
{
if (ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED){
Log.i(TAG, "Android Permission Needed");
requestPermissions(permissions, androidPermissionRequestCode);
callback = new ExamplePermissionCallback();
callback.grant();
}
else{
Log.i(TAG, "Android Permission Granted");
callback.grant();
}
}
#Override
public void onContentPermissionRequest (GeckoSession session, String uri, int type, String access, Callback callback)
{
Log.i(TAG, "Content Permission Needed");
}
#Override
public void onMediaPermissionRequest (GeckoSession session, String uri, MediaSource[] video, MediaSource[] audio, MediaCallback callback)
{
Log.i(TAG, "Media Permission Needed");
}
}
and this is my PermissionDelegateCallback
public class ExamplePermissionCallback implements GeckoSession.PermissionDelegate.Callback{
#Override
public void grant() {
int permission = ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.RECORD_AUDIO);
if (permission != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.RECORD_AUDIO},
RECORD_REQUEST_CODE);}
}
#Override
public void reject() {
}
}
The result says 'Android Permission Granted' and after that, it shows the Log I put which is 'Media Permission Needed' and the website says 'Waiting for microphone'
I also checked the application on my phone and it already has the microphone permission.
GeckoView has two levels of permissions:
The Android-level permission which Android grants to your app and you seem to be requesting correctly
The content-level permission which GeckoView grants to the specific web page and is not granted in your example.
In short, just because your app has permission to listen to the microphone, that doesn't mean that all web pages that you open in GeckoView will have access to the microphone.
When a page requests a media permission you get a onMediaPermission callback which you would need to accept using callback.grant, an example of this is here: https://searchfox.org/mozilla-central/rev/3483fb259b4edbe4594cfcc3911db97d5441b67d/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java#927
The audio argument of onMediaPermission contains the list of all the audio sources (most likely you'll only have one, the microphone) which you can use to accept the prompt for the right audio source calling
#Override
public void onMediaPermissionRequest(
GeckoSession session,
String uri,
MediaSource[] video,
MediaSource[] audio,
MediaCallback callback)
{
// Find out which audio source is the microphone
final int MICROPHONE_INDEX = ...;
// Grant the request
callback.grant(null, audio[MICROPHONE_INDEX]);
}
Note if you also need video, you can do the same thing with the video argument and the appropriate video source.
To figure out which one is the microphone you can use this snippet as example (look for SOURCE_MICROPHONE) https://searchfox.org/mozilla-central/source/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java#1201-1223

On Android 8.1&Pixel 2, how to draw on top and check this permission?

Background
I've noticed that ever since Android O came, the function of Settings.canDrawOverlays had issues telling us if the app was granted with the draw-on-top permission (AKA "Display over other apps"), so I've reported about this:
https://issuetracker.google.com/issues/62047810
https://issuetracker.google.com/issues/68465333
The problem
Not sure why they were marked as fixed, but now that I've tested on my Pixel 2 with Android 8.1, I've noticed this function still ALWAYS returns false (so I've reported about this here) .
Thing is, I've tried finding an alternative and saw others also having this issue here on StackOverflow:
Settings.canDrawOverlays is returning false even after turning the permission on from settings
Settings.canDrawOverlays(context) returns false on Android Oreo
Why in Android O method Settings.canDrawOverlays() returns "false" when user has granted permission to draw overlays and returns to my application?
I don't know if this is an issue with Android 8.1, or just my Pixel 2, but I'm using the latest, stock rom version of it (OPM171019.013) .
What I've tried
I've tried all of the solutions that were suggested on the above links.
All of them return me the result that the permission is not granted, ever.
The only solution I've found that does something is this one, which just tells me that the permission was toggled.
One way to "solve" this, is to assume the permission is granted in case the app was installed via the Play Store, because it's granted by default when installing from there.
But this is not a good solution. Apps can be installed from outside of the Play Store, and the user can always turn off this permission manually.
EDIT: I don't know if that's even a possible "solution", because when I try on the app, I can't really draw on top, even if the permission is granted.
The questions
How can I check for sure if the current app is granted with this permission?
From which Android version does this issue occur? Or maybe it's an issue only for Pixel 2 ?
Seeing that even after granting the permission, the app fails to draw on top, is there a possible solution for it?
EDIT: Speaking with others, I think this is a very specific issue on Pixel 2 with Android 8.1 . The reason is that the issue doesn't seem to appear on other devices with Android 8.1. I hope it gets fixed.
I had the same issue, there is a problem with the function Settings.canDrawOverlays because that needs an app reboot to return true when you granted the permission, try these.
I have a button that calls a Config Activity to enable Overlay
public void setOverlayPermissions(){
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:"+getPackageName()));
startActivityForResult(intent,app.REQUEST_ID_OVERLAY_PERMISSIONS);
}
After that, in onActivityResult funtion
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == app.REQUEST_ID_OVERLAY_PERMISSIONS){
new WaitingFor().execute();
}
}
And then
private class WaitingFor extends AsyncTask{
private ProgressDialog progressDialog;
#Override
protected void onPreExecute() {
super.onPreExecute();
progressDialog = new ProgressDialog(MainActivity.this);
progressDialog.setCancelable(false);
progressDialog.setMessage("Espera un momento...");
progressDialog.show();
}
#Override
protected Object doInBackground(Object[] objects) {
try{
Thread.sleep(2000);
}catch (Exception ex){
Log.e(TAG,ex.getMessage()+"");
}
return null;
}
#Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
progressDialog.dismiss();
if(verifyWindowOverlay()){
FL.w(TAG,"Acepto Overlay");
setPaso2();
}else{
FL.w(TAG,"NO acepto Overlay [Permission error]");
Toast.makeText(MainActivity.this,"Falta configuracion, reintenta!", Toast.LENGTH_SHORT).show();
}
}
}
My verifyWindowsOverlay function
public boolean verifyWindowOverlay(){
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
return Settings.canDrawOverlays(this);
if(appOpsMgr.checkOpNoThrow(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW, android.os.Process.myUid(), getPackageName()) == 0 || Settings.canDrawOverlays(this))
return true;
return false;
}
Good luck.

How to handle permission requests outside Activity and Fragment?

I'm developing a custom compound View that needs to access external storage. How can I implement the permission handling without involving outside parties, i.e. Activity or Fragment?
I get that I can request the permissions using the View's context, but how can I handle onRequestPermissionsResult() inside the View? Is it even possible?
If it's not possible, what would be the most elegant solution to handle something like this?
I'm developing a custom compound View that needs to access external storage
IMHO, that's an architecture bug. A View is for displaying stuff to the user, and sometimes for collecting low-level input events and turning them into higher-order constructs (e.g., clicks, swipes). A View should not have any connection to files, databases, etc. See the MVC, MVP, MVVM, and similar GUI architecture patterns.
WebView, which does not abide by this, causes problems (e.g., doing disk I/O on the main application thread) as a result.
How can I implement the permission handling without involving outside parties, i.e. Activity or Fragment?
You can't. It is the responsibility of the activity or fragment to request the permission, presumably before your view needs this data.
what would be the most elegant solution to handle something like this?
Extract the data-access portion of this View into something else that is managed by the activity or fragment, where the threading, permissions, and other work associated with that data access can be managed.
You can't work with permissions without the instance of the activity, but you can do your code prettier. If you want to send a request and handle it in one place, then you can use the example below.
Just create something looks like BaseActivity and put there such code
public class PermActivity extends Activity {
interface OnPermissionCallback{
void requestResult(String[] permissions, int[] grantResults);
}
private SparseArray<OnPermissionCallback> permissionCallback = new SparseArray<>();
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
permissionCallback.get(requestCode).requestResult(permissions, grantResults);
}
public void addPermissionCallback(int requestCode, OnPermissionCallback callback){
permissionCallback.put(requestCode, callback);
}
}
And now in our client code, we can do something like that
class SomeClasThatWorksWithPerms{
private PermActivity activity;
public SomeClasWorksWithPerms(PermActivity activity) {
this.activity = activity;
}
void foo(){
if (ContextCompat.checkSelfPermission(activity, WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED){
// do something
}else {
activity.addPermissionCallback(0, (perms, grantResults) -> {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
foo(); // try one more
}
});
activity.requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE}, 0);
}
}
}
I have used spareArray and indexation by the request code but you can use another way of storing callbacks.
It's very simple example, you can see something more serious there
https://github.com/mrizyver/Fl_Launcher/blob/master/app/src/main/java/com/izyver/fllauncher/presentation/activity/FlActivity.kt - as you can see, it is activity
https://github.com/mrizyver/Fl_Launcher/blob/master/app/src/main/java/com/izyver/fllauncher/presentation/loaders/WallpaperLoader.kt - our client code that works with permissions
let us assume you need to call the requestPermissionLauncher from a dialog fragment when a user clicks on "OK" or some other button. here is the requestPermissionLauncher found in MainActivity or you can put it in any other activity where the dialog fragment is called from.
public ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
if (isGranted) {
// Permission is granted. Continue the action or workflow in your
// app.
} else {
// Explain to the user that the feature is unavailable because the
// features requires a permission that the user has denied. At the
// same time, respect the user's decision. Don't link to system
// settings in an effort to convince the user to change their
// decision.
}
});
here is the code source if you want to refer https://developer.android.com/training/permissions/requesting
Then in your dialog fragment use the following code to call to the instance requestPermissionLauncher
((MainActivity)getContext()).requestPermissionLauncher.launch(Manifest.permission.[*your permission goes here*]);
It's only possible in Activities and Fragments.
What you can do is copy public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) in your View and call that method in the corresponding one in the Activity or Fragment where the Context is.

Duplicate permission request after orientation change

Because the Android SDK 23 gives users the possibility to deny apps access to certain functionalities I wanted to update one of my apps to request permissions as it is described in here: https://developer.android.com/preview/features/runtime-permissions.html.
In one of the activities I embed a SupportMapFragment. To make it work you need to have the WRITE_EXTERNAL_STORAGE permission, so I request it when I start the activity which results in a creation of a permission request dialog.
Now the problem is that when the dialog is still open and I rotate the device the activity will be restarted and open a new permission request dialog while the old one is still there. The result is two of those dialogs on top of each other and only one of it being useful.
Is there a way to get rid of the dialog that was started first?
As CommonsWare said in his comment the best solution is to put a boolean into the savedInstanceState-Bundle to know if the dialog is still open.
Example:
// true if dialog already open
private boolean alreadyAskedForStoragePermission = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null) {
alreadyAskedForStoragePermission = savedInstanceState.getBoolean(STORAGE_PERMISSION_DIALOG_OPEN_KEY, false);
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(KEY, alreadyAskedForStoragePermission);
}
private void checkStoragePermission(){
if(alreadyAskedForStoragePermission){
// don't check again because the dialog is still open
return;
}
if(ActivityCompat.checkSelfPermission(this, STORAGE_PERMISSIONS[0]) != PackageManager.PERMISSION_GRANTED){
// the dialog will be opened so we have to keep that in memory
alreadyAskedForStoragePermission = true;
ActivityCompat.requestPermissions(this, STORAGE_PERMISSIONS, STORAGE_PERMISSION_REQUEST_CODE);
} else {
onStoragePermissionGranted();
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
switch (requestCode){
case STORAGE_PERMISSION_REQUEST_CODE:
// the request returned a result so the dialog is closed
alreadyAskedForStoragePermission = false;
if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
onStoragePermissionGranted();
}
break;
}
}
As #user1991776 mentioned there is actually an undocumented extra that contains whether or not there is a permission dialog open at the moment, in Activity:
private static final String HAS_CURENT_PERMISSIONS_REQUEST_KEY =
"android:hasCurrentPermissionsRequest";
However there is a better way. When you request a permission dialog the second time (due to a rotation), Activity automatically cancels the old dialog by calling your onRequestPermissionResult() with empty arrays:
public final void requestPermissions(#NonNull String[] permissions, int requestCode) {
if (mHasCurrentPermissionsRequest) {
Log.w(TAG, "Can reqeust only one set of permissions at a time");
// Dispatch the callback with empty arrays which means a cancellation.
onRequestPermissionsResult(requestCode, new String[0], new int[0]);
return;
}
Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
mHasCurrentPermissionsRequest = true;
}
Or course this behaviour isn't documented because this is Android, and who wants to document complex behaviour?
Anyway you can just always request permissions in onCreate() and then ignore calls to onRequestPermissionsResult() with zero-length permissions arrays.
I guess as this is a system dialog you cannot control it. You could instead prevent that your activity gets reloaded if you turn your device.

Categories

Resources