diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2c35d52b..7fa15839 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -174,7 +174,6 @@ THE SOFTWARE.
-
diff --git a/res/layout/audiopicker.xml b/res/layout/audiopicker.xml
index 38900545..185440b9 100644
--- a/res/layout/audiopicker.xml
+++ b/res/layout/audiopicker.xml
@@ -43,12 +43,18 @@ THE SOFTWARE.
android:src="@drawable/icon" />
+
diff --git a/src/ch/blinkenlights/android/vanilla/AudioPickerActivity.java b/src/ch/blinkenlights/android/vanilla/AudioPickerActivity.java
index 7124633f..e089a508 100644
--- a/src/ch/blinkenlights/android/vanilla/AudioPickerActivity.java
+++ b/src/ch/blinkenlights/android/vanilla/AudioPickerActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014-2016 Adrian Ulrich
+ * Copyright (C) 2014-2017 Adrian Ulrich
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -21,19 +21,51 @@ import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.View;
import android.view.Window;
import android.widget.TextView;
import android.widget.Button;
+import android.widget.ProgressBar;
import java.io.File;
-
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
public class AudioPickerActivity extends PlaybackActivity {
-
+ /**
+ * The cancel button
+ */
+ private Button mCancelButton;
+ /**
+ * The enqueue button
+ */
+ private Button mEnqueueButton;
+ /**
+ * The play button
+ */
+ private Button mPlayButton;
+ /**
+ * The general purpose text view
+ */
+ private TextView mTextView;
+ /**
+ * Our endless progress bar
+ */
+ private ProgressBar mProgressBar;
+ /**
+ * Song we found, or failed to find
+ */
private Song mSong;
+ /**
+ * Our async worker task to search for mSong
+ */
+ private AudioPickerWorker mWorker;
+
@Override
public void onCreate(Bundle icicle) {
@@ -56,30 +88,30 @@ public class AudioPickerActivity extends PlaybackActivity {
return;
}
- mSong = getSongForUri(uri);
- if (mSong == null) {
- // unsupported intent or song not found
- finish();
- return;
- }
-
+ // Basic sanity test done: Create worker
+ // and setup window.
+ mWorker = new AudioPickerWorker();
+ // Basic sanity test done: Setup window
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.audiopicker);
- String displayName = new File(mSong.path).getName();
- TextView filePath = (TextView)findViewById(R.id.filepath);
- filePath.setText(displayName);
+ // ...and resolve + bind all elements
+ mCancelButton = (Button)findViewById(R.id.cancel);
+ mCancelButton.setEnabled(true);
+ mCancelButton.setOnClickListener(this);
- // Bind all 3 clickbuttons
- Button cancelButton = (Button)findViewById(R.id.cancel);
- cancelButton.setOnClickListener(this);
- Button enqueueButton = (Button)findViewById(R.id.enqueue);
- enqueueButton.setOnClickListener(this);
- enqueueButton.setEnabled( PlaybackService.hasInstance() ); // only active if vanilla is still running
- Button playButton = (Button)findViewById(R.id.play);
- playButton.setOnClickListener(this);
+ mEnqueueButton = (Button)findViewById(R.id.enqueue);
+ mEnqueueButton.setOnClickListener(this);
+ mPlayButton = (Button)findViewById(R.id.play);
+ mPlayButton.setOnClickListener(this);
+
+ mTextView = (TextView)findViewById(R.id.filepath);
+ mProgressBar = (ProgressBar)findViewById(R.id.progress);
+
+ // UI is ready, we can now execute the actual task.
+ mWorker.execute(uri);
}
@Override
@@ -96,6 +128,7 @@ public class AudioPickerActivity extends PlaybackActivity {
mode = SongTimeline.MODE_ENQUEUE;
break;
default:
+ mWorker.cancel(false);
finish();
return;
}
@@ -114,43 +147,151 @@ public class AudioPickerActivity extends PlaybackActivity {
finish();
}
+ /**
+ * Called after AudioPickerWorker finished.
+ * This inspects the result and sets up the view
+ * if we got a song, cancels the activity otherwise.
+ *
+ * @param song the song we found, may be null
+ */
+ private void onSongResolved(Song song) {
+ mSong = song;
+
+ if (song == null) {
+ finish();
+ return;
+ }
+
+ // Enable enqueue button if playback service is already
+ // active (= we are most likely playing a song)
+ if (PlaybackService.hasInstance())
+ mEnqueueButton.setEnabled(true);
+
+ mPlayButton.setEnabled(true);
+
+ // Set the title to display, we use the filename as a fallback
+ // if the title is empty for whatever reason.
+ String displayName = song.title;
+ if ("".equals(song.title))
+ displayName = new File(song.path).getName();
+
+ mTextView.setText(song.title);
+ mTextView.setVisibility(View.VISIBLE);
+ mProgressBar.setVisibility(View.GONE);
+ }
/**
- * Attempts to resolve given uri to a song object
- *
- * @param uri The uri to resolve
- * @return A song object, null on failure
+ * Background worker to resolve a song from an Uri.
+ * Will call onSongResolved(Song) on completion.
*/
- private Song getSongForUri(Uri uri) {
- Song song = new Song(-1);
- Cursor cursor = null;
+ private class AudioPickerWorker extends AsyncTask {
+ @Override
+ protected Song doInBackground(Uri... uri) {
+ return getSongForUri(uri[0]);
+ }
+ @Override
+ protected void onPostExecute(Song song) {
+ onSongResolved(song);
+ }
- if (uri.getScheme().equals("content")) {
- // check if the native content resolver has a path for this
+ /**
+ * Attempts to resolve given uri to a song object
+ *
+ * @param uri The uri to resolve
+ * @return A song object, null on failure
+ */
+ private Song getSongForUri(Uri uri) {
+ Song song = new Song(-1);
+ Cursor cursor = null;
+
+ if (uri.getScheme().equals("content")) {
+ if (uri.getHost().equals("media")) {
+ cursor = getCursorForMediaContent(uri);
+ } else {
+ cursor = getCursorForAnyContent(uri);
+ }
+ }
+
+ if (uri.getScheme().equals("file")) {
+ cursor = MediaUtils.getCursorForFileQuery(uri.getPath());
+ }
+
+ if (cursor != null) {
+ if (cursor.moveToNext()) {
+ song.populate(cursor);
+ }
+ cursor.close();
+ }
+ return song.isFilled() ? song : null;
+ }
+
+ /**
+ * Returns the cursor for a file stored in androids media library.
+ *
+ * @param uri the uri to query - expected to be content://media/...
+ * @return cursor the cursor, may be null.
+ */
+ private Cursor getCursorForMediaContent(Uri uri) {
+ Cursor cursor = null;
Cursor pathCursor = getContentResolver().query(uri, new String[]{ MediaStore.Audio.Media.DATA }, null, null, null);
if (pathCursor != null) {
if (pathCursor.moveToNext()) {
String mediaPath = pathCursor.getString(0);
if (mediaPath != null) { // this happens on android 4.x sometimes?!
QueryTask query = MediaUtils.buildFileQuery(mediaPath, Song.FILLED_PROJECTION);
- cursor = query.runQuery(this);
+ cursor = query.runQuery(getApplicationContext());
}
}
pathCursor.close();
}
+ return cursor;
}
- if (uri.getScheme().equals("file")) {
- cursor = MediaUtils.getCursorForFileQuery(uri.getPath());
- }
+ /**
+ * Returns the cursor for any content:// uri. The contents will be stored
+ * in our application cache.
+ *
+ * @param uri the uri to query
+ * @return cursor the cursor, may be null.
+ */
+ private Cursor getCursorForAnyContent(Uri uri) {
+ Cursor cursor = null;
+ File outFile = null;
+ InputStream ins = null;
+ OutputStream ous = null;
- if (cursor != null) {
- if (cursor.moveToNext()) {
- song.populate(cursor);
+ // Cache a local copy, this should really run in a background thread, but we
+ // are usually reading local files, which is fast enough.
+ try {
+ byte[] buffer = new byte[8192];
+ ins = getContentResolver().openInputStream(uri);
+ outFile = File.createTempFile("cached-download-", ".bin", getCacheDir());
+ ous = new FileOutputStream(outFile);
+
+ int len = 0;
+ while ((len = ins.read(buffer)) != -1) {
+ ous.write(buffer, 0, len);
+ if (isCancelled()) {
+ throw new IOException("Canceled");
+ }
+ }
+ outFile.deleteOnExit();
+ } catch (IOException e) {
+ if (outFile != null) {
+ outFile.delete();
+ }
+ outFile = null; // signals failure.
+ } finally {
+ try { if (ins != null) ins.close(); } catch(IOException e) {}
+ try { if (ous != null) ous.close(); } catch(IOException e) {}
}
- cursor.close();
+
+ if (outFile != null) {
+ cursor = MediaUtils.getCursorForFileQuery(outFile.getPath());
+ }
+ return cursor;
}
- return song.isFilled() ? song : null;
}
+
}