Android: Uri Access Life Time from one activity to another - java

I have a helper method for choosing images and videos, let's call it Activity B.
So, this is how it works:
// were in Activity A
// user wants to choose a video
startActivityB(callbacks);
------------------------------------
// were in Activity B now
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("video/*");
chooseVideoLauncher.launch(intent);
------------------------------------
// were in chooseVideoLauncher now
Uri videoURI = ...;
callbacks.passVideoURI(videoURI); // this way, Activity A gets the videoURI
// do some more things...
finish(); // so the helper activity B is finished now, as the video is chosen already
------------------------------------
// were in activity A again, but now we have the videoURI
// user clicked a button: "Upload video"
uploadVideo(videoURI);
This is the error I get:
java.lang.SecurityException: Permission Denial: opening provider com.miui.gallery.provider.GalleryOpenProvider from ProcessRecord{f5899ab 29899:com.xxx} (pid=xxx, uid=xxx) that is not exported from UID xxx
I have googled the error and found this SO thread: here
#CommonsWare explains the error in a comment and links his blog post: Uri Access Lifetime: Shorter Than You Might Think
So the error happens because the helper Activity B chose the file, so the access is tied to Activity B. No other activity has access, and as soon as Activity B is destroyed (what happens in my code), the access to videoURI is completely gone. So when I later try to upload the video, it throws this error.
I tried these solutions:
Create a local copy of the video and pass that copy to Activity A. This works, but is a bad solution. For longer videos the app crashes with a memory overflow. So it's not an option.
Setting the flags #CommonsWare mentioned. So the code looks like this:
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("video/*");
intent.setFlags(FLAG_GRANT_READ_URI_PERMISSION);
intent.setFlags(FLAG_GRANT_WRITE_URI_PERMISSION);
chooseVideoLauncher.launch(intent);
But this doesn't seem to change anything. The error message remains exactly the same. Am I setting them wrong?
#CommonsWare says using a service would also be a solution. I would prefer not to create a service purely for fixing this permission error. If there's no other solution, I will of course.
But is there no way to grant Activity A permission to that Uri as well?

The best solution, by far, is to combine Activity A and Activity B into a single activity. Use fragments or composables for separate screens.
Setting the flags #CommonsWare mentioned. So the code looks like this:
You would not set the flags on the ACTION_PICK Intent. Instead, Activity B needs to start Activity A (in addition to finish()). You would put the Uri into the "data" facet of the Intent (e.g., via setData()), and you would put the flags on that Intent. You would also need something like FLAG_ACTIVITY_REORDER_TO_FRONT or something to avoid having two copies of Activity A on the back stack.

For the second way Set the flags to get the long lifetime, you can try to add the following code, to get the permission, then start the activity
val contentResolver = applicationContext.contentResolver
val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
// Check for the freshest data.
contentResolver.takePersistableUriPermission(uri, takeFlags)

Related

Android - app goes to launcher after calling CAPTURE_IMAGE intent and taking picture (doesn't return to the fragment)

So I have a Fragment that calls the following method which launches the camera:
private void launchCamera() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getActivity().getPackageManager()) != null) {
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
}
And I expect to receive the picture data in this method in my fragment:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (!(resultCode == RESULT_OK)) return;
if (requestCode == REQUEST_IMAGE_CAPTURE || requestCode == REQUEST_GALLERY_IMAGE) {
Uri imageURI = data.getData();
// do something
}
}
However, after I take a picture and confirm it, the app goes to my launcher. After setting breakpoints in the onActivityResult method, the app never even reaches this method before crashing. I've granted made sure to grant all permissions in both the manifest and at runtime.
There are also no outputs to Logcat with this crash, both in the app logs and the device logs. I have also tested on both my device (Moto G5 Plus) & Pixel XL API 26 emulator; both have the same result.
I think you need to call super.onActivityResult().
The fragment is the one making the startActivityForResult() call, but the activity gets the first shot at handling the result so you need to implement super.onActivityResult() to make the fragment handle the result.
the app immediately crashes to my launcher
No, the camera app immediately brings up the launcher. Apparently, the developers of this camera app wrote it to bring up the home screen when the user is done taking the picture. This is a bug, of course, but there is little that you can do about it.
Use ACTION_IMAGE_CAPTURE when you would like a picture but do not mind if you do not get it, due to bugs in some of the hundreds of camera apps that you are integrating with. Otherwise, use a camera library (Fotoapparat, CameraKit-Android, etc.) to take the picture directly in your own app.
Also, and FWIW, ACTION_IMAGE_CAPTURE does not return a Uri, so your onActivityResult() code will not work anyway. Given your particular ACTION_IMAGE_CAPTURE Intent configuration, in onActivityResult(), data.getParcelableExtra("data") will return a Bitmap representing a thumbnail-sized image.
I believe this is yet another manifestation of the Android: Activity getting Destroyed after calling Camera Intent.
The bottom line is, the system restarts your app from scratch and your activity is created but has a chance to restore its state.
So you must implement onRestoreInstanceState(). I am not sure you can guarantee that the fragment will be ready to receive onActiviyResult() timely, so to be in the safe side, I prefer to handle the captured image in activity itself.
So this issue was actually because of an intent flag I had attached to the activity. My activity was started using the NO_HISTORY intent flag, which apparently prevented it from being recreated when returned from a startActivityForResult call.

