This related with my previous question after I changed readFile and make it read from URI for devices running in android 11 and above I got ANR error while I tried to read file
gif showing the error
this my full code
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_CODE_DOC = 1;
private static final String TAG = "MainActivity";
private ActivityMainBinding activityMainBinding = null;
private File file;
private Uri selectedFileURI;
BufferedReader bufferedReader;
InputStream inputStream;
FileReader fileReader;
#Override
protected void onDestroy() {
super.onDestroy();
activityMainBinding = null;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activityMainBinding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(activityMainBinding.getRoot());
}
#Override
protected void onStart() {
super.onStart();
activityMainBinding.textView.setMovementMethod(new ScrollingMovementMethod());
activityMainBinding.browseButton.setOnClickListener(view -> {
browseDocuments();
});
activityMainBinding.read.setOnClickListener(view -> {
if (TextUtils.isEmpty(activityMainBinding.editTextPath.getText())) {
activityMainBinding.editTextPath.setError("The file path cannot be empty");
} else {
readFile();
}
});
activityMainBinding.clear.setOnClickListener(view -> activityMainBinding.textView.setText(null));
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_DOC && resultCode == Activity.RESULT_OK) {
try {
if (data != null) {
selectedFileURI = data.getData();
file = new File(selectedFileURI.getPath());
activityMainBinding.editTextPath.setText(file.getAbsolutePath());
Log.d(TAG, "onActivityResult: " + file.getAbsolutePath());
} else {
Toast.makeText(this, "Allow permission for storage access!", Toast.LENGTH_SHORT).show();
}
String mimeType = getContentResolver().getType(selectedFileURI);
Log.i("Type of file", mimeType + "");
} catch (Exception exception) {
if (exception.getMessage() != null) {
Log.e("test Exception", exception.getMessage());
} else if (exception.getCause() != null) {
Log.e("test Exception", Objects.requireNonNull(exception.getCause()).toString());
}
}
}
}
public String getPath(Uri uri) {
String[] projection = {MediaStore.Images.Media.DATA};
Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
if (cursor == null) return null;
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
String s = cursor.getString(column_index);
cursor.close();
return s;
}
private void readFile() {
try {
StringBuilder sb = new StringBuilder();
String line;
if (SDK_INT >= Build.VERSION_CODES.R) {
inputStream = getContentResolver().openInputStream(selectedFileURI);
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
} else {
fileReader = new FileReader(file);
bufferedReader = new BufferedReader(fileReader);
}
while ((line = bufferedReader.readLine()) != null) {
sb.append(line).append("\n");
}
activityMainBinding.textView.setText(sb.toString());
if(inputStream != null) {
inputStream.close();
}else if(bufferedReader != null) {
bufferedReader.close();
}else if(fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
Log.e("IOException", e.getMessage());
Log.e("IOException2", e.getCause() + "");
Log.e("IOException3", "exception", e);
Toast.makeText(MainActivity.this, "Cannot read this file", Toast.LENGTH_LONG).show();
}
}
private boolean checkPermission() {
if (SDK_INT >= Build.VERSION_CODES.R) {
return Environment.isExternalStorageManager();
} else {
int result = ContextCompat.checkSelfPermission(this, READ_EXTERNAL_STORAGE);
int result1 = ContextCompat.checkSelfPermission(this, WRITE_EXTERNAL_STORAGE);
return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED;
}
}
private void requestPermission() {
if (SDK_INT >= Build.VERSION_CODES.R) {
try {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.addCategory("android.intent.category.DEFAULT");
intent.setData(Uri.parse(String.format("package:%s", getApplicationContext().getPackageName())));
startActivityForResult(intent, 1);
} catch (Exception e) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
startActivityForResult(intent, 1);
}
} else {
ActivityCompat.requestPermissions(this, new String[]{READ_EXTERNAL_STORAGE,
WRITE_EXTERNAL_STORAGE}, 1);
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case REQUEST_CODE_DOC:
if (grantResults.length > 0) {
boolean READ_EXTERNAL_STORAGE = grantResults[0] == PackageManager.PERMISSION_GRANTED;
boolean WRITE_EXTERNAL_STORAGE = grantResults[1] == PackageManager.PERMISSION_GRANTED;
if (READ_EXTERNAL_STORAGE && WRITE_EXTERNAL_STORAGE) {
readFile();
} else {
Toast.makeText(this, "Allow permission for storage access!", Toast.LENGTH_SHORT).show();
}
}
break;
}
}
private void browseDocuments() {
if (!checkPermission()) {
requestPermission();
} else {
String[] mimeTypes =
{"text/plain", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation",
"application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"textView/plain",
"application/pdf"};
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
setResult(Activity.RESULT_OK);
intent.setType("*/*");
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
startActivityForResult(Intent.createChooser(intent, "ChooseFile"), REQUEST_CODE_DOC);
}
}
}
That's because you reading files on the Main UI thread which blocks it and causes ANR until is finished, you need to do this work on a background thread, I suggest to take look at the answers on this question even it's old "since 10 years approximately" but it's a good place to start trying, you will find some methods to do the process in background threads like AsyncTask, Callbacks and Executors all this ways can help you to do the fix the issue, but I'll focus in my answer on the newest and recommended way is using "RX Java" I suggest to take look at this RxJava Tutorial you will learn more about it.
let's start to fix this
Add Rx Java dependencies to your project
in build.gradle(app)
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
// Because RxAndroid releases are few and far between, it is recommended you also
// explicitly depend on RxJava's latest version for bug fixes and new features.
// (see https://github.com/ReactiveX/RxJava/releases for latest 3.x.x version)
implementation 'io.reactivex.rxjava3:rxjava:3.0.3'
in read button onClick create a new Observable and call method readFile on it,
the subscribeOn it's defines the thread that the observable will work on it I choosed Schedulers.computation() because you will not be able to determine the size of the doc/text file or How long this process will take, but you can choose other threads like Schedulers.io(), in observeOn you added the thread that observer will work on it, in your case, it's the main thread, and finally call subscribe to connect observable with the observer, I also suggest to add progressBar on your layout to show it while reading the file and hide it when finished the process
activityMainBinding.read.setOnClickListener(view -> {
if (TextUtils.isEmpty(activityMainBinding.editTextPath.getText())) {
activityMainBinding.editTextPath.setError("The file path cannot be empty");
} else {
Observable.fromCallable(this::readFile)
.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Object>() {
#Override public void onSubscribe(Disposable d) {
activityMainBinding.progressBar.setVisibility(View.VISIBLE);
}
#Override public void onNext(Object o) {
if(o instanceof StringBuilder){
activityMainBinding.textView.setText((StringBuilder) o);
}
}
#Override public void onError(Throwable e) {
}
#Override public void onComplete() {
activityMainBinding.progressBar.setVisibility(View.INVISIBLE);
}
});
}
});
also, I would suggest you if the file is PDF use AndroidPdfViewer library it makes a lot easier for you and you will not need all these permissions to read PDF files, you can check these this article to learn more about it.
The major problem is caused by blocking the main thread.
You are reading from memory, which is usually taking some time. You are doing it on the main thread, which is used for all UI operations (layout, detecting inputs etc.). While you are occupying it, no rendering or input detection can happen. That is why you are seeing the ANR warning.
To solve your problem, you need to put your work to a background thread. There are several ways to do so.
This is a good starting point. Also that one, if you are running tasks frequently.
Now to give you a quick solution to your problem, you can create a class to do your work:
public class ReadFile extends Worker
{
private File file;
private BufferedReader bufferedReader;
private InputStream inputStream;
private FileReader fileReader;
public ReadFile(#NonNull Context context, #NonNull WorkerParameters workerParams)
{
super(context, workerParams);
}
#NonNull
#Override
public Result doWork()
{
try
{
StringBuilder sb = new StringBuilder();
String line;
if (SDK_INT >= Build.VERSION_CODES.R)
{
Uri uri = Uri.parse(getInputData().getString("URI"));
inputStream = getApplicationContext().getContentResolver().openInputStream(uri);
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
}
else
{
fileReader = new FileReader(file);
bufferedReader = new BufferedReader(fileReader);
}
while ((line = bufferedReader.readLine()) != null)
{
sb.append(line).append("\n");
}
if (inputStream != null)
{
inputStream.close();
}
else if (bufferedReader != null)
{
bufferedReader.close();
}
else if (fileReader != null)
{
fileReader.close();
}
Data myData = new Data.Builder()
.putString("text", sb.toString())
.build();
return Result.success(myData);
}
catch (IOException e)
{
Log.e("IOException", e.getMessage());
Log.e("IOException2", e.getCause() + "");
Log.e("IOException3", "exception", e);
return Result.failure();
}
}
}
If you are using an inner class of your activity, just make sure you are not using any fields in the UI thread, that you are using inside the worker as well. If you do so, you need to synchronize them. That's why I used input and output data in the example.
Then finally in your activity call:
Data myData = new Data.Builder()
.putString("URI", selectedFileURI.toString())
.build();
OneTimeWorkRequest readFile = new OneTimeWorkRequest.Builder(ReadFile.class).setInputData(myData).build();
WorkManager.getInstance(getContext()).enqueue(readFile);
WorkManager.getInstance(getContext()).getWorkInfoByIdLiveData(readFile.getId()).observe(this, info ->
{
String myResult = info.getOutputData().getString("text");
activityMainBinding.textView.setText(myResult);
});
this is because you are using the main thread to handle a lengthy operations
(ANR) error stands for Application Not Responding and this is happening because the main thread handle the application UI and other important staff , but you try to do a lengthy operation by get the image so this makes the main thread unable to manage the UI and the main app.
that's why this message appear to make you chose between waiting for the main thread to be ready for the UI or to close the app.
a good solution is to make this lengthy operation (get the image) handled by another thread , so you need to create a thread that will work with the operation and when the mission completed you need to switch back to the main thread so you can apply the changes on the UI.
Note that you can not touch or change the UI from any thread but the Main Thread because the main thread is the responsible for the application UI , that's why you need to switch back to the main thread after the mission complete.
private boolean checkPermissions() {
listPermissionsNeeded = new ArrayList<>();
int permissionRead = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
int permissionWrite = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (permissionRead != PackageManager.PERMISSION_GRANTED) {
listPermissionsNeeded.add(Manifest.permission.READ_EXTERNAL_STORAGE);
}
if (permissionWrite != PackageManager.PERMISSION_GRANTED) {
listPermissionsNeeded.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if (!listPermissionsNeeded.isEmpty()) {
ActivityCompat.requestPermissions(this, listPermissionsNeeded.toArray(new String[listPermissionsNeeded.size()]), REQUEST_ID_MULTIPLE_PERMISSIONS);
return false;
}
return true;
}
and
public static void parseBase64ToPDFAndNoOpen(String base64, String cardId) throws IOException {
FileOutputStream os;
File dwldsPath = new File(Environment.getExternalStorageDirectory() + "/" + File.separator + cardId + ".pdf");
if (!dwldsPath.exists()) {
dwldsPath.createNewFile();
byte[] pdfAsBytes = Base64.decode(base64, 0);
os = new FileOutputStream(dwldsPath, false);
os.write(pdfAsBytes);
os.flush();
os.close();
}
}
On android 9 all works corretly but on andorid 10 I have this : java.io.IOException: Permission denied All permissions are granded
Android 10 prohibits the use of external storage. Use instead scoped storage.
Scoped storage is the folders inside your app directory
After Android 4.4, the SD card needs permission to write files.
You can request to write to the SD card by StorageVolume. createAccessIntent ().
The stream is then accessed using ContentResolver().openoutputstream (file.geturi ()) to write data to the file.
How do I write data in the NDK?
public void startRequestPermissions(Context context) {
Intent intent = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
if (mStorageManager != null) {
StorageVolume volume = mStorageManager.getStorageVolume(new File(timePath));
if (volume != null) {
intent = volume.createAccessIntent(null);
}
}
}
if (intent == null) {
intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
}
((Activity) context).startActivityForResult(intent, FileBrowserConfig.OPEN_DOCUMENT_TREE_CODE);
}
public OutputStream getOutputStream(Context context, File destFile) {
OutputStream out = null;
try {
DocumentFile file = getDocumentFile(destFile, false, context);
out = context.getContentResolver().openOutputStream(file.getUri());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return out;
}
Can write data in sd card with NDK!
Instead of openOutputStream(), you can get a file descriptor with openAssetFileDescriptor().
I am trying to create a text document with user inputted information but nothing is being created... Here is the code I have used for the button click:
runnerTestResultBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (capitanPasswordValidate()& permissionGranted) {
if (capitanPasswordET.getText().toString().equals(capitanPassword)) {
createFile();
} else {
capitanPasswordError.setText("Wrong Password!");
capitanPasswordError.requestFocus();
}
}
}
});
Also, here is the code I am using to create the file:
private void createFile() {
String familiaName = familiaNamesSpinner.getSelectedItem().toString();
String FILE_NAME = familiaName + "_Runner_Test_Results.txt";
String cafeteroTestResultString = runnerTestResult1.getText().toString();
FileOutputStream fos = null;
File file = new File(FILE_NAME);
try {
capitanPasswordError.setText("");
fos = new FileOutputStream(file);
fos.write(cafeteroTestResultString.getBytes());
fos.close();
Toast.makeText(this, "worked", Toast.LENGTH_LONG).show();
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(this, "did not work", Toast.LENGTH_SHORT).show();
} finally {
if (fos != null) try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Just a side note, I am making it so the user selects a choice from a spinner View in order to track down which file belongs to which person. it is under familiaName and will be part of the name of the file created. any feedback would be amazing, thank you!!
You must check for permissions before reading or writing a file, Please add permission requests to manifests and request permissions for READ, WRITE operations at runtime,
Here i have a simple solution, - (Multiple permission checking)
String[] permissions = new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE}; // Here i used multiple permission check
Then call it in Oncreate
if (checkPermissions()) {
// permissions granted.
getCallDetails();
}
Finally, copy the below code
private boolean checkPermissions() {
int result;
List<String> listPermissionsNeeded = new ArrayList<>();
for (String p : permissions) {
result = ContextCompat.checkSelfPermission(getApplicationContext(), p);
if (result != PackageManager.PERMISSION_GRANTED) {
listPermissionsNeeded.add(p);
}
}
if (!listPermissionsNeeded.isEmpty()) {
ActivityCompat.requestPermissions(this, listPermissionsNeeded.toArray(new String[listPermissionsNeeded.size()]), MULTIPLE_PERMISSIONS);
return false;
}
return true;
}
#Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case MULTIPLE_PERMISSIONS: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permissions granted.
getCallDetails(); // Now you call here what ever you want :)
} else {
String perStr = "";
for (String per : permissions) {
perStr += "\n" + per;
}
// permissions list of don't granted permission
}
return;
}
}
}
i seem to be having problems creating folders in LOLLIPOP and up although code works just fine for previous versions
there is no error in the log-cast it simply does not crate the folder can someone help
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
private static Uri getOutputMediaFileUri(int type){
return Uri.fromFile(getOutputMediaFile(type));
}
private static File getOutputMediaFile(int type){
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MOVIES), "reelyChat/vids");
if(!mediaStorageDir.exists()){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
mediaStorageDir.mkdirs();
try {
mediaStorageDir.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}else{
if(!mediaStorageDir.mkdirs()){
Log.d("reelyChat", "failed to create directory");
return null;
}
}
}
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
File mediaFile;
if(type == MEDIA_TYPE_VIDEO){
vid_name = "RC_"+my_user_id+"_profile.mp4";
mediaFile = new File(mediaStorageDir.getPath() + File.separator + vid_name);
}else{
return null;
}
return mediaFile;
}
You need write permission at run time.
//Just call this function
getWirtePermissionAndCreateDir();
private void getWirtePermissionAndCreateDir() {
if (Build.VERSION.SDK_INT < 23) {
createDir();
} else {
final String[] PERMISSIONS_STORAGE = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
//Asking request Permissions
ActivityCompat.requestPermissions(mActivity, PERMISSIONS_STORAGE, 9);
}
}
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
boolean writeAccepted = false;
switch (requestCode) {
case 9:
writeAccepted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
break;
}
if (writeAccepted) {
createDir();
} else {
Toast.makeText(mActivity, "You don't assign permission.", Toast.LENGTH_LONG).show();
}
}
private void createDir(){
File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MOVIES), "reelyChat/vids");
mediaStorageDir.mkdirs();
}
It looks like you have took permissions but not at run time. Device running on Marshmallow or above needs to take permissions at run time.
For more information visit Developer Site