package com.amaze.filemanager.filesystem; import android.annotation.TargetApi; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.ParcelFileDescriptor; import android.preference.PreferenceManager; import android.provider.BaseColumns; import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.v4.provider.DocumentFile; import android.util.Log; import com.amaze.filemanager.R; import com.amaze.filemanager.database.CloudHandler; import com.amaze.filemanager.exceptions.RootNotPermittedException; import com.amaze.filemanager.ui.icons.MimeTypes; import com.amaze.filemanager.utils.CloudUtil; import com.amaze.filemanager.utils.OTGUtil; import com.amaze.filemanager.utils.RootUtils; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.List; import jcifs.smb.SmbException; import jcifs.smb.SmbFile; /** * Utility class for helping parsing file systems. * <p> * Created by Arpit on 04-06-2015. */ public abstract class FileUtil { /** * Determine the camera folder. There seems to be no Android API to work for real devices, so this is a best guess. * * @return the default camera folder. **/ //TODO the function? /** * Copy a file. The target file may even be on external SD card for Kitkat. * * @param source The source file * @param target The target file * @return true if the copying was successful. */ @SuppressWarnings("null") private static boolean copyFile(final File source, final File target, Context context) { FileInputStream inStream = null; OutputStream outStream = null; FileChannel inChannel = null; FileChannel outChannel = null; try { inStream = new FileInputStream(source); // First try the normal way if (isWritable(target)) { // standard way outStream = new FileOutputStream(target); inChannel = inStream.getChannel(); outChannel = ((FileOutputStream) outStream).getChannel(); inChannel.transferTo(0, inChannel.size(), outChannel); } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Storage Access Framework DocumentFile targetDocument = getDocumentFile(target, false, context); outStream = context.getContentResolver().openOutputStream(targetDocument.getUri()); } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { // Workaround for Kitkat ext SD card Uri uri = MediaStoreHack.getUriFromFile(target.getAbsolutePath(), context); outStream = context.getContentResolver().openOutputStream(uri); } else { return false; } if (outStream != null) { // Both for SAF and for Kitkat, write to output stream. byte[] buffer = new byte[16384]; // MAGIC_NUMBER int bytesRead; while ((bytesRead = inStream.read(buffer)) != -1) { outStream.write(buffer, 0, bytesRead); } } } } catch (Exception e) { Log.e("AmazeFileUtils", "Error when copying file from " + source.getAbsolutePath() + " to " + target.getAbsolutePath(), e); return false; } finally { try { inStream.close(); } catch (Exception e) { // ignore exception } try { outStream.close(); } catch (Exception e) { // ignore exception } try { inChannel.close(); } catch (Exception e) { // ignore exception } try { outChannel.close(); } catch (Exception e) { // ignore exception } } return true; } public static OutputStream getOutputStream(final File target, Context context) throws Exception { return getOutputStream(target, context, 0); } public static OutputStream getOutputStream(final File target, Context context, long s) throws Exception { OutputStream outStream = null; try { // First try the normal way if (isWritable(target)) { // standard way outStream = new FileOutputStream(target); } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Storage Access Framework DocumentFile targetDocument = getDocumentFile(target, false, context); outStream = context.getContentResolver().openOutputStream(targetDocument.getUri()); } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { // Workaround for Kitkat ext SD card return MediaStoreHack.getOutputStream(context, target.getPath()); } } } catch (Exception e) { Log.e("AmazeFileUtils", "Error when copying file from " + target.getAbsolutePath(), e); throw new Exception(); } return outStream; } /** * Delete a file. May be even on external SD card. * * @param file the file to be deleted. * @return True if successfully deleted. */ static boolean deleteFile(@NonNull final File file, Context context) { // First try the normal deletion. if (file == null) return true; boolean fileDelete = deleteFilesInFolder(file, context); if (file.delete() || fileDelete) return true; // Try with Storage Access Framework. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && FileUtil.isOnExtSdCard(file, context)) { DocumentFile document = getDocumentFile(file, false, context); return document.delete(); } // Try the Kitkat workaround. if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { ContentResolver resolver = context.getContentResolver(); try { Uri uri = MediaStoreHack.getUriFromFile(file.getAbsolutePath(), context); resolver.delete(uri, null, null); return !file.exists(); } catch (Exception e) { Log.e("AmazeFileUtils", "Error when deleting file " + file.getAbsolutePath(), e); return false; } } return !file.exists(); } private static boolean rename(File f, String name, boolean root) throws RootNotPermittedException { String newPath = f.getParent() + "/" + name; if (f.getParentFile().canWrite()) { return f.renameTo(new File(newPath)); } else if (root) { RootUtils.rename(f.getPath(), newPath); return true; } return false; } /** * Rename a folder. In case of extSdCard in Kitkat, the old folder stays in place, but files are moved. * * @param source The source folder. * @param target The target folder. * @return true if the renaming was successful. */ static boolean renameFolder(@NonNull final File source, @NonNull final File target, Context context) throws RootNotPermittedException { // First try the normal rename. if (rename(source, target.getName(), false)) { return true; } if (target.exists()) { return false; } // Try the Storage Access Framework if it is just a rename within the same parent folder. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && source.getParent().equals(target.getParent()) && FileUtil.isOnExtSdCard(source, context)) { DocumentFile document = getDocumentFile(source, true, context); if (document.renameTo(target.getName())) { return true; } } // Try the manual way, moving files individually. if (!mkdir(target, context)) { return false; } File[] sourceFiles = source.listFiles(); if (sourceFiles == null) { return true; } for (File sourceFile : sourceFiles) { String fileName = sourceFile.getName(); File targetFile = new File(target, fileName); if (!copyFile(sourceFile, targetFile, context)) { // stop on first error return false; } } // Only after successfully copying all files, delete files on source folder. for (File sourceFile : sourceFiles) { if (!deleteFile(sourceFile, context)) { // stop on first error return false; } } return true; } /** * Get a temp file. * * @param file The base file for which to create a temp file. * @return The temp file. */ public static File getTempFile(@NonNull final File file, Context context) { File extDir = context.getExternalFilesDir(null); return new File(extDir, file.getName()); } /** * Create a folder. The folder may even be on external SD card for Kitkat. * * @deprecated use {@link #mkdirs(Context, HFile)} * @param file The folder to be created. * @return True if creation was successful. */ public static boolean mkdir(final File file, Context context) { if(file==null) return false; if (file.exists()) { // nothing to create. return file.isDirectory(); } // Try the normal way if (file.mkdirs()) { return true; } // Try with Storage Access Framework. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && FileUtil.isOnExtSdCard(file, context)) { DocumentFile document = getDocumentFile(file, true, context); // getDocumentFile implicitly creates the directory. return document.exists(); } // Try the Kitkat workaround. if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { try { return MediaStoreHack.mkdir(context, file); } catch (IOException e) { return false; } } return false; } public static boolean mkdirs(Context context, HFile file) { boolean isSuccessful = true; switch (file.mode) { case SMB: try { SmbFile smbFile = new SmbFile(file.getPath()); smbFile.mkdirs(); } catch (MalformedURLException e) { e.printStackTrace(); isSuccessful = false; } catch (SmbException e) { e.printStackTrace(); isSuccessful = false; } break; case OTG: DocumentFile documentFile = RootHelper.getDocumentFile(file.getPath(), context, true); isSuccessful = documentFile != null; break; case FILE: isSuccessful = mkdir(new File(file.getPath()), context); break; default: isSuccessful = true; break; } return isSuccessful; } public static boolean mkfile(final File file,Context context) throws IOException { if(file==null) return false; if (file.exists()) { // nothing to create. return !file.isDirectory(); } // Try the normal way try { if (file.createNewFile()) { return true; } } catch (IOException e) { e.printStackTrace(); } // Try with Storage Access Framework. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && FileUtil.isOnExtSdCard(file, context)) { DocumentFile document = getDocumentFile(file.getParentFile(), true, context); // getDocumentFile implicitly creates the directory. try { return document.createFile(MimeTypes.getMimeType(file), file.getName()) != null; } catch (Exception e) { e.printStackTrace(); return false; } } if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { try { return MediaStoreHack.mkfile(context, file); } catch (Exception e) { return false; } } return false; } /** * Delete a folder. * * @param file The folder name. * @return true if successful. */ private static boolean rmdir1(final File file, Context context) { if (file == null) return false; boolean b = true; for (File file1 : file.listFiles()) { if (file1.isDirectory()) { if (!rmdir1(file1, context)) b = false; } else { if (!deleteFile(file1, context)) b = false; } } return b; } private static boolean rmdir(final File file, Context context) { if (file == null) return false; if (!file.exists()) { return true; } if (!file.isDirectory()) { return false; } String[] fileList = file.list(); if (fileList != null && fileList.length > 0) { // empty the folder. rmdir1(file, context); } String[] fileList1 = file.list(); if (fileList1 != null && fileList1.length > 0) { // Delete only empty folder. return false; } // Try the normal way if (file.delete()) { return true; } // Try with Storage Access Framework. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { DocumentFile document = getDocumentFile(file, true, context); return document.delete(); } // Try the Kitkat workaround. if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { ContentResolver resolver = context.getContentResolver(); ContentValues values = new ContentValues(); values.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath()); resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); // Delete the created entry, such that content provider will delete the file. resolver.delete(MediaStore.Files.getContentUri("external"), MediaStore.MediaColumns.DATA + "=?", new String[]{file.getAbsolutePath()}); } return !file.exists(); } /** * Delete all files in a folder. * * @param folder the folder * @return true if successful. */ private static final boolean deleteFilesInFolder(final File folder, Context context) { boolean totalSuccess = true; if (folder == null) return false; if (folder.isDirectory()) { for (File child : folder.listFiles()) { deleteFilesInFolder(child, context); } if (!folder.delete()) totalSuccess = false; } else { if (!folder.delete()) totalSuccess = false; } return totalSuccess; } /** * Delete a directory asynchronously. * * @param activity The activity calling this method. * @param file The folder name. * @param postActions Commands to be executed after success. */ public static void rmdirAsynchronously(final Activity activity, final File file, final Runnable postActions, final Context context) { if (file == null) return; new Thread() { @Override public void run() { int retryCounter = 5; // MAGIC_NUMBER while (!FileUtil.rmdir(file, context) && retryCounter > 0) { try { Thread.sleep(100); // MAGIC_NUMBER } catch (InterruptedException e) { // do nothing } retryCounter--; } if (file.exists()) { /* DialogUtil.displayError(activity, R.string.message_dialog_failed_to_delete_folder, false, file.getAbsolutePath()); */ } else { activity.runOnUiThread(postActions); } } }.start(); } /** * Check if a file is readable. * * @param file The file * @return true if the file is reabable. */ public static boolean isReadable(final File file) { if (file == null) return false; if (!file.exists()) return false; boolean result; try { result = file.canRead(); } catch (SecurityException e) { return false; } return result; } /** * Check if a file is writable. Detects write issues on external SD card. * * @param file The file * @return true if the file is writable. */ public static boolean isWritable(final File file) { if (file == null) return false; boolean isExisting = file.exists(); try { FileOutputStream output = new FileOutputStream(file, true); try { output.close(); } catch (IOException e) { // do nothing. } } catch (FileNotFoundException e) { return false; } boolean result = file.canWrite(); // Ensure that file is not created during this process. if (!isExisting) { file.delete(); } return result; } // Utility methods for Android 5 /** * Check for a directory if it is possible to create files within this directory, either via normal writing or via * Storage Access Framework. * * @param folder The directory * @return true if it is possible to write in this directory. */ public static boolean isWritableNormalOrSaf(final File folder, Context c) { // Verify that this is a directory. if (folder == null) return false; if (!folder.exists() || !folder.isDirectory()) { return false; } // Find a non-existing file in this directory. int i = 0; File file; do { String fileName = "AugendiagnoseDummyFile" + (++i); file = new File(folder, fileName); } while (file.exists()); // First check regular writability if (isWritable(file)) { return true; } // Next check SAF writability. DocumentFile document = getDocumentFile(file, false, c); if (document == null) { return false; } // This should have created the file - otherwise something is wrong with access URL. boolean result = document.canWrite() && file.exists(); // Ensure that the dummy file is not remaining. deleteFile(file, c); return result; } /** * Get a list of external SD card paths. (Kitkat or higher.) * * @return A list of external SD card paths. */ @TargetApi(Build.VERSION_CODES.KITKAT) private static String[] getExtSdCardPaths(Context context) { List<String> paths = new ArrayList<>(); for (File file : context.getExternalFilesDirs("external")) { if (file != null && !file.equals(context.getExternalFilesDir("external"))) { int index = file.getAbsolutePath().lastIndexOf("/Android/data"); if (index < 0) { Log.w("AmazeFileUtils", "Unexpected external file dir: " + file.getAbsolutePath()); } else { String path = file.getAbsolutePath().substring(0, index); try { path = new File(path).getCanonicalPath(); } catch (IOException e) { // Keep non-canonical path. } paths.add(path); } } } if (paths.isEmpty()) paths.add("/storage/sdcard1"); return paths.toArray(new String[0]); } @TargetApi(Build.VERSION_CODES.KITKAT) public static String[] getExtSdCardPathsForActivity(Context context) { List<String> paths = new ArrayList<>(); for (File file : context.getExternalFilesDirs("external")) { if (file != null) { int index = file.getAbsolutePath().lastIndexOf("/Android/data"); if (index < 0) { Log.w("AmazeFileUtils", "Unexpected external file dir: " + file.getAbsolutePath()); } else { String path = file.getAbsolutePath().substring(0, index); try { path = new File(path).getCanonicalPath(); } catch (IOException e) { // Keep non-canonical path. } paths.add(path); } } } if (paths.isEmpty()) paths.add("/storage/sdcard1"); return paths.toArray(new String[0]); } /** * Determine the main folder of the external SD card containing the given file. * * @param file the file. * @return The main folder of the external SD card containing this file, if the file is on an SD card. Otherwise, * null is returned. */ @TargetApi(Build.VERSION_CODES.KITKAT) private static String getExtSdCardFolder(final File file, Context context) { String[] extSdPaths = getExtSdCardPaths(context); try { for (int i = 0; i < extSdPaths.length; i++) { if (file.getCanonicalPath().startsWith(extSdPaths[i])) { return extSdPaths[i]; } } } catch (IOException e) { return null; } return null; } /** * Determine if a file is on external sd card. (Kitkat or higher.) * * @param file The file. * @return true if on external sd card. */ @TargetApi(Build.VERSION_CODES.KITKAT) public static boolean isOnExtSdCard(final File file, Context c) { return getExtSdCardFolder(file, c) != null; } /** * Get a DocumentFile corresponding to the given file (for writing on ExtSdCard on Android 5). If the file is not * existing, it is created. * * @param file The file. * @param isDirectory flag indicating if the file should be a directory. * @return The DocumentFile */ public static DocumentFile getDocumentFile(final File file, final boolean isDirectory, Context context) { String baseFolder = getExtSdCardFolder(file, context); boolean originalDirectory = false; if (baseFolder == null) { return null; } String relativePath = null; try { String fullPath = file.getCanonicalPath(); if (!baseFolder.equals(fullPath)) relativePath = fullPath.substring(baseFolder.length() + 1); else originalDirectory = true; } catch (IOException e) { return null; } catch (Exception f) { originalDirectory = true; //continue } String as = PreferenceManager.getDefaultSharedPreferences(context).getString("URI", null); Uri treeUri = null; if (as != null) treeUri = Uri.parse(as); if (treeUri == null) { return null; } // start with root of SD card and then parse through document tree. DocumentFile document = DocumentFile.fromTreeUri(context, treeUri); if (originalDirectory) return document; String[] parts = relativePath.split("\\/"); for (int i = 0; i < parts.length; i++) { DocumentFile nextDocument = document.findFile(parts[i]); if (nextDocument == null) { if ((i < parts.length - 1) || isDirectory) { nextDocument = document.createDirectory(parts[i]); } else { nextDocument = document.createFile("image", parts[i]); } } document = nextDocument; } return document; } // Utility methods for Kitkat /** * Copy a resource file into a private target directory, if the target does not yet exist. Required for the Kitkat * workaround. * * @param resource The resource file. * @param folderName The folder below app folder where the file is copied to. * @param targetName The name of the target file. * @return the dummy file. * @throws IOException */ private static File copyDummyFile(final int resource, final String folderName, final String targetName, Context context) throws IOException { File externalFilesDir = context.getExternalFilesDir(folderName); if (externalFilesDir == null) { return null; } File targetFile = new File(externalFilesDir, targetName); if (!targetFile.exists()) { InputStream in = null; OutputStream out = null; try { in = context.getResources().openRawResource(resource); out = new FileOutputStream(targetFile); byte[] buffer = new byte[4096]; // MAGIC_NUMBER int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } } finally { if (in != null) { try { in.close(); } catch (IOException ex) { // do nothing } } if (out != null) { try { out.close(); } catch (IOException ex) { // do nothing } } } } return targetFile; } /** * Checks whether the target path exists or is writable * @param f the target path * @param context * @return 1 if exists or writable, 0 if not writable */ public static int checkFolder(final String f,Context context) { if(f==null)return 0; if(f.startsWith("smb://") || f.startsWith(OTGUtil.PREFIX_OTG) || f.startsWith(CloudHandler.CLOUD_PREFIX_BOX) || f.startsWith(CloudHandler.CLOUD_PREFIX_GOOGLE_DRIVE) || f.startsWith(CloudHandler.CLOUD_PREFIX_DROPBOX) || f.startsWith(CloudHandler.CLOUD_PREFIX_ONE_DRIVE) ) return 1; File folder=new File(f); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && FileUtil.isOnExtSdCard(folder, context)) { if (!folder.exists() || !folder.isDirectory()) { return 0; } // On Android 5, trigger storage access framework. if (FileUtil.isWritableNormalOrSaf(folder, context)) { return 1; } } else if (Build.VERSION.SDK_INT == 19 && FileUtil.isOnExtSdCard(folder, context)) { // Assume that Kitkat workaround works return 1; } else if (folder.canWrite()) { return 1; } else { return 0; } return 0; } /** * Copy the dummy image and dummy mp3 into the private folder, if not yet there. Required for the Kitkat workaround. * * @return the dummy mp3. */ private static File copyDummyFiles(Context c) { try { copyDummyFile(R.mipmap.ic_launcher, "mkdirFiles", "albumart.jpg", c); return copyDummyFile(R.raw.temptrack, "mkdirFiles", "temptrack.mp3", c); } catch (IOException e) { Log.e("AmazeFileUtils", "Could not copy dummy files.", e); return null; } } static class MediaFile { private static final String NO_MEDIA = ".nomedia"; private static final String ALBUM_ART_URI = "content://media/external/audio/albumart"; private static final String[] ALBUM_PROJECTION = {BaseColumns._ID, MediaStore.Audio.AlbumColumns.ALBUM_ID, "media_type"}; private static File getExternalFilesDir(Context context) { try { Method method = Context.class.getMethod("getExternalFilesDir", String.class); return (File) method.invoke(context, (String) null); } catch (SecurityException ex) { // Log.d(Maui.LOG_TAG, "Unexpected reflection error.", ex); return null; } catch (NoSuchMethodException ex) { // Log.d(Maui.LOG_TAG, "Unexpected reflection error.", ex); return null; } catch (IllegalArgumentException ex) { // Log.d(Maui.LOG_TAG, "Unexpected reflection error.", ex); return null; } catch (IllegalAccessException ex) { //Log.d(Maui.LOG_TAG, "Unexpected reflection error.", ex); return null; } catch (InvocationTargetException ex) { //Log.d(Maui.LOG_TAG, "Unexpected reflection error.", ex); return null; } } private final File file; private final Context context; private final ContentResolver contentResolver; Uri filesUri; MediaFile(Context context, File file) { this.file = file; this.context = context; contentResolver = context.getContentResolver(); filesUri = MediaStore.Files.getContentUri("external"); } /** * Deletes the file. Returns true if the file has been successfully deleted or otherwise does not exist. This operation is not * recursive. */ public boolean delete() throws IOException { if (!file.exists()) { return true; } boolean directory = file.isDirectory(); if (directory) { // Verify directory does not contain any files/directories within it. String[] files = file.list(); if (files != null && files.length > 0) { return false; } } String where = MediaStore.MediaColumns.DATA + "=?"; String[] selectionArgs = new String[]{file.getAbsolutePath()}; // Delete the entry from the media database. This will actually delete media files (images, audio, and video). contentResolver.delete(filesUri, where, selectionArgs); if (file.exists()) { // If the file is not a media file, create a new entry suggesting that this location is an image, even // though it is not. ContentValues values = new ContentValues(); values.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath()); contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); // Delete the created entry, such that content provider will delete the file. contentResolver.delete(filesUri, where, selectionArgs); } return !file.exists(); } public File getFile() { return file; } private int getTemporaryAlbumId() { final File temporaryTrack; try { temporaryTrack = installTemporaryTrack(); } catch (IOException ex) { return 0; } final String[] selectionArgs = {temporaryTrack.getAbsolutePath()}; Cursor cursor = contentResolver.query(filesUri, ALBUM_PROJECTION, MediaStore.MediaColumns.DATA + "=?", selectionArgs, null); if (cursor == null || !cursor.moveToFirst()) { if (cursor != null) { cursor.close(); cursor = null; } ContentValues values = new ContentValues(); values.put(MediaStore.MediaColumns.DATA, temporaryTrack.getAbsolutePath()); values.put(MediaStore.MediaColumns.TITLE, "{MediaWrite Workaround}"); values.put(MediaStore.MediaColumns.SIZE, temporaryTrack.length()); values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mpeg"); values.put(MediaStore.Audio.AudioColumns.IS_MUSIC, true); contentResolver.insert(filesUri, values); } cursor = contentResolver.query(filesUri, ALBUM_PROJECTION, MediaStore.MediaColumns.DATA + "=?", selectionArgs, null); if (cursor == null) { return 0; } if (!cursor.moveToFirst()) { cursor.close(); return 0; } int id = cursor.getInt(0); int albumId = cursor.getInt(1); int mediaType = cursor.getInt(2); cursor.close(); ContentValues values = new ContentValues(); boolean updateRequired = false; if (albumId == 0) { values.put(MediaStore.Audio.AlbumColumns.ALBUM_ID, 13371337); updateRequired = true; } if (mediaType != 2) { values.put("media_type", 2); updateRequired = true; } if (updateRequired) { contentResolver.update(filesUri, values, BaseColumns._ID + "=" + id, null); } cursor = contentResolver.query(filesUri, ALBUM_PROJECTION, MediaStore.MediaColumns.DATA + "=?", selectionArgs, null); if (cursor == null) { return 0; } try { if (!cursor.moveToFirst()) { return 0; } return cursor.getInt(1); } finally { cursor.close(); } } private File installTemporaryTrack() throws IOException { File externalFilesDir = getExternalFilesDir(context); if (externalFilesDir == null) { return null; } File temporaryTrack = new File(externalFilesDir, "temptrack.mp3"); if (!temporaryTrack.exists()) { InputStream in = null; OutputStream out = null; try { in = context.getResources().openRawResource(R.raw.temptrack); out = new FileOutputStream(temporaryTrack); byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } } finally { if (in != null) { try { in.close(); } catch (IOException ex) { return null; } } if (out != null) { try { out.close(); } catch (IOException ex) { return null; } } } } return temporaryTrack; } public boolean mkdir() throws IOException { if (file.exists()) { return file.isDirectory(); } File tmpFile = new File(file, ".MediaWriteTemp"); int albumId = getTemporaryAlbumId(); if (albumId == 0) { throw new IOException("Fail"); } Uri albumUri = Uri.parse(ALBUM_ART_URI + '/' + albumId); ContentValues values = new ContentValues(); values.put(MediaStore.MediaColumns.DATA, tmpFile.getAbsolutePath()); if (contentResolver.update(albumUri, values, null, null) == 0) { values.put(MediaStore.Audio.AlbumColumns.ALBUM_ID, albumId); contentResolver.insert(Uri.parse(ALBUM_ART_URI), values); } try { ParcelFileDescriptor fd = contentResolver.openFileDescriptor(albumUri, "r"); fd.close(); } finally { MediaFile tmpMediaFile = new MediaFile(context, tmpFile); tmpMediaFile.delete(); } return file.exists(); } /** * Returns an OutputStream to write to the file. The file will be truncated immediately. */ public OutputStream write(long size) throws IOException { if (NO_MEDIA.equals(file.getName().trim())) { throw new IOException("Unable to create .nomedia file via media content provider API."); } if (file.exists() && file.isDirectory()) { throw new IOException("File exists and is a directory."); } // Delete any existing entry from the media database. // This may also delete the file (for media types), but that is irrelevant as it will be truncated momentarily in any case. String where = MediaStore.MediaColumns.DATA + "=?"; String[] selectionArgs = new String[]{file.getAbsolutePath()}; contentResolver.delete(filesUri, where, selectionArgs); ContentValues values = new ContentValues(); values.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath()); values.put(MediaStore.MediaColumns.SIZE, size); Uri uri = contentResolver.insert(filesUri, values); if (uri == null) { // Should not occur. throw new IOException("Internal error."); } return contentResolver.openOutputStream(uri); } } }