...
 
Commits (3)
......@@ -215,6 +215,7 @@
<string name="add_custom_path">Add a custom path</string>
<string name="add_custom_path_description">Enter additional custom directory to scan:</string>
<string name="remove_custom_path">Remove custom path</string>
<string name="ext_sdcard_write">Enable writing to external SD card</string>
<string name="hardware_acceleration">Hardware Acceleration</string>
<string name="hardware_acceleration_summary">Disabled: better stability.\nDecoding: may improve performance.\nFull: may improve performance further.</string>
<string name="hardware_acceleration_disabled">Disabled</string>
......
......@@ -7,6 +7,10 @@
android:summary="@string/directories_summary"
android:title="@string/directories" />
<PreferenceScreen
android:key="ext_sdcard_write"
android:title="@string/ext_sdcard_write" />
<ListPreference
android:defaultValue="-1"
android:entries="@array/hardware_acceleration_list"
......
......@@ -23,15 +23,24 @@
package org.videolan.vlc.gui.preferences;
import android.app.Fragment;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.UriPermission;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.provider.DocumentFile;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceManager;
import android.util.Log;
import org.videolan.libvlc.util.AndroidUtil;
import org.videolan.vlc.BuildConfig;
import org.videolan.vlc.R;
import org.videolan.vlc.VLCApplication;
import org.videolan.vlc.gui.SecondaryActivity;
......@@ -39,9 +48,12 @@ import org.videolan.vlc.util.AndroidDevices;
import org.videolan.vlc.util.Util;
import org.videolan.vlc.util.VLCInstance;
import java.util.List;
public class PreferencesFragment extends BasePreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
public final static String TAG = "VLC/PreferencesFragment";
public final static int REQUEST_CODE_STORAGE_ACCES = 42;
@Override
protected int getXml() {
......@@ -62,6 +74,25 @@ public class PreferencesFragment extends BasePreferenceFragment implements Share
findPreference("enable_black_theme").setEnabled(false);
}
// Writing to external sd card
Preference extSdCardWritePref = findPreference("ext_sdcard_write");
if (AndroidUtil.isLolliPopOrLater() && BuildConfig.DEBUG) {
extSdCardWritePref.setSummary(getUriPermissions());
extSdCardWritePref.setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean onPreferenceClick(Preference preference) {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, REQUEST_CODE_STORAGE_ACCES);
return true;
}
});
}
else {
extSdCardWritePref.setVisible(false);
}
// Screen orientation
ListPreference screenOrientationPref = (ListPreference) findPreference("screen_orientation");
screenOrientationPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
......@@ -80,6 +111,49 @@ public class PreferencesFragment extends BasePreferenceFragment implements Share
sharedPrefs.registerOnSharedPreferenceChangeListener(this);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private String getUriPermissions() {
StringBuilder sb = new StringBuilder();
Context context = getContext();
List<UriPermission> persistedUriPermissions = context.getContentResolver().getPersistedUriPermissions();
for (UriPermission uriPermission : persistedUriPermissions) {
final DocumentFile file = DocumentFile.fromTreeUri(context, uriPermission.getUri());
sb.append(uriPermission.getUri().getPath() + "\n");
}
return sb.toString();
}
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_STORAGE_ACCES && AndroidUtil.isLolliPopOrLater()) {
if (resultCode == Activity.RESULT_OK) {
Context context = getContext();
Uri treeUri = data.getData();
final DocumentFile treeFile = DocumentFile.fromTreeUri(context, treeUri);
ContentResolver contentResolver = context.getContentResolver();
// revoke access if a permission already exists
final List<UriPermission> persistedUriPermissions = contentResolver.getPersistedUriPermissions();
for (UriPermission uriPermission : persistedUriPermissions) {
final DocumentFile file = DocumentFile.fromTreeUri(context, uriPermission.getUri());
if (treeFile.getName().equals(file.getName())) {
Log.d(TAG, "Revoking permission to " + treeFile);
contentResolver.releasePersistableUriPermission(uriPermission.getUri(), Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
findPreference("ext_sdcard_write").setSummary(getUriPermissions());
return;
}
}
// else set permission
Log.d(TAG, "Taking permission to " + treeUri);
contentResolver.takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
findPreference("ext_sdcard_write").setSummary(getUriPermissions());
}
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(key.equalsIgnoreCase("hardware_acceleration")
......
......@@ -120,7 +120,7 @@ public class MediaInfoFragment extends ListFragment {
VLCApplication.runBackground(new Runnable() {
@Override
public void run() {
boolean deleted = Util.deleteFile(mItem.getLocation());
boolean deleted = Util.deleteFile(MediaInfoFragment.this.getContext(), mItem.getLocation());
if (deleted) {
mHandler.obtainMessage(EXIT).sendToTarget();
}
......
......@@ -57,6 +57,7 @@ import android.widget.TextView;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.util.AndroidUtil;
import org.videolan.vlc.BuildConfig;
import org.videolan.vlc.MediaDatabase;
import org.videolan.vlc.MediaGroup;
import org.videolan.vlc.MediaLibrary;
......@@ -174,6 +175,9 @@ public class VideoGridFragment extends MediaBrowserFragment implements ISortable
mMediaLibrary.setBrowser(null);
mMediaLibrary.removeUpdateHandler(mHandler);
/* Clear current deletion */
mDeleteHandler.removeMessages(DELETE_MEDIA);
/* Stop the thumbnailer */
if (mThumbnailer != null)
mThumbnailer.stop();
......@@ -319,7 +323,7 @@ public class VideoGridFragment extends MediaBrowserFragment implements ISortable
Snackbar.make(getView(), getString(R.string.file_deleted), DELETE_DURATION)
.setAction(android.R.string.cancel, mCancelDeleteMediaListener)
.show();
Message msg = mDeleteHandler.obtainMessage(DELETE_MEDIA, position, 0);
Message msg = mDeleteHandler.obtainMessage(DELETE_MEDIA, media.getLocation());
mDeleteHandler.sendMessageDelayed(msg, DELETE_DURATION);
return true;
}
......@@ -350,8 +354,7 @@ public class VideoGridFragment extends MediaBrowserFragment implements ISortable
hasInfo = true;
media.release();
menu.findItem(R.id.video_list_info).setVisible(hasInfo);
menu.findItem(R.id.video_list_delete).setVisible(!AndroidUtil.isLolliPopOrLater() ||
mediaWrapper.getLocation().startsWith("file://" + AndroidDevices.EXTERNAL_PUBLIC_DIRECTORY));
menu.findItem(R.id.video_list_delete).setVisible(BuildConfig.DEBUG);
}
@Override
......@@ -555,8 +558,8 @@ public class VideoGridFragment extends MediaBrowserFragment implements ISortable
mVideoAdapter.clear();
}
public void deleteMedia(int position){
final MediaWrapper media = mVideoAdapter.getItem(position);
public void deleteMedia(String location){
final MediaWrapper media = mVideoAdapter.getItem(location);
final String path = media.getUri().getPath();
VLCApplication.runBackground(new Runnable() {
public void run() {
......@@ -573,7 +576,6 @@ public class VideoGridFragment extends MediaBrowserFragment implements ISortable
}
}
View.OnClickListener mCancelDeleteMediaListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
......@@ -594,7 +596,7 @@ public class VideoGridFragment extends MediaBrowserFragment implements ISortable
super.handleMessage(msg);
switch (msg.what){
case DELETE_MEDIA:
getOwner().deleteMedia(msg.arg1);
getOwner().deleteMedia(msg.obj.toString());
}
}
}
......
......@@ -89,6 +89,15 @@ public class VideoListAdapter extends ArrayAdapter<MediaWrapper>
notifyDataSetChanged();
}
public MediaWrapper getItem(String location) {
for (int i = 0; i < getCount(); ++i) {
MediaWrapper media = getItem(i);
if (media.getLocation().equals(location))
return media;
}
return null;
}
public int sortDirection(int sortby) {
if (sortby == mSortBy)
return mSortDirection;
......
......@@ -27,6 +27,7 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.UriPermission;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.TypedArray;
......@@ -35,16 +36,15 @@ import android.os.Build;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.support.design.widget.Snackbar;
import android.support.v4.provider.DocumentFile;
import android.text.TextUtils.TruncateAt;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.TextView;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.util.AndroidUtil;
import org.videolan.vlc.MediaLibrary;
import org.videolan.vlc.MediaWrapper;
import org.videolan.vlc.PlaybackService;
import org.videolan.vlc.R;
......@@ -321,7 +321,7 @@ public class Util {
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static boolean deleteFile (String path){
public static boolean deleteFile (Context context, String path){
boolean deleted = false;
path = Uri.decode(Strings.removeFileProtocole(path));
//Delete from Android Medialib, for consistency with device MTP storing and other apps listing content:// media
......@@ -332,18 +332,46 @@ public class Util {
MediaStore.Files.FileColumns.DATA + "=?", selectionArgs) > 0;
}
File file = new File(path);
if (AndroidUtil.isLolliPopOrLater()){
List<UriPermission> persistedUriPermissions = context.getContentResolver().getPersistedUriPermissions();
for (UriPermission uriPermission : persistedUriPermissions) {
final DocumentFile root = DocumentFile.fromTreeUri(context, uriPermission.getUri());
DocumentFile docfile = recursiveFindFile(root, file.getName());
if (docfile != null && docfile.exists()) {
deleted |= docfile.delete();
Log.w(TAG, "media deleted : " + docfile.getUri());
break;
}
}
}
if (file.exists())
deleted |= file.delete();
return deleted;
}
// FIXME: DocumentFile.fromFile is cleaner, but it just doesn't work, so use this dirty way for now
// see https://code.google.com/p/android/issues/detail?id=185871
public static DocumentFile recursiveFindFile(DocumentFile root, String name) {
for (DocumentFile doc : root.listFiles()) {
if (doc.isDirectory()) {
DocumentFile file = recursiveFindFile(doc, name);
if (file != null)
return file;
}
if (doc.isFile() && name.equals(doc.getName())) {
return doc;
}
}
return null;
}
public static boolean recursiveDelete(Context context, File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
for (File child : fileOrDirectory.listFiles())
recursiveDelete(context, child);
return fileOrDirectory.delete();
} else {
return deleteFile (fileOrDirectory.getPath());
return deleteFile (context, fileOrDirectory.getPath());
}
}
......