How to copy images from app's private folder to Android's public folder(Documents, Pictures) programatically in Android 10/Q?

Hello programmers!

Android keeps adding newer functionalities and also keeps changing some of its implementations. Some functions or classes get deprecated while others get updated. All of this is done to patch existing bugs, add security, and provide a better user experience.

While these changes are good for the users, these are irritating for us programmers at times. 

In the new update of Android 10/Q, Google has told not to keep any files(e.g. images, pdf, audio,etc.) into the public folders(e.g. Documents, Pictures, etc. at the root), instead, keep them in the apps private storage space at
Android/<your_app's_package_name>/files/

This change is made by Google to restrain the programmers from flooding the user's phone with unnecessary files because the files stored in android's public directories(e.g. Documents, Pictures, etc.) does not get deleted when the app is uninstalled. They sit there eating the phone's storage unnecessarily. 

While, on the other hand, the files stored in the app's private folder(Android/<your_app's_package_name>/files/), get deleted automatically when the app is uninstalled by the user.

But here, I am going to show you, how you can use public folders in Android 10/Q.

So, if you want to update your app, which was storing images in the app's private folder previously to a newer version where it uses Android's public folder to store those files.

OR

If your app was already using Android's public folder to store those files but your code broke in Android 10, then you have come to the right place...


Below is the example code in which I have copied pre-existing images from app's private folder to Android's public folder(i.e. Pictures) in Android 10/Q:

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.abhi.imagecopier">

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

MainActivity.java
package com.abhi.imagecopier;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.widget.Toast;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.util.Objects;

public class MainActivity extends AppCompatActivity {

private static final int STORAGE_PERMISSION_CODE = 101;
public static final String TAG = "MainActivity";
String internal_folder_path;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

checkPermission(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
STORAGE_PERMISSION_CODE);
}

// Function to check and request permission
public void checkPermission(String permission, int requestCode) {

// Checking if permission is not granted
if (ContextCompat.checkSelfPermission(
MainActivity.this,
permission)
== PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(
MainActivity.this,
new String[]{permission},
requestCode);
} else {
Toast.makeText(MainActivity.this,
"Permission already granted",
Toast.LENGTH_SHORT).show();
permissionSuccessfullyGranted();
}
}


// This function is called when user accept or decline the permission.
// Request Code is used to check which permission called this function.
// This request code is provided when user is prompt for permission.

@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode,
permissions,
grantResults);

if (requestCode == STORAGE_PERMISSION_CODE) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(MainActivity.this,
"Storage Permission Granted",
Toast.LENGTH_SHORT)
.show();
permissionSuccessfullyGranted();
} else {
Toast.makeText(MainActivity.this,
"Storage Permission Denied",
Toast.LENGTH_SHORT)
.show();
}
}
}


private void permissionSuccessfullyGranted() {

// Creating folder in internal storage
internal_folder_path = getExternalFilesDir(null) + "/" + "tempBooks";
Log.e("internal_dir_path", internal_folder_path);
File direct = new File(internal_folder_path);
if (!direct.exists()) {
if (direct.mkdir()) ; //directory is created;
}

//todo now mannually copy photos into this folder
// 'Android/data/com.abhi.imagecopier/files/tempBooks/'
// for testing purpose

copyFiles(internal_folder_path);

}

private void copyFiles(String from) {
new TestAsync().execute(from);
}

class TestAsync extends AsyncTask<String, Void, String> {

ProgressDialog dialog;
protected void onPreExecute() {
dialog = new ProgressDialog(MainActivity.this);
dialog.setCanceledOnTouchOutside(false);
dialog.setCancelable(false);
this.dialog.setMessage("Please wait, the images directory is creating!");
this.dialog.show();
}

protected String doInBackground(String...arg0) {
try{
copyFileOrDirectory(arg0[0]);

} catch (Exception e) {
e.printStackTrace();
}

return "";
}


protected void onPostExecute(String result) {
if (dialog.isShowing()) {
dialog.dismiss();
}
Toast.makeText(MainActivity.this, result, Toast.LENGTH_SHORT).show();

}
}

public void copyFileOrDirectory(String srcDir) {

try {
File src = new File(srcDir);

if (src.isDirectory()) {

String files[] = src.list();
int filesLength = files.length;
for (int i = 0; i < filesLength; i++) {
String src1 = (new File(src, files[i]).getPath());
copyFileOrDirectory(src1);

}
} else {
copyYFile(src);
}
} catch (Exception e) {
e.printStackTrace();
}
}

public void copyYFile(File sourceFile) throws IOException {
Log.e("file path", sourceFile.getAbsolutePath());
Log.e("file name", sourceFile.getName());
Bitmap picBitmap = BitmapFactory.decodeFile(sourceFile.getAbsolutePath());
saveImage(picBitmap, sourceFile.getName());

}


private void saveImage(Bitmap bitmap, @NonNull String name)throws IOException{

OutputStream imageOutStream;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {

ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, name);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

imageOutStream = getContentResolver().openOutputStream(uri);
} else {
String imagePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString();
File image = new File(imagePath, name);
imageOutStream = new FileOutputStream(image);
}

try {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, imageOutStream);
} finally {
imageOutStream.close();
}

}


}

If you have any doubts or questions about the code, do not hesitate to comment below.

Thanks for reading! Have a good day :)

Comments