Reusing code for sharing/saving pdf document - android

So I have a fragment where I show the user terms and conditions for something and those terms and conditions are in the form of pdf file which is retrieved from the server.
This is the code that retrieves the pdf and gives the pdfView an inputstream to show the data.
class RetrievePDFFromUrl extends AsyncTask<String, Void, InputStream> {
#Override
protected InputStream doInBackground(String... strings) {
InputStream inputStream = null;
try {
URL url = new URL(strings[0]);
HttpURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
if (urlConnection.getResponseCode() == 200) {
inputStream = new BufferedInputStream(urlConnection.getInputStream());
}
} catch (IOException e) {
e.printStackTrace();
return null;
}
return inputStream;
}
#Override
protected void onPostExecute(InputStream inputStream) {
pdfView.fromStream(inputStream).load();
}
}
So far so good.
But now I have to add the functionality to share and save the document.
The problem is that I have to use other pieces of code to accomplish the task.
And since I cant share the document without downloading it, its a little messy.
Heres how I download the document.
private void downloadPDFContent(){
String fileName = getCurrentDocumentName();;
String urlToDownload = !secondDocument ? documentUrl1 : documentUrl2;
File outputFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName);
if (outputFile.exists()) {
return;
}
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(urlToDownload));
request.setTitle(fileName);
request.setMimeType("application/pdf");
request.allowScanningByMediaScanner();
request.setAllowedOverMetered(true);
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
DownloadManager downloadManager = (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
downloadManager.enqueue(request);
}
The problem comes when trying to share the document, Its just wrong to put 200ms delay before trying to share it, because no one knows how slow a connection can be sometimes and it wont work.
private void shareDocument() {
downloadPDFContent();
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
#Override
public void run() {
File outputFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), getCurrentDocumentName());
Uri uri = FileProvider.getUriForFile(getContext(),
getContext().getPackageName() + ".provider", outputFile);
Intent share = new Intent();
share.setAction(Intent.ACTION_SEND);
share.setType("application/pdf");
share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
share.putExtra(Intent.EXTRA_STREAM, uri);
startActivity(Intent.createChooser(share, "Share document"));
}
}, 200);
}
Does anyone have any better ideas how can I achieve those 3 tasks - using inputstream to load the doc for the user to view and to share/save it also while reusing code and not doing it in different and unstable ways?
**UPDATE: I added a broadcastreceiver to start when the downloading is finished, instead of waiting a fixed amount of time like i do here.
Which is one idea better but still not what i wanted.

You have been using many legacy tools for this task and it is not clear do you have business constraints for it or not.
If your business use case just to download pdf and share it with another android app within your device, I would use Kotlin Flow for the async download task.
When you download your pdf and save it in storage, you could use a callback from Kotlin Flow as a trigger for your sharing intent. You would not need anymore any delay.
Please note, depends on your business use case you could use ContentProvider to give access to your app's files and p2p 3rd party tools for downloading and sharing your files.

Related

Downloading file with JSF on Android's WebView

