After modifying build.gradle with :
compileSdkVersion 28 to 32
targetSdkVersion 28 to 32
I can't write a local file from retrofit response.
I get error :
java.io.FileNotFoundException: /storage/emulated/0/DontEat/rangt_00001.jpg: open failed: EPERM (Operation not permitted)
AndroidManifest.xml contains :
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
and my code is :
public static void downloadFile(SyncCalls.CallbacksDownload callbacks, final String type, final File file, final Boolean last) {
final WeakReference<SyncCalls.CallbacksDownload> callbacksWeakReference = new WeakReference<>(callbacks);
SyncService service = SyncService.retrofit.create(SyncService.class);
String filename = file.toString().split("/")[5];
Call<ResponseBody> call = service.downloadFile(filename);
call.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(#NonNull Call<ResponseBody> call, #NonNull Response<ResponseBody> response) {
try {
OutputStream out = null;
InputStream in = null;
ResponseBody body = response.body();
try {
if (body != null) {
in = body.byteStream();
out = new FileOutputStream(file);
byte[] fileReader = new byte[4096];
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
while (true) {
int read = in.read(fileReader);
if (read == -1) {
break;
}
out.write(fileReader, 0, read);
}
out.flush();
}
} catch (IOException e) {
if (BuildConfig.DEBUG) Log.v(Consts.TAG, e.toString());
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
} catch (IOException e) {
if (BuildConfig.DEBUG) Log.v(Consts.TAG, e.toString());
}
if (BuildConfig.DEBUG) Log.v(Consts.TAG, "download " + file.toString());
if (callbacksWeakReference.get() != null)
callbacksWeakReference.get().onResponseSync(type, "down", last);
}
Permissions are verified with :
// Storage Permissions variables
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static final String[] PERMISSIONS_STORAGE = {
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
//permission method.
public static void verifyStoragePermissions(Activity activity) {
// Check if we have read or write permission
int writePermission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
int readPermission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE);
if (writePermission != PackageManager.PERMISSION_GRANTED || readPermission != PackageManager.PERMISSION_GRANTED) {
// We don't have permission so prompt the user
ActivityCompat.requestPermissions(
activity,
PERMISSIONS_STORAGE,
REQUEST_EXTERNAL_STORAGE
);
}
}
Is my mistake in the code or the permissions please?
I don't develop more because of my very limited English.
(It's an app I made many years ago, for personal use only, family, but I can't scale it because of this bug that appeared with new android versions while it was working perfectly so far)
Edit:
Actually, I think I'm taking the problem upside down and I'd better use the application's internal data directory with getFilesDir() and I won't have permission problems anymore, if I understood correctly.
Add the line in AndroidManifest.xml, inside application tag..
Hope it'll help you.
android:requestLegacyExternalStorage=”true”
Related
I want to call the camera through intent, capture images, and save it locally in the gallery and in the app-specific storage how can I achieve that?
I went through this question also but the code is not working I don't know why.
I'm assuming the app-specific storage is some sort of cache, in your onActivityResult() wouldn't you save the picture first to app-specific storage and then copy it to Environment.DIRECTORY_PICTURES?
I have done something like this Before, I Share my code with you and others that may help.
in this scenario if you want to save captured image, you have to check which api level that device is running with. for api level 28 and below you have to use specific method and for api level 28 above you have specific way.
so first step, start from:
AndroidManifest.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" tools:ignore="ScopedStorage"/>
and add this line in to your AndroidManifest.xml application tag:
android:requestLegacyExternalStorage="true"
if your project targetSdkVersion is 28 and above, you have to use ActivityResultLauncher otherwise you have to override onRequestPermissionResult in your fragment or activity.
if you use fragment you have to register your ActivityResultLauncher in onAttach method, if not register it in onCreate of your Activity, so do this:
public class YourFragment extends Fragment {
private Bitmap bitmap;
private ActivityResultLauncher<String> storageResultActivity;
#Override
public void onAttach(#NonNull #NotNull Context context) {
super.onAttach(context);
registerWriteExternalStoragePermission();
}
registerWriteExternalStoragePermission has this codes:
private void registerWriteExternalStoragePermission() {
storageResultActivity = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
if (isGranted) {
checkStoragePermissionAndSaveImage(bitmap);
}
});
}
so next step is to get back result of captured image from intent and change it to Bitmap for this section we need another activityResultLauncher so do this in onAttach of your fragment or onCreate of your activity:
ActivityResultLauncher<Intent> activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode() == RESULT_OK) {
Intent data = result.getData();
if (data == null || data.getData() == null) {
//showError
}
Uri uri = data.getData();
if (Build.VERSION.SDK_INT < 29) {
bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri);
} else {
ImageDecoder.Source source = ImageDecoder.createSource(context.getContentResolver(), uri);
bitmap = ImageDecoder.decodeBitmap(source);
}
}
so next step is to check your app has storage permission or not:
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
saveImageInAndroidApi28AndBelow(bitmap);
} else if (ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
//show user app can't save image for storage permission denied
} else {
storageResultActivity.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
ok as we near the end of this answer :) you have to implement saveImageInAndroidApi28AndBelow method, se lets go to do it:
private boolean saveImageInAndroidApi28AndBelow(Bitmap bitmap) {
OutputStream fos;
String imagesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString();
File image = new File(imagesDir, "IMG_" + System.currentTimeMillis() + ".png");
try {
fos = new FileOutputStream(image);
bitmap.compress(Bitmap.CompressFormat.PNG, 95, fos);
Objects.requireNonNull(fos).close();
} catch (IOException e) {
e.printStackTrace();
//isSuccess = false;
return false;
}
//isSuccess = true;
return true;
}
and the final step is to implement saveImageInAndroidApi29AndAbove method do it like this:
#NonNull
public Uri saveImageInAndroidApi29AndAbove(#NonNull final Bitmap bitmap) throws IOException {
final ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, "IMG_" + System.currentTimeMillis());
values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
if (SDK_INT >= Build.VERSION_CODES.Q) {
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
}
final ContentResolver resolver = requireContext().getContentResolver();
Uri uri = null;
try {
final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
uri = resolver.insert(contentUri, values);
if (uri == null) {
//isSuccess = false;
throw new IOException("Failed to create new MediaStore record.");
}
try (final OutputStream stream = resolver.openOutputStream(uri)) {
if (stream == null) {
//isSuccess = false;
throw new IOException("Failed to open output stream.");
}
if (!bitmap.compress(Bitmap.CompressFormat.PNG, 95, stream)) {
//isSuccess = false;
throw new IOException("Failed to save bitmap.");
}
}
//isSuccess = true;
return uri;
} catch (IOException e) {
if (uri != null) {
resolver.delete(uri, null, null);
}
throw e;
}
}
so you have to check which API Level that device is running and with with little code handle what you want:
if (SDK_INT >= Build.VERSION_CODES.Q) {
try {
saveImageInAndroidApi29AndAbove(bitmap);
} catch (Exception e) {
//show error to user that operatoin failed
}
} else {
saveImageInAndroidApi28AndBelow(bitmap);
}
Don't FORGET TO call .launch ON YOUR ActivityResultLauncher
have a good time with the codes :)
Simple, make a Launcher attach the camera and gallery and you have everything in one place, here I send you 2 links to make a launcher
https://code.tutsplus.com/es/tutorials/build-a-custom-launcher-on-android--cms-21358
https://steemit.com/utopian-io/#ideba/how-to-build-a-custom-android-launcher-and-home-screen-application-part-1
Whenever you take a photo from the device, you the image uri in the onActivityResult. Now to save it to another location, you can use the code given below
void savefile(Uri sourceuri){
String sourceFilename= sourceuri.getPath();
String destinationFilename = android.os.Environment.getExternalStorageDirectory().getPath()+File.separatorChar+"abc.mp3";
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream(sourceFilename));
bos = new BufferedOutputStream(new FileOutputStream(destinationFilename, false));
byte[] buf = new byte[1024];
bis.read(buf);
do {
bos.write(buf);
} while(bis.read(buf) != -1);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bis != null) bis.close();
if (bos != null) bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
And in the onActivityResult, you can call it like this
saveFile(data.getData());
I am trying to get the file from picking image from photos and get intent data, save the file in internal memory and use the file to load on Image views.
But I am getting the error as follows:
java.io.FileNotFoundException: /storage/emulated/0/Android/data/com.dailyfaithapp.dailyfaith/Files/MI_10052020_1711.png: open failed: ENOENT (No such file or directory)
I checked on the lower version of api i.e. on 24 it worked once or twice but again it failed.
And on api 29 its not working at all. For that I followed this url :
https://medium.com/#sriramaripirala/android-10-open-failed-eacces-permission-denied-da8b630a89df
I checked the code in java and tried the same but still its giving the error.
I am also checking for runtime permissions and have specified permissions in manifest file.
Following is my code:
<uses-permission android:name = "android.permission.INTERNET" />
<uses-permission android:name = "android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name = "android.permission.WAKE_LOCK" />
<uses-permission android:name = "android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name = "android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name = "android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name = "com.google.android.apps.photos.permission.GOOGLE_PHOTOS" />
Checking runtime permissions :
private boolean checkPermission() {
return ContextCompat.checkSelfPermission(this, WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(this, READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
;
}
private void requestPermissionAndContinue() {
if (ContextCompat.checkSelfPermission(this, WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
&& ContextCompat.checkSelfPermission(this, READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, WRITE_EXTERNAL_STORAGE)
&& ActivityCompat.shouldShowRequestPermissionRationale(this, READ_EXTERNAL_STORAGE)) {
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
alertBuilder.setCancelable(true);
alertBuilder.setTitle("Allow Daily Faith to access photos," +
"media, and files on your device?");
alertBuilder.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(ThemesActivity.this,
new String[]{WRITE_EXTERNAL_STORAGE
, READ_EXTERNAL_STORAGE}, 200);
}
});
AlertDialog alert = alertBuilder.create();
alert.show();
Log.e("", "permission denied, show dialog");
} else {
ActivityCompat.requestPermissions(ThemesActivity.this,
new String[]{WRITE_EXTERNAL_STORAGE,
READ_EXTERNAL_STORAGE}, 200);
}
} else {
selectImageFromGallery();
}
}
Checking if permission is given:
if (!checkPermission()) {
selectImageFromGallery();
} else {
if (checkPermission()) {
requestPermissionAndContinue();
} else {
selectImageFromGallery();
}
}
Opening intent :
public void selectImageFromGallery()
{
Intent pickPhoto = new Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(pickPhoto , 2);
}
Get intent data on result :
#RequiresApi(api = Build.VERSION_CODES.KITKAT) #Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// Check which request we're responding to
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 2) {
// Make sure the request was successful
if (resultCode == RESULT_OK) {
// The user picked a image.
// The Intent's data Uri identifies which item was selected.
if (data != null) {
customTheme = true;
// This is the key line item, URI specifies the name of the data
mImageUri = data.getData();
// Saves image URI as string to Default Shared Preferences
SharedPreferencesData sharedPreferencesData =
new SharedPreferencesData(this);
sharedPreferencesData.setStr("customThemeSet","true");
try {
Bitmap bitmap =
MediaStore.Images.Media.getBitmap(getContentResolver(), mImageUri);
/* Utils.storeImage(bitmap,
ThemesActivity.this);
File file = Utils.getOutputMediaFile(ThemesActivity.this);
*/
try {
final ParcelFileDescriptor parcelFileDescriptor = getContentResolver().openFileDescriptor(
mImageUri, "r");
final FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
parcelFileDescriptor.close();
Utils.storeImage(bitmap,
ThemesActivity.this);
File file = Utils.getOutputMediaFile(ThemesActivity.this);
int color = Utils.getDominantColor(bitmap);
Log.d("Bitmap", bitmap.toString());
Boolean isDark = Utils.isColorDark(color);
if(customTheme) {
for (Themes themes : themesArrayList) {
themes.setCustomTheme(file.getPath());
themes.setDark(isDark);
}
if(isDark)
sharedPreferencesData.setStr("ThemeColor","dark");
else
sharedPreferencesData.setStr("ThemeColor","light");
themesAdapter = new ThemesAdapter(themesArrayList, this,customTheme);
recyclerView.setAdapter(themesAdapter);
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
Log.e("Failed", "Failed to Parse Image Uri", e);
try {
throw new Exception("failed to parse image uri");
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
}
Saving and getting file
public static File getOutputMediaFile(Context context){
// To be safe, you should check that the SDCard is mounted
// using Environment.getExternalStorageState() before doing this.
File mediaStorageDir = new File(Environment.getExternalStorageDirectory()
+ "/Android/data/"
+ context.getApplicationContext().getPackageName()
+ "/Files");
// This location works best if you want the created images to be shared
// between applications and persist after your app has been uninstalled.
// Create the storage directory if it does not exist
if (! mediaStorageDir.exists()){
mediaStorageDir.mkdirs();
}
else {
return null;
}
// Create a media file name
String timeStamp = new SimpleDateFormat("ddMMyyyy_HHmm").format(new Date());
File mediaFile;
String mImageName="MI_"+ timeStamp +".png";
mediaFile = new File(mediaStorageDir.getPath() + File.separator + mImageName);
return mediaFile;
}
public static void storeImage(Bitmap image,Context context) {
File pictureFile = getOutputMediaFile(context);
if (pictureFile == null) {
Log.d(TAG,
"Error creating media file, check storage permissions: ");// e.getMessage());
return;
}
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
image.compress(Bitmap.CompressFormat.PNG, 90, fos);
fos.close();
} catch (FileNotFoundException e) {
Log.d(TAG, "File not found: " + e.getMessage());
} catch (IOException e) {
Log.d(TAG, "Error accessing file: " + e.getMessage());
}
}
Before I was getting EACCESS error when I only used above two funtions to save the file.
Later I tried Parcel file descriptor but not working
Why am I getting this error? Is it only on api level 29 or below too?
What can be the solution for this to run on all devices?
Here is what worked for me Using the downloads folder which all phones old and new have public access to..
try{
File csvfile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/brcOrdersaved.csv");
final String filename = csvfile.toString();
if (!csvfile.exists()) {
// displayMsg(context, "No Saved Order: ");
return (false);
}
FileReader fr = new FileReader(filename);
BufferedReader reader = new BufferedReader(fr);
String csvLine = "";
final char Separator = ',';
final char Delimiter = '"';
final char LF = '\n';
final char CR = '\r';
boolean quote_open = false;
if (reader.equals(null)) {
displayMsg(context, "NULL");
return (false);//rww 11/13/2021
}
int i = 0;
while (!myendofcsvfile) {
csvLine = reader.readLine();
if (csvLine == null) {
myendofcsvfile = true;
}
// do stuff here
}
fr.close();
fileexists = true;
} catch (Exception e) {
String msg = "Can not Load Saved Order ";
fileexists = false;
return (fileexists);
}
I add this permissions, and it works.
<uses-permission
enter code hereandroid:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
tools:ignore="ProtectedPermissions" />
This question already has answers here:
FileNotFoundException: /storage/emulated/0/Android
(3 answers)
java.io.FileNotFoundException: /storage/emulated/0/New file.txt: open failed: EACCES (Permission denied)
(5 answers)
Closed 2 years ago.
I need to create a folder and unzip a document in the external storage. On certain devices, like an Honor 10, Samsung J7 and Xiaomi Mi A2 it works flawlessly, but on other devices, like Huawei Mate 20 and Samsung S8 neither the creation of the folder nor the unzipping work.
These are the requested permissions in the AndroidManifest.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
They are outside the <application> tag
In the MainActivity I request the permissions
private boolean checkPermissions() {
if (ActivityCompat.checkSelfPermission(ActivityCompat.checkSelfPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
PackageManager.PERMISSION_GRANTED) {
return true;
}
requestPermissions();
return false;
}
private void requestPermissions() {
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSION_ID
);
}
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == LOCATION_PERMISSION_ID) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
setFiles();
}
}
}
After Permission has been granted I execute setFiles(); which creates the folder, a .nomedia file and unzips videos.zip into the created folder
public void setFiles(){
createFolder();
unzip();
}
private void createFolder() {
//Create Folder
File folder = new File(Environment.getExternalStorageDirectory().toString()+"/.videoFiles");
folder.mkdirs();
//Save the path as a string value
extStorageDirectory = folder.toString();
createNomedia();
}
private void createNomedia() {
String filepath = extStorageDirectory;
File file = new File(filepath+ "/.nomedia");
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
private boolean unzip() {
String zipFile = Environment.getExternalStorageDirectory().toString() + "/videos.zip";
try{
FileUnzipper.unzip(zipFile, extStorageDirectory);
unzipComplete = true;
} catch (IOException ex){
Log.e("unzipping", "unzip Exception" + ex);
unzipComplete = false;
}
}
This is the FileUnzipper class:
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import static android.content.ContentValues.TAG;
public class FileUnzipper{
final static int BUFFER_SIZE = 2048;
public static void unzip(String zipFile, String location) throws IOException {
int size;
byte[] buffer = new byte[BUFFER_SIZE];
try {
if ( !location.endsWith(File.separator) ) {
location += File.separator;
}
File f = new File(location);
if(!f.isDirectory()) {
f.mkdirs();
}
ZipInputStream zin = new ZipInputStream(new BufferedInputStream(new FileInputStream(zipFile), BUFFER_SIZE));
try {
ZipEntry ze = null;
while ((ze = zin.getNextEntry()) != null) {
String path = location + ze.getName();
File unzipFile = new File(path);
if (ze.isDirectory()) {
if(!unzipFile.isDirectory()) {
unzipFile.mkdirs();
}
} else {
// check for and create parent directories if they don't exist
File parentDir = unzipFile.getParentFile();
if ( null != parentDir ) {
if ( !parentDir.isDirectory() ) {
parentDir.mkdirs();
}
}
// unzip the file
FileOutputStream out = new FileOutputStream(unzipFile, false);
BufferedOutputStream fout = new BufferedOutputStream(out, BUFFER_SIZE);
try {
while ( (size = zin.read(buffer, 0, BUFFER_SIZE)) != -1 ) {
fout.write(buffer, 0, size);
}
zin.closeEntry();
}
finally {
fout.flush();
fout.close();
}
}
}
}
finally {
zin.close();
}
}
catch (Exception e) {
Log.e(TAG, "Unzip exception", e);
}
}
}
Now on some devices this all works, the folder and the .nomedia File are created and videos.zip is unzipped, on other devices I get this:
E/ContentValues: Unzip exception
java.io.FileNotFoundException: /storage/emulated/0/videos.zip: open failed: EACCESS (Permission denied)
at libcore.io.IOBridge.open(IoBridge.java:496)
at java.io.FileInputStream.<init>(FileInputStream.java:159)
at java.io.FileInputStream.<init>(FileInputStream.java:115)
at com.example.test.FileUnzipper.unzip(FileUnzipper.java:32)
FileUnzipper.java:32 is ZipInputStream zin = new ZipInputStream(new BufferedInputStream(new FileInputStream(zipFile), BUFFER_SIZE));
I can't work out why it doesn't work on some devices and I don't know how to fix it...
Try to opt-out of scoped storage with requestLegacyExternalStorage:
<manifest ... >
<application android:requestLegacyExternalStorage="true" ... >
...
</application>
</manifest>
And see data and file storage overview.
Try to add this strings to your Application.onCreate() method or if you don't have it put it to your first activiy's onCreate() method:
StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());
As a preface, I am familiar with coding (2 years in highschool) but am a complete novice when it comes to Android Studio (and Java, in general). I've been searching for solutions for my problem, but due to my inexperience I have no idea how to implement the solutions to my project.
In short, I need to download a pdf from some external URL, store it into external storage, and display it using some pdf-viewer application. I've been getting error:
...
android.os.FileUriExposedException: file:///storage/emulated/0/pdf/Read.pdf exposed beyond app through Intent.getData()
...
I've been using this source on using an intent to open a pdf-viewer and this source on "File Provider" as references.
Below is what I have so far:
Fire (with a clickable TextView that should download and display the pdf)
public class Fire extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fire);
final TextView testButton = findViewById(R.id.testPDF);
// File file = getFilesDir();
testButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(Fire.this, pdfView.class);
startActivity(intent);
}
});
pdfView activity
public class pdfView extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pdf_view);
String extStorageDirectory = Environment.getExternalStorageDirectory().toString();
File folder = new File(extStorageDirectory, "pdf");
folder.mkdir();
File file = new File(folder, "Read.pdf");
try {
file.createNewFile();
} catch (IOException e1) {
e1.printStackTrace();
}
Downloader.DownloadFile(__PDF_URL___, file);
showPdf();
}
public void showPdf()
{
File file = new File(Environment.getExternalStorageDirectory()+"/pdf/Read.pdf");
PackageManager packageManager = getPackageManager();
Intent testIntent = new Intent(Intent.ACTION_VIEW);
testIntent.setType("application/pdf");
List list = packageManager.queryIntentActivities(testIntent, PackageManager.MATCH_DEFAULT_ONLY);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
Uri uri = Uri.fromFile(file);
intent.setDataAndType(uri, "application/pdf");
startActivity(intent);
}
Downloader class
public class Downloader {
public static void DownloadFile(String fileURL, File directory) {
try {
FileOutputStream f = new FileOutputStream(directory);
URL u = new URL(fileURL);
HttpURLConnection c = (HttpURLConnection) u.openConnection();
c.setRequestMethod("GET");
c.setDoOutput(true);
c.connect();
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) {
e.printStackTrace();
}
}
From my research, it seems that my problem stems from my usage of URI instead of a "File provider." Also, it seems the solution that implements a "File Provider" uses Context in some form or another, and I'm confused on what the purpose of context is and how to implement it.
If you do not have answers that is fine. Any information on how to figure this out or even understand the concept is good enough for me.
If your targetSdkVersion >= 24, then we have to use FileProvider class to give access to the particular file or folder to make them accessible for other apps.
1) First Add a FileProvider tag in AndroidManifest.xml under tag as below:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
<application
...
<provider
android:name=".GenericFileProvider"
android:authorities="${applicationId}.my.package.name.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"/>
</provider>
</application>
</manifest>
2) Then create a provider_paths.xml file in res/xml folder. Folder may be needed to created if it doesn't exist.
<paths>
<external-path name="external_files" path="."/>
</paths>
3) Now create PdfDownload.java class file and paste below code:
public class PdfDownload extends Activity {
TextView tv_loading;
Context context;
int downloadedSize = 0, totalsize;
float per = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
isStoragePermissionGranted();
super.onCreate(savedInstanceState);
tv_loading = new TextView(this);
tv_loading.setGravity(Gravity.CENTER);
tv_loading.setTypeface(null, Typeface.BOLD);
setContentView(tv_loading);
downloadAndOpenPDF();
}
public static String getLastBitFromUrl(final String url) {
return url.replaceFirst(".*/([^/?]+).*", "$1");
}
void downloadAndOpenPDF() {
final String download_file_url = getIntent().getStringExtra("url");
new Thread(new Runnable() {
public void run() {
Uri path = Uri.fromFile(downloadFile(download_file_url));
try {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri uri = FileProvider.getUriForFile(PdfDownload.this, BuildConfig.APPLICATION_ID, downloadFile(download_file_url));
intent.setDataAndType(uri, "application/pdf");
startActivity(intent);
finish();
} catch (ActivityNotFoundException e) {
tv_loading
.setError("PDF Reader application is not installed in your device");
}
}
}).start();
}
File downloadFile(String dwnload_file_path) {
File file = null;
try {
URL url = new URL(dwnload_file_path);
HttpURLConnection urlConnection = (HttpURLConnection) url
.openConnection();
urlConnection.connect();
String test = getLastBitFromUrl(dwnload_file_path);
String dest_file_path = test.replace("%20", "_");
// set the path where we want to save the file
File SDCardRoot = Environment.getExternalStorageDirectory();
// // create a new file, to save the downloaded file
file = new File(SDCardRoot, dest_file_path);
if (file.exists()) {
return file;
}
FileOutputStream fileOutput = new FileOutputStream(file);
// Stream used for reading the data from the internet
InputStream inputStream = urlConnection.getInputStream();
// this is the total size of the file which we are
// downloading
totalsize = urlConnection.getContentLength();
setText("Starting PDF download...");
// create a buffer...
byte[] buffer = new byte[1024 * 1024];
int bufferLength = 0;
while ((bufferLength = inputStream.read(buffer)) > 0) {
fileOutput.write(buffer, 0, bufferLength);
downloadedSize += bufferLength;
per = ((float) downloadedSize / totalsize) * 100;
if ((totalsize / 1024) <= 1024) {
setText("Total PDF File size : " + (totalsize / 1024)
+ " KB\n\nDownloading PDF " + (int) per + "% complete");
} else {
setText("Total PDF File size : " + (totalsize / 1024) / 1024
+ " MB\n\nDownloading PDF " + (int) per + "% complete");
}
// setText("configuring your book pleease wait a moment");
}
// close the output stream when complete //
fileOutput.close();
// setText("Download Complete. Open PDF Application installed in the device.");
setText("configuaration is completed now your book is ready to read");
} catch (final MalformedURLException e) {
setTextError("Some error occured. Press back and try again.",
Color.RED);
} catch (final IOException e) {
setTextError("Some error occured. Press back and try again.",
Color.RED);
} catch (final Exception e) {
setTextError(
"Failed to download image. Please check your internet connection.",
Color.RED);
}
return file;
}
void setTextError(final String message, final int color) {
runOnUiThread(new Runnable() {
public void run() {
tv_loading.setTextColor(color);
tv_loading.setText(message);
}
});
}
void setText(final String txt) {
runOnUiThread(new Runnable() {
public void run() {
tv_loading.setText(txt);
}
});
}
private static final String TAG = "MyActivity";
public boolean isStoragePermissionGranted() {
if (Build.VERSION.SDK_INT >= 23) {
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED) {
Log.v(TAG, "Permission is granted");
return true;
} else {
Log.v(TAG, "Permission is revoked");
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
return false;
}
} else { //permission is automatically granted on sdk<23 upon installation
Log.v(TAG, "Permission is granted");
return true;
}
}
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.v(TAG, "Permission: " + permissions[0] + "was " + grantResults[0]);
}
}
}
So what I'm trying to achieve is creating a service that downloads a large (~200 MB) file to the app's internal directory. It should resume (not restart) when it got canceled and automatically resume after a system reboot. It should sometimes (as written in a propeties file) only download using WiFi connection.
To do so I created an IntentService (is that a suitable solution?) that gets started by an Frgament's UI or by a BroadcastReceiver (after boot-up):
#Override
public void onHandleIntent(final Intent intent) {
c.registerReceiver(receiver, new IntentFilter(
ConnectivityManager.CONNECTIVITY_ACTION));
c.registerReceiver(receiver, new IntentFilter(
WifiManager.NETWORK_STATE_CHANGED_ACTION));
receiver.onReceive(null, null); // To start the download the first time
}
The last line calls a BroadcastReceiver that handles the download process. This is the way I tried to make sure that there is only one ongoing download at a time:
BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
boolean allowed = isConnectionAllowed();
if (allowed && !thread.isAlive()) {
thread.start();
} else if (allowed && thread.isAlive()) {
thread.notify(); // Can this cause any problems in this case?
} else if (!allowed && thread.isAlive()) {
try {
thread.wait();
} catch (InterruptedException e) {
if (StartActivity.DEV_MODE)
Log.i(StartActivity.LOG_TAG, Log.getStackTraceString(e));
}
}
}
};
And the download itselv runs in a seperate Thread.
private Thread thread = new Thread() {
#SuppressWarnings("resource")
#Override
public void run() {
HttpURLConnection connection = null;
BufferedInputStream input = null;
RandomAccessFile output = null;
try {
long already_downloaded = 0;
URL url = new URL(getSource());
File outputFileCache = new File(getDestination());
connection = (HttpURLConnection) url.openConnection();
if (outputFileCache.exists()) {
connection.setAllowUserInteraction(true);
connection.setRequestProperty("Range", "bytes="
+ outputFileCache.length() + "-");
}
connection.setConnectTimeout(14000);
connection.setReadTimeout(20000);
connection.connect();
if (connection.getResponseCode() / 100 != 2)
throw new Exception("Invalid response code!");
else {
String connectionField = connection
.getHeaderField("content-range");
if (connectionField != null) {
String[] connectionRanges = connectionField.substring(
"bytes=".length()).split("-");
already_downloaded = Long.valueOf(connectionRanges[0]);
}
if (connectionField == null && outputFileCache.exists())
outputFileCache.delete();
long fileLength = connection.getContentLength()
+ already_downloaded;
input = new BufferedInputStream(connection.getInputStream());
output = new RandomAccessFile(outputFileCache, "rw");
output.seek(already_downloaded);
byte data[] = new byte[1024];
int count = 0;
int progress = 0;
int progress_last_value = 0;
while ((count = input.read(data, 0, 1024)) != -1
&& progress != 100) {
already_downloaded += count;
output.write(data, 0, count);
progress = (int) ((already_downloaded * 100) / fileLength);
if (progress != progress_last_value) {
// Update notification
}
}
}
} catch (MalformedURLException e) {
if (StartActivity.DEV_MODE)
Log.i(StartActivity.LOG_TAG, Log.getStackTraceString(e));
} catch (IOException e) {
if (StartActivity.DEV_MODE)
Log.i(StartActivity.LOG_TAG, Log.getStackTraceString(e));
} catch (Exception e) {
if (StartActivity.DEV_MODE)
Log.i(StartActivity.LOG_TAG, Log.getStackTraceString(e));
} finally {
try {
if (output != null)
output.close();
if (input != null)
input.close();
if (connection != null)
connection.disconnect();
} catch (IOException e) {
if (StartActivity.DEV_MODE)
Log.i(StartActivity.LOG_TAG, Log.getStackTraceString(e));
}
}
// notify the Fragment using a `LocalBroadcast`.
}
};
And to make sure that the WiFi connection ist kept alive, I use the WifiLock class.
Note: I shortened the code in a few cases but I already managed to write that code.
So I'm not sure whether this is a good solution for my problem:
Should I use an IntentService or a normal Service for that?
Is that solution using a Thread a good one and will it work?
Won't the HttpURLConnection expire?
Can I access the Thread that way? (referres to wait() and notify())
I know this is a lot of code but it was the closest I could get. I hope someone knows a solution for that becuase I couldn't find anything usful on that topic ("only download while WiFi in ON") using Google and StackOverflow.
Thanks in advance,
Felix
(Comment if you need the whole Class)