Target device Android 7 (API 24) to Android 16 (API 36)
AndroidManifest.xml permission
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<!-- targeting api 13++ use media store -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
To write contact (vcard) to folder com.dedetok.wredacontactbackup in folder Download
final static String subFolder = "com.dedetok.wredacontactbackup"; // folder ref
private static File backupFolder, backupFile; //
private static boolean writeSingleVcf(Context context, Collection<VCard> vCards) {
try {
String date = new SimpleDateFormat("yyyyMMddHHmmSS", Locale.US).format(new Date());
String fileName = date + ".vcf";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// ✅ API 29-36 (Android 10+): Use MediaStore (Scoped Storage)
return MyWorker.writeMediaStore(context, fileName, vCards);
} else {
// ✅ API 24-28 (Android 7-9): Use traditional File API
// ✅ API 29-36 (Android 10+): Use MediaStore (Scoped Storage)
return MyWorker.writeLegacy(context, fileName, vCards);
}
} catch (Exception e) {
//Log.e("dedetok",e.getMessage()); // debug
return false;
}
}
/*
* write backup MediaStore
*/
@RequiresApi(api = Build.VERSION_CODES.Q)
private static boolean writeMediaStore (Context context, String fileName, Collection<VCard> vCards) throws IOException{
ContentValues values = new ContentValues();
//Log.e("dedetok", "writeMediaStore"); // debug
values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
values.put(MediaStore.MediaColumns.MIME_TYPE, "text/x-vcard");
// Creates 'Download/YourSubFolder/' automatically
values.put(MediaStore.MediaColumns.RELATIVE_PATH,
Environment.DIRECTORY_DOWNLOADS + File.separator + subFolder);
Uri uri = context.getContentResolver().insert(
MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
if (uri == null) return false;
OutputStream os = context.getContentResolver().openOutputStream(uri);
Ezvcard.write(vCards).version(VCardVersion.V3_0).go(os);
return true;
}
/*
* write backup in legacy mode
*/
private static boolean writeLegacy(Context context, String fileName, Collection<VCard> vCards) throws IOException{
// Setup Directory
File downloads = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
backupFolder = new File(downloads, MySharePreferences.PREF_NAME);
// Create folder if missing
if (!backupFolder.exists()) {
boolean success = backupFolder.mkdirs();
if (!success) {
//Log.e("dedetok", "Failed to create folder. Check permissions!"); // debug
return false;
}
}
backupFile = new File(backupFolder, fileName);
try (FileOutputStream fos = new FileOutputStream(backupFile)) {
Ezvcard.write(vCards).version(VCardVersion.V3_0).go(fos);
}
//Log.d("dedetok", "File saved at: " + backupFolder.getName()); // debug
// Tell Android to scan the file so it shows up in Windows/File Manager
MediaScannerConnection.scanFile(
context,
new String[]{ backupFile.getAbsolutePath() },
null,
new MediaScannerConnection.OnScanCompletedListener() {
@Override
public void onScanCompleted(String path, Uri uri) {
//Log.e("dedetok", "Scanned: " + path);
}
}
);
//Log.e("dedetok", "File created successfully "+backupFile.getName()); // debug
return true;
}
To delete file created by application from folder com.dedetok.wredacontactbackup in folder Download
/*
* delete File helper for android 7 yo 16
*/
static private boolean deleteFile(Context context, File fileToDelete) {
boolean returnValue = false;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
// Android 7–9
if (fileToDelete.exists()) {
returnValue = fileToDelete.delete();
if (returnValue) {
// IMPORTANT: Tell the system the file is gone so Google Files updates
MediaScannerConnection.scanFile(context, new String[]{fileToDelete.getAbsolutePath()}, null, null);
}
}
} else {
// Android 10 to 16
ContentResolver resolver = context.getContentResolver();
Uri collection = MediaStore.Downloads.EXTERNAL_CONTENT_URI;
// IMPORTANT: RELATIVE_PATH must end with a slash /
String relativePath = Environment.DIRECTORY_DOWNLOADS + "/" + subFolder + "/";
String selection = MediaStore.MediaColumns.DISPLAY_NAME + "=? AND " +
MediaStore.MediaColumns.RELATIVE_PATH + "=?";
String[] selectionArgs = new String[]{
fileToDelete.getName(),
relativePath
};
int rows = 0;
try {
// Directly delete from MediaStore - this usually deletes the physical file too
rows = resolver.delete(collection, selection, selectionArgs);
} catch (Exception e) {
//Log.e("dedetok", "MediaStore delete failed", e);
}
if (rows > 0) {
returnValue = true;
} else {
// FALLBACK: Physical delete if MediaStore didn't find it
if (fileToDelete.exists()) {
returnValue = fileToDelete.delete();
if (returnValue) {
// Sync the index
MediaScannerConnection.scanFile(context, new String[]{fileToDelete.getAbsolutePath()}, null, null);
}
}
}
}
return returnValue;
}