I have a Java Web Application that uses JSF and PrimeFaces. On a backing bean, I download a local pdf or document file:
public void download() throws FileNotFoundException, IOException {
File file = new File("C:/file.pdf");
FacesContext facesContext = FacesContext.getCurrentInstance();
HttpServletResponse response =
(HttpServletResponse) facesContext.getExternalContext().getResponse();
response.reset();
response.setHeader("Content-Type", "application/pdf");
OutputStream responseOutputStream = response.getOutputStream();
InputStream fileInputStream = new FileInputStream(file);
byte[] bytesBuffer = new byte[2048];
int bytesRead;
while ((bytesRead = fileInputStream.read(bytesBuffer)) > 0)
{
responseOutputStream.write(bytesBuffer, 0, bytesRead);
}
responseOutputStream.flush();
fileInputStream.close();
responseOutputStream.close();
facesContext.responseComplete();
}
This is how I call the download method on the xhtml:
<p:commandButton action="#{mybean.download()}" value="Download File" ajax="false" >
And I created a simple Android application for my Java application just by calling it's url inside a WebView:
mWebView.loadUrl("http://myappurl.com");
Then I listen for downloads on the WebView using the DownloadListener event:
mWebView.setDownloadListener(new DownloadListener() {
public void onDownloadStart(String url, String userAgent,
String contentDisposition, String mimetype,
long contentLength) {
DownloadManager.Request request = new DownloadManager.Request(
Uri.parse(url));
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, url);
DownloadManager dm = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
dm.enqueue(request);
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
Toast.makeText(getApplicationContext(), "Downloading File",
Toast.LENGTH_LONG).show();
}
});
The problem is, the DownloadListener event for WebView does not get called when I press the download button. It works fine by accessing the application URL from the phone's browser, but not in the android application that I created.
It also works on the android application if I put the pdf inside the application directory and call it using a simple <a href="/file.pdf" download>, but that is not what I want, because the pdf files will be on the server's directory and not in the application.
What I've tried:
Using PrimeFaces p:fileDownload - got the same result, nothing happens when button is pressed;
I looked into google docs online viewer, but I cant use that because my files are local;
I tried PDF.js, but this same download button will be used for all kinds of documents, not only pdf, so I don't really need to view the file, I just need to download and save it on the device.
Please help me. Thanks in advance.
EDIT
I tried Chrome Custom Tabs that replaces the Android WebView, it works great, my JSF download was recognized and the file was downloaded, I didn't have to do anything. The only problem is that there is no way of removing the toolbar from Chrome Custom Tabs, because I want my application to seem like it's a native application and not a webpage.

[Android ]Intent.ACTION_VIEW - Not found

I am having an issue, I have never had problem opening files via ACTION_VIEW the next way:
File file = new File(getActivity().getFilesDir(), TEMP_FILE_NAME);
String dataType = "image/*";
if (file.exists()) {
Intent fileIntent = new Intent(Intent.ACTION_VIEW);
fileIntent.setDataAndType(Uri.fromFile(file), dataType);
fileIntent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
Intent intent = Intent.createChooser(fileIntent, "Open file");
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
Log.e(TAG, "There is a problem when opening the file");
}
} else {
Toast.makeText(getContext(), "Invalido", Toast.LENGTH_LONG).show();
}
The problem I am having right now is that even though the file exists when I choose the app to open the file it immediately closes and tells me Not found. I have put the image I am loading in an image view and there is no problem, so the file is valid but for some reason it has conflicts when I am opening it via intent.
I am aware that it may have something to do with the way I am creating the file, I am retrieving it from Google drive so I am writing the file using the Apache Commons library the next way:
DriveContents contents = result.getDriveContents();
InputStream inputStream = contents.getInputStream();
File file = new File(getActivity().getFilesDir(), TEMP_FILE_NAME);
try {
OutputStream outputStream = new FileOutputStream(file);
IOUtils.copy(inputStream, outputStream);
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
} catch (IOException e) {
e.printStackTrace();
}
What is it I am doing wrong? I am not totally sure if the problem has to do with the copy method executing asynchronously or something like that.
Thanks in advance.
I have never had problem opening files via ACTION_VIEW the next way
That code will never work, as third-party apps have no rights to work with files on getFilesDir() of your app.
What is it I am doing wrong?
You are attempting to serve an inaccessible file to third-party programs. Use FileProvider to serve the file, using FileProvider.getUriForFile() to get the Uri to use in your ACTION_VIEW Intent.

Android - Google Drive SDK - Open file