Initiliazing ParseLoginUI?

Where does one actually place the code to launch the ParseLoginUI activity?
ParseLoginBuilder builder = new ParseLoginBuilder(MainActivity.this);
startActivityForResult(builder.build(), 0);
Is it in the ParseLoginDispatchActivity? This was not made very clear at all within any of the official documentation:
https://github.com/ParsePlatform/ParseUI-Android
https://www.parse.com/docs/android/guide#user-interface
I'm importing ParseLoginUI into my existing app. What do I once I've installed everything, updated my manifests, my build.gradle and now want to actually launch the Login activity once my app launches?
Do I put something in my manifest to indicate that the ParseLoginActivity should launch first? That doesn't seem to work as an Activity from my main application is required to launch as the initial intent. I'm a little lost here... Any thoughts?
Well I did find one solution, albeit a trivial one:
Intent loginIntent = new Intent(MainActivity.this, ParseLoginActivity.class); startActivity(loginIntent);
I launched the above Intent with an options menu item, but you could do it with a button or whatever else suits your needs.
If you're importing ParseLoginUI into an existing app, it appears you can just launch ParseLoginActivity with a simple Intent. I wish they mentioned this on their integration tutorial. Seems like the most straightforward way to get it running.
This solution definitely launches the Activity you want, but it doesn't check for whether the user is logged in or not and hence doesn't redirect you to the appropriate pages in your log-in flow (which I believe has more to do with your Manifest). It does, however, allow you to successfully register a user and log in with Parse, which is a great start.
A better solution would be to add the following to the onCreate method in the Activity that launches when your app launches. So if when your app launches you land on FirstActivity, the following will check to see if you are logged in. If you are not, you will be sent the login screen, and if you are logged in you will be sent to the second Activity, which is presumably where your users will want to be when they open your app.
ParseUser currentUser = ParseUser.getCurrentUser();
if (currentUser != null) {
Intent launchMainActivity = new Intent(this, SecondActivity.class);
startActivity(launchMainActivity );
} else {
ParseLoginBuilder builder = new ParseLoginBuilder(FirstActivity.this);
startActivityForResult(builder.build(), 0);
}

Android: OutOfMemory error and the backstack

the following sheet represents the working flow in the application this question is about.
I ran into problems with OutOfMemory Errors, mostly because users were able to switch from activity B to activity D multiple times (They are showing different content for every attempt), without the previous activity being destroyed. This led into a very large backstack resulting in an OutOfMemory error.
To avoid this, I followed the recommendation to add parent activites to the manifest and create new backstacks with the TaskStackBuilder class:
void openDFromB()
{
Intent i = new Intent(this, ActivityD.class);
TaskStackBuilder.create(this)
.addParentStack(ActivityD.class)
.addNextIntent(i)
.startActivities();
}
If a user now switches from activity B to D, MainMenu, A and B are destroyed and a new Backstack (MainMenu, C, D) is created.
This solves my memory problems for api level greater 10, but unfortunately the TaskStackBuilder does not create backstacks for devices pre api 11.
Any idea what to do in order to avoid users stacking infinite amounts of activities prior api 11? Is this even possible or should I try to free as many resources as possible in onPause to gain a maximum amount of stacked activities before OoM?
thanks in advance!
Try this-
Intent intent = new Intent(getApplicationContext(),yourActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
Another solution is-
Setting android:noHistory="true" on the activity in your manifest file will remove an activity from the stack whenever it is navigated away from.
But the most appropriate way is to call finish() method whenever you start new activity like in the following sample code-
Intent intent = new Intent(this, yourActivity.class);
startActivity(intent);
finish();
You can also create custom Broadcast receiver and register it in every activity which can be fired on event of your choice. For more help you can check this link.
Zabri answer is good, but i would propose another probably is better:
You dont close every activity, but when you are opening a new activity that has been open before, you tell the system that it can clear the way back and reopen that one, of course with a new intent and all the new data that you need.
So, if you do A - B - D - B - D - B, instead of having that big stack,you will have
A - B
A - B - D
A - B
A - B - D
A - B
So, if you do C - D - B - D - B - D, your stack will be
C - D
C - D - B
C - D
C - D - B
C - D
and so on.
In this way, i think the stack actually reflects what the user can have in mind, and also navigation is very logic, you don't even need to captre back button to have the navigation making sense, (of course you can do it if you want a more specific user experience)
The code to achieve this, is:
create a function that receives a class (something like ActivityD.class), there you create an intent, with the flags Intent.FLAG_ACTIVITY_CLEAR_TOP and Intent.FLAG_ACTIVITY_NEW_TASK and then when you need to open an activity, just call this function. You dont care about if the activity is in the stack or not. If it isnt,it is created normally.
protected void startActivity(Class<?> clase) {
final Intent i = new Intent(this, clase);
int flags = Intent.FLAG_ACTIVITY_CLEAR_TOP;
flags |= Intent.FLAG_ACTIVITY_NEW_TASK;
i.setFlags(flags);
startActivity(i);
}
startActivity(ActivityD.class);
May be your application having memory leaks and due to that older activities not getting reclaim by the GC and eventually getting heap size increase every-time activity switch occurs.
How to find leaks?
Please watch: http://www.youtube.com/watch?v=_CruQY55HOk
References:
http://android-developers.blogspot.in/2011/03/memory-analysis-for-android.html.
http://kohlerm.blogspot.in/2009/07/eclipse-memory-analyzer-10-useful.html
I don't know how it works with the Navigation Drawer, but I was creating an Android application some time ago which started with a loading screen and then moved to the main activity of the app. Since I didn't need/want the user ever to go back to the loading screen, I just called finish() before moving to the main activity. Like this:
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
finish();
And then, when the user pressed the back button from the main activity, they didn't get to the loading activity, but to wherever they were even before that.
So you could also try to do this:
void openDFromB()
{
Intent i = new Intent(this, ActivityD.class);
startActivity(i);
finish();
}
As I said, I have never implemented a Navigation Drawer, but something tells me that doing this might break its whole behaviour. Also, calling finish() in an activity is such a basic concept that I would be surprised if you hadn't tried it already. :) But still, I am posting it as a shot in the dark, just in case it helps you. Good luck. :)
Hello you can try to add the following statement in your manifest file under application tag:
android:largeHeap="true"
Also make sure your android:targetSdkVersion is 14.

How do I create an android shortcut to an activity?

OK,I'm new at this forum, so don't blame me for putting this in the wrong tags,not putting something in,eg.
I want to learn how to create a shortcut(I did that by Googling) and link it to an activity (In this case, com.android.mms.ui.ComposeMessageActivity)
I tried doing it, but it only showed me a toast saying "Application not installed" and I'm pretty sure it is.
It would be better if you can display a "complete action with another application" dialog.
If I assumed your question correctly, you mean a button or something within an activity that leads to another activity, that being -- "com.android.mms.ui.ComposeMessageActivity"
if your activity that you want to link to is in another application-- then
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.mine", "com.android.mms.ui.ComposeMessageActivity"));
startActivity(intent);
if it is within the same application, then
Intent intent = new Intent(this, ComposeMessageActivity.class);
startActivity(intent);
//optional add this to your manifest to finish the current loading activity so
//as to not keep it in the activity stack
//<activity android:name="yourActivity" android:noHistory="true" ... />
EDIT If you mean a shortcut on a homescreen, then I would create a tiny application that only has one activity which uses the above method to link to a different application. Then I would drag that application to the home screen, and boom. If there's a better way, then please feel free to correct me

EXTRA_OUTPUT ignored on Eris, causes data to return null on G1

So I'm trying to launch the Camera activity using the following code:
//In public void captureImage()
...
Intent cameraIntent = new Intent(MediaStore.ACTION_CAPTURE_IMAGE);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT,
Uri.fromFile(new File("/sdcard/image.jpg")));
startActivityForResult(cameraIntent, REQUEST_CAMERA);
And then to handle the result:
//In onActivityResult()
...
case REQUEST_CAMERA:
Intent intent = new Intent (CurrentScreen.this, NextScreen.this);
intent.putExtra(data);
startActivity(intent);
CurrentScreen.this.finish();
...
Where I use intent.putExtra(data) to attach the small bitmap to the intent, and use it as a thumbnail in the next activity, and the full sized file is supposedly saved as /sdcard/image.jpg.
This is the expected behavior (according to the documentation), to have a small bitmap for a thumbnail, and a large file saved. However when testing this on a G1 and an Eris, I have been seeing some strange behavior.
On the G1:
Although the resultCode shows RESULT_OK, the intent data that is returned to the result handler is null.
Also EXTRA_OUTPUT seems to be completely ignored, I have no idea where it is saving the image.
On the Eris:
The intent data comes back OK
EXTRA_OUTPUT is also ignored, but it is saving the images to the regular media store at /sdcard/dcim/100media
So my question is this: is there any way to get consistent behavior for what I am trying to do using the standard camera activity? I could write up a custom activity to try and get it to work the way I want, but I'd prefer to avoid that route.
I do not have answers to your question as I am new to the Java/Android development world. But I am attempting something similar to what you are doing, except I want to simply take the picture then attach it to an Email message.
I implemented part of your example and was able to verify that the camera created the file I specified and that if I use the same file name for the next picture that it overwrites the previous file which is what I would expect.
But what I was really going to say is perhaps you will have to test if the pat "/sdcard/..." actually exists or not. Also you could possibly simplify your process by passing the path to the next activity.
Good Luck,
Jamie Irwin

Categories