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.
Related
So I am working on a project where if a manager registers a user he gets an email with a QR code (bitmap). The QR code is saved in cache. I want the QR code removed after the QR code is sent to the user, but a "cache" folder gets created (also shows up in gallery), and the image itself gets deleted but it remains there ( you cant see it, but its there as a grey square).
Any idea how to remove the created folder and the created bitmap compeletely?
My code:
BitmapSaver(Context mContext){
this.mContext=mContext;
this.cache = new DiskBasedCache(mContext.getCacheDir(), 1024 * 1024);
}
public static File saveImageToExternalStorage(Context context, Bitmap finalBitmap) {
destFolder =context.getCacheDir().getAbsolutePath();
String root = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString();
// myDir = new File(root + "/saved_images");
// myDir.mkdirs();
long n = System.currentTimeMillis() / 1000L;
fname = "Image-" + n + ".jpg";
//file2 = new File(destFolder);
file = new File(destFolder+"/"+fname);
if (file.exists())
file.delete();
try {
Log.i("path",destFolder+"/"+fname);
FileOutputStream out = new FileOutputStream(destFolder+"/"+fname);
finalBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
MediaScannerConnection.scanFile(context, new String[]{file.toString()}, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
Log.i("ExternalStorage", "Scanned " + path + ":");
Log.i("ExternalStorage", "-> uri=" + uri);
url = Uri.parse(path);
Log.i("External",url.toString());
}
});
return file;
}
In Activity after email is sent:
BitmapSaver bms = new BitmapSaver(RegisterActivity.this);
bms.saveImageToExternalStorage(RegisterActivity.this, bitmap);
bms.file.delete();
First of all you need to know what MediaScannerConnection.scanFile does. It will update the file information to the current device, so other applications like gallery, file explorer, etc, can show the correct file information and content.
From your code, when you are saving the temporary file, you are also scanning it, because your app has changed the corresponding file, which is creating the file to be exact. So the file will be available to the other applications right away. But, since the file location is in your application cache directory, it will not accessible by other applications. Usually you must restart your device to update the file information if you don't call MediaScanner.scanFile. If you are creating a temporary file, I think you don't need to call MediaScanner.scanFile, since you will delete it right away.
Then after delete, you also need to re-scan the file again, so other applications will know that the file has been deleted.
Also, despite of using MediaScannerConnection.scanFile directly, if you are supporting android version < KitKat, you should broadcast with intent action Intent.ACTION_MEDIA_MOUNTED instead. And I also recommend that you are broadcasting the data directly, because from my experience MediaScannerConnection.scanFile failed from one of my test devices.
Intent mediaScanIntent;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
mediaScanIntent.setData(Uri.fromFile(new File(imagePath)));
} else {
Uri fileUri = Uri.parse("file://" + imagePath);
mediaScanIntent = new Intent(Intent.ACTION_MEDIA_MOUNTED, fileUri);
}
context.sendBroadcast(mediaScanIntent);
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?
I want to create a XML file inside my Android app.
This file I want to write into the documents folder of my Android device.
Later I want to connect my Android device to my PC using USB and read that XML file out of the documents folder.
My Device is an Android Galaxy Tab Pro 10.1, Android 4.4.2.
I tried already:
String fileName = "example.xml";
String myDirectory = "myDirectory";
String externalStorage = Environment.getExternalStorageDirectory().getAbsolutePath();
File outputFile = new File(externalStorage + File.separator + myDirectory + File.separator + fileName);
But no file is created. I also want later to read that file out of the documents folder into may app again.
Any help is appreciated, thanks!
I know this is late, but you can get the documents directory like this:
File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
File file = new File(dir, "example.txt");
//Write to file
try (FileWriter fileWriter = new FileWriter(file)) {
fileWriter.append("Writing to file!");
} catch (IOException e) {
//Handle exception
}
Set permission in Android Manifest
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Use this code to write to external directory
String fileName = "example.xml";
String dirName = "MyDirectory";
String contentToWrite = "Your Content Goes Here";
File myDir = new File("sdcard", dirName);
/*if directory doesn't exist, create it*/
if(!myDir.exists())
myDir.mkdirs();
File myFile = new File(myDir, fileName);
/*Write to file*/
try {
FileWriter fileWriter = new FileWriter(myFile);
fileWriter.append(contentToWrite);
fileWriter.flush();
fileWriter.close();
}
catch(IOException e){
e.printStackTrace();
}
Before creating file you have to create directory in which you are saving the file.
Try like this one:-
String fileName = "example.xml";
String myDirectory = "myDirectory";
String externalStorage = Environment.getExternalStorageDirectory().getAbsolutePath();
File outputDirectory = new File(externalStorage + File.separator + myDirectory );
if(!outputDirectory.exist()){
outputDirectory.mkDir();
}
File outputFile = new File(externalStorage + File.separator + myDirectory + File.separator + fileName);
outputFile.createFile();
Try restarting you device and then check if the file exists. If so, you are creating it (which it looks like you should be based on your code) but it is not showing up until the media is scanned on your device. Try implementing MediaScannerConnectionClient so it will show become visible after creation.
public class MainActivity extends Activity implements MediaScannerConnectionClient {
private MediaScannerConnection msConn;
private File example;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
msConn = new MediaScannerConnection(this.getApplicationContext(), this);
String dir = Environment.getExternalStorageDirectory() + "/Documents/";
example = new File(dir, "example.xml");
msConn.connect();
}
#Override
public void onMediaScannerConnected() {
msConn.scanFile(example.getAbsolutePath(), null);
}
#Override
public void onScanCompleted(String path, Uri uri) {
msConn.disconnect();
}
From Android 10 onwards, Android started using Scoped Storage model to protect user privacy.
If you want to share this file with the User, then you should write this file in Shared Storage. To write a file in Shared Storage, this has to be done in 3 steps:-
Step 1: Launch System Picker to choose the destination by the user. This will return Uri of the destination directory.
private ActivityResultLauncher<Intent> launcher; // Initialise this object in Activity.onCreate()
private Uri baseDocumentTreeUri;
public void launchBaseDirectoryPicker() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
launcher.launch(intent);
}
Step 2: Launch System Picker to choose the destination by the user. This will return the Uri of the destination directory. Also, you can optionally persist the permissions and Uri for future use.
#Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
baseDocumentTreeUri = Objects.requireNonNull(result.getData()).getData();
final int takeFlags = (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// take persistable Uri Permission for future use
context.getContentResolver().takePersistableUriPermission(result.getData().getData(), takeFlags);
SharedPreferences preferences = context.getSharedPreferences("com.example.fileutility", Context.MODE_PRIVATE);
preferences.edit().putString("filestorageuri", result.getData().getData().toString()).apply();
} else {
Log.e("FileUtility", "Some Error Occurred : " + result);
}
}
Step 3: Write CSV content into a file.
public void writeFile(String fileName, String content) {
try {
DocumentFile directory = DocumentFile.fromTreeUri(context, baseDocumentTreeUri);
DocumentFile file = directory.createFile("text/*", fileName);
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(file.getUri(), "w");
FileOutputStream fos = new FileOutputStream(pfd.getFileDescriptor());
fos.write(content.getBytes());
fos.close();
} catch (IOException e) {
}
}
For more explanation, you can read "How to Save a file in Shared Storage in Android 10 or Higher" or Android official documentation.
I have the following code that correctly attaches the image to the email and sends:
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
sharingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// Set tht type to image/* and add the extra text field for the message to send
sharingIntent.setType(Application2.instance().getResString(R.string.share_intent_type_text_image));
sharingIntent.putExtra(Intent.EXTRA_TEXT, String.format(Application2.instance().getResString(R.string.share_intent_body_question), question.question));
if (destFile != null)
{
Uri uri = Uri.fromFile(destFile);
sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
((ActivityMain) getActivity()).startActivity(Intent.createChooser(sharingIntent, "Share via"));
}
R.string.share_intent_type_text_image is defined as "image/png"
destFile is an image grabbed from the external cache directory of the app, (((ActivityMain) getActivity()).getExternalCacheDir()
However, when I attempt to open the file in Gmail, a dialog appears that says: Info - No app can open this attachment for viewing. I've downloaded the file via my PC and the extension comes up as .File. I can open it with paint and other image viewers.
Anyone experience this before?
Considering the FileProvider problems, and also because I wanted to implement a max cache size for collected temp files, I went with a ContentProvider solution and it works a treat. Basically, you're allowed to use your internal cache without any problem but still provide third party apps with a URI they can use to reference your temporary files you want to share with them. Because you use your internal cache, there will be no unnecessary WRITE_EXTERNAL_STORAGE permission to ask for.
The added max cache size limit (that you can remove from the class by simply deleting everything from checkSize() to the end of the class, for instance, if you can make sure you delete all files directly after sharing, so they won't remain on the device) works by checking the cumulated max size upon each call and clearing up half the cache (deleting the oldest files) if necessary.
public class TemporaryFile extends ContentProvider {
private static final long MAX_SIZE = 512 * 1024;
// commented out on purpose so that you don't forget to rewrite it...
// public static final String AUTHORITY = "com.example.tempfile";
private UriMatcher uriMatcher;
#Override
public boolean onCreate() {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "*", 1);
return true;
}
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
if (uriMatcher.match(uri) == 1) {
final String file = getContext().getCacheDir() + File.separator + uri.getLastPathSegment();
return ParcelFileDescriptor.open(new File(file), ParcelFileDescriptor.MODE_READ_ONLY);
}
else
throw new FileNotFoundException(uri.toString());
}
#Override
public int update (Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
#Override
public int delete (Uri uri, String selection, String[] selectionArgs) {
return 0;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
#Override
public String getType(Uri uri) {
return null;
}
#Override
public Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
public static File getFile(Context context, String prefix, String extension) throws IOException {
checkSize(context);
File file = File.createTempFile(prefix, extension, context.getCacheDir());
file.setReadable(true);
file.deleteOnExit();
return file;
}
public static Uri getPublicUri(File file) {
return Uri.withAppendedPath(Uri.parse("content://" + AUTHORITY), file.getName());
}
public static void checkSize(Context context) throws IOException {
File dir = context.getCacheDir();
if (getDirSize(dir) > MAX_SIZE)
cleanDir(dir, MAX_SIZE / 2);
}
private static long getDirSize(File dir) {
long size = 0;
for (File file : dir.listFiles())
if (file.isFile())
size += file.length();
return size;
}
private static void cleanDir(File dir, long atLeast) {
long deleted = 0;
File[] files = dir.listFiles();
Arrays.sort(files, new Comparator<File>() {
public int compare(File f1, File f2) {
return Long.valueOf(f1.lastModified()).compareTo(f2.lastModified());
}
});
for (File file : files) {
deleted += file.length();
file.delete();
if (deleted >= atLeast)
break;
}
}
}
Using it couldn't be simpler, just call
File file = TemporaryFile.getFile(this, "prefix", ".extension");
whenever you want to create a new file and
TemporaryFile.getPublicUri(file)
whenever you want to get a public Uri to the file, eg. to pass it to an intent as data or Intent.EXTRA_STREAM.
Being a provider, don't forget to add the necessary manifest entry, either:
<provider
android:name=".TemporaryFile"
android:authorities="com.example.tempfile"
android:exported="true"
tools:ignore="ExportedContentProvider" >
</provider>
This works but requires external storage and the relating permissions. When downloading an app, a dialog will show that the app is requesting to be able to read/write data which may turn users away. Use the FileProvider as Simon suggested in my initial post if that's a concern.
Useful links:
https://developer.android.com/reference/android/support/v4/content/FileProvider.html
I attempted to use the File Provider as Simon suggested in my initial post to no avail. I received a NullPointerException on the following line:
final ProviderInfo info = context.getPackageManager()
.resolveContentProvider(authority, PackageManager.GET_META_DATA);
I was unable to track the problem after following the guide at:
https://developer.android.com/reference/android/support/v4/content/FileProvider.html
as well as the other thread at:
How to use support FileProvider for sharing content to other apps?
At this point I realized there is no file type set for the images being used. I simply added .png to the files and the attachments work correctly in Gmail as well as the previous apps that already worked.
I provided the following code if anyone was curious how I shared an internal file. It's not complete and does not handle errors completely but it may be useful for someone as a start.
// Copy image file to external memory and send with the intent
File srcFile = getImage();
File destDir = new File(((ActivityMain) getActivity()).getExternalCacheDir(),
Application2.instance().getResString(R.string.temporary_external_image_path));
if(!destDir.exists())
{
destDir.mkdirs();
}
if(destDir != null && srcFile != null)
{
File destFile = new File(destDir, srcFile.getName());
if (!destFile.exists())
{
try
{
Application2.instance().copy(srcFile, destFile);
}
catch (IOException e)
{
if (BuildConfig.DEBUG) Log.e("Failed to copy file '" + srcFile.getName() + "'");
}
}
if (destFile != null)
{
Uri uri = Uri.fromFile(destFile);
sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
((ActivityMain) getActivity()).startActivity(Intent.createChooser(sharingIntent, "Share via"));
}
}
I have many .mp3 files stored in res/raw folder.
I am getting URI of .mp3 file using following code.
Uri.parse("android.resource:///com.my.android.sharesound/"+resId);
This returns : android.resource:///com.my.android.sharesound/2130968609
Now I am using this URI in creating Share Intent
shareIntent.putExtra(Intent.EXTRA_STREAM,Uri.parse("android.resource://com.my.android.sharesound/"+resId));
startActivity(Intent.createChooser(shareIntent,"Share Sound");
When i select any Mail application eg. Gmail or YahooMail from the Share Intent, the mp3 file attached successfully. But it shows that 2130968609(as an attachment)
i dont want resourceId(2130968609) to appear.
I want to display fileName.mp3
How can i do that? Am i missing something ?
OR
Is there any other way to attach .mp3 file stored in res/raw to mail via Share Intent.
Finally after searching a lot i found the solution.
If you want to get any resource URI then there are two ways :
Using Resource Name
Syntax : android.resource://[package]/[res type]/[res name]
Example : Uri.parse("android.resource://com.my.package/drawable/icon");
Using Resource Id
Syntax : android.resource://[package]/[resource_id]
Example : Uri.parse("android.resource://com.my.package/" + R.drawable.icon);
This were the examples to get the URI of any image file stored in drawable folder.
Similarly you can get URIs of res/raw folder.
In my case i used 1st way ie. Using Resource Name
This work like magic:
Assuming the mp3 is saved in the raw folder as
notification_sound.mp3
then simple doing this works:
Uri uri=Uri.parse("android.resource://"+getPackageName()+"/raw/notification_sound");
Don't construct string on your own, use Uri.Builder:
/**
* #param resourceId identifies an application resource
* #return the Uri by which the application resource is accessed
*/
internal fun Context.getResourceUri(#AnyRes resourceId: Int): Uri = Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(packageName)
.path(resourceId.toString())
.build()
From AOSP-DeskClock.
Here are some methods that might help someone:
public Uri getRawUri(String filename) {
return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + File.pathSeparator + File.separator + getPackageName() + "/raw/" + filename);
}
public Uri getDrawableUri(String filename) {
return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + File.pathSeparator + File.separator + getPackageName() + "/drawable/" + filename);
}
public Uri getMipmapUri(String filename) {
return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + File.pathSeparator + File.separator + getPackageName() + "/mipmap/" + filename);
}
Just call the method like this:
Uri rawUri = getRawUri("myFile.filetype");
One line Answer
RingtoneManager.getRingtone(getContext(),Uri.parse("android.resource://com.my.package/" + R.raw.fileName)).play();
Here is a clean way to get any resource,
Uri.parse(String.format("android.resource://%s/%s/%s",this.getPackageName(),"resource_folder_name","file_name"));
Try saving the file to your SDCard and maybe then send the new URI,
public boolean saveAs(int resSoundId){
byte[] buffer = null;
InputStream fIn = getBaseContext().getResources().openRawResource(resSoundId);
int size=0;
try {
size = fIn.available();
buffer = new byte[size];
fIn.read(buffer);
fIn.close();
} catch (IOException e) {
// TODO Auto-generated catch block
return false;
}
String path = "/sdcard/yourapp/temp/";
String filename = "filename"+".mp3";
boolean exists = (new File(path)).exists();
if (!exists){new File(path).mkdirs();}
FileOutputStream save;
try {
save = new FileOutputStream(path+filename);
save.write(buffer);
save.flush();
save.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
return false;
} catch (IOException e) {
// TODO Auto-generated catch block
return false;
}
Remember to delete the file after the email is sent successfully.