I am used to opening my files in my apps using the next code:
public void openFile(#NonNull String uri) {
checkNotNull(uri);
File file = new File(uri);
String dataType = null;
if (ContentTypeUtils.isPdf(uri)) dataType = "application/pdf";
else if (ContentTypeUtils.isImage(uri)) dataType = "image/*";
if (file.exists() && dataType != null) {
Intent target = new Intent(Intent.ACTION_VIEW);
target.setDataAndType(Uri.fromFile(file), dataType);
target.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
Intent intent = Intent.createChooser(target, "Open file");
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
Log.e(TAG, "There is a problem when opening the file :(");
}
} else {
Toast.makeText(getContext(), "Invalido", Toast.LENGTH_LONG).show();
}
}
I had always used static files so this was enough, but now I am using the Google Drive SDK for Android. I possess the driveId of the file I want to open but the problem is I cannot find a clean way to open the file contents I obtain by doing this:
Drive.DriveApi.fetchDriveId(mGoogleApiClient, documentFile.getDriveId())
.setResultCallback(driveIdResult -> {
PendingResult<DriveApi.DriveContentsResult> open =
driveIdResult.getDriveId().asDriveFile().open(
mGoogleApiClient,
DriveFile.MODE_READ_ONLY,
null);
open.setResultCallback(result -> {
DriveContents contents = result.getDriveContents();
InputStream inputStream = contents.getInputStream();
// I know I can get the input stream, and use it to write a new file.
});
});
So the only thing that comes to my mind is creating a static route to create a file every time I have to open it, and erasing it every time I have to open a new file.
What I have understood up until now is that the Google Drive API for Android already saves an instance of the file so what I have in mind sounds unnecessary, I would like to know if there is a better way to achieve this. Is there a way I can open the file and do something similar to what I do with the Intent.ACTION_VIEW in a cleaner way?
Thanks in advance.
Well since it seems this will not be answered I will post what I did. All I did was create a temp file where I put my contents to be read. I still don't know if it was the best choice so this question will still be opened for a better answer.
open.setResultCallback(result -> {
DriveContents contents = result.getDriveContents();
InputStream inputStream = contents.getInputStream();
writeTempFile(inputStream);
});
And here the implementation of the `writeTempFile`:
private synchronized File writeTempFile(#NonNull InputStream inputStream) {
checkNotNull(inputStream);
File filePath = new File(mActivity.getFilesDir(), "TempFiles");
if (!filePath.exists()) filePath.mkdirs();
File file = new File(filePath, TEMP_FILE);
try {
OutputStream outputStream = new FileOutputStream(file);
IOUtils.copyLarge(inputStream, outputStream);
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
} catch (IOException e) {
e.printStackTrace();
}
return file;
}

How to download a file from OneDrive after using the OneDrive picker to get the weblink to the File?

I have implemented the OneDrive picker in my app to allow the user to easily select a file from the OneDrive cloud storage. My app then gets the weblink that points to the file. I would now like to download (make a copy) of the file to the local storage on the Android device.
I would have thought this would be simple (As Dropbox made downloading a local copy part of their picker function) but it seems OneDrive has not. I've done some searches to find out how to download a file give the weblink, but they either seem way to complicated or have not worked.
Below is the code for the onactivity result from the OneDrive picker. Now that I have the Uri, please help me with how to get a local copy of the file so that I can use it. Thanks!
if (requestCode == ONEDRIVE_CHOOSER_REQUEST)
{
// Get the results from the picker
IPickerResult result = mPicker.getPickerResult(requestCode, resultCode, data);
// Handle the case if nothing was picked
if (result != null) {
// Do something with the picked file
Uri fileUri = result.getLink();
Okay, third time was the charm. Although I really was surprised there isn't and easier way to download a file. If anyone has a simpler solution, I'd love to hear it.
I followed some advice from this post How to download and save a file from Internet using Java?
and have managed to download the file from OneDrive.
public static void download(String address, String localFileName) {
OutputStream out = null;
URLConnection conn = null;
InputStream in = null;
try {
URL url = new URL(address);
out = new BufferedOutputStream(new FileOutputStream(localFileName));
conn = url.openConnection();
in = conn.getInputStream();
byte[] buffer = new byte[1024];
int numRead;
long numWritten = 0;
while ((numRead = in.read(buffer)) != -1) {
out.write(buffer, 0, numRead);
numWritten += numRead;
}
System.out.println(localFileName + "\t" + numWritten);
}
catch (Exception exception) {
exception.printStackTrace();
}
finally {
try {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
catch (IOException ioe) {
}
}
I don't know what URL the picker gives you but doing this via OneDrive web you get a link which just shows the File. But you can translate this link to get a direct download link:
The link you get:
https://onedrive.live.com/redir?resid=8F99649728BEB2F3%212780
You just need to replace redir by download:
https://onedrive.live.com/download?resid=8F99649728BEB2F3%212780
Reference: How to get direct download link from OneDrive
But you should keep in mind that this is no official way (at least I couldn't find this information on a Microsoft website) so it's possible that Microsoft changes this and your links don't work anymore.

How to check whether the process of download image is completed or not?

In my application the system will download images from an url and save it into phone memory. (I did not included url address in the question) On top of that, it will also save data into sqlite database. Data that save is file name, filepath and file size. But currently once I go through the download process whether the download complete or fail in the middle of the process, it also will insert into the database.
Is there any way that I can check whether the download process is completed or not?
GalleryScreen.this.runOnUiThread(new Runnable() {
#Override
public void run() {
boolean isDownloadResult = false;
int NumIncrease = 0;
Log.i(TAG, "NumberIncrease:" +NumIncrease);
Toast.makeText(getApplicationContext(), "Downloading.............>>>>>>>>>>>", Toast.LENGTH_SHORT).show();
Bitmap bm;
InputStream in;
try{
in = new java.net.URL(URL).openStream();
bm = BitmapFactory.decodeStream(new PatchInputStream(in));
File storage = new File(Environment.getExternalStorageDirectory() + File.separator + "/Images/");
Log.i(TAG,"storage:" +storage);
Log.i(TAG,"storage:" +storage.getAbsolutePath());
if(!storage.exists()){
storage.mkdirs();
}
String FileName = "/"+CONTENT_ID+".jpg";
FileOutputStream fos = new FileOutputStream(storage + FileName);
bm.compress(Bitmap.CompressFormat.JPEG, 85, fos);
String filepath = storage + FileName;
File filecheck = new File (filepath);
long fileSize = filecheck.length();
fos.flush();
fos.close();
Log.i(TAG, "bm:" +bm);
Log.i(TAG, "fos:" +fos);
Log.i(TAG, "filesize:" +fileSize);
Log.i(TAG, "filepath:" +filepath);
helper.insert_content(filepath, fileSize, requestTime);
isDownload = false;
}
catch(IOException e1){
e1.printStackTrace();
}
}
});
Please use AsyncTask. AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.
AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.)
you can just use an anonymous class for the async task. This would like this:
ImageView mChart = (ImageView) findViewById(R.id.imageview);
String URL = "http://www...anything ...";
mChart.setTag(URL);
new DownloadImageTask.execute(mChart);
The Task class:
public class DownloadImagesTask extends AsyncTask<ImageView, Void, Bitmap> {
ImageView imageView = null;
#Override
protected Bitmap doInBackground(ImageView... imageViews) {
this.imageView = imageViews[0];
return download_Image((String)imageView.getTag());
}
#Override
protected void onPostExecute(Bitmap result) {
imageView.setImageBitmap(result);
}
private Bitmap download_Image(String url) {
...
}
Here, onPostExecute, you can easily check whether the process of download image is completed or not.
Further reading
http://developer.android.com/reference/android/os/AsyncTask.html
Loading Image using AsyncTask
Android : Loading an image from the Web with Asynctask
Android Help with adding a progress dialog while image loading?
EDIT:
If you are trying to download larger files, you might consider
putting your application into some type of Service as this would
potentially take a few hours.
You can consider using Download Manger for newer devices
(with Android 2.3+)
Also a nice resource ->
Download a file with Android, and showing the progress in a ProgressDialog

Categories

Resources