diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4ed887e6..d0c73348 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -113,9 +113,6 @@ android:name=".ui.activities.intro.AppIntroActivity" android:label="@string/intro_label" android:theme="@style/Theme.Intro" /> - diff --git a/app/src/main/java/com/kabouzeid/gramophone/dialogs/DeleteSongsDialog.java b/app/src/main/java/com/kabouzeid/gramophone/dialogs/DeleteSongsDialog.java index 00b5eb84..da7ef178 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/dialogs/DeleteSongsDialog.java +++ b/app/src/main/java/com/kabouzeid/gramophone/dialogs/DeleteSongsDialog.java @@ -1,41 +1,24 @@ package com.kabouzeid.gramophone.dialogs; -import android.annotation.TargetApi; -import android.app.Activity; import android.app.Dialog; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; -import android.support.v4.app.FragmentActivity; import android.text.Html; -import android.widget.Toast; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.kabouzeid.gramophone.R; -import com.kabouzeid.gramophone.misc.DialogAsyncTask; import com.kabouzeid.gramophone.model.Song; -import com.kabouzeid.gramophone.ui.activities.saf.SAFGuideActivity; import com.kabouzeid.gramophone.util.MusicUtil; -import com.kabouzeid.gramophone.util.SAFUtil; -import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.Collections; -import java.util.List; /** * @author Karim Abou Zeid (kabouzeid), Aidan Follestad (afollestad) */ public class DeleteSongsDialog extends DialogFragment { - private ArrayList songsToRemove; - private Song currentSong; - @NonNull public static DeleteSongsDialog create(Song song) { ArrayList list = new ArrayList<>(); @@ -71,171 +54,14 @@ public class DeleteSongsDialog extends DialogFragment { .content(content) .positiveText(R.string.delete_action) .negativeText(android.R.string.cancel) - .autoDismiss(false) .onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { - songsToRemove = songs; - new DeleteSongsAsyncTask(DeleteSongsDialog.this).execute(new DeleteSongsAsyncTask.LoadingInfo(songs, null)); - } - }) - .onNegative(new MaterialDialog.SingleButtonCallback() { - @Override - public void onClick(@NonNull MaterialDialog materialDialog, @NonNull DialogAction dialogAction) { - dismiss(); + if (getActivity() == null) + return; + MusicUtil.deleteTracks(getActivity(), songs); } }) .build(); } - - private void deleteSongs(List songs, List safUris) { - MusicUtil.deleteTracks(getActivity(), songs, safUris); - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - private void deleteSongsKitkat() { - if (songsToRemove.size() < 1) { - dismiss(); - return; - } - - currentSong = songsToRemove.remove(0); - - if (!SAFUtil.isSAFRequired(currentSong)) { - deleteSongs(Collections.singletonList(currentSong), null); - deleteSongsKitkat(); - } else { - Toast.makeText(getActivity(), String.format(getString(R.string.saf_pick_file), currentSong.data), Toast.LENGTH_LONG).show(); - SAFUtil.openFilePicker(this); - } - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent intent) { - super.onActivityResult(requestCode, resultCode, intent); - switch (requestCode) { - case SAFGuideActivity.REQUEST_CODE_SAF_GUIDE: - SAFUtil.openTreePicker(this); - break; - - case SAFUtil.REQUEST_SAF_PICK_TREE: - case SAFUtil.REQUEST_SAF_PICK_FILE: - new DeleteSongsAsyncTask(this).execute(new DeleteSongsAsyncTask.LoadingInfo(requestCode, resultCode, intent)); - break; - } - } - - private static class DeleteSongsAsyncTask extends DialogAsyncTask { - private WeakReference dialog; - private WeakReference activity; - - public DeleteSongsAsyncTask(DeleteSongsDialog dialog) { - super(dialog.getActivity()); - this.dialog = new WeakReference<>(dialog); - this.activity = new WeakReference<>(dialog.getActivity()); - } - - @Override - protected Void doInBackground(LoadingInfo... params) { - try { - LoadingInfo info = params[0]; - - DeleteSongsDialog dialog = this.dialog.get(); - FragmentActivity activity = this.activity.get(); - - if (dialog == null || activity == null) - return null; - - if (!info.isIntent) { - if (!SAFUtil.isSAFRequiredForSongs(info.songs)) { - dialog.deleteSongs(info.songs, null); - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (SAFUtil.isSDCardAccessGranted(activity)) { - dialog.deleteSongs(info.songs, null); - } else { - dialog.startActivityForResult(new Intent(activity, SAFGuideActivity.class), SAFGuideActivity.REQUEST_CODE_SAF_GUIDE); - } - } else { - dialog.deleteSongsKitkat(); - } - } - } else { - switch (info.requestCode) { - case SAFUtil.REQUEST_SAF_PICK_TREE: - if (info.resultCode == Activity.RESULT_OK) { - SAFUtil.saveTreeUri(activity, info.intent); - dialog.deleteSongs(dialog.songsToRemove, null); - } - break; - - case SAFUtil.REQUEST_SAF_PICK_FILE: - if (info.resultCode == Activity.RESULT_OK) { - dialog.deleteSongs(Collections.singletonList(dialog.currentSong), Collections.singletonList(info.intent.getData())); - } - break; - } - } - } catch (Exception e) { - e.printStackTrace(); - } - - return null; - } - - @Override - protected void onPostExecute(Void v) { - super.onPostExecute(v); - - DeleteSongsDialog dialog = this.dialog.get(); - FragmentActivity activity = this.activity.get(); - if (dialog != null && activity != null && !activity.isFinishing()) { - dialog.dismiss(); - } - } - - @Override - protected void onCancelled() { - super.onCancelled(); - - DeleteSongsDialog dialog = this.dialog.get(); - FragmentActivity activity = this.activity.get(); - if (dialog != null && activity != null && !activity.isFinishing()) { - dialog.dismiss(); - } - } - - @Override - protected Dialog createDialog(@NonNull Context context) { - return new MaterialDialog.Builder(context) - .title(R.string.deleting_songs) - .cancelable(false) - .progress(true, 0) - .build(); - } - - public static class LoadingInfo { - public boolean isIntent; - - public List songs; - public List safUris; - - public int requestCode; - public int resultCode; - public Intent intent; - - public LoadingInfo(List songs, List safUris) { - this.isIntent = false; - this.songs = songs; - this.safUris = safUris; - } - - public LoadingInfo(int requestCode, int resultCode, Intent intent) { - this.isIntent = true; - this.requestCode = requestCode; - this.resultCode = resultCode; - this.intent = intent; - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/saf/SAFGuideActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/saf/SAFGuideActivity.java deleted file mode 100644 index aff54327..00000000 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/saf/SAFGuideActivity.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.kabouzeid.gramophone.ui.activities.saf; - -import android.os.Build; -import android.os.Bundle; - -import com.heinrichreimersoftware.materialintro.app.IntroActivity; -import com.heinrichreimersoftware.materialintro.slide.SimpleSlide; -import com.kabouzeid.gramophone.R; - - -public class SAFGuideActivity extends IntroActivity { - - public static final int REQUEST_CODE_SAF_GUIDE = 98; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setButtonCtaVisible(false); - setButtonNextVisible(false); - setButtonBackVisible(false); - - setButtonCtaTintMode(BUTTON_CTA_TINT_MODE_TEXT); - - String title = String.format(getString(R.string.saf_guide_slide1_title), getString(R.string.app_name)); - - addSlide(new SimpleSlide.Builder() - .title(title) - .description(Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1 ? R.string.saf_guide_slide1_description_before_o : R.string.saf_guide_slide1_description) - .image(R.drawable.saf_guide_1) - .background(R.color.md_indigo_300) - .backgroundDark(R.color.md_indigo_400) - .layout(R.layout.fragment_simple_slide_large_image) - .build()); - addSlide(new SimpleSlide.Builder() - .title(R.string.saf_guide_slide2_title) - .description(R.string.saf_guide_slide2_description) - .image(R.drawable.saf_guide_2) - .background(R.color.md_indigo_500) - .backgroundDark(R.color.md_indigo_600) - .layout(R.layout.fragment_simple_slide_large_image) - .build()); - addSlide(new SimpleSlide.Builder() - .title(R.string.saf_guide_slide3_title) - .description(R.string.saf_guide_slide3_description) - .image(R.drawable.saf_guide_3) - .background(R.color.md_indigo_700) - .backgroundDark(R.color.md_indigo_800) - .layout(R.layout.fragment_simple_slide_large_image) - .build()); - } -} diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/tageditor/AbsTagEditorActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/tageditor/AbsTagEditorActivity.java index 06dfcada..cf9ab563 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/tageditor/AbsTagEditorActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/tageditor/AbsTagEditorActivity.java @@ -1,6 +1,5 @@ package com.kabouzeid.gramophone.ui.activities.tageditor; -import android.annotation.TargetApi; import android.app.Activity; import android.app.Dialog; import android.app.SearchManager; @@ -10,7 +9,6 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.MediaScannerConnection; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -22,7 +20,6 @@ import android.view.View; import android.view.animation.OvershootInterpolator; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; import com.github.ksoichiro.android.observablescrollview.ObservableScrollView; @@ -34,25 +31,25 @@ import com.kabouzeid.gramophone.misc.DialogAsyncTask; import com.kabouzeid.gramophone.misc.SimpleObservableScrollViewCallbacks; import com.kabouzeid.gramophone.misc.UpdateToastMediaScannerCompletionListener; import com.kabouzeid.gramophone.ui.activities.base.AbsBaseActivity; -import com.kabouzeid.gramophone.ui.activities.saf.SAFGuideActivity; import com.kabouzeid.gramophone.util.MusicUtil; -import com.kabouzeid.gramophone.util.SAFUtil; import com.kabouzeid.gramophone.util.Util; import org.jaudiotagger.audio.AudioFile; import org.jaudiotagger.audio.AudioFileIO; +import org.jaudiotagger.audio.exceptions.CannotReadException; +import org.jaudiotagger.audio.exceptions.CannotWriteException; +import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException; +import org.jaudiotagger.audio.exceptions.ReadOnlyFileException; import org.jaudiotagger.tag.FieldKey; import org.jaudiotagger.tag.Tag; +import org.jaudiotagger.tag.TagException; import org.jaudiotagger.tag.images.Artwork; import org.jaudiotagger.tag.images.ArtworkFactory; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -98,11 +95,6 @@ public abstract class AbsTagEditorActivity extends AbsBaseActivity { }; private List songPaths; - private List savedSongPaths; - private String currentSongPath; - private Map savedTags; - private ArtworkInfo savedArtworkInfo; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -265,16 +257,6 @@ public abstract class AbsTagEditorActivity extends AbsBaseActivity { fab.setEnabled(true); } - private void hideFab() { - fab.animate() - .setDuration(500) - .setInterpolator(new OvershootInterpolator()) - .scaleX(0) - .scaleY(0) - .start(); - fab.setEnabled(false); - } - protected void setImageBitmap(@Nullable final Bitmap bitmap, int bgColor) { if (bitmap == null) { image.setImageResource(R.drawable.default_album_art); @@ -296,52 +278,15 @@ public abstract class AbsTagEditorActivity extends AbsBaseActivity { protected void writeValuesToFiles(@NonNull final Map fieldKeyValueMap, @Nullable final ArtworkInfo artworkInfo) { Util.hideSoftKeyboard(this); - hideFab(); - - savedSongPaths = getSongPaths(); - savedTags = fieldKeyValueMap; - savedArtworkInfo = artworkInfo; - - if (!SAFUtil.isSAFRequired(savedSongPaths)) { - writeTags(savedSongPaths); - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (SAFUtil.isSDCardAccessGranted(this)) { - writeTags(savedSongPaths); - } else { - startActivityForResult(new Intent(this, SAFGuideActivity.class), SAFGuideActivity.REQUEST_CODE_SAF_GUIDE); - } - } else { - writeTagsKitkat(); - } - } - } - - private void writeTags(List paths) { - new WriteTagsAsyncTask(this).execute(new WriteTagsAsyncTask.LoadingInfo(paths, savedTags, savedArtworkInfo)); - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - private void writeTagsKitkat() { - if (savedSongPaths.size() < 1) return; - - currentSongPath = savedSongPaths.remove(0); - - if (!SAFUtil.isSAFRequired(currentSongPath)) { - writeTags(Collections.singletonList(currentSongPath)); - writeTagsKitkat(); - } else { - Toast.makeText(this, String.format(getString(R.string.saf_pick_file), currentSongPath), Toast.LENGTH_LONG).show(); - SAFUtil.openFilePicker(this); - } + new WriteTagsAsyncTask(this).execute(new WriteTagsAsyncTask.LoadingInfo(getSongPaths(), fieldKeyValueMap, artworkInfo)); } private static class WriteTagsAsyncTask extends DialogAsyncTask { - private WeakReference activity; + Context applicationContext; - public WriteTagsAsyncTask(Activity activity) { - super(activity); - this.activity = new WeakReference<>(activity); + public WriteTagsAsyncTask(Context context) { + super(context); + applicationContext = context; } @Override @@ -367,14 +312,6 @@ public abstract class AbsTagEditorActivity extends AbsBaseActivity { for (String filePath : info.filePaths) { publishProgress(++counter, info.filePaths.size()); try { - Uri safUri = null; - - if (filePath.contains(SAFUtil.SEPARATOR)) { - String[] fragments = filePath.split(SAFUtil.SEPARATOR); - filePath = fragments[0]; - safUri = Uri.parse(fragments[1]); - } - AudioFile audioFile = AudioFileIO.read(new File(filePath)); Tag tag = audioFile.getTagOrCreateAndSetDefault(); @@ -399,10 +336,8 @@ public abstract class AbsTagEditorActivity extends AbsBaseActivity { } } - Activity activity = this.activity.get(); - - SAFUtil.write(activity, audioFile, safUri); - } catch (@NonNull Exception e) { + audioFile.commit(); + } catch (@NonNull CannotReadException | IOException | CannotWriteException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) { e.printStackTrace(); } } @@ -416,18 +351,7 @@ public abstract class AbsTagEditorActivity extends AbsBaseActivity { } } - Collection paths = info.filePaths; - - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) { // remove SAF URI from paths - paths = new ArrayList<>(info.filePaths.size()); - for (String path : info.filePaths) { - if (path.contains(SAFUtil.SEPARATOR)) - path = path.split(SAFUtil.SEPARATOR)[0]; - paths.add(path); - } - } - - return paths.toArray(new String[paths.size()]); + return info.filePaths.toArray(new String[info.filePaths.size()]); } catch (Exception e) { e.printStackTrace(); return null; @@ -447,10 +371,8 @@ public abstract class AbsTagEditorActivity extends AbsBaseActivity { } private void scan(String[] toBeScanned) { - Activity activity = this.activity.get(); - if (activity != null) { - MediaScannerConnection.scanFile(activity, toBeScanned, null, new UpdateToastMediaScannerCompletionListener(activity, toBeScanned)); - } + Context context = getContext(); + MediaScannerConnection.scanFile(applicationContext, toBeScanned, null, context instanceof Activity ? new UpdateToastMediaScannerCompletionListener((Activity) context, toBeScanned) : null); } @Override @@ -499,32 +421,14 @@ public abstract class AbsTagEditorActivity extends AbsBaseActivity { } @Override - protected void onActivityResult(int requestCode, int resultCode, @NonNull Intent intent) { - super.onActivityResult(requestCode, resultCode, intent); + protected void onActivityResult(int requestCode, int resultCode, @NonNull Intent imageReturnedIntent) { + super.onActivityResult(requestCode, resultCode, imageReturnedIntent); switch (requestCode) { case REQUEST_CODE_SELECT_IMAGE: if (resultCode == RESULT_OK) { - Uri selectedImage = intent.getData(); + Uri selectedImage = imageReturnedIntent.getData(); loadImageFromFile(selectedImage); } - break; - - case SAFGuideActivity.REQUEST_CODE_SAF_GUIDE: - SAFUtil.openTreePicker(this); - break; - - case SAFUtil.REQUEST_SAF_PICK_TREE: - if (resultCode == RESULT_OK) { - SAFUtil.saveTreeUri(this, intent); - writeTags(savedSongPaths); - } - break; - - case SAFUtil.REQUEST_SAF_PICK_FILE: - if (resultCode == RESULT_OK) { - writeTags(Collections.singletonList(currentSongPath + SAFUtil.SEPARATOR + intent.getDataString())); - } - break; } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/FileUtil.java b/app/src/main/java/com/kabouzeid/gramophone/util/FileUtil.java index 3a5f43e6..df54e766 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/util/FileUtil.java +++ b/app/src/main/java/com/kabouzeid/gramophone/util/FileUtil.java @@ -12,7 +12,6 @@ import com.kabouzeid.gramophone.loader.SortedCursor; import com.kabouzeid.gramophone.model.Song; import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; @@ -191,15 +190,4 @@ public final class FileUtil { fin.close(); return ret; } - - public static byte[] readBytes(InputStream stream) throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - byte[] buffer = new byte[4096]; - int count; - while ((count = stream.read(buffer)) != -1) { - baos.write(buffer, 0, count); - } - stream.close(); - return baos.toByteArray(); - } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/MusicUtil.java b/app/src/main/java/com/kabouzeid/gramophone/util/MusicUtil.java index 8728564c..4f299565 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/util/MusicUtil.java +++ b/app/src/main/java/com/kabouzeid/gramophone/util/MusicUtil.java @@ -1,6 +1,5 @@ package com.kabouzeid.gramophone.util; -import android.app.Activity; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; @@ -15,6 +14,7 @@ import android.provider.Settings; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.TextUtils; +import android.util.Log; import android.widget.Toast; import com.kabouzeid.gramophone.R; @@ -23,6 +23,7 @@ import com.kabouzeid.gramophone.loader.PlaylistLoader; import com.kabouzeid.gramophone.loader.SongLoader; import com.kabouzeid.gramophone.model.Artist; import com.kabouzeid.gramophone.model.Playlist; +import com.kabouzeid.gramophone.model.PlaylistSong; import com.kabouzeid.gramophone.model.Song; import com.kabouzeid.gramophone.model.lyrics.AbsSynchronizedLyrics; @@ -174,7 +175,7 @@ public class MusicUtil { return albumArtDir; } - public static void deleteTracks(@NonNull final Activity activity, @NonNull final List songs, @Nullable final List safUris) { + public static void deleteTracks(@NonNull final Context context, @NonNull final List songs) { final String[] projection = new String[]{ BaseColumns._ID, MediaStore.MediaColumns.DATA }; @@ -189,7 +190,7 @@ public class MusicUtil { selection.append(")"); try { - final Cursor cursor = activity.getContentResolver().query( + final Cursor cursor = context.getContentResolver().query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(), null, null); if (cursor != null) { @@ -198,35 +199,37 @@ public class MusicUtil { cursor.moveToFirst(); while (!cursor.isAfterLast()) { final int id = cursor.getInt(0); - final Song song = SongLoader.getSong(activity, id); + final Song song = SongLoader.getSong(context, id); MusicPlayerRemote.removeFromQueue(song); cursor.moveToNext(); } // Step 2: Remove selected tracks from the database - activity.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + context.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, selection.toString(), null); // Step 3: Remove files from card cursor.moveToFirst(); - int i = 0; while (!cursor.isAfterLast()) { final String name = cursor.getString(1); - final Uri safUri = safUris == null || safUris.size() <= i ? null : safUris.get(i); - SAFUtil.delete(activity, name, safUri); - i++; - cursor.moveToNext(); + try { // File.delete can throw a security exception + final File f = new File(name); + if (!f.delete()) { + // I'm not sure if we'd ever get here (deletion would + // have to fail, but no exception thrown) + Log.e("MusicUtils", "Failed to delete file " + name); + } + cursor.moveToNext(); + } catch (@NonNull final SecurityException ex) { + cursor.moveToNext(); + } catch (NullPointerException e) { + Log.e("MusicUtils", "Failed to find file " + name); + } } cursor.close(); } - activity.getContentResolver().notifyChange(Uri.parse("content://media"), null); - - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(activity, activity.getString(R.string.deleted_x_songs, songs.size()), Toast.LENGTH_SHORT).show(); - } - }); + context.getContentResolver().notifyChange(Uri.parse("content://media"), null); + Toast.makeText(context, context.getString(R.string.deleted_x_songs, songs.size()), Toast.LENGTH_SHORT).show(); } catch (SecurityException ignored) { } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtil.java b/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtil.java index 69df6d5e..565df23e 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtil.java +++ b/app/src/main/java/com/kabouzeid/gramophone/util/PreferenceUtil.java @@ -3,7 +3,6 @@ package com.kabouzeid.gramophone.util; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; -import android.net.Uri; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.StyleRes; @@ -72,8 +71,6 @@ public final class PreferenceUtil { public static final String SYNCHRONIZED_LYRICS_SHOW = "synchronized_lyrics_show"; - public static final String SAF_SDCARD_URI = "saf_sdcard_uri"; - private static PreferenceUtil sInstance; private final SharedPreferences mPreferences; @@ -409,12 +406,4 @@ public final class PreferenceUtil { public final boolean synchronizedLyricsShow() { return mPreferences.getBoolean(SYNCHRONIZED_LYRICS_SHOW, true); } - - public final String getSAFSDCardUri() { - return mPreferences.getString(SAF_SDCARD_URI, ""); - } - - public final void setSAFSDCardUri(Uri uri) { - mPreferences.edit().putString(SAF_SDCARD_URI, uri.toString()).apply(); - } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/SAFUtil.java b/app/src/main/java/com/kabouzeid/gramophone/util/SAFUtil.java deleted file mode 100644 index 80038481..00000000 --- a/app/src/main/java/com/kabouzeid/gramophone/util/SAFUtil.java +++ /dev/null @@ -1,291 +0,0 @@ -package com.kabouzeid.gramophone.util; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.UriPermission; -import android.net.Uri; -import android.os.Build; -import android.os.ParcelFileDescriptor; -import android.provider.DocumentsContract; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.provider.DocumentFile; -import android.text.TextUtils; -import android.util.Log; -import android.widget.Toast; - -import com.kabouzeid.gramophone.R; -import com.kabouzeid.gramophone.model.Song; - -import org.jaudiotagger.audio.AudioFile; -import org.jaudiotagger.audio.exceptions.CannotWriteException; -import org.jaudiotagger.audio.generic.Utils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class SAFUtil { - - public static final String TAG = SAFUtil.class.getSimpleName(); - public static final String SEPARATOR = "###/SAF/###"; - - public static final int REQUEST_SAF_PICK_FILE = 42; - public static final int REQUEST_SAF_PICK_TREE = 43; - - public static boolean isSAFRequired(File file) { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && !file.canWrite(); - } - - public static boolean isSAFRequired(String path) { - return isSAFRequired(new File(path)); - } - - public static boolean isSAFRequired(AudioFile audio) { - return isSAFRequired(audio.getFile()); - } - - public static boolean isSAFRequired(Song song) { - return isSAFRequired(song.data); - } - - public static boolean isSAFRequired(List paths) { - for (String path : paths) { - if (isSAFRequired(path)) return true; - } - return false; - } - - public static boolean isSAFRequiredForSongs(List songs) { - for (Song song : songs) { - if (isSAFRequired(song)) return true; - } - return false; - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - public static void openFilePicker(Activity activity) { - Intent i = new Intent(Intent.ACTION_CREATE_DOCUMENT); - i.addCategory(Intent.CATEGORY_OPENABLE); - i.setType("audio/*"); - i.putExtra("android.content.extra.SHOW_ADVANCED", true); - activity.startActivityForResult(i, SAFUtil.REQUEST_SAF_PICK_FILE); - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - public static void openFilePicker(Fragment fragment) { - Intent i = new Intent(Intent.ACTION_CREATE_DOCUMENT); - i.addCategory(Intent.CATEGORY_OPENABLE); - i.setType("audio/*"); - i.putExtra("android.content.extra.SHOW_ADVANCED", true); - fragment.startActivityForResult(i, SAFUtil.REQUEST_SAF_PICK_FILE); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public static void openTreePicker(Activity activity) { - Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - i.putExtra("android.content.extra.SHOW_ADVANCED", true); - activity.startActivityForResult(i, SAFUtil.REQUEST_SAF_PICK_TREE); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public static void openTreePicker(Fragment fragment) { - Intent i = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - i.putExtra("android.content.extra.SHOW_ADVANCED", true); - fragment.startActivityForResult(i, SAFUtil.REQUEST_SAF_PICK_TREE); - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - public static void saveTreeUri(Context context, Intent data) { - Uri uri = data.getData(); - context.getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); - PreferenceUtil.getInstance(context).setSAFSDCardUri(uri); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public static boolean isTreeUriSaved(Context context) { - return !TextUtils.isEmpty(PreferenceUtil.getInstance(context).getSAFSDCardUri()); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public static boolean isSDCardAccessGranted(Context context) { - if (!isTreeUriSaved(context)) return false; - - String sdcardUri = PreferenceUtil.getInstance(context).getSAFSDCardUri(); - - List perms = context.getContentResolver().getPersistedUriPermissions(); - for (UriPermission perm : perms) { - if (perm.getUri().toString().equals(sdcardUri) && perm.isWritePermission()) return true; - } - - return false; - } - - /** - * https://github.com/vanilla-music/vanilla-music-tag-editor/commit/e00e87fef289f463b6682674aa54be834179ccf0#diff-d436417358d5dfbb06846746d43c47a5R359 - * Finds needed file through Document API for SAF. It's not optimized yet - you can still gain wrong URI on - * files such as "/a/b/c.mp3" and "/b/a/c.mp3", but I consider it complete enough to be usable. - * - * @param dir - document file representing current dir of search - * @param segments - path segments that are left to find - * @return URI for found file. Null if nothing found. - */ - @Nullable - public static Uri findDocument(DocumentFile dir, List segments) { - for (DocumentFile file : dir.listFiles()) { - int index = segments.indexOf(file.getName()); - if (index == -1) { - continue; - } - - if (file.isDirectory()) { - segments.remove(file.getName()); - return findDocument(file, segments); - } - - if (file.isFile() && index == segments.size() - 1) { - // got to the last part - return file.getUri(); - } - } - - return null; - } - - public static void write(Context context, AudioFile audio, Uri safUri) { - if (isSAFRequired(audio)) { - writeSAF(context, audio, safUri); - } else { - try { - writeFile(audio); - } catch (CannotWriteException e) { - e.printStackTrace(); - } - } - } - - public static void writeFile(AudioFile audio) throws CannotWriteException { - audio.commit(); - } - - public static void writeSAF(Context context, AudioFile audio, Uri safUri) { - Uri uri = null; - - if (context == null) { - Log.e(TAG, "writeSAF: context == null"); - return; - } - - if (isTreeUriSaved(context)) { - List pathSegments = new ArrayList<>(Arrays.asList(audio.getFile().getAbsolutePath().split("/"))); - Uri sdcard = Uri.parse(PreferenceUtil.getInstance(context).getSAFSDCardUri()); - uri = findDocument(DocumentFile.fromTreeUri(context, sdcard), pathSegments); - } - - if (uri == null) { - uri = safUri; - } - - if (uri == null) { - Log.e(TAG, "writeSAF: Can't get SAF URI"); - toast(context, context.getString(R.string.saf_error_uri)); - return; - } - - try { - // copy file to app folder to use jaudiotagger - final File original = audio.getFile(); - File temp = File.createTempFile("tmp-media", '.' + Utils.getExtension(original)); - Utils.copy(original, temp); - temp.deleteOnExit(); - audio.setFile(temp); - writeFile(audio); - - ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "rw"); - if (pfd == null) { - Log.e(TAG, "writeSAF: SAF provided incorrect URI: " + uri); - return; - } - - // now read persisted data and write it to real FD provided by SAF - FileInputStream fis = new FileInputStream(temp); - byte[] audioContent = FileUtil.readBytes(fis); - FileOutputStream fos = new FileOutputStream(pfd.getFileDescriptor()); - fos.write(audioContent); - fos.close(); - - temp.delete(); - } catch (final Exception e) { - Log.e(TAG, "writeSAF: Failed to write to file descriptor provided by SAF", e); - - toast(context, String.format(context.getString(R.string.saf_write_failed), e.getLocalizedMessage())); - } - } - - public static void delete(Context context, String path, Uri safUri) { - if (isSAFRequired(path)) { - deleteSAF(context, path, safUri); - } else { - try { - deleteFile(path); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - public static void deleteFile(String path) { - new File(path).delete(); - } - - @TargetApi(Build.VERSION_CODES.KITKAT) - public static void deleteSAF(Context context, String path, Uri safUri) { - Uri uri = null; - - if (context == null) { - Log.e(TAG, "deleteSAF: context == null"); - return; - } - - if (isTreeUriSaved(context)) { - List pathSegments = new ArrayList<>(Arrays.asList(path.split("/"))); - Uri sdcard = Uri.parse(PreferenceUtil.getInstance(context).getSAFSDCardUri()); - uri = findDocument(DocumentFile.fromTreeUri(context, sdcard), pathSegments); - } - - if (uri == null) { - uri = safUri; - } - - if (uri == null) { - Log.e(TAG, "deleteSAF: Can't get SAF URI"); - toast(context, context.getString(R.string.saf_error_uri)); - return; - } - - try { - DocumentsContract.deleteDocument(context.getContentResolver(), uri); - } catch (final Exception e) { - Log.e(TAG, "deleteSAF: Failed to delete a file descriptor provided by SAF", e); - - toast(context, String.format(context.getString(R.string.saf_delete_failed), e.getLocalizedMessage())); - } - } - - private static void toast(final Context context, final String message) { - if (context instanceof Activity) { - ((Activity) context).runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); - } - }); - } - } - -} diff --git a/app/src/main/res/drawable-v21/saf_guide_1.png b/app/src/main/res/drawable-v21/saf_guide_1.png deleted file mode 100644 index d31be5b5..00000000 Binary files a/app/src/main/res/drawable-v21/saf_guide_1.png and /dev/null differ diff --git a/app/src/main/res/drawable-v21/saf_guide_2.png b/app/src/main/res/drawable-v21/saf_guide_2.png deleted file mode 100644 index 24a9c294..00000000 Binary files a/app/src/main/res/drawable-v21/saf_guide_2.png and /dev/null differ diff --git a/app/src/main/res/drawable-v21/saf_guide_3.png b/app/src/main/res/drawable-v21/saf_guide_3.png deleted file mode 100644 index 2317a82c..00000000 Binary files a/app/src/main/res/drawable-v21/saf_guide_3.png and /dev/null differ diff --git a/app/src/main/res/drawable-v26/saf_guide_1.png b/app/src/main/res/drawable-v26/saf_guide_1.png deleted file mode 100644 index f4904601..00000000 Binary files a/app/src/main/res/drawable-v26/saf_guide_1.png and /dev/null differ diff --git a/app/src/main/res/drawable-v26/saf_guide_2.png b/app/src/main/res/drawable-v26/saf_guide_2.png deleted file mode 100644 index ddc84b27..00000000 Binary files a/app/src/main/res/drawable-v26/saf_guide_2.png and /dev/null differ diff --git a/app/src/main/res/drawable-v26/saf_guide_3.png b/app/src/main/res/drawable-v26/saf_guide_3.png deleted file mode 100644 index c3ada22a..00000000 Binary files a/app/src/main/res/drawable-v26/saf_guide_3.png and /dev/null differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0f8d58e0..050cdca7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -262,7 +262,6 @@ Copied device info to clipboard. Your account data is only used for authentication. You will be forwarded to the issue tracker website. - Deleting songs @string/action_shuffle_all Shuffle @@ -273,18 +272,4 @@ Playlist is empty The playing notification provides actions for play/pause etc. Playing notification - - Can\'t get SAF URI - File write failed: %s - File delete failed: %s - SD card access required. Please pick root directory of SD card - File access required. Pick %s - - %s needs SD card access - Enable \'Show SD card\' in overflow menu - Open navigation drawer - Select your SD card in navigation drawer - You need to select your SD card root directory - Tap \'select\' button at the bottom of the screen - Do not open any subfolders