Created
November 11, 2015 06:08
-
-
Save shunix/1e2987efacdad085ea5f to your computer and use it in GitHub Desktop.
Bypass sdcard write restriction in Android Kitkat
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (C) 2014 NextApp, Inc. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" | |
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language | |
* governing permissions and limitations under the License. | |
*/ | |
package nextapp.mediafile; | |
import java.io.File; | |
import java.io.IOException; | |
import java.io.OutputStream; | |
import android.content.ContentResolver; | |
import android.content.ContentValues; | |
import android.net.Uri; | |
import android.provider.MediaStore; | |
/** | |
* Wrapper for manipulating files via the Android Media Content Provider. As of Android 4.4 KitKat, applications can no longer write | |
* to the "secondary storage" of a device. Write operations using the java.io.File API will thus fail. This class restores access to | |
* those write operations by way of the Media Content Provider. | |
* | |
* Note that this class relies on the internal operational characteristics of the media content provider API, and as such is not | |
* guaranteed to be future-proof. Then again, we did all think the java.io.File API was going to be future-proof for media card | |
* access, so all bets are off. | |
* | |
* If you're forced to use this class, it's because Google/AOSP made a very poor API decision in Android 4.4 KitKat. | |
* Read more at https://plus.google.com/+TodLiebeck/posts/gjnmuaDM8sn | |
* | |
* Your application must declare the permission "android.permission.WRITE_EXTERNAL_STORAGE". | |
*/ | |
public class MediaFile { | |
private final File file; | |
private final ContentResolver contentResolver; | |
private final Uri filesUri; | |
private final Uri imagesUri; | |
public MediaFile(ContentResolver contentResolver, File file) { | |
this.file = file; | |
this.contentResolver = contentResolver; | |
filesUri = MediaStore.Files.getContentUri("external"); | |
imagesUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; | |
} | |
/** | |
* 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.Files.FileColumns.DATA, file.getAbsolutePath()); | |
contentResolver.insert(imagesUri, 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; | |
} | |
/** | |
* Creates a new directory. Returns true if the directory was successfully created or exists. | |
*/ | |
public boolean mkdir() | |
throws IOException { | |
if (file.exists()) { | |
return file.isDirectory(); | |
} | |
ContentValues values; | |
Uri uri; | |
// Create a media database entry for the directory. This step will not actually cause the directory to be created. | |
values = new ContentValues(); | |
values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath()); | |
contentResolver.insert(filesUri, values); | |
// Create an entry for a temporary image file within the created directory. | |
// This step actually causes the creation of the directory. | |
values = new ContentValues(); | |
values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath() + "/temp.jpg"); | |
uri = contentResolver.insert(imagesUri, values); | |
// Delete the temporary entry. | |
contentResolver.delete(uri, null, null); | |
return file.exists(); | |
} | |
/** | |
* Returns an OutputStream to write to the file. The file will be truncated immediately. | |
*/ | |
public OutputStream write() | |
throws IOException { | |
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.Files.FileColumns.DATA, file.getAbsolutePath()); | |
Uri uri = contentResolver.insert(filesUri, values); | |
if (uri == null) { | |
// Should not occur. | |
throw new IOException("Internal error."); | |
} | |
return contentResolver.openOutputStream(uri); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment