Support for adding any content:// style URI

This commit is contained in:
Adrian Ulrich 2017-06-10 12:31:26 +02:00
parent 9cdce55d95
commit 3481503633
3 changed files with 189 additions and 40 deletions

View File

@ -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"/>

View File

@ -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>

View File

@ -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;
}
}