Support for adding any content:// style URI
This commit is contained in:
parent
9cdce55d95
commit
3481503633
@ -174,7 +174,6 @@ THE SOFTWARE.
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="content"/>
|
||||
<data android:host="media"/>
|
||||
<data android:mimeType="audio/*"/>
|
||||
<data android:mimeType="application/ogg"/>
|
||||
<data android:mimeType="application/x-ogg"/>
|
||||
|
@ -43,12 +43,18 @@ THE SOFTWARE.
|
||||
android:src="@drawable/icon" />
|
||||
<TextView
|
||||
android:id="@+id/filepath"
|
||||
android:visibility="gone"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dip"
|
||||
android:layout_marginTop="16dip"
|
||||
android:layout_marginLeft="16dip"
|
||||
android:layout_marginRight="8dip" />
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|center_horizontal" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
@ -63,6 +69,7 @@ THE SOFTWARE.
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@android:string/cancel"
|
||||
android:enabled="false"
|
||||
android:singleLine="true" />
|
||||
<Button
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
@ -71,6 +78,7 @@ THE SOFTWARE.
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/enqueue"
|
||||
android:enabled="false"
|
||||
android:singleLine="true" />
|
||||
<Button
|
||||
style="?android:attr/buttonBarButtonStyle"
|
||||
@ -79,6 +87,7 @@ THE SOFTWARE.
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/play"
|
||||
android:enabled="false"
|
||||
android:singleLine="true" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2014-2016 Adrian Ulrich <adrian@blinkenlights.ch>
|
||||
* Copyright (C) 2014-2017 Adrian Ulrich <adrian@blinkenlights.ch>
|
||||
*
|
||||
* 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<Uri, Void, Song> {
|
||||
@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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user