I'm updating one of my apps for Android 6 and above. The app lets you attach a file to its data to provide further information. This can be an Image from taken in-app with the camera, and Image from the gallery, or a random file from the file system. I have a ListView that shows the attached files and their metadata and with onItemClick I want to show them to the user. I used to get the URI for that file with
Uri.fromFile(f)
but this is no longer supported as I understand it. Now my code looks like this
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
doc = getItem(arg2);
File f = doc.getFile(context);
if (f.exists()) {
Intent myIntent = new Intent(Intent.ACTION_VIEW);
myIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
String mime = "";
try {
Uri uri = FileProvider.getUriForFile(context, android.support.v4.BuildConfig.APPLICATION_ID + ".provider", f);
mime = URLConnection.guessContentTypeFromStream(new FileInputStream(f));
if (mime == null) mime = URLConnection.guessContentTypeFromName(f.getName());
myIntent.setDataAndType(uri, mime);
context.startActivity(myIntent);
} catch (Exception e) {
e.printStackTrace();
}
} else {
//Error handling
}
}
This works fine with files on the phone storage. But since FileProvider does not work with files on mountable storage, it will throw an Exception. My question is: how do I get the file URI without using FileProvider?
Related
In my app I have this code allowing the user to select a file :
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("text/plain");
startActivityForResult(intent,1);
The user can select the .txt file from anywhere in his phone, even from google drive. When the file selection is done I retrieve a Uri object corresponding to the file. The problem is I can't use this Uri to read the file because it is not valid. Here is my code :
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1) {
if (resultCode == RESULT_OK) {
Uri uri = data.getData();
File file = new File(uri.toString());
try{
InputStream inputStream = new FileInputStream(file);
int content;
while ((content = inputStream.read()) != -1) {
Log.d("===>", String.valueOf((char) content));
}
}catch (Exception e){
Log.d("===>", e.toString());
}
}
}
}
I always get a fileNotFoundException. My question is, is there a way to read the selected file (without knowing in advance the location it will come from). And if not, is there a way to copy the selected file in a folder from which I would easily get it ?
The problem is I can't use this Uri to read the file because it is not valid.
That is because a Uri is not a file.
is there a way to read the selected file (without knowing in advance the location it will come from)
The user did not select a file. The user selected a piece of content.
To consume the content represented by the Uri, call openInputStream() on a ContentResolver, passing in the Uri. This gives you an InputStream that you can use to read in the content.
I hope someone can help me with this problem.
I have an app which enables the user to take a video (.mp4) or pick existing one. If taking a video, the video is saved to a public directory and I triggered the Android to scan the file to add it to media gallery.
Problem is, taking the video is working fine, I can preview the finished video just fine in my app. But this same video won't appear in media gallery and won't be accessible by other apps -except the FileManager-, even though other .mp4 in the same folder appear in the list just fine.
Further info:
In FileManager app, The not-appearing files have icon video icon while the appearing ones got a thumbnail. I can trigger these not-appearing files to be added to media gallery apps by cut and paste the files in FileManager app (so I believe is not due to files being corrupted).
The scan code works fine for the my code that take images from existing camera app, it just won't work for the video ones...
Is there any need for additional permission for this to work? I've added/asked/request permission for write and read from ext. storage and camera in my manifest and code.
This below is how I take the Video and scan it into gallery :
private void takeVideo() {
Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
if (takeVideoIntent.resolveActivity(ctx.getPackageManager()) != null) {
// Create the file where photo should go
File mediaFile = null;
try {
mediaFile = getOutputMediaFile(ctx, MEDIA_TYPE_VIDEO);
} catch (IOException ex) {
Log.e("FragCamera", "takeVideo() : Error occurred while creating File." + ex);
}
if (mediaFile != null) {
Uri mediaUri = Uri.fromFile(mediaFile);
Log.d("FragCamera", "takeVideo() mediaUri: " + mediaUri);
currMediaUri = mediaUri;
currPhotoPath = mediaFile.getAbsolutePath();
Log.d("FragCamera", "takeVideo() currPhotoPath: " + currPhotoPath);
//make the new file available for other apps
updateMediaGallery(mediaFile);
MediaScannerConnection.scanFile(
ctx,
new String[]{currPhotoPath},
new String[]{"video/mp4"},
new MediaScannerConnection.OnScanCompletedListener() {
#Override
public void onScanCompleted(String path, Uri uri) {
Log.v("FragCameraScan",
"file " + path + " was scanned seccessfully: " + uri);
}
});
takeVideoIntent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, mediaUri);
this.startActivityForResult(takeVideoIntent, I_REQUEST_VIDEO_CAPTURE);
}
}
}
private void galleryAddPic(String filePath) {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
File f = new File(filePath);
Uri contentUri = Uri.fromFile(f);
mediaScanIntent.setData(contentUri);
ctx.sendBroadcast(mediaScanIntent);
}
Logcat value for each Log in the code above :
D/FragCamera: takeVideo() mediaUri: file:///storage/emulated/0/DCIM/Camera/VID_20161207_142021.mp4
D/FragCamera: takeVideo() currPhotoPath: /storage/emulated/0/DCIM/Camera/VID_20161207_142021.mp4
V/FragCameraScan: file /storage/emulated/0/DCIM/Camera/VID_20161207_142021.mp4 was scanned seccessfully: null
try using this function
public Uri addVideo(File videoFile) {
ContentValues values = new ContentValues(3);
values.put(MediaStore.Video.Media.TITLE, "My video title");
values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
values.put(MediaStore.Video.Media.DATA, videoFile.getAbsolutePath());
return getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
}
the 'values' is simply meta data about the video
My app has a ListView containing several audio files. If the user long clicks a ListViewItem it will share the selected audio file.
I've read that in order to share something, first I have to copy the file to external storage and then share it.
I've used the following code to do so:
private void copyToExternalStorage(String sourceFilePath, String fileName)
{
File sourceFile = new File(sourceFilePath);
String destinationPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/myApp";
File destinationFile = new File(destinationPath);
if (!destinationFile.exists())
destinationFile.mkdirs();
destinationFile = new File(destinationPath + "/" + fileName + ".mp3");
try
{
FileUtils.copyFile(sourceFile, destinationFile);
} catch (IOException e)
{
e.printStackTrace();
}
}
private void share(String file, String trackName)
{
copyToExternalStorage(file, trackName);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("audio/*");
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/myApp/" +
trackName +
".mp3";
shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(path));
startActivity(Intent.createChooser(shareIntent, "Share audio"));
}
#Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l)
{
share(getResources().getStringArray(R.array.sounds)[i], getResources().getStringArray(R.array.tracks)
[i]);
return true;
}
R.array.sounds is where I stored my audio files, and R.array.tracks is where I stored the track names.
The problem I'm getting is that my app does not find any files in the given sourceFilePath, therefore it doesn't copy nor share anything.
Any help will be much appreciated.
You don't have to copy files anywhere. It's cleaner and safer (but more work) for your app to expose a ContentProvider that responds to a Uri that you share. The Uri essentially becomes the locator for your file/resource that other apps can use to read it.
Have a look at the official docs on file sharing to get started.
I use the following snippet to open or share any file from device's storage (MyFile is my own class which extends File and should be considered as File. The flag String I'm passing is either Intent.ACTION_VIEW or Intent_ACTION_SEND):
public void openOrShare(String flag, MyFile f){
try {
MimeTypeMap mmap = MimeTypeMap.getSingleton();
String type = MimeTypeMap.getFileExtensionFromUrl(f
.getName());
String ftype = mmap.getMimeTypeFromExtension(type);
if (ftype == null)
ftype = "*/*";
Intent intent = new Intent(flag);
Uri data = Uri.fromFile(f);
intent.setDataAndType(data, ftype);
startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
Tools.gimmeToast(
getActivity(),
"no application found to handle this file type",
Toast.LENGTH_LONG);
} catch (Exception e) {
e.printStackTrace();
}
}
While passing Intent.ACTION_VIEW everything works fine for any (also custom) types, the system creates a chooser and lists the apps, for well-known files it launches the correct Activity to handle the file immediately .
The problem: passing Intent.ACTION_SEND seems to be working halfway - it creates the chooser as well, however most apps (Dropbox and many more that I've tested with) just crash with an NPE when confirming the action. Testing with various email clients also failed: they don't crash like most of the other apps, but create a message and put the local Uri (like //storage/.....) in the To field (gmail) OR simply create a new empty message, ignoring the Intent data (yahoo! mail), while I expect them to attach the file to a new message.
The question: what am I doing wrong to share any file type?
EDIT
I figured out that it works if using intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f));, however there's an ActivityNotFoundException being caught when trying to share some specific files (like .dat). As far as I know, apps like Dropbox support adding any file types. Looking for a workaround.
If you don't know the MIME Type of the file, then do not set it.
So I would try:
Intent intent = new Intent(flag);
Uri data = Uri.fromFile(f);
String ftype = mmap.getMimeTypeFromExtension(type);
if (ftype != null) {
intent.setDataAndType(data, ftype);
} else {
intent.setData(data);
}
setDataAndType needs data and explicit mime type (maybe */* is not).
Never mind, I finally figured that out. So here's the working solution which allows to share any type of data using any app*:
*Note: the below code actually also lists apps that might be not able to handle specific file types, those apps (at least they should) notify the user that the file type is not supported if this is the case
public void doShare(MyFile f) {
try {
Intent intent = new Intent(Intent.ACTION_SEND);
MimeTypeMap mmap = MimeTypeMap.getSingleton();
String type = MimeTypeMap.getFileExtensionFromUrl(f.getName());
String ftype = mmap.getMimeTypeFromExtension(type);
if (ftype == null)
ftype = "*/*";
intent.setType(ftype);
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f));
startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
Tools.gimmeToast(getActivity(),
"no application found to handle this file type",
Toast.LENGTH_LONG);
} catch (Exception e) {
e.printStackTrace();
}
}
I know i can play an mp3 file in the media player like that:
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
File file = new File(YOUR_SONG_URI);
intent.setDataAndType(Uri.fromFile(file), "audio/*");
startActivity(intent);
Following this link I tried to get the URI like:
Uri audio = Uri.parse("android.resource://com.audio.test/"+R.raw.audio1);
Log.d(TAG,"uri:"+audio.toString());
and
Uri audio = Uri.parse("android.resource://com.audio.test/raw/audio");
Log.d(TAG,"uri:"+audio.toString());
Which outputs the expected result:
01-24 15:28:23.190: D/MyClass(30234): uri:android.resource://com.audio.test/2131034112
01-24 15:29:13.: D/MyClass(30234): uri:android.resource://com.audio.test/raw/audio1
But it doesn't work. The media player does not start. Any ideas why?
Update
I included a createChooser and instead of the expected list with players i get a "Unable to find application to perform this action" message. This is my exact code:
public void playAudio(){
Intent viewMediaIntent = new Intent();
viewMediaIntent.setAction(android.content.Intent.ACTION_VIEW);
Uri audio = Uri.parse("android.resource://com.audio.test/raw/"+R.raw.audio1);
Log.d(TAG,"uri:"+audio.toString());
viewMediaIntent.setDataAndType(audio, "video/*");
viewMediaIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
Log.d(TAG,"Starting");
Intent i = Intent.createChooser(viewMediaIntent, "Play Music");
mGap.startActivity(i);
Log.d(TAG,"Started");
}
Update 2
Thank you #CommonsWare for the explanation. Now I understand why it doesn't work. But the problem remains, can I achieve what I want? Get a Uri of a file stored in the raw/assets folder with a file:// scheme?
Update 3
I found a way to do it, although it's not the best it works. I have only 3 files and this doesn't delay the execution at all. I am copying the file from the res/raw to a local directory on the phone and getting the Uri from that file. Any suggestions on how to avoid that step are appreciated.
public void copyFileToDir(String audioFile){
File test = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC + "/" + audioFile + ".mp3");
if (test.exists()){
Toast.makeText(mGap, "Exists", Toast.LENGTH_SHORT).show();
return;
}
File dest = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
int i = mGap.getResources().getIdentifier("raw/"+audioFile, "string", mGap.getPackageName());
InputStream in = mGap.getResources().openRawResource(i);
// Used the File-constructor
OutputStream out;
try {
out = new FileOutputStream(new File(dest, audioFile + ".mp3"));
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
try {
// A little more explicit
while ( (len = in.read(buf, 0, buf.length)) != -1){
out.write(buf, 0, len);
}
} finally {
// Ensure the Streams are closed:
in.close();
out.close();
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void playAudio(String audioFile){
copyFileToDir(audioFile);
File dest = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC + "/" + audioFile + ".mp3");
Uri r = Uri.fromFile(dest);
Intent viewMediaIntent = new Intent();
viewMediaIntent.setAction(android.content.Intent.ACTION_VIEW);
viewMediaIntent.setDataAndType(r, "audio/*");
viewMediaIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
Intent i = Intent.createChooser(viewMediaIntent, "Play Music");
mGap.startActivity(i);
}
public void playVideo(String movieurl){
Intent intentToPlayVideo = new Intent(Intent.ACTION_VIEW);
intentToPlayVideo.setDataAndType(Uri.parse(movieurl), "video/*");
Log.d(TAG,"Playing:" + movieurl);
mGap.startActivity(intentToPlayVideo);
}
Any reason why don't you use the MediaPlayer directly? You can pass the resource id directly
int resourceId = R.raw.your_media_resource;
MediaPlayer mediaPlayer = MediaPlayer.create( context, resourceId );
mediaPlayer.setOnCompletionListener( new OnCompletionListener()
{
#Override
public void onCompletion( MediaPlayer mp )
{
mp.release();
}
} );
mediaPlayer.start();
When you call startActivity(), you are trying to start an activity. The Intent you pass to startActivity() indicates what activity -- or selection out of a set of available activities -- you want to start. In your case, you are trying to view an android.resource:// Uri. This is not an http:// Uri, nor an https:// Uri, nor a file:// Uri.
Activities that advertise themselves as supporting operations like this have, in their <intent-filter> a statement of what Uri schemes they support. You are assuming that there is an app, on the user's device, that supports an android.resource:// scheme. Personally, I do not think that this is a safe assumption.
http://, https://, and file:// should be safe, and content:// (for a ContentProvider) is fairly likely as well.
For example, the AOSP Music app does not support the android.resource scheme, based on its current manifest contents.
If you just need to set the sound URI on the Notification.Builder, use
Uri.parse("android.resource://com.my.great.application/"
+ R.raw.mysound);
where R.raw.mysound can be something like mysoud.ogg in the raw resources folder.
However I am not sure if MP3 is exactly supported. As these are just internal resources of your application, try to convert to OGG.
It is not possible to use content from your RES folder outside of the App. The content is inside the App and not reachable from outside the App. If you want to make the content available you have to implement your own ContentProvider returning the data.
#CommonsWare is right. See http://iserveandroid.blogspot.nl/2011/01/how-to-access-resources-from-other.html