Following up on this question, is there a way to start an intent in android without prompting the user for anything?
Right now, I am retrieving the image like this:
public void changeImage(View view) {
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(
Intent.createChooser(intent, getResources().getString(R.string.select_picture)),
PICK_IMAGE);
}
Then I store the Uri, and when necessary display the image (I actually resize it first, but that doesn't matter):
Uri _uri = Uri.parse(_path);
InputStream imageStream = null;
try {
imageStream = getContentResolver().openInputStream(_uri);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Bitmap b = BitmapFactory.decodeStream(imageStream);
iv.setImageBitmap(b);
I would like to retrieve the image data given its Uri by "silently" invoking the intent so as to get the relevant permission. So I would need something like:
Edit:
I tried the setPackage() method. This code has the following behavior:
If the ACTION_VIEW intent is used, the gallery opens and shows the specific image.
If the ACTION_GET_CONTENT intent is used, I get prompted to pick an image from the gallery, even though I supply the specific Uri.
>
Uri _uri = Uri.parse(_path);
InputStream imageStream = null;
Bitmap b = null;
try {
imageStream = getContentResolver().openInputStream(_uri);
b = BitmapFactory.decodeStream(imageStream);
ImageView iv = (ImageView) findViewById(R.id.playerImage);
iv.setImageBitmap(b);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
Intent dummyIntent = new Intent(Intent.ACTION_GET_CONTENT);
//Intent dummyIntent = new Intent(Intent.ACTION_VIEW);
dummyIntent.setDataAndType(_uri,"image/*");
dummyIntent.setPackage("com.google.android.gallery3d");
startActivityForResult(dummyIntent, PICK_IMAGE);
}
Any ideas?
You have different types of Intents, the silent ones are the broadcast and service intents. And by silent I mean that there's nothing visible going on for the user by default (no activity is launched).
An intent will just ask the system: is there something capable of handling it?
If there is, it will pass it to that something (or in case of multiple options will ask the user). And you have no control after that unless what's handling it is your code.
Your problem is actually with Picasa.
When the user pick an image from Picasa you get an Uri. You can use that Uri with the ContentResolver to get the image.
What's actually happening when you do is that a ContentProvider from Picasa will process the Uri and return you the InputStream for the image.
From the other question I understood that you do not immediately get the image, instead you save the Uri in the database and later process it.
Since Picasa use a permission mechanism to provide you the image and since that permission is expired when you try to use the Uri you want to obtain the Uri (along with the permission) again from Picasa without asking the user again.
The problem is that Picasa is in control on that permission and so if Picasa do not provide you with an intent to obtain the permission by knowing the Uri there's nothing you can do.
This is not even a bug in my opinion: you have the permission to access the image when the user decide so, you do not automatically get the permission to access every image from Picasa. A service that "silently" provide you with the permission to access every Picasa image by knowing its Uri would just let you get any image from the user without him knowing. This is probably not what the Picasa developers wants.
The best suggestion I can give you is the same one that you got in the other question: when the user chose the image immediately obtain it and save locally, then use the local Uri to access the image.
Instead of trying to send a "silent" intent, a possible solution could be to save the URI of the file using "Shared Preferences" -- you could save the user and URI as a key/value pair (for example).
When you want to open the file later, it could then be opened without sending an intent.
You should be able to get the "real" uri (one that won't change) in the following way:
This is done for audio file, so use "MediaStore.Images.Media.EXTERNAL_CONTENT_URI" and change other details to match your case.
Pre kit-kat:
String songName = "";
Cursor cursor = getContext().getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, new String[]{MediaStore.Audio.Media.TITLE}, MediaStore.MediaColumns.DATA + " =?", new String[]{uri.toString()}, null);
if (cursor != null && cursor.moveToNext()) {
songName = cursor.getString(0);
}
if (cursor != null) {
cursor.close();
}
return songName;
After Kitkat, there is the change due to the document provider, this code should do the trick:
Cursor cursor = null;
String songName = "";
try {
String[] column = new String[]{MediaStore.Audio.Media.TITLE};
String wholeID = DocumentsContract.getDocumentId(uri);
// Split at colon, use second item in the array
String id = wholeID.split(":")[1];
// where id is equal to
String sel = MediaStore.Images.Media._ID + " =?";
cursor = getContext().getContentResolver().
query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, column, sel, new String[]{id}, null);
if (cursor != null && cursor.moveToNext()) {
songName = cursor.getString(0);
}
}
catch (Exception e){
Logger.log(e);
}
finally {
if (cursor != null) {
cursor.close();
}
}
return songName;
Again, change the details to match your case and save the real uri taken from the cursor you get (probably get this field in your projections : MediaStore.Images.Media.DATA).
Related
What i'm trying to achieve is to delete a file on the sd card, i tried the file.delete method which didn't work because sd cards are read only now.
So i read a post about SAF (Storage Access Framework) to gain sd card write access by storing the treeUri we get in onActivityResult.
File deleting works fine now, but when i start the intent Intent.ACTION_OPEN_DOCUMENT_TREE sometimes it returns the recent folder which is empty and the way to show the files on the sdcard is to click on the overflow icon and then select show SDCARD or Internal Storage which may confuse some people when they run my app.
i tried adding these to my intent: intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
intent.putExtra("android.content.extra.FANCY", true);
intent.putExtra("android.content.extra.SHOW_FILESIZE", true);
which works on some devices, but it's a private API and on some it doesn't work.
So is there a way to like automatically open a specific directory or show a hint dialog with steps explaining which directory they should chose?
private void getSDCardAccess(){
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath());
startActivityForResult(intent, REQUEST_EXTERNAL_ACCESS);
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_EXTERNAL_ACCESS && resultCode == RESULT_OK) {
Uri treeUri = null;
if (data != null){
treeUri = data.getData();
}
if (treeUri != null && getActivity() != null) {
getActivity().getContentResolver().takePersistableUriPermission(treeUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
StorageUtil storageUtil = new StorageUtil(getActivity());
//Takes the access so that we can use it again after the app reopens
storageUtil.storeTreeUri(treeUri.toString());
Log.d(TAG, "treeUri: " + treeUri.toString());
}else{
Log.d(TAG,"uri is empty!");
}
}
}
is there a way to like automatically open a specific directory
If you have a Uri to it that you got from ACTION_OPEN_DOCUMENT_TREE previously, you should be able to supply that via DocumentsContract.EXTRA_INITIAL_URI. Per the ACTION_OPEN_DOCUMENT_TREE documentation:
Callers can set a document URI through DocumentsContract.EXTRA_INITIAL_URI to indicate the initial location of documents navigator. System will do its best to launch the navigator in the specified document if it's a folder, or the folder that contains the specified document if not.
or show a hint dialog with steps explaining which directory they should chose?
You would need to do that yourself, prior to calling startActivityForResult() for the ACTION_OPEN_DOCUMENT_TREE request.
I have this code working well on Android 4.0.4.
// Create the new Intent using the 'Send' action.
Intent share = new Intent(Intent.ACTION_SEND);
// Set the MIME type
share.setType(type);
// Create the URI from the media
java.io.File media = new java.io.File(mediaPath);
Uri uri = Uri.fromFile(media);
// Add the URI and the caption to the Intent.
share.putExtra(Intent.EXTRA_STREAM, uri);
share.putExtra(Intent.EXTRA_TEXT, caption);
// Broadcast the Intent.
mActivity.startActivity(Intent.createChooser(share, "Share to"));
But on Android 4.4.2 it crashes the Facebook app. Facebook app opens, the image is not shown and the FB app is dead.
In log dump I've noticed this message:
E/JHEAD ( 5850): can't open '/data/data/cz.volten.brili.android.free/files/product_preview_shared.jpg'
V/ContextImpl( 5850): ----- packageName = com.facebook.katana is NOT LOCKED -----
Could the reason be some security restrictions, e.g. The FB app does not have rights to access the image in the application folder even though it is invoked from an intent?
If so, what would be a proper location for an image shared between the apps?
Shall I use something like this: how to share image to facebook via intent
Could the reason be some security restrictions, e.g. The FB app does not have rights to access the image in the application folder even though it is invoked from an intent?
Correct. That image is on internal storage for your app, which is private to your app.
If so, what would be a proper location for an image shared between the apps?
You can stick with internal storage, though you will need to use a FileProvider, perhaps with my LegacyCompatCursorWrapper, to serve the file. This sample app demonstrates this, albeit with a PDF rather than an image.
Or, put the file on external storage.
Shall I use something like this: how to share image to facebook via intent
You could, though that would seem to be overkill, compared to using FileProvider.
This is what I usually use
private void initShareIntent(String type) {
boolean found = false;
Intent share = new Intent(android.content.Intent.ACTION_SEND);
share.setType("image/jpeg");
// gets the list of intents that can be loaded.
List<ResolveInfo> resInfo = getPackageManager().queryIntentActivities(share, 0);
if (!resInfo.isEmpty()) {
for (ResolveInfo info : resInfo) {
if (info.activityInfo.packageName.toLowerCase().contains(type) ||
info.activityInfo.name.toLowerCase().contains(type)) {
share.putExtra(Intent.EXTRA_TEXT, "Elevator Express");
share.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(imagePath))); // Optional, just if you wanna share an image.
share.setPackage(info.activityInfo.packageName);
found = true;
break;
}
}
if (!found) {
Toast.makeText(getApplicationContext(), "Facebook does not exist", Toast.LENGTH_SHORT).show();
return;
}
startActivity(Intent.createChooser(share, "Select"));
}
}
and call it like this :
iniShareIntent("face");
This code works for me.....here "updateImage" is my image location.
if (isFacebookExist()) {
if (hashClick.isChecked()) {
SharePhoto sharePhoto = new SharePhoto.Builder()
.setBitmap(updateImage)
.build();
if (ShareDialog.canShow(SharePhotoContent.class)) {
SharePhotoContent content = new SharePhotoContent.Builder()
.addPhoto(sharePhoto)
.setShareHashtag(new ShareHashtag.Builder()
.setHashtag("#HashTag")
.build())
.build();
shareDialog.show(content);
}
} else {
SharePhoto sharePhoto = new SharePhoto.Builder()
.setBitmap(updateImage)
.build();
if (ShareDialog.canShow(SharePhotoContent.class)) {
SharePhotoContent content = new SharePhotoContent.Builder()
.addPhoto(sharePhoto)
.build();
shareDialog.show(content);
}
}
} else {
showToast(" Facebook is not install.");
}
private boolean isFacebookExist() {
PackageManager pm = getPackageManager();
try {
PackageInfo info = pm.getPackageInfo("com.facebook.katana", PackageManager.GET_META_DATA);
} catch (PackageManager.NameNotFoundException e) {
return false;
}
return true;
}
In my app you can download some files. I used the Android DownloadManager class for downloading. After the download is completed, it should show me a message that the file was downloaded. The problem is, there could be 2,3 or 4 downloads at the same time. My BroadcastReceiver code looks like this:
receiver_complete = new BroadcastReceiver(){
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE) ){
Toast.makeText(MainActivity.this, MainActivity.this.getString(R.string.download_finished, "Here should be the name", Toast.LENGTH_SHORT).show();
}
}
};
How can I get the current filename of the finished download?
Thank you very much.
I think you want to put something like this inside your if block. Replace YOUR_DM with your DownloadManager instance.
Bundle extras = intent.getExtras();
DownloadManager.Query q = new DownloadManager.Query();
q.setFilterById(extras.getLong(DownloadManager.EXTRA_DOWNLOAD_ID));
Cursor c = YOUR_DM.query(q);
if (c.moveToFirst()) {
int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
if (status == DownloadManager.STATUS_SUCCESSFUL) {
// process download
title = c.getString(c.getColumnIndex(DownloadManager.COLUMN_TITLE));
// get other required data by changing the constant passed to getColumnIndex
}
}
Ian Shannon was totally right with his answer, but I sugest some improvement:
Remember to close that Cursor after using it, avoiding "Cursor Leaking". This Cursor consumes a lot of resources and must be released as soon as possible.
If you put some title for the download, such as:
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setTitle("Some title");
The value given by the DownloadManager.COLUMN_TITLE will be "Some title" instead of the file name. So I would recommend this instead:
String filePath = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
title = filePath.substring( filePath.lastIndexOf('/')+1, filePath.length() );
The COLUMN_LOCAL_FILENAME returns the entire path (/storage/sdcard0/Android/data/.../filename.ext), but with this code, we will only get the file name.
Final code:
Bundle extras = intent.getExtras();
DownloadManager.Query q = new DownloadManager.Query();
q.setFilterById(extras.getLong(DownloadManager.EXTRA_DOWNLOAD_ID));
Cursor c = YOUR_DM.query(q);
if (c.moveToFirst()) {
int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
if (status == DownloadManager.STATUS_SUCCESSFUL) {
String filePath = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
filename = filePath.substring( filePath.lastIndexOf('/')+1, filePath.length() );
}
}
c.close();
Edit: Replace YOUR_DM with your DownloadManager instance.
I think what you are looking for is the DownloadManager.COLUMN_TITLE
Here is a link to the Android Documents: http://developer.android.com/reference/android/app/DownloadManager.html#COLUMN_TITLE
And here is a tutorial that will explain more than just getting the title. It is for downloading an image from a URL and then displaying it in an application. Mighty useful I think, overall speaking.
http://wptrafficanalyzer.in/blog/downloading-an-image-from-an-http-url-using-downloadmanager-and-displaying-in-imageview-by-dynamically-registered-broadcastreceiver/
There's a simpler way to retrieve the downloaded file URI, and that is with getUriForDownloadedFile:
download_receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Bundle b = intent.getExtras();
DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
long file_id = b.getLong(DownloadManager.EXTRA_DOWNLOAD_ID);
Uri uri = dm.getUriForDownloadedFile(downloaded_file_id);
}
}
I found this method here in a full description of how to use a BroadcastReceiver for handling DOWNLOAD_COMPLETE events.
u try this code to get the file name
DownloadManager.COLUMN_LOCAL_FILENAME
i am not sure about it
I am writing an application wherein I want to detect if a download has started and retrieve the URI of the file being downloaded and then cancel the download from the Download Manager. I am doing this so that I can send this URI somewhere else.
The trouble is that I can detect when a download begins by querying the Download Manager, but is there a method or a constant variable in Download Manager from which I can also get the URL of the file being downloaded
Ok its weird answering your own question, but I finally figured out how to do this. There is a DownloadManager class in android.app, which stores a list of all http downloads initiated and their statuses. These can be filtered out based on whether the download is 'RUNNING', 'PENDING', 'PAUSED' and so on.
This list can be read into a cursor and one of the columns of the result is 'COLUMN_URI', which is the url from where the file is being downloaded. A sample code where I have used it is as given below:
public void readDownloadManager() {
DownloadManager.Query query = null;
DownloadManager downloadManager = null;
Cursor c = null;
try {
query = new DownloadManager.Query();
downloadManager = (DownloadManager)getSystemService(DOWNLOAD_SERVICE);
//Just for testing I initiated my own download from this url. When an http
// reuest for this url is made, since download is taking place, it gets saved in
// the download manager.
Request request = new Request(Uri.parse("http://ocw.mit.edu/courses" +
"/aeronautics-and-astronautics/16-100-aerodynamics-fall-2005" +
"/lecture-notes/16100lectre1_kvm.pdf"));
downloadManager.enqueue(request);
query.setFilterByStatus(DownloadManager.STATUS_PENDING);
c = downloadManager.query(query);
if(true){
int statusColumnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
int urlColumnIndex = c.getColumnIndex(DownloadManager.COLUMN_URI);
long downloadProcessIdColumnNo = c.getColumnIndex(DownloadManager.COLUMN_ID);
Log.d("Column Count", ((Integer)c.getCount()).toString());
if(c.getCount() > 0){
String url="";
c.moveToLast();
if(c.isLast()){
url = c.getString(urlColumnIndex);
downloadManager.remove(downloadProcessIdColumnNo);
Log.d("Count after remove", ((Integer)c.getCount()).toString());
}
Log.d("After", "Stopped Working");
//Here I am sending the url to another activity, where I can work with it.
Intent intent = new Intent(EasyUploadMainMenu.this, EasyUploadActivity.class);
Bundle b = new Bundle();
b.putString("url", url);
intent.putExtras(b);
startActivity(intent);
Log.d("url:", url);
}
}
} catch (NullPointerException ex) {
ex.printStackTrace();
}
}
I have an android app which stores information in an SQLite DB. On the activity I can open the Gallery, select a video, and then click on the "Watch Video" button and play that video.
HOWEVER, if I leave that activity and come back later, the saved URI IS in my DB, but loading it through the SAME onclick function produces this Exception error. ANY IDEAS WHY??
public void launchVideo(View view) {
if (my_video != null) {
Uri uri = Uri.parse(my_video);
Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(uri, "video/*");
startActivity(intent);//THROWS ILLEGAL ACTIVITY WHEN WORKING FROM SAVED URI
}
else{...
Using debugger I see the following...
#Override
public void onClick(#NonNull View v) {
if (mResolvedMethod == null) {
resolveMethod(mHostView.getContext());
}
try {
mResolvedMethod.invoke(mResolvedContext, v);
} catch (IllegalAccessException e) {
throw new IllegalStateException(
"Could not execute non-public method for android:onClick", e);
} catch (InvocationTargetException e) {
throw new IllegalStateException(
"Could not execute method for android:onClick", e);
}
}
Similar posts have mentioned issues being the xml or use of methods with the same name. I do not have any methods with the same name and the xml is below for the button:
<Button
android:id="#+id/watchVideo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:onClick="launchVideo"
android:text="#string/watch_my_video"
app:layout_constraintStart_toStartOf="#+id/youtube_link"
app:layout_constraintTop_toBottomOf="#+id/youTubeEditText" />
The URI being passed in both the working and non working case is: "content://com.android.providers.media.documents/document/video%3A595"
Finally in my Debugger I see the following which I take a confirmation everything is public:
mResolvedMethod = {Method#6254} "public void com.android.mybrazilianjiu_jitsudictionary.Controller.AttackDetail.launchVideo(android.view.View)"
accessFlags = 134742017
artMethod = 3966325964
declaringClass = {Class#6043} "class com.android.mybrazilianjiu_jitsudictionary.Controller.AttackDetail"
declaringClassOfOverriddenMethod = {Class#6043} "class com.android.mybrazilianjiu_jitsudictionary.Controller.AttackDetail"
dexMethodIndex = 555
hasRealParameterData = false
parameters = null
override = false
shadow$_klass_ = {Class#3637} "class java.lang.reflect.Method"
shadow$_monitor_ = -2092042593
My latest guess is it is related to:
try {
mResolvedMethod.invoke(mResolvedContext, v);
However, I am just trying to have the video appear in the user's video player via implicit intent which IS what happens until I leave the activity and come back. Note: via checking the database and Debugger the SAME URI is present and being passed to the method in all scenarios.
THANK YOU FOR YOUR INSIGHTS!
By the following, I was able to fix the problem:
IllegalStateException: Could not execute method for android:onClick when trying to migrate to another page? was VERY helpful. On this advice I replaced my Button on click method with the following IN the OnCreate:
Button watchVideoButton = findViewById(R.id.watchVideo);
watchVideoButton.setOnClickListener(new View.OnClickListener() {...
The problem that then surfaced was lack of persistent permissions for the Uri when returning to the activity.
Persistent permission was taken via the following:
getContentResolver().takePersistableUriPermission(uri, FLAG_GRANT_READ_URI_PERMISSION);
...which I placed BEFORE the onCreate in:
androidx.activity.result.ActivityResultLauncher<String> mGetContent = registerForActivityResult(new ActivityResultContracts.GetContent(),
new ActivityResultCallback<Uri>() {
#Override
public void onActivityResult(Uri uri) {
my_video = uri.toString();
getContentResolver().takePersistableUriPermission(uri, FLAG_GRANT_READ_URI_PERMISSION);
Move move;
move = new Move(MoveID,PositionTableID, MoveName, MoveStatus, GiY,AttackY, DefenseY, Description, internetVideoLink, my_video);
myJiuJitsuDictionaryRepository.insert(move);
}
});