I have an API that provides me with InputStream of a pdf file. And my objective is to allow user to view this file with another pdf viewer application.
In order to acheive this, I copied the InputStream to a pdf file that has been created programmatically. Then I tried to open that pdf file.
However, I'm not able to open the pdf file through my application or file explorer. Drive PDF viewer tells in toast that it cannot open the pdf file, Adobe Acrobat tells that the pdf file cannot be accessed, and Mi PDF reader tells that the pdf is in invalid format.
Here's the code inside onResponse() of OkHttpClient's call:
if (response.isSuccessful()) {
InputStream inputStream = responseBody.byteStream();
runOnUiThread(new Runnable() {
#Override
public void run() {
File file = null;
try {
file = MyFileUtils.createPdfFile(PdfViewActivity.this);
} catch (IOException e) {
e.printStackTrace();
}
MyFileUtils.copyInputStreamToFile(inputStream, file);
String pdfFilePath = file.getAbsolutePath();
openPdf(pdfFilePath);
}
});
}
Here's the method responsible for opening pdf:
private void openPdf(String pdfFilePath) {
Intent pdfViewIntent = new Intent(Intent.ACTION_VIEW);
pdfViewIntent.setDataAndType(Uri.parse(pdfFilePath), "application/pdf");
Intent chooser = Intent.createChooser(pdfViewIntent, "Open this note with");
startActivity(chooser);
finish();
}
I created the pdf using MyFileUtils.createPdfFile() method. Here's how I implemented it:
public static File createPdfFile(Activity associatedActivity) throws IOException {
String timeStamp = new SimpleDateFormat("HHmmss").format(new Date());
String pdfFileName = "PDF_" + timeStamp + "_";
File storageDir = associatedActivity.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);
Log.d(TAG, "createPdfFile: storageDir: " + storageDir.getAbsolutePath());
return File.createTempFile(
pdfFileName, /* prefix */
".pdf", /* suffix */
storageDir /* directory */
);
}
I copied the InputStream to that file using the following method in MyFileUtils class:
// Copy an InputStream to a File.
public static void copyInputStreamToFile(InputStream in, File file) {
OutputStream out = null;
try {
out = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// Ensure that the InputStreams are closed even if there's an exception.
try {
if (out != null) {
out.close();
}
// If you want to close the "in" InputStream yourself then remove this
// from here but ensure that you close it yourself eventually.
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
I've implemented the provider in the following way:
AndroidManifest.xml :
<application>
...
...
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.xyz.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
</application>
file_paths.xml :
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="my_images" path="Pictures" />
<external-files-path name="my_documents" path="Documents" />
</paths>
NOTE:
The application has necessary read-write permissions. I've tested it programmatically. I'm not including those codes here because it might not be relevant.
The absolute path of the created pdf file is something like this: /storage/emulated/0/Android/data/<package name>/files/Documents/PDF_164257_4832620770047519807.pdf
You need to download on device download directory.
Your directory is not visible to other's application that's by its not opening.
Only your application can use this directory.
Not able to comment due to less reputation
I am not sure if this helps but I see that the file you copy the InputStream into seems not be closed before you call openPdf(pdfFilePath); which means the pdf viewers get trouble opening it either because it is blocked or not all content has been written into the file.
Related
For some time I've been trying to implement the functionality of sending an audio file from my app through WhatsApp. When debugging everything seems to work correctly in the application, the audio file is generated and saved correctly in the external storage of the device, the WhatsApp window opens and allows me to select the chat to which I want to send the audio. The problem is that when I press the send button, WhatsApp returns the message "Failed to share. Please try again" (I leave a screenshot of the error so that it can be better viewed, in addition to the code used to implement the functionality).
Capture of the error shown on the screen by WhatsApp when trying to share the audio:
enter image description here
Code used:
//////Boton//////
btn1.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View view) {
try {
String mediaPath = copyFiletoExternalStorage(R.raw.audio1, "audio1.mp3");
File myFile = new File(mediaPath);
Uri newUri = getUriForFile(wspActivity.this, "com.restart.shareaudiofiles.fileprovider", myFile);
Intent compartirAudio = new Intent(android.content.Intent.ACTION_SEND);
compartirAudio.setType("com.whatsapp");
compartirAudio.setType("audio/mp3");
compartirAudio.putExtra(Intent.EXTRA_STREAM,newUri);
startActivity(Intent.createChooser(compartirAudio, "Compartir vía"));
} catch (Exception e) {
Toast.makeText(getApplicationContext(), "Whatsapp no se encuentra instalado", Toast.LENGTH_LONG).show();
}
}
});
/////funcion auxiliar/////
private String copyFiletoExternalStorage(int resourceId, String resourceName){
String pathi= Environment.getExternalStorageDirectory() + "/Android/data/myProject/";
boolean exists = (new File(pathi)).exists();
if (!exists) {
new File(pathi).mkdirs();
}
String pathSDCard = Environment.getExternalStorageDirectory() + "/Android/data/TeLoResumoBotonera/" + resourceName;
try{
InputStream in = getResources().openRawResource(resourceId);
FileOutputStream out = null;
out = new FileOutputStream(pathSDCard);
byte[] buff = new byte[1024];
int read = 0;
try {
while ((read = in.read(buff)) > 0) {
out.write(buff, 0, read);
}
} finally {
in.close();
out.close();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return pathSDCard;
}
/////Elementos agregados al manifest/////
<queries>
<package android:name="com.whatsapp" />
<package android:name="com.whatsapp.w4b" />
</queries>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.restart.shareaudiofiles.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" />
</provider>
I researched and implemented various resources to the manifest such as the fileprovider and queries with specific packages for WhatsApp (as can be seen in the code). However, the app still doesn't work. This makes me think that the problem could be in the mobile device that I am using to test the application (I would like to use another one to rule out this option, but I don't have an extra one). Another factor that I think may be causing problems is the version of Android (Android 11 in this case). This is because similar codes worked correctly in previous versions. If the device has nothing to do with it, then clearly there is a bug in my code. Due to this, in case someone manages to identify the improvement that could make the application work correctly, I would be very grateful if you can share it with me, or at least give me an idea of where to address the problem.
Thank you very much for your time,
Regards!
As I see your error in the storage path. here is my code that useful to you.
If I am correct, the problem is that you declared the storage path incorrectly:
The path you declared: Environment.getExternalStorageDirectory() + "/Android/data/myProject/"
updated path: context.getExternalFilesDir("MyProject")
The context.getExternalFilesDir("MyProject") method will return the same path as you did.
first change your copyFiletoExternalStorage method like:
private String copyFilesToStorage(Context context, int resourceId, String resourceName) {
File destinationPath = context.getExternalFilesDir("MyProject");
if (!destinationPath.exists()) {
destinationPath.mkdirs();
}
String pathSDCard = new File(destinationPath, resourceName).getAbsolutePath();
try {
InputStream in = getResources().openRawResource(resourceId);
FileOutputStream out = null;
out = new FileOutputStream(pathSDCard);
byte[] buff = new byte[1024];
int read = 0;
try {
while ((read = in.read(buff)) > 0) {
out.write(buff, 0, read);
}
} finally {
in.close();
out.close();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
return pathSDCard;
}
change share Intent code to like:
File file = new File(copyFilesToStorage(this, R.raw.audio1, "audio1.mp3"));
Uri path = FileProvider.getUriForFile(this,"com.restart.shareaudiofiles.fileprovider", file);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, path);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setType("audio/mp3");//Replace with audio/* to choose other extensions
startActivity(Intent.createChooser(intent, "Share Audio"));
don't forgot about permission:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
I'm following the Google Camera Tutorial for an Android application. At this moment, I'm able to take a picture, save it, show the path and show the bitmap into an ImageView.
Here is an exemple of the logcat when I ask for the absolute path of a picture I just took :
D/PATH:: /storage/emulated/0/Pictures/JPEG_20160210_140144_217642556.jpg
Now, I would like to transfer it on a PC via USB. When I broswe into the device storage, I can see the public folder Picturethat I called earlier in my code with the variable Environment.DIRECTORY_PICTURES. However, there is nothing in this folder.
Screenshot of my device's folders
I can't insert a SD Card in my device to test. Also, I don't want to put the pictures into cache directory for preventing to be deleted.
Here is my permissions in Manifest :
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
When the user click on the camera buttons :
dispatchTakePictureIntent();
[...]
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
// Error occurred while creating the File
}
// Continue only if the File was successfully created
if (photoFile != null) {
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
Uri.fromFile(photoFile));
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
}
}
This is method creating the file
private File createImageFile() throws IOException {
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
// Save a file: path for use with ACTION_VIEW intents
mCurrentPhotoPath = "file:" + image.getAbsolutePath();
Log.d("PATH:", image.getAbsolutePath());
return image;
}
I guess I misunderstood something about the External Storage. Can someone explain me why I can't save a picture and access it on a PC ? Thank you !
-- EDIT --
After reading an answer below, I tried to get the file in OnActivityResult and to save it with Java IO. Unfortunately, there is no file in Pictures folder when I look with Explorer.
if (requestCode == REQUEST_TAKE_PHOTO) {
Log.d("AFTER", absolutePath);
// Bitmap bitmap = BitmapFactory.decodeFile(absolutePath);
// imageTest.setImageBitmap(Bitmap.createScaledBitmap(bitmap, 2100, 3100, false));
moveFile(absolutePath, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString());
}
private void moveFile(String inputFile, String outputPath) {
InputStream in = null;
OutputStream out = null;
try {
//create output directory if it doesn't exist
File dir = new File (outputPath);
if (!dir.exists())
{
dir.mkdirs();
}
in = new FileInputStream(inputFile);
out = new FileOutputStream(outputPath + imageFileName + ".jpg");
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
in.close();
in = null;
// write the output file
out.flush();
out.close();
out = null;
// delete the original file
new File(inputFile).delete();
}
You're currently saving the file as a temporary file, so it won't persist on disk after the application lifecycle. Use something like:
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
imageBitmap.compress(Bitmap.CompressFormat.JPEG, 90, bytes);
File f = new File(Environment.getExternalStorageDirectory() + [filename])
And then create a FileOutputStream to write to it.
FileOutStream fo = new FileOutputStream(f);
fo.write(bytes.toByteArray());
To solve my problem, I had to write the file into the application's data folder and to user the MediaScannerConnection. I've put a .txt file for testing, but after it works you can put any other file.
I'll share the solution for those who have a similar problem :
try
{
// Creates a trace file in the primary external storage space of the
// current application.
// If the file does not exists, it is created.
File traceFile = new File(((Context)this).getExternalFilesDir(null), "TraceFile.txt");
if (!traceFile.exists())
traceFile.createNewFile();
// Adds a line to the trace file
BufferedWriter writer = new BufferedWriter(new FileWriter(traceFile, true /*append*/));
writer.write("This is a test trace file.");
writer.close();
// Refresh the data so it can seen when the device is plugged in a
// computer. You may have to unplug and replug the device to see the
// latest changes. This is not necessary if the user should not modify
// the files.
MediaScannerConnection.scanFile((Context)(this),
new String[] { traceFile.toString() },
null,
null);
}
catch (IOException e)
{
Log.d("FileTest", "Unable to write to the TraceFile.txt file.");
}
I have tried to implement over 10-15 different download mechanisms for android java, I have not been able to succeed at all.
I don't care about progress bars or background processes.
I just want one functional download code in fewest lines possible
and I want it to download a binary file (foreground) to the directory in the device wherever it can be accessed as
File pf = new File("filename");
if (pf.exists()) { ... }
Try this (modified from here):
try {
URL url = new URL("http://url.com");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setDoOutput(true);
urlConnection.connect();
//THIS IS WHERE YOU GET THE DIRECTORY TO SAVE TO
File SDCardRoot = Environment.getExternalStorageDirectory();
//THIS IS WHERE YOU WILL SET THE FILE NAME
File file = new File(SDCardRoot,"somefile.txt");
FileOutputStream fileOutput = new FileOutputStream(file);
InputStream inputStream = urlConnection.getInputStream();
//create a buffer...
byte[] buffer = new byte[1024];
int bufferLength = 0;
while ( (bufferLength = inputStream.read(buffer)) > 0 ) {
fileOutput.write(buffer, 0, bufferLength);
}
fileOutput.close();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
You may also need to add permissions to access the phone directory:
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
For information on accessing certain folders on the internal directory, see the android developer page: http://developer.android.com/training/basics/data-storage/files.html#WriteInternalStorage
In fact, the solution on that page is also fairly short:
String filename = "myfile";
String string = "Hello world!";
FileOutputStream outputStream;
try {
outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
outputStream.write(string.getBytes());
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
I created a raw folder inside res (res/raw) and I also created my_text_file.txt file.
Now I want to write something in this file.
I wrote some code but I cannot write (for example) a simple string.
This is my code.
If anyone knows what is wrong in my code, please help me
try {
FileOutputStream fos = openFileOutput("my_text_file.txt",
Context.MODE_PRIVATE);
OutputStreamWriter osw = new OutputStreamWriter(fos);
osw.write("17");
osw.flush();
osw.close();
} catch (java.io.IOException e) {
// do something if an IOException occurs.
}
You can read your file but not alter the file on the resources folder.
What you can do is to save the file in the external storage then start to alter the file.
Dont forget to set the permission:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
You shouldn't write to resource files. It exist to store data that you put here before compilation. If you want to save some information in file, during runtime you can do something like this:
public static void writeToFile(String fileName, String encoding, String text) {
Writer writer = null;
try {
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName), encoding));
writer.write(text);
} catch (IOException ex) {
Log.e(TAG, "", ex);
} finally {
try {
writer.close();
} catch (Exception ex) {
}
}
}
To find path to SD card you can use this method:
Environment.getExternalStorageState()
So you can use this method like this:
writeToFile(Environment.getExternalStorageState() + "/" + "my_text_file.txt", "UTF-8", "my_text");
And don't forgot to set permission
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
UPD:
You shouldn't use SD card to store some secure information! More information here.
To write data that will be available only for you application, use this code:
public static void writeToInternalFile(String fileName, String text) {
FileOutputStream fos = openFileOutput(fileName, Context.MODE_PRIVATE);
fos.write(text.getBytes());
fos.close();
}
To read from this file:
public static String readFromInternalFile(String fileName) {
FileInputStream fis = openFileInput(, Context.MODE_PRIVATE);
StringBuilder sb = new StringBuilder();
int ch;
while((ch = fis.read()) != -1){
sb .append((char)ch);
}
return sb.toString();
}
I am using the following code to download a file from my server then write it to the root directory of the SD card, it all works fine:
package com.downloader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import android.os.Environment;
import android.util.Log;
public class Downloader {
public void DownloadFile(String fileURL, String fileName) {
try {
File root = Environment.getExternalStorageDirectory();
URL u = new URL(fileURL);
HttpURLConnection c = (HttpURLConnection) u.openConnection();
c.setRequestMethod("GET");
c.setDoOutput(true);
c.connect();
FileOutputStream f = new FileOutputStream(new File(root, fileName));
InputStream in = c.getInputStream();
byte[] buffer = new byte[1024];
int len1 = 0;
while ((len1 = in.read(buffer)) > 0) {
f.write(buffer, 0, len1);
}
f.close();
} catch (Exception e) {
Log.d("Downloader", e.getMessage());
}
}
}
However, using Environment.getExternalStorageDirectory(); means that the file will always write to the root /mnt/sdcard. Is it possible to specify a certain folder to write the file to?
For example: /mnt/sdcard/myapp/downloads
File sdCard = Environment.getExternalStorageDirectory();
File dir = new File (sdCard.getAbsolutePath() + "/dir1/dir2");
dir.mkdirs();
File file = new File(dir, "filename");
FileOutputStream f = new FileOutputStream(file);
...
Add Permission to Android Manifest
Add this WRITE_EXTERNAL_STORAGE permission to your applications manifest.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="your.company.package"
android:versionCode="1"
android:versionName="0.1">
<application android:icon="#drawable/icon" android:label="#string/app_name">
<!-- ... -->
</application>
<uses-sdk android:minSdkVersion="7" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</manifest>
Check availability of external storage
You should always check for availability first. A snippet from the official android documentation on external storage.
boolean mExternalStorageAvailable = false;
boolean mExternalStorageWriteable = false;
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
// We can read and write the media
mExternalStorageAvailable = mExternalStorageWriteable = true;
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
// We can only read the media
mExternalStorageAvailable = true;
mExternalStorageWriteable = false;
} else {
// Something else is wrong. It may be one of many other states, but all we need
// to know is we can neither read nor write
mExternalStorageAvailable = mExternalStorageWriteable = false;
}
Use a Filewriter
At last but not least forget about the FileOutputStream and use a FileWriter instead. More information on that class form the FileWriter javadoc. You'll might want to add some more error handling here to inform the user.
// get external storage file reference
FileWriter writer = new FileWriter(getExternalStorageDirectory());
// Writes the content to the file
writer.write("This\n is\n an\n example\n");
writer.flush();
writer.close();
Found the answer here - http://mytechead.wordpress.com/2014/01/30/android-create-a-file-and-write-to-external-storage/
It says,
/**
* Method to check if user has permissions to write on external storage or not
*/
public static boolean canWriteOnExternalStorage() {
// get the state of your external storage
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
// if storage is mounted return true
Log.v("sTag", "Yes, can write to external storage.");
return true;
}
return false;
}
and then let’s use this code to actually write to the external storage:
// get the path to sdcard
File sdcard = Environment.getExternalStorageDirectory();
// to this path add a new directory path
File dir = new File(sdcard.getAbsolutePath() + "/your-dir-name/");
// create this directory if not already created
dir.mkdir();
// create the file in which we will write the contents
File file = new File(dir, "My-File-Name.txt");
FileOutputStream os = outStream = new FileOutputStream(file);
String data = "This is the content of my file";
os.write(data.getBytes());
os.close();
And this is it. If now you visit your /sdcard/your-dir-name/ folder you will see a file named - My-File-Name.txt with the content as specified in the code.
PS:- You need the following permission -
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
In order to download a file to Download or Music Folder In SDCard
File downlodDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);// or DIRECTORY_PICTURES
And dont forget to add these permission in manifest
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />