diff options
Diffstat (limited to 'src/de/danoeh/antennapod')
60 files changed, 1371 insertions, 572 deletions
diff --git a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java index 0a4c8ae14..db4373036 100644 --- a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java +++ b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java @@ -12,7 +12,9 @@ import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.view.Window; +import android.view.View.OnLongClickListener; import android.widget.ArrayAdapter; +import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView.ScaleType; import android.widget.ListView; @@ -22,11 +24,14 @@ import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.ChapterListAdapter; import de.danoeh.antennapod.asynctask.ImageLoader; +import de.danoeh.antennapod.dialog.VariableSpeedDialog; import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.MediaType; import de.danoeh.antennapod.feed.SimpleChapter; import de.danoeh.antennapod.fragment.CoverFragment; import de.danoeh.antennapod.fragment.ItemDescriptionFragment; +import de.danoeh.antennapod.preferences.UserPreferences; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.service.PlaybackService; import de.danoeh.antennapod.util.playback.ExternalMedia; import de.danoeh.antennapod.util.playback.Playable; @@ -56,6 +61,7 @@ public class AudioplayerActivity extends MediaplayerActivity { private TextView txtvTitle; private TextView txtvFeed; + private Button butPlaybackSpeed; private ImageButton butNavLeft; private ImageButton butNavRight; @@ -218,7 +224,7 @@ public class AudioplayerActivity extends MediaplayerActivity { if (savedPosition != -1) { switchToFragment(savedPosition); } - + } @Override @@ -363,6 +369,7 @@ public class AudioplayerActivity extends MediaplayerActivity { txtvFeed = (TextView) findViewById(R.id.txtvFeed); butNavLeft = (ImageButton) findViewById(R.id.butNavLeft); butNavRight = (ImageButton) findViewById(R.id.butNavRight); + butPlaybackSpeed = (Button) findViewById(R.id.butPlaybackSpeed); butNavLeft.setOnClickListener(new OnClickListener() { @@ -390,6 +397,65 @@ public class AudioplayerActivity extends MediaplayerActivity { } } }); + + butPlaybackSpeed.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + if (controller != null && controller.canSetPlaybackSpeed()) { + String[] availableSpeeds = UserPreferences + .getPlaybackSpeedArray(); + String currentSpeed = UserPreferences.getPlaybackSpeed(); + + // Provide initial value in case the speed list has changed + // out from under us + // and our current speed isn't in the new list + String newSpeed; + if (availableSpeeds.length > 0) { + newSpeed = availableSpeeds[0]; + } else { + newSpeed = "1.0"; + } + + for (int i = 0; i < availableSpeeds.length; i++) { + if (availableSpeeds[i].equals(currentSpeed)) { + if (i == availableSpeeds.length - 1) { + newSpeed = availableSpeeds[0]; + } else { + newSpeed = availableSpeeds[i + 1]; + } + break; + } + } + UserPreferences.setPlaybackSpeed(newSpeed); + controller.setPlaybackSpeed(Float.parseFloat(newSpeed)); + } + } + }); + + butPlaybackSpeed.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + VariableSpeedDialog.showDialog(AudioplayerActivity.this); + return true; + } + }); + } + + @Override + protected void onPlaybackSpeedChange() { + super.onPlaybackSpeedChange(); + updateButPlaybackSpeed(); + } + + private void updateButPlaybackSpeed() { + if (controller == null + || (controller.getCurrentPlaybackSpeedMultiplier() == -1)) { + butPlaybackSpeed.setVisibility(View.GONE); + } else { + butPlaybackSpeed.setVisibility(View.VISIBLE); + butPlaybackSpeed.setText(UserPreferences.getPlaybackSpeed()); + } } @Override @@ -421,7 +487,7 @@ public class AudioplayerActivity extends MediaplayerActivity { ((AudioplayerContentFragment) currentlyShownFragment) .onDataSetChanged(media); } - + updateButPlaybackSpeed(); } public void notifyMediaPositionChanged() { diff --git a/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java b/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java index 984491174..62273c960 100644 --- a/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java +++ b/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java @@ -347,7 +347,9 @@ public class DirectoryChooserActivity extends ActionBarActivity { * CREATE_DIRECTORY_NAME. */ private int createFolder() { - if (selectedDir != null && selectedDir.canWrite()) { + if (selectedDir == null) { + return R.string.create_folder_error; + } else if (selectedDir.canWrite()) { File newDir = new File(selectedDir, CREATE_DIRECTORY_NAME); if (!newDir.exists()) { boolean result = newDir.mkdir(); @@ -359,10 +361,8 @@ public class DirectoryChooserActivity extends ActionBarActivity { } else { return R.string.create_folder_error_already_exists; } - } else if (selectedDir.canWrite() == false) { - return R.string.create_folder_error_no_write_access; } else { - return R.string.create_folder_error; + return R.string.create_folder_error_no_write_access; } } diff --git a/src/de/danoeh/antennapod/activity/DownloadActivity.java b/src/de/danoeh/antennapod/activity/DownloadActivity.java index 40c75d336..57c86f760 100644 --- a/src/de/danoeh/antennapod/activity/DownloadActivity.java +++ b/src/de/danoeh/antennapod/activity/DownloadActivity.java @@ -121,7 +121,7 @@ public class DownloadActivity extends ActionBarActivity implements contentRefresher.cancel(true); } contentRefresher = new AsyncTask<Void, Void, Void>() { - private final int WAITING_INTERVALL = 1000; + private static final int WAITING_INTERVAL = 1000; @Override protected void onProgressUpdate(Void... values) { @@ -137,7 +137,7 @@ public class DownloadActivity extends ActionBarActivity implements protected Void doInBackground(Void... params) { while (!isCancelled()) { try { - Thread.sleep(WAITING_INTERVALL); + Thread.sleep(WAITING_INTERVAL); publishProgress(); } catch (InterruptedException e) { return null; diff --git a/src/de/danoeh/antennapod/activity/DownloadLogActivity.java b/src/de/danoeh/antennapod/activity/DownloadLogActivity.java index 949834596..5e48371b8 100644 --- a/src/de/danoeh/antennapod/activity/DownloadLogActivity.java +++ b/src/de/danoeh/antennapod/activity/DownloadLogActivity.java @@ -37,7 +37,7 @@ public class DownloadLogActivity extends ActionBarActivity { getSupportActionBar().setDisplayHomeAsUpEnabled(true); - listview = (ListView) findViewById(R.layout.listview_activity); + listview = (ListView) findViewById(R.id.listview); dla = new DownloadLogAdapter(this, itemAccess); listview.setAdapter(dla); diff --git a/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java b/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java index c53a4cc7b..8fba44e5c 100644 --- a/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java +++ b/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java @@ -7,6 +7,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.TypedArray; +import android.media.AudioManager; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.FragmentManager; @@ -57,6 +58,7 @@ public class FeedItemlistActivity extends ActionBarActivity { getSupportActionBar().setDisplayHomeAsUpEnabled(true); setContentView(R.layout.feeditemlist_activity); + setVolumeControlStream(AudioManager.STREAM_MUSIC); long feedId = getIntent().getLongExtra( FeedlistFragment.EXTRA_SELECTED_FEED, -1); @@ -122,6 +124,8 @@ public class FeedItemlistActivity extends ActionBarActivity { SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search_item)); + + searchView.setIconifiedByDefault(true); searchView.setSearchableInfo( diff --git a/src/de/danoeh/antennapod/activity/ItemviewActivity.java b/src/de/danoeh/antennapod/activity/ItemviewActivity.java index 4c0fdfdba..c2833760d 100644 --- a/src/de/danoeh/antennapod/activity/ItemviewActivity.java +++ b/src/de/danoeh/antennapod/activity/ItemviewActivity.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.activity; +import android.media.AudioManager; import android.os.AsyncTask; import android.os.Bundle; import android.support.v4.app.FragmentManager; @@ -45,6 +46,7 @@ public class ItemviewActivity extends ActionBarActivity { requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); getSupportActionBar().setDisplayShowTitleEnabled(false); EventDistributor.getInstance().register(contentUpdate); + setVolumeControlStream(AudioManager.STREAM_MUSIC); long itemId = getIntent().getLongExtra( ItemlistFragment.EXTRA_SELECTED_FEEDITEM, -1); diff --git a/src/de/danoeh/antennapod/activity/MainActivity.java b/src/de/danoeh/antennapod/activity/MainActivity.java index 20c53553b..4f25a07f1 100644 --- a/src/de/danoeh/antennapod/activity/MainActivity.java +++ b/src/de/danoeh/antennapod/activity/MainActivity.java @@ -6,6 +6,7 @@ import android.app.SearchManager; import android.app.SearchableInfo; import android.content.Context; import android.content.Intent; +import android.media.AudioManager; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentPagerAdapter; @@ -55,8 +56,9 @@ public class MainActivity extends ActionBarActivity { StorageUtils.checkStorageAvailability(this); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.main); + setVolumeControlStream(AudioManager.STREAM_MUSIC); - getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); viewpager = (ViewPager) findViewById(R.id.viewpager); pagerAdapter = new TabsAdapter(this, viewpager); @@ -181,7 +183,12 @@ public class MainActivity extends ActionBarActivity { SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); - SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search_item)); + MenuItem searchItem = menu.findItem(R.id.search_item); + SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem); + if (searchView == null) { + MenuItemCompat.setActionView(searchItem, new SearchView(this)); + searchView = (SearchView) MenuItemCompat.getActionView(searchItem); + } searchView.setIconifiedByDefault(true); SearchableInfo info = searchManager.getSearchableInfo(getComponentName()); diff --git a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java index b5b694995..af244f2ed 100644 --- a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -4,6 +4,7 @@ import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.graphics.PixelFormat; +import android.media.AudioManager; import android.net.Uri; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; @@ -128,10 +129,19 @@ public abstract class MediaplayerActivity extends ActionBarActivity public void onPlaybackEnd() { finish(); } + + @Override + public void onPlaybackSpeedChange() { + MediaplayerActivity.this.onPlaybackSpeedChange(); + } }; } + protected void onPlaybackSpeedChange() { + + } + protected void onServiceQueried() { supportInvalidateOptionsMenu(); } @@ -143,8 +153,9 @@ public abstract class MediaplayerActivity extends ActionBarActivity if (AppConfig.DEBUG) Log.d(TAG, "Creating Activity"); StorageUtils.checkStorageAvailability(this); + setVolumeControlStream(AudioManager.STREAM_MUSIC); - orientation = getResources().getConfiguration().orientation; + orientation = getResources().getConfiguration().orientation; getWindow().setFormat(PixelFormat.TRANSPARENT); getSupportActionBar().setDisplayHomeAsUpEnabled(true); } diff --git a/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java b/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java index c039e96f8..e3d77a68a 100644 --- a/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java +++ b/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java @@ -22,7 +22,7 @@ import de.danoeh.antennapod.preferences.UserPreferences; public class MiroGuideCategoryActivity extends ActionBarActivity { private static final String TAG = "MiroGuideCategoryActivity"; - public static String EXTRA_CATEGORY = "category"; + public static final String EXTRA_CATEGORY = "category"; private ViewPager viewpager; private CategoryPagerAdapter pagerAdapter; diff --git a/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java b/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java index cb1c66cab..fbac7057d 100644 --- a/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java +++ b/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java @@ -157,7 +157,7 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity { @Override public void run() { - String reasonDetailed = new String(); + String reasonDetailed = ""; boolean successful = false; FeedHandler handler = new FeedHandler(); try { diff --git a/src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java b/src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java index dc698a851..58e3a96dd 100644 --- a/src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java +++ b/src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java @@ -7,6 +7,7 @@ import java.net.URL; import android.app.AlertDialog; import android.os.Bundle; import de.danoeh.antennapod.preferences.UserPreferences; +import de.danoeh.antennapod.util.LangUtils; /** Lets the user start the OPML-import process. */ public class OpmlImportFromIntentActivity extends OpmlImportBaseActivity { @@ -20,7 +21,8 @@ public class OpmlImportFromIntentActivity extends OpmlImportBaseActivity { try { URL mOpmlURL = new URL(getIntent().getData().toString()); - BufferedReader in = new BufferedReader(new InputStreamReader(mOpmlURL.openStream())); + BufferedReader in = new BufferedReader(new InputStreamReader(mOpmlURL.openStream(), + LangUtils.UTF_8)); startImport(in); } catch (Exception e) { new AlertDialog.Builder(this).setMessage("Cannot open XML - Reason: " + e.getMessage()).show(); diff --git a/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java b/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java index 259689abf..ece78006f 100644 --- a/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java +++ b/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java @@ -1,8 +1,10 @@ package de.danoeh.antennapod.activity; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileReader; +import java.io.InputStreamReader; +import java.io.IOException; import java.io.Reader; import android.app.AlertDialog; @@ -20,6 +22,7 @@ import android.widget.Toast; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.preferences.UserPreferences; +import de.danoeh.antennapod.util.LangUtils; import de.danoeh.antennapod.util.StorageUtils; /** @@ -125,8 +128,10 @@ public class OpmlImportFromPathActivity extends OpmlImportBaseActivity { } private void startImport(File file) { + Reader mReader = null; try { - Reader mReader = new FileReader(file); + mReader = new InputStreamReader(new FileInputStream(file), + LangUtils.UTF_8); if (AppConfig.DEBUG) Log.d(TAG, "Parsing " + file.toString()); startImport(mReader); } catch (FileNotFoundException e) { diff --git a/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java b/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java index 77dae303e..e376b08b8 100644 --- a/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java +++ b/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java @@ -51,6 +51,7 @@ public class OrganizeQueueActivity extends ActionBarActivity implements listView = (DragSortListView) findViewById(android.R.id.list); listView.setDropListener(dropListener); listView.setRemoveListener(removeListener); + listView.setEmptyView(findViewById(android.R.id.empty)); loadData(); undoBarController = new UndoBarController(findViewById(R.id.undobar), @@ -155,9 +156,11 @@ public class OrganizeQueueActivity extends ActionBarActivity implements public void onUndo(Parcelable token) { // Perform the undo UndoToken undoToken = (UndoToken) token; - long itemId = undoToken.getFeedItemId(); - int position = undoToken.getPosition(); - DBWriter.addQueueItemAt(OrganizeQueueActivity.this, itemId, position, false); + if (token != null) { + long itemId = undoToken.getFeedItemId(); + int position = undoToken.getPosition(); + DBWriter.addQueueItemAt(OrganizeQueueActivity.this, itemId, position, false); + } } private static class OrganizeAdapter extends BaseAdapter { diff --git a/src/de/danoeh/antennapod/activity/PreferenceActivity.java b/src/de/danoeh/antennapod/activity/PreferenceActivity.java index 880724c28..96471d06d 100644 --- a/src/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/src/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -25,6 +25,7 @@ import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.asynctask.FlattrClickWorker; import de.danoeh.antennapod.asynctask.OpmlExportWorker; +import de.danoeh.antennapod.dialog.VariableSpeedDialog; import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.util.flattr.FlattrUtils; @@ -41,7 +42,8 @@ public class PreferenceActivity extends android.preference.PreferenceActivity { private static final String PREF_ABOUT = "prefAbout"; private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir"; private static final String AUTO_DL_PREF_SCREEN = "prefAutoDownloadSettings"; - + private static final String PREF_PLAYBACK_SPEED_LAUNCHER = "prefPlaybackSpeedLauncher"; + private CheckBoxPreference[] selectedNetworks; @SuppressWarnings("deprecation") @@ -156,6 +158,14 @@ public class PreferenceActivity extends android.preference.PreferenceActivity { return true; } }); + findPreference(PREF_PLAYBACK_SPEED_LAUNCHER) + .setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + VariableSpeedDialog.showDialog(PreferenceActivity.this); + return true; + } + }); buildUpdateIntervalPreference(); buildAutodownloadSelectedNetworsPreference(); setSelectedNetworksEnabled(UserPreferences diff --git a/src/de/danoeh/antennapod/activity/SearchActivity.java b/src/de/danoeh/antennapod/activity/SearchActivity.java index 257ae86ae..86f7301cf 100644 --- a/src/de/danoeh/antennapod/activity/SearchActivity.java +++ b/src/de/danoeh/antennapod/activity/SearchActivity.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; import android.annotation.SuppressLint; +import android.app.Activity; import android.app.SearchManager; import android.content.Intent; import android.os.Bundle; @@ -140,32 +141,34 @@ public class SearchActivity extends ActionBarActivity implements AdapterView.OnI @Override public void run() { Log.d(TAG, "Starting background work"); + final Activity activity = SearchActivity.this; final List<SearchResult> result = FeedSearcher - .performSearch(SearchActivity.this, query, feedID); - if (SearchActivity.this != null) { - SearchActivity.this.runOnUiThread(new Runnable() { - - @Override - public void run() { - if (AppConfig.DEBUG) - Log.d(TAG, "Background work finished"); - if (AppConfig.DEBUG) - Log.d(TAG, "Found " + result.size() - + " results"); - - searchAdapter.clear(); - searchAdapter.addAll(result); - searchAdapter.notifyDataSetChanged(); - txtvStatus - .setText(R.string.search_status_no_results); - if (!searchAdapter.isEmpty()) { - txtvStatus.setVisibility(View.GONE); - } else { - txtvStatus.setVisibility(View.VISIBLE); - } + .performSearch(activity, query, feedID); + activity.runOnUiThread(new Runnable() { + + @Override + public void run() { + if (AppConfig.DEBUG) + Log.d(TAG, "Background work finished"); + if (AppConfig.DEBUG) + Log.d(TAG, "Found " + result.size() + + " results"); + + searchAdapter.clear(); + for (SearchResult s : result) { + searchAdapter.add(s); } - }); - } + searchAdapter.notifyDataSetChanged(); + txtvStatus + .setText(R.string.search_status_no_results); + if (!searchAdapter.isEmpty()) { + txtvStatus.setVisibility(View.GONE); + } else { + txtvStatus.setVisibility(View.VISIBLE); + } + } + }); + } }; thread.start(); diff --git a/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java b/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java index 3e9b586ce..5a8dfb2bf 100644 --- a/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java +++ b/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java @@ -144,53 +144,6 @@ public class ChapterListAdapter extends ArrayAdapter<Chapter> { TextView link; } - private LinkMovementMethod linkMovementMethod = new LinkMovementMethod() { - - @Override - public boolean onTouchEvent(TextView widget, Spannable buffer, - MotionEvent event) { - Object text = widget.getText(); - if (text instanceof Spanned) { - int action = event.getAction(); - - if (action == MotionEvent.ACTION_UP - || action == MotionEvent.ACTION_DOWN) { - int x = (int) event.getX(); - int y = (int) event.getY(); - - x -= widget.getTotalPaddingLeft(); - y -= widget.getTotalPaddingTop(); - - x += widget.getScrollX(); - y += widget.getScrollY(); - - Layout layout = widget.getLayout(); - int line = layout.getLineForVertical(y); - int off = layout.getOffsetForHorizontal(line, x); - - ClickableSpan[] link = buffer.getSpans(off, off, - ClickableSpan.class); - - if (link.length != 0) { - if (action == MotionEvent.ACTION_UP) { - link[0].onClick(widget); - } else if (action == MotionEvent.ACTION_DOWN) { - Selection.setSelection(buffer, - buffer.getSpanStart(link[0]), - buffer.getSpanEnd(link[0])); - } - return true; - } - } - - } - - return false; - - } - - }; - @Override public int getCount() { // ignore invalid chapters diff --git a/src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java b/src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java index 4380bc6ea..7ba68ae22 100644 --- a/src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java +++ b/src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java @@ -47,7 +47,7 @@ public class BitmapDecodeWorkerTask extends Thread { */ protected boolean tagsMatching(ImageView target) { return target.getTag() == null - || target.getTag() == imageResource.getImageLoaderCacheKey(); + || target.getTag().equals(imageResource.getImageLoaderCacheKey()); } protected void onPostExecute() { diff --git a/src/de/danoeh/antennapod/asynctask/ImageLoader.java b/src/de/danoeh/antennapod/asynctask/ImageLoader.java index fb807f469..45a99e704 100644 --- a/src/de/danoeh/antennapod/asynctask/ImageLoader.java +++ b/src/de/danoeh/antennapod/asynctask/ImageLoader.java @@ -77,7 +77,7 @@ public class ImageLoader { }); } - public static ImageLoader getInstance() { + public static synchronized ImageLoader getInstance() { if (singleton == null) { singleton = new ImageLoader(); } diff --git a/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java b/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java index e14e22917..745bc7079 100644 --- a/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java +++ b/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java @@ -1,8 +1,9 @@ package de.danoeh.antennapod.asynctask; import java.io.File; -import java.io.FileWriter; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStreamWriter; import java.util.Arrays; import android.annotation.SuppressLint; @@ -16,6 +17,7 @@ import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.opml.OpmlWriter; import de.danoeh.antennapod.preferences.UserPreferences; +import de.danoeh.antennapod.util.LangUtils; import de.danoeh.antennapod.storage.DBReader; /** Writes an OPML file into the export directory in the background. */ @@ -49,13 +51,21 @@ public class OpmlExportWorker extends AsyncTask<Void, Void, Void> { output.delete(); } } + OutputStreamWriter writer = null; try { - FileWriter writer = new FileWriter(output); + writer = new OutputStreamWriter(new FileOutputStream(output), LangUtils.UTF_8); opmlWriter.writeDocument(DBReader.getFeedList(context), writer); - writer.close(); } catch (IOException e) { e.printStackTrace(); exception = e; + } finally { + if (writer != null) { + try { + writer.close(); + } catch (IOException ioe) { + exception = ioe; + } + } } return null; } diff --git a/src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java b/src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java index 4d9c9867e..64e678086 100644 --- a/src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java +++ b/src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.asynctask; +import java.util.Arrays; import java.util.Date; import android.annotation.SuppressLint; @@ -22,7 +23,7 @@ public class OpmlFeedQueuer extends AsyncTask<Void, Void, Void> { public OpmlFeedQueuer(Context context, int[] selection) { super(); this.context = context; - this.selection = selection; + this.selection = Arrays.copyOf(selection, selection.length); } @Override diff --git a/src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java b/src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java index 5af06895f..4816c25ab 100644 --- a/src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java +++ b/src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java @@ -64,6 +64,13 @@ public class OpmlImportWorker extends @Override protected void onPostExecute(ArrayList<OpmlElement> result) { + if (mReader != null) { + try { + mReader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } progDialog.dismiss(); if (exception != null) { if (AppConfig.DEBUG) diff --git a/src/de/danoeh/antennapod/dialog/VariableSpeedDialog.java b/src/de/danoeh/antennapod/dialog/VariableSpeedDialog.java new file mode 100644 index 000000000..e6cbe37d1 --- /dev/null +++ b/src/de/danoeh/antennapod/dialog/VariableSpeedDialog.java @@ -0,0 +1,100 @@ +package de.danoeh.antennapod.dialog; + +import java.util.Arrays; +import java.util.List; + +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.preferences.UserPreferences; + +public class VariableSpeedDialog { + private VariableSpeedDialog() { + } + + public static void showDialog(final Context context) { + if (com.aocate.media.MediaPlayer.isPrestoLibraryInstalled(context)) { + showSpeedSelectorDialog(context); + } else { + showGetPluginDialog(context); + } + } + + private static void showGetPluginDialog(final Context context) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.no_playback_plugin_title); + builder.setMessage(R.string.no_playback_plugin_msg); + builder.setNegativeButton(R.string.close_label, null); + builder.setPositiveButton(R.string.download_plugin_label, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + try { + Intent playStoreIntent = new Intent( + Intent.ACTION_VIEW, + Uri.parse("market://details?id=com.falconware.prestissimo")); + context.startActivity(playStoreIntent); + } catch (ActivityNotFoundException e) { + // this is usually thrown on an emulator if the Android market is not installed + e.printStackTrace(); + } + } + }); + builder.create().show(); + } + + private static void showSpeedSelectorDialog(final Context context) { + final String[] speedValues = context.getResources().getStringArray( + R.array.playback_speed_values); + // According to Java spec these get initialized to false on creation + final boolean[] speedChecked = new boolean[speedValues.length]; + + // Build the "isChecked" array so that multiChoice dialog is + // populated correctly + List<String> selectedSpeedList = Arrays.asList(UserPreferences + .getPlaybackSpeedArray()); + for (int i = 0; i < speedValues.length; i++) { + speedChecked[i] = selectedSpeedList.contains(speedValues[i]); + } + + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(R.string.set_playback_speed_label); + builder.setMultiChoiceItems(R.array.playback_speed_values, + speedChecked, new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int which, + boolean isChecked) { + speedChecked[which] = isChecked; + } + + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int choiceCount = 0; + for (int i = 0; i < speedChecked.length; i++) { + if (speedChecked[i]) { + choiceCount++; + } + } + String[] newSpeedValues = new String[choiceCount]; + int newSpeedIndex = 0; + for (int i = 0; i < speedChecked.length; i++) { + if (speedChecked[i]) { + newSpeedValues[newSpeedIndex++] = speedValues[i]; + } + } + + UserPreferences.setPlaybackSpeedArray(newSpeedValues); + + } + }); + builder.create().show(); + } +} diff --git a/src/de/danoeh/antennapod/feed/EventDistributor.java b/src/de/danoeh/antennapod/feed/EventDistributor.java index c538808e2..56333da52 100644 --- a/src/de/danoeh/antennapod/feed/EventDistributor.java +++ b/src/de/danoeh/antennapod/feed/EventDistributor.java @@ -39,7 +39,7 @@ public class EventDistributor extends Observable { events = new ConcurrentLinkedQueue<Integer>(); } - public static EventDistributor getInstance() { + public static synchronized EventDistributor getInstance() { if (instance == null) { instance = new EventDistributor(); } diff --git a/src/de/danoeh/antennapod/feed/Feed.java b/src/de/danoeh/antennapod/feed/Feed.java index 9cee1a86a..032930f83 100644 --- a/src/de/danoeh/antennapod/feed/Feed.java +++ b/src/de/danoeh/antennapod/feed/Feed.java @@ -55,7 +55,11 @@ public class Feed extends FeedFile { super(fileUrl, downloadUrl, downloaded); this.id = id; this.title = title; - this.lastUpdate = lastUpdate; + if (lastUpdate != null) { + this.lastUpdate = (Date) lastUpdate.clone(); + } else { + this.lastUpdate = null; + } this.link = link; this.description = description; this.paymentLink = paymentLink; @@ -83,7 +87,7 @@ public class Feed extends FeedFile { */ public Feed(String url, Date lastUpdate) { super(null, url, false); - this.lastUpdate = lastUpdate; + this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null; } /** @@ -314,19 +318,12 @@ public class Feed extends FeedFile { this.items = list; } - /** - * Returns an array that contains all the feeditems of this feed. - */ - public FeedItem[] getItemsArray() { - return items.toArray(new FeedItem[items.size()]); - } - public Date getLastUpdate() { - return lastUpdate; + return (lastUpdate != null) ? (Date) lastUpdate.clone() : null; } public void setLastUpdate(Date lastUpdate) { - this.lastUpdate = lastUpdate; + this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null; } public String getFeedIdentifier() { diff --git a/src/de/danoeh/antennapod/feed/FeedItem.java b/src/de/danoeh/antennapod/feed/FeedItem.java index 54682397e..a80460ece 100644 --- a/src/de/danoeh/antennapod/feed/FeedItem.java +++ b/src/de/danoeh/antennapod/feed/FeedItem.java @@ -48,6 +48,19 @@ public class FeedItem extends FeedComponent implements this.read = true; } + /** + * This constructor should be used for creating test objects. + * */ + public FeedItem(long id, String title, String itemIdentifier, String link, Date pubDate, boolean read, Feed feed) { + this.id = id; + this.title = title; + this.itemIdentifier = itemIdentifier; + this.link = link; + this.pubDate = (pubDate != null) ? (Date) pubDate.clone() : null; + this.read = read; + this.feed = feed; + } + public void updateFromOther(FeedItem other) { super.updateFromOther(other); if (other.title != null) { @@ -123,19 +136,35 @@ public class FeedItem extends FeedComponent implements } public Date getPubDate() { - return pubDate; + if (pubDate != null) { + return (Date) pubDate.clone(); + } else { + return null; + } } public void setPubDate(Date pubDate) { - this.pubDate = pubDate; + if (pubDate != null) { + this.pubDate = (Date) pubDate.clone(); + } else { + this.pubDate = null; + } } public FeedMedia getMedia() { return media; } + /** + * Sets the media object of this FeedItem. If the given + * FeedMedia object is not null, it's 'item'-attribute value + * will also be set to this item. + * */ public void setMedia(FeedMedia media) { this.media = media; + if (media != null && media.getItem() != this) { + media.setItem(this); + } } public Feed getFeed() { diff --git a/src/de/danoeh/antennapod/feed/FeedMedia.java b/src/de/danoeh/antennapod/feed/FeedMedia.java index 2e0eac7a4..492867983 100644 --- a/src/de/danoeh/antennapod/feed/FeedMedia.java +++ b/src/de/danoeh/antennapod/feed/FeedMedia.java @@ -53,7 +53,8 @@ public class FeedMedia extends FeedFile implements Playable { this.position = position; this.size = size; this.mime_type = mime_type; - this.playbackCompletionDate = playbackCompletionDate; + this.playbackCompletionDate = playbackCompletionDate == null + ? null : (Date) playbackCompletionDate.clone(); } public FeedMedia(long id, FeedItem item) { @@ -164,16 +165,25 @@ public class FeedMedia extends FeedFile implements Playable { return item; } + /** + * Sets the item object of this FeedMedia. If the given + * FeedItem object is not null, it's 'media'-attribute value + * will also be set to this media object. + * */ public void setItem(FeedItem item) { this.item = item; + if (item != null && item.getMedia() != this) { + item.setMedia(this); + } } public Date getPlaybackCompletionDate() { - return playbackCompletionDate; - } + return playbackCompletionDate == null + ? null : (Date) playbackCompletionDate.clone(); } public void setPlaybackCompletionDate(Date playbackCompletionDate) { - this.playbackCompletionDate = playbackCompletionDate; + this.playbackCompletionDate = playbackCompletionDate == null + ? null : (Date) playbackCompletionDate.clone(); } public boolean isInProgress() { @@ -304,7 +314,7 @@ public class FeedMedia extends FeedFile implements Playable { @Override public void saveCurrentPosition(SharedPreferences pref, int newPosition) { position = newPosition; - DBWriter.setFeedMediaPosition(PodcastApp.getInstance(), this); + DBWriter.setFeedMediaPlaybackInformation(PodcastApp.getInstance(), this); } @Override diff --git a/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java b/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java index 10312b20b..933263d7d 100644 --- a/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java +++ b/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java @@ -173,6 +173,12 @@ public class ExternalPlayerFragment extends Fragment { .newOnPlayButtonClickListener()); } } + + @Override + public void onPlaybackSpeedChange() { + // TODO Auto-generated method stub + + } }; } diff --git a/src/de/danoeh/antennapod/fragment/FeedlistFragment.java b/src/de/danoeh/antennapod/fragment/FeedlistFragment.java index 3e8679bca..0dc29c415 100644 --- a/src/de/danoeh/antennapod/fragment/FeedlistFragment.java +++ b/src/de/danoeh/antennapod/fragment/FeedlistFragment.java @@ -3,6 +3,7 @@ package de.danoeh.antennapod.fragment; import java.util.List; import android.annotation.SuppressLint; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.AsyncTask; @@ -29,9 +30,9 @@ import de.danoeh.antennapod.storage.FeedItemStatistics; import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler; public class FeedlistFragment extends Fragment implements - ActionMode.Callback, AdapterView.OnItemClickListener, - AdapterView.OnItemLongClickListener { - private static final String TAG = "FeedlistFragment"; + ActionMode.Callback, AdapterView.OnItemClickListener, + AdapterView.OnItemLongClickListener { + private static final String TAG = "FeedlistFragment"; private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | EventDistributor.DOWNLOAD_QUEUED @@ -94,12 +95,16 @@ public class FeedlistFragment extends Fragment implements AsyncTask<Void, Void, List[]> loadTask = new AsyncTask<Void, Void, List[]>() { @Override protected List[] doInBackground(Void... params) { - return new List[]{DBReader.getFeedList(getActivity()), - DBReader.getFeedStatisticsList(getActivity())}; + Context context = getActivity(); + if (context != null) { + return new List[]{DBReader.getFeedList(context), + DBReader.getFeedStatisticsList(context)}; + } else { + return null; + } } - @Override protected void onPostExecute(List[] result) { super.onPostExecute(result); @@ -160,9 +165,14 @@ public class FeedlistFragment extends Fragment implements } @Override + public void onDestroy() { + super.onDestroy(); + EventDistributor.getInstance().unregister(contentUpdate); + } + + @Override public void onPause() { super.onPause(); - EventDistributor.getInstance().unregister(contentUpdate); if (mActionMode != null) { mActionMode.finish(); } @@ -255,9 +265,9 @@ public class FeedlistFragment extends Fragment implements public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { Feed selection = fla.getItem(position); - if (AppConfig.DEBUG) - Log.d(TAG, "Selected Feed with title " + selection.getTitle()); if (selection != null) { + if (AppConfig.DEBUG) + Log.d(TAG, "Selected Feed with title " + selection.getTitle()); if (mActionMode != null) { mActionMode.finish(); } diff --git a/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java b/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java index 9183180c1..c996f497e 100644 --- a/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java +++ b/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.fragment; +import android.content.*; import android.support.v4.app.Fragment; import android.support.v7.app.ActionBarActivity; import de.danoeh.antennapod.feed.FeedItem; @@ -9,10 +10,6 @@ import org.apache.commons.lang3.StringEscapeUtils; import android.annotation.SuppressLint; import android.app.Activity; -import android.content.ClipData; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; import android.content.res.TypedArray; import android.net.Uri; import android.os.AsyncTask; @@ -117,7 +114,12 @@ public class ItemDescriptionFragment extends Fragment { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - startActivity(intent); + try { + startActivity(intent); + } catch (ActivityNotFoundException e) { + e.printStackTrace(); + return false; + } return true; } @@ -138,6 +140,7 @@ public class ItemDescriptionFragment extends Fragment { } }); + registerForContextMenu(webvDescription); return webvDescription; } @@ -371,11 +374,10 @@ public class ItemDescriptionFragment extends Fragment { Callable<String> shownotesLoadTask = shownotesProvider.loadShownotes(); final String shownotes = shownotesLoadTask.call(); - data = ""; data = StringEscapeUtils.unescapeHtml4(shownotes); Activity activity = getActivity(); if (activity != null) { - TypedArray res = getActivity() + TypedArray res = activity .getTheme() .obtainStyledAttributes( new int[]{android.R.attr.textColorPrimary}); diff --git a/src/de/danoeh/antennapod/fragment/ItemlistFragment.java b/src/de/danoeh/antennapod/fragment/ItemlistFragment.java index 40637544d..282bb4d5c 100644 --- a/src/de/danoeh/antennapod/fragment/ItemlistFragment.java +++ b/src/de/danoeh/antennapod/fragment/ItemlistFragment.java @@ -50,7 +50,6 @@ public class ItemlistFragment extends ListFragment { public static final String EXTRA_SELECTED_FEEDITEM = "extra.de.danoeh.antennapod.activity.selected_feeditem"; public static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id"; protected InternalFeedItemlistAdapter fila; - protected DownloadRequester requester = DownloadRequester.getInstance(); private Feed feed; protected List<Long> queue; @@ -61,6 +60,8 @@ public class ItemlistFragment extends ListFragment { /** Argument for FeeditemlistAdapter */ protected boolean showFeedtitle; + private AsyncTask<Long, Void, Feed> currentLoadTask; + public ItemlistFragment(boolean showFeedtitle) { super(); this.showFeedtitle = showFeedtitle; @@ -116,11 +117,21 @@ public class ItemlistFragment extends ListFragment { return inflater.inflate(R.layout.feeditemlist, container, false); } - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - loadData(); - } + @Override + public void onStart() { + super.onStart(); + EventDistributor.getInstance().register(contentUpdate); + loadData(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + EventDistributor.getInstance().unregister(contentUpdate); + if (currentLoadTask != null) { + currentLoadTask.cancel(true); + } + } protected void loadData() { final long feedId; @@ -156,8 +167,6 @@ public class ItemlistFragment extends ListFragment { } else { Log.e(TAG, "Could not load queue"); } - if (result.getItems().isEmpty()) { - } setEmptyViewIfListIsEmpty(); if (fila != null) { fila.notifyDataSetChanged(); @@ -171,6 +180,7 @@ public class ItemlistFragment extends ListFragment { } } }; + currentLoadTask = loadTask; loadTask.execute(feedId); } @@ -188,17 +198,6 @@ public class ItemlistFragment extends ListFragment { } @Override - public void onPause() { - super.onPause(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - EventDistributor.getInstance().unregister(contentUpdate); - } - - @Override public void onResume() { super.onResume(); getActivity().runOnUiThread(new Runnable() { @@ -209,7 +208,6 @@ public class ItemlistFragment extends ListFragment { } }); updateProgressBarVisibility(); - EventDistributor.getInstance().register(contentUpdate); } @Override diff --git a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java b/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java index 99bef4bd8..ee1a3ea21 100644 --- a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java +++ b/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java @@ -16,6 +16,8 @@ import org.json.JSONObject; import android.net.Uri; +import de.danoeh.antennapod.util.LangUtils; + /** Executes HTTP requests and returns the results. */ public class MiroGuideConnector { private HttpClient httpClient; @@ -73,12 +75,14 @@ public class MiroGuideConnector { if (response.getStatusLine().getStatusCode() == 200) { HttpEntity entity = response.getEntity(); if (entity != null) { - InputStream in = entity.getContent(); - BufferedReader reader = new BufferedReader( - new InputStreamReader(in)); - result = reader.readLine(); - in.close(); + new InputStreamReader(entity.getContent(), + LangUtils.UTF_8)); + try { + result = reader.readLine(); + } finally { + reader.close(); + } } } else { throw new MiroGuideException(response.getStatusLine() diff --git a/src/de/danoeh/antennapod/miroguide/model/MiroGuideItem.java b/src/de/danoeh/antennapod/miroguide/model/MiroGuideItem.java index 89a2250df..cb5b15c56 100644 --- a/src/de/danoeh/antennapod/miroguide/model/MiroGuideItem.java +++ b/src/de/danoeh/antennapod/miroguide/model/MiroGuideItem.java @@ -12,7 +12,7 @@ public class MiroGuideItem { super(); this.name = name; this.description = description; - this.date = date; + this.date = (Date) date.clone(); this.url = url; } @@ -30,7 +30,7 @@ public class MiroGuideItem { } public Date getDate() { - return date; + return (Date) date.clone(); } public String getUrl() { diff --git a/src/de/danoeh/antennapod/preferences/UserPreferences.java b/src/de/danoeh/antennapod/preferences/UserPreferences.java index a7df27ddc..0530ab466 100644 --- a/src/de/danoeh/antennapod/preferences/UserPreferences.java +++ b/src/de/danoeh/antennapod/preferences/UserPreferences.java @@ -2,9 +2,13 @@ package de.danoeh.antennapod.preferences; import java.io.File; import java.io.IOException; +import java.util.LinkedList; +import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.StringUtils; +import org.json.JSONArray; +import org.json.JSONException; import android.app.AlarmManager; import android.app.PendingIntent; @@ -41,12 +45,14 @@ public class UserPreferences implements public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter"; private static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks"; public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize"; + private static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed"; + private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray"; public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss"; private static int EPISODE_CACHE_SIZE_UNLIMITED = -1; private static UserPreferences instance; - private Context context; + private final Context context; // Preferences private boolean pauseOnHeadsetDisconnect; @@ -61,6 +67,8 @@ public class UserPreferences implements private boolean enableAutodownloadWifiFilter; private String[] autodownloadSelectedNetworks; private int episodeCacheSize; + private String playbackSpeed; + private String[] playbackSpeedArray; private boolean pauseForFocusLoss; private UserPreferences(Context context) { @@ -85,6 +93,7 @@ public class UserPreferences implements createNoMediaFile(); PreferenceManager.getDefaultSharedPreferences(context) .registerOnSharedPreferenceChangeListener(instance); + } private void loadPreferences() { @@ -110,6 +119,9 @@ public class UserPreferences implements episodeCacheSize = readEpisodeCacheSize(sp.getString( PREF_EPISODE_CACHE_SIZE, "20")); enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false); + playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0"); + playbackSpeedArray = readPlaybackSpeedArray(sp.getString( + PREF_PLAYBACK_SPEED_ARRAY, null)); pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false); } @@ -138,6 +150,36 @@ public class UserPreferences implements } } + private String[] readPlaybackSpeedArray(String valueFromPrefs) { + String[] selectedSpeeds = null; + // If this preference hasn't been set yet, return the default options + if (valueFromPrefs == null) { + String[] allSpeeds = context.getResources().getStringArray( + R.array.playback_speed_values); + List<String> speedList = new LinkedList<String>(); + for (String speedStr : allSpeeds) { + float speed = Float.parseFloat(speedStr); + if (speed < 2.0001 && speed * 10 % 1 == 0) { + speedList.add(speedStr); + } + } + selectedSpeeds = speedList.toArray(new String[speedList.size()]); + } else { + try { + JSONArray jsonArray = new JSONArray(valueFromPrefs); + selectedSpeeds = new String[jsonArray.length()]; + for (int i = 0; i < jsonArray.length(); i++) { + selectedSpeeds[i] = jsonArray.getString(i); + } + } catch (JSONException e) { + Log.e(TAG, + "Got JSON error when trying to get speeds from JSONArray"); + e.printStackTrace(); + } + } + return selectedSpeeds; + } + private static void instanceAvailable() { if (instance == null) { throw new IllegalStateException( @@ -172,7 +214,8 @@ public class UserPreferences implements public static boolean isDisplayOnlyEpisodes() { instanceAvailable(); - return instance.displayOnlyEpisodes; + //return instance.displayOnlyEpisodes; + return false; } public static boolean isAutoDelete() { @@ -199,6 +242,16 @@ public class UserPreferences implements return EPISODE_CACHE_SIZE_UNLIMITED; } + public static String getPlaybackSpeed() { + instanceAvailable(); + return instance.playbackSpeed; + } + + public static String[] getPlaybackSpeedArray() { + instanceAvailable(); + return instance.playbackSpeedArray; + } + /** * Returns the capacity of the episode cache. This method will return the * negative integer EPISODE_CACHE_SIZE_UNLIMITED if the cache size is set to @@ -258,11 +311,31 @@ public class UserPreferences implements PREF_EPISODE_CACHE_SIZE, "20")); } else if (key.equals(PREF_ENABLE_AUTODL)) { enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false); + } else if (key.equals(PREF_PLAYBACK_SPEED)) { + playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0"); + } else if (key.equals(PREF_PLAYBACK_SPEED_ARRAY)) { + playbackSpeedArray = readPlaybackSpeedArray(sp.getString( + PREF_PLAYBACK_SPEED_ARRAY, null)); } else if (key.equals(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS)) { pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false); } } + public static void setPlaybackSpeed(String speed) { + PreferenceManager.getDefaultSharedPreferences(instance.context).edit() + .putString(PREF_PLAYBACK_SPEED, speed).apply(); + } + + public static void setPlaybackSpeedArray(String[] speeds) { + JSONArray jsonArray = new JSONArray(); + for (String speed : speeds) { + jsonArray.put(speed); + } + PreferenceManager.getDefaultSharedPreferences(instance.context).edit() + .putString(PREF_PLAYBACK_SPEED_ARRAY, jsonArray.toString()) + .apply(); + } + public static void setAutodownloadSelectedNetworks(Context context, String[] value) { SharedPreferences.Editor editor = PreferenceManager diff --git a/src/de/danoeh/antennapod/service/PlaybackService.java b/src/de/danoeh/antennapod/service/PlaybackService.java index d04046223..a5deb0a8a 100644 --- a/src/de/danoeh/antennapod/service/PlaybackService.java +++ b/src/de/danoeh/antennapod/service/PlaybackService.java @@ -45,9 +45,13 @@ import de.danoeh.antennapod.storage.DBTasks; import de.danoeh.antennapod.storage.DBWriter; import de.danoeh.antennapod.util.BitmapDecoder; import de.danoeh.antennapod.util.QueueAccess; +import de.danoeh.antennapod.util.DuckType; import de.danoeh.antennapod.util.flattr.FlattrUtils; +import de.danoeh.antennapod.util.playback.AudioPlayer; +import de.danoeh.antennapod.util.playback.IPlayer; import de.danoeh.antennapod.util.playback.Playable; import de.danoeh.antennapod.util.playback.Playable.PlayableException; +import de.danoeh.antennapod.util.playback.VideoPlayer; import de.danoeh.antennapod.util.playback.PlaybackController; /** @@ -119,7 +123,12 @@ public class PlaybackService extends Service { */ public static final int NOTIFICATION_TYPE_PLAYBACK_END = 7; - /** + /** + * Playback speed has changed + * */ + public static final int NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE = 8; + + /** * Returned by getPositionSafe() or getDurationSafe() if the playbackService * is in an invalid state. */ @@ -132,13 +141,12 @@ public class PlaybackService extends Service { private static final int NOTIFICATION_ID = 1; + private volatile IPlayer player; + private RemoteControlClient remoteControlClient; private AudioManager audioManager; private ComponentName mediaButtonReceiver; - private MediaPlayer player; - private RemoteControlClient remoteControlClient; - - private Playable media; + private volatile Playable media; /** * True if media should be streamed (Extracted from Intent Extra) . @@ -252,7 +260,6 @@ public class PlaybackService extends Service { } ); dbLoaderExecutor = Executors.newSingleThreadExecutor(); - player = createMediaPlayer(); mediaButtonReceiver = new ComponentName(getPackageName(), MediaButtonReceiver.class.getName()); @@ -273,22 +280,43 @@ public class PlaybackService extends Service { loadQueue(); } - private MediaPlayer createMediaPlayer() { - return createMediaPlayer(new MediaPlayer()); - } - - private MediaPlayer createMediaPlayer(MediaPlayer mp) { - if (mp != null) { - mp.setOnPreparedListener(preparedListener); - mp.setOnCompletionListener(completionListener); - mp.setOnSeekCompleteListener(onSeekCompleteListener); - mp.setOnErrorListener(onErrorListener); - mp.setOnBufferingUpdateListener(onBufferingUpdateListener); - mp.setOnInfoListener(onInfoListener); + private IPlayer createMediaPlayer() { + IPlayer player; + if (media == null || media.getMediaType() == MediaType.VIDEO) { + player = new VideoPlayer(); + } else { + player = new AudioPlayer(this); } - return mp; + return createMediaPlayer(player); } + private IPlayer createMediaPlayer(IPlayer mp) { + if (mp != null && media != null) { + if (media.getMediaType() == MediaType.AUDIO) { + ((AudioPlayer) mp).setOnPreparedListener(audioPreparedListener); + ((AudioPlayer) mp) + .setOnCompletionListener(audioCompletionListener); + ((AudioPlayer) mp) + .setOnSeekCompleteListener(audioSeekCompleteListener); + ((AudioPlayer) mp).setOnErrorListener(audioErrorListener); + ((AudioPlayer) mp) + .setOnBufferingUpdateListener(audioBufferingUpdateListener); + ((AudioPlayer) mp).setOnInfoListener(audioInfoListener); + } else { + ((VideoPlayer) mp).setOnPreparedListener(videoPreparedListener); + ((VideoPlayer) mp) + .setOnCompletionListener(videoCompletionListener); + ((VideoPlayer) mp) + .setOnSeekCompleteListener(videoSeekCompleteListener); + ((VideoPlayer) mp).setOnErrorListener(videoErrorListener); + ((VideoPlayer) mp) + .setOnBufferingUpdateListener(videoBufferingUpdateListener); + ((VideoPlayer) mp).setOnInfoListener(videoInfoListener); + } + } + return mp; + } + @SuppressLint("NewApi") @Override public void onDestroy() { @@ -482,7 +510,7 @@ public class PlaybackService extends Service { seekDelta(-PlaybackController.DEFAULT_SEEK_DELTA); break; } - } + } } /** @@ -575,6 +603,7 @@ public class PlaybackService extends Service { Log.d(TAG, "Setting up media player"); try { MediaType mediaType = media.getMediaType(); + player = createMediaPlayer(); if (mediaType == MediaType.AUDIO) { if (AppConfig.DEBUG) Log.d(TAG, "Mime type is audio"); @@ -669,105 +698,169 @@ public class PlaybackService extends Service { } } - private MediaPlayer.OnPreparedListener preparedListener = new MediaPlayer.OnPreparedListener() { - @Override - public void onPrepared(MediaPlayer mp) { - if (AppConfig.DEBUG) - Log.d(TAG, "Resource prepared"); - mp.seekTo(media.getPosition()); - if (media.getDuration() == 0) { - if (AppConfig.DEBUG) - Log.d(TAG, "Setting duration of media"); - media.setDuration(mp.getDuration()); - } - setStatus(PlayerStatus.PREPARED); - if (chapterLoader != null) { - chapterLoader.interrupt(); - } - chapterLoader = new Thread() { - @Override - public void run() { - if (AppConfig.DEBUG) - Log.d(TAG, "Chapter loader started"); - if (media != null && media.getChapters() == null) { - media.loadChapterMarks(); - if (!isInterrupted() && media.getChapters() != null) { - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, - 0); - } - } - if (AppConfig.DEBUG) - Log.d(TAG, "Chapter loader stopped"); - } - }; - chapterLoader.start(); + private final com.aocate.media.MediaPlayer.OnPreparedListener audioPreparedListener = new com.aocate.media.MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(com.aocate.media.MediaPlayer mp) { + genericOnPrepared(mp); + } + }; - if (startWhenPrepared) { - play(); - } - } - }; + private final android.media.MediaPlayer.OnPreparedListener videoPreparedListener = new android.media.MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(android.media.MediaPlayer mp) { + genericOnPrepared(mp); + } + }; + + private final void genericOnPrepared(Object inObj) { + IPlayer mp = DuckType.coerce(inObj).to(IPlayer.class); + if (AppConfig.DEBUG) + Log.d(TAG, "Resource prepared"); + mp.seekTo(media.getPosition()); + if (media.getDuration() == 0) { + if (AppConfig.DEBUG) + Log.d(TAG, "Setting duration of media"); + media.setDuration(mp.getDuration()); + } + setStatus(PlayerStatus.PREPARED); + if (chapterLoader != null) { + chapterLoader.interrupt(); + } + chapterLoader = new Thread() { + @Override + public void run() { + if (AppConfig.DEBUG) + Log.d(TAG, "Chapter loader started"); + if (media != null && media.getChapters() == null) { + media.loadChapterMarks(); + if (!isInterrupted() && media.getChapters() != null) { + sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, + 0); + } + } + if (AppConfig.DEBUG) + Log.d(TAG, "Chapter loader stopped"); + } + }; + chapterLoader.start(); - private MediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new MediaPlayer.OnSeekCompleteListener() { + if (startWhenPrepared) { + play(); + } + } - @Override - public void onSeekComplete(MediaPlayer mp) { - if (status == PlayerStatus.SEEKING) { - setStatus(statusBeforeSeek); - } + private final com.aocate.media.MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener = new com.aocate.media.MediaPlayer.OnSeekCompleteListener() { + @Override + public void onSeekComplete(com.aocate.media.MediaPlayer mp) { + genericSeekCompleteListener(); + } + }; - } - }; + private final android.media.MediaPlayer.OnSeekCompleteListener videoSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() { + @Override + public void onSeekComplete(android.media.MediaPlayer mp) { + genericSeekCompleteListener(); + } + }; - private MediaPlayer.OnInfoListener onInfoListener = new MediaPlayer.OnInfoListener() { + private final void genericSeekCompleteListener() { + if (status == PlayerStatus.SEEKING) { + setStatus(statusBeforeSeek); + } + } - @Override - public boolean onInfo(MediaPlayer mp, int what, int extra) { - switch (what) { - case MediaPlayer.MEDIA_INFO_BUFFERING_START: - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0); - return true; - case MediaPlayer.MEDIA_INFO_BUFFERING_END: - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_END, 0); - return true; - default: - return false; - } - } - }; + private final com.aocate.media.MediaPlayer.OnInfoListener audioInfoListener = new com.aocate.media.MediaPlayer.OnInfoListener() { + @Override + public boolean onInfo(com.aocate.media.MediaPlayer mp, int what, + int extra) { + return genericInfoListener(what); + } + }; - private MediaPlayer.OnErrorListener onErrorListener = new MediaPlayer.OnErrorListener() { - private static final String TAG = "PlaybackService.onErrorListener"; + private final android.media.MediaPlayer.OnInfoListener videoInfoListener = new android.media.MediaPlayer.OnInfoListener() { + @Override + public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) { + return genericInfoListener(what); + } + }; - @Override - public boolean onError(MediaPlayer mp, int what, int extra) { - Log.w(TAG, "An error has occured: " + what); - if (mp.isPlaying()) { - pause(true, true); - } - sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what); - setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING); - stopSelf(); - return true; - } - }; + private boolean genericInfoListener(int what) { + switch (what) { + case MediaPlayer.MEDIA_INFO_BUFFERING_START: + sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0); + return true; + case MediaPlayer.MEDIA_INFO_BUFFERING_END: + sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_END, 0); + return true; + default: + return false; + } + } - private MediaPlayer.OnCompletionListener completionListener = new MediaPlayer.OnCompletionListener() { + private final com.aocate.media.MediaPlayer.OnErrorListener audioErrorListener = new com.aocate.media.MediaPlayer.OnErrorListener() { + @Override + public boolean onError(com.aocate.media.MediaPlayer mp, int what, + int extra) { + return genericOnError(mp, what, extra); + } + }; - @Override - public void onCompletion(MediaPlayer mp) { - endPlayback(true); - } - }; + private final android.media.MediaPlayer.OnErrorListener videoErrorListener = new android.media.MediaPlayer.OnErrorListener() { + @Override + public boolean onError(android.media.MediaPlayer mp, int what, int extra) { + return genericOnError(mp, what, extra); + } + }; - private MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() { + private boolean genericOnError(Object inObj, int what, int extra) { + final String TAG = "PlaybackService.onErrorListener"; + Log.w(TAG, "An error has occured: " + what + " " + extra); + IPlayer mp = DuckType.coerce(inObj).to(IPlayer.class); + if (mp.isPlaying()) { + pause(true, true); + } + sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what); + setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING); + stopSelf(); + return true; + } - @Override - public void onBufferingUpdate(MediaPlayer mp, int percent) { - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent); + private final com.aocate.media.MediaPlayer.OnCompletionListener audioCompletionListener = new com.aocate.media.MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(com.aocate.media.MediaPlayer mp) { + genericOnCompletion(); + } + }; - } - }; + private final android.media.MediaPlayer.OnCompletionListener videoCompletionListener = new android.media.MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(android.media.MediaPlayer mp) { + genericOnCompletion(); + } + }; + + private void genericOnCompletion() { + endPlayback(true); + } + + private final com.aocate.media.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new com.aocate.media.MediaPlayer.OnBufferingUpdateListener() { + @Override + public void onBufferingUpdate(com.aocate.media.MediaPlayer mp, + int percent) { + genericOnBufferingUpdate(percent); + } + }; + + private final android.media.MediaPlayer.OnBufferingUpdateListener videoBufferingUpdateListener = new android.media.MediaPlayer.OnBufferingUpdateListener() { + @Override + public void onBufferingUpdate(android.media.MediaPlayer mp, int percent) { + genericOnBufferingUpdate(percent); + } + }; + + private void genericOnBufferingUpdate(int percent) { + sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent); + } private void endPlayback(boolean playNextEpisode) { if (AppConfig.DEBUG) @@ -790,7 +883,6 @@ public class PlaybackService extends Service { DBWriter.removeQueueItem(PlaybackService.this, item.getId(), true); } DBWriter.addItemToPlaybackHistory(PlaybackService.this, (FeedMedia) media); - DBWriter.setFeedMedia(PlaybackService.this, (FeedMedia) media); long autoDeleteMediaId = ((FeedComponent) media).getId(); if (shouldStream) { autoDeleteMediaId = -1; @@ -870,7 +962,7 @@ public class PlaybackService extends Service { /** * Saves the current position and pauses playback. Note that, if audiofocus * is abandoned, the lockscreen controls will also disapear. - * + * * @param abandonFocus * is true if the service should release audio focus * @param reinit @@ -946,6 +1038,7 @@ public class PlaybackService extends Service { Log.d(TAG, "Resuming/Starting playback"); writePlaybackPreferences(); + setSpeed(Float.parseFloat(UserPreferences.getPlaybackSpeed())); player.start(); if (status != PlayerStatus.PAUSED) { player.seekTo((int) media.getPosition()); @@ -1131,7 +1224,7 @@ public class PlaybackService extends Service { /** * Seek a specific position from the current position - * + * * @param delta * offset from current position (positive or negative) * */ @@ -1289,18 +1382,20 @@ public class PlaybackService extends Service { isPlaying = true; } - Intent i = new Intent(AVRCP_ACTION_PLAYER_STATUS_CHANGED); - i.putExtra("id", 1); - i.putExtra("artist", ""); - i.putExtra("album", media.getFeedTitle()); - i.putExtra("track", media.getEpisodeTitle()); - i.putExtra("playing", isPlaying); - if (queue != null) { - i.putExtra("ListSize", queue.size()); + if (media != null) { + Intent i = new Intent(AVRCP_ACTION_PLAYER_STATUS_CHANGED); + i.putExtra("id", 1); + i.putExtra("artist", ""); + i.putExtra("album", media.getFeedTitle()); + i.putExtra("track", media.getEpisodeTitle()); + i.putExtra("playing", isPlaying); + if (queue != null) { + i.putExtra("ListSize", queue.size()); + } + i.putExtra("duration", media.getDuration()); + i.putExtra("position", media.getPosition()); + sendBroadcast(i); } - i.putExtra("duration", media.getDuration()); - i.putExtra("position", media.getPosition()); - sendBroadcast(i); } /** @@ -1377,7 +1472,7 @@ public class PlaybackService extends Service { } } } - }; + }; /** Periodically saves the position of the media file */ class PositionSaver implements Runnable { @@ -1479,7 +1574,7 @@ public class PlaybackService extends Service { return media; } - public MediaPlayer getPlayer() { + public IPlayer getPlayer() { return player; } @@ -1492,6 +1587,53 @@ public class PlaybackService extends Service { postStatusUpdateIntent(); } + public boolean canSetSpeed() { + if (player != null && media != null && media.getMediaType() == MediaType.AUDIO) { + return ((AudioPlayer) player).canSetSpeed(); + } + return false; + } + + public boolean canSetPitch() { + if (player != null && media != null && media.getMediaType() == MediaType.AUDIO) { + return ((AudioPlayer) player).canSetPitch(); + } + return false; + } + + public void setSpeed(float speed) { + if (media != null && media.getMediaType() == MediaType.AUDIO) { + AudioPlayer audioPlayer = (AudioPlayer) player; + if (audioPlayer.canSetSpeed()) { + audioPlayer.setPlaybackSpeed((float) speed); + if (AppConfig.DEBUG) + Log.d(TAG, "Playback speed was set to " + speed); + sendNotificationBroadcast( + NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE, 0); + } + } + } + + public void setPitch(float pitch) { + if (media != null && media.getMediaType() == MediaType.AUDIO) { + AudioPlayer audioPlayer = (AudioPlayer) player; + if (audioPlayer.canSetPitch()) { + audioPlayer.setPlaybackPitch((float) pitch); + } + } + } + + public float getCurrentPlaybackSpeed() { + if (media.getMediaType() == MediaType.AUDIO + && player instanceof AudioPlayer) { + AudioPlayer audioPlayer = (AudioPlayer) player; + if (audioPlayer.canSetSpeed()) { + return audioPlayer.getCurrentSpeedMultiplier(); + } + } + return -1; + } + /** * call getDuration() on mediaplayer or return INVALID_TIME if player is in * an invalid state. This method should be used instead of calling diff --git a/src/de/danoeh/antennapod/service/download/DownloadService.java b/src/de/danoeh/antennapod/service/download/DownloadService.java index 2056efab2..4040c85a8 100644 --- a/src/de/danoeh/antennapod/service/download/DownloadService.java +++ b/src/de/danoeh/antennapod/service/download/DownloadService.java @@ -184,7 +184,7 @@ public class DownloadService extends Service { public int onStartCommand(Intent intent, int flags, int startId) { if (intent.getParcelableExtra(EXTRA_REQUEST) != null) { onDownloadQueued(intent); - } else if (numberOfDownloads.equals(0)) { + } else if (numberOfDownloads.get() == 0) { stopSelf(); } return Service.START_NOT_STICKY; @@ -421,52 +421,24 @@ public class DownloadService extends Service { return null; } - @SuppressLint("NewApi") - public void onDownloadCompleted(final Downloader downloader) { - final AsyncTask<Void, Void, Void> handlerTask = new AsyncTask<Void, Void, Void>() { - boolean successful; - - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - if (!successful) { - queryDownloads(); - } - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - removeDownload(downloader); - } - - @Override - protected Void doInBackground(Void... params) { - - - return null; - } - }; - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - handlerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - handlerTask.execute(); - } - } - /** * Remove download from the DownloadRequester list and from the * DownloadService list. */ private void removeDownload(final Downloader d) { - if (AppConfig.DEBUG) - Log.d(TAG, "Removing downloader: " - + d.getDownloadRequest().getSource()); - boolean rc = downloads.remove(d); - if (AppConfig.DEBUG) - Log.d(TAG, "Result of downloads.remove: " + rc); - DownloadRequester.getInstance().removeDownload(d.getDownloadRequest()); - sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED)); + handler.post(new Runnable() { + @Override + public void run() { + if (AppConfig.DEBUG) + Log.d(TAG, "Removing downloader: " + + d.getDownloadRequest().getSource()); + boolean rc = downloads.remove(d); + if (AppConfig.DEBUG) + Log.d(TAG, "Result of downloads.remove: " + rc); + DownloadRequester.getInstance().removeDownload(d.getDownloadRequest()); + sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED)); + } + }); } /** @@ -561,7 +533,7 @@ public class DownloadService extends Service { Log.d(TAG, numberOfDownloads.get() + " downloads left"); } - if (numberOfDownloads.get() <= 0) { + if (numberOfDownloads.get() <= 0 && DownloadRequester.getInstance().hasNoDownloads()) { if (AppConfig.DEBUG) Log.d(TAG, "Number of downloads is " + numberOfDownloads.get() + ", attempting shutdown"); stopSelf(); @@ -647,28 +619,21 @@ public class DownloadService extends Service { Log.d(TAG, "Feed has image; Downloading...."); savedFeed.getImage().setFeed(savedFeed); final Feed savedFeedRef = savedFeed; - handler.post(new Runnable() { - - @Override - public void run() { - try { - requester.downloadImage(DownloadService.this, - savedFeedRef.getImage()); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DBWriter.addDownloadStatus( - DownloadService.this, - new DownloadStatus( - savedFeedRef.getImage(), - savedFeedRef - .getImage() - .getHumanReadableIdentifier(), - DownloadError.ERROR_REQUEST_ERROR, - false, e.getMessage())); - } - } - }); - + try { + requester.downloadImage(DownloadService.this, + savedFeedRef.getImage()); + } catch (DownloadRequestException e) { + e.printStackTrace(); + DBWriter.addDownloadStatus( + DownloadService.this, + new DownloadStatus( + savedFeedRef.getImage(), + savedFeedRef + .getImage() + .getHumanReadableIdentifier(), + DownloadError.ERROR_REQUEST_ERROR, + false, e.getMessage())); + } } } catch (SAXException e) { @@ -730,7 +695,7 @@ public class DownloadService extends Service { } private boolean hasValidFeedItems(Feed feed) { - for (FeedItem item : feed.getItemsArray()) { + for (FeedItem item : feed.getItems()) { if (item.getTitle() == null) { Log.e(TAG, "Item has no title"); return false; @@ -835,8 +800,9 @@ public class DownloadService extends Service { media.setFile_url(request.getDestination()); // Get duration - MediaPlayer mediaplayer = new MediaPlayer(); + MediaPlayer mediaplayer = null; try { + mediaplayer = new MediaPlayer(); mediaplayer.setDataSource(media.getFile_url()); mediaplayer.prepare(); media.setDuration(mediaplayer.getDuration()); @@ -845,8 +811,13 @@ public class DownloadService extends Service { mediaplayer.reset(); } catch (IOException e) { e.printStackTrace(); + } catch (RuntimeException e) { + // Thrown by MediaPlayer initialization on some devices + e.printStackTrace(); } finally { - mediaplayer.release(); + if (mediaplayer != null) { + mediaplayer.release(); + } } if (media.getItem().getChapters() == null) { diff --git a/src/de/danoeh/antennapod/service/download/DownloadStatus.java b/src/de/danoeh/antennapod/service/download/DownloadStatus.java index 62e54cbb4..487c3b3de 100644 --- a/src/de/danoeh/antennapod/service/download/DownloadStatus.java +++ b/src/de/danoeh/antennapod/service/download/DownloadStatus.java @@ -52,7 +52,7 @@ public class DownloadStatus { this.feedfileId = feedfileId; this.reason = reason; this.successful = successful; - this.completionDate = completionDate; + this.completionDate = (Date) completionDate.clone(); this.reasonDetailed = reasonDetailed; this.feedfileType = feedfileType; } @@ -133,7 +133,7 @@ public class DownloadStatus { } public Date getCompletionDate() { - return completionDate; + return (Date) completionDate.clone(); } public long getFeedfileId() { @@ -162,6 +162,7 @@ public class DownloadStatus { this.successful = false; this.reason = reason; this.reasonDetailed = reasonDetailed; + this.done = true; } public void setCancelled() { @@ -172,7 +173,7 @@ public class DownloadStatus { } public void setCompletionDate(Date completionDate) { - this.completionDate = completionDate; + this.completionDate = (Date) completionDate.clone(); } public void setId(long id) { diff --git a/src/de/danoeh/antennapod/service/download/HttpDownloader.java b/src/de/danoeh/antennapod/service/download/HttpDownloader.java index c9671ceb3..582fb9575 100644 --- a/src/de/danoeh/antennapod/service/download/HttpDownloader.java +++ b/src/de/danoeh/antennapod/service/download/HttpDownloader.java @@ -6,12 +6,12 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import org.apache.commons.io.IOUtils; +import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; @@ -30,161 +30,186 @@ import de.danoeh.antennapod.util.DownloadError; import de.danoeh.antennapod.util.StorageUtils; public class HttpDownloader extends Downloader { - private static final String TAG = "HttpDownloader"; - - private static final int MAX_REDIRECTS = 5; - - private static final int BUFFER_SIZE = 8 * 1024; - private static final int CONNECTION_TIMEOUT = 30000; - private static final int SOCKET_TIMEOUT = 30000; - - public HttpDownloader(DownloadRequest request) { - super(request); - } - - private DefaultHttpClient createHttpClient() { - DefaultHttpClient httpClient = new DefaultHttpClient(); - HttpParams params = httpClient.getParams(); - params.setIntParameter("http.protocol.max-redirects", MAX_REDIRECTS); - params.setBooleanParameter("http.protocol.reject-relative-redirect", - false); - HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT); - HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT); - HttpClientParams.setRedirecting(params, true); - - // Workaround for broken URLs in redirection - ((AbstractHttpClient) httpClient) - .setRedirectHandler(new APRedirectHandler()); - return httpClient; - } - - @Override - protected void download() { - DefaultHttpClient httpClient = null; - OutputStream out = null; - InputStream connection = null; - try { - HttpGet httpGet = new HttpGet(request.getSource()); - httpClient = createHttpClient(); - HttpResponse response = httpClient.execute(httpGet); - HttpEntity httpEntity = response.getEntity(); - int responseCode = response.getStatusLine().getStatusCode(); - if (AppConfig.DEBUG) - Log.d(TAG, "Response code is " + responseCode); - if (responseCode == HttpURLConnection.HTTP_OK && httpEntity != null) { - if (StorageUtils.storageAvailable(PodcastApp.getInstance())) { - File destination = new File(request.getDestination()); - if (!destination.exists()) { - connection = AndroidHttpClient - .getUngzippedContent(httpEntity); - InputStream in = new BufferedInputStream(connection); - out = new BufferedOutputStream(new FileOutputStream( - destination)); - byte[] buffer = new byte[BUFFER_SIZE]; - int count = 0; - request.setStatusMsg(R.string.download_running); - if (AppConfig.DEBUG) - Log.d(TAG, "Getting size of download"); - request.setSize(httpEntity.getContentLength()); - if (AppConfig.DEBUG) - Log.d(TAG, "Size is " + request.getSize()); - if (request.getSize() < 0) { - request.setSize(DownloadStatus.SIZE_UNKNOWN); - } - - long freeSpace = StorageUtils.getFreeSpaceAvailable(); - if (AppConfig.DEBUG) - Log.d(TAG, "Free space is " + freeSpace); - if (request.getSize() == DownloadStatus.SIZE_UNKNOWN - || request.getSize() <= freeSpace) { - if (AppConfig.DEBUG) - Log.d(TAG, "Starting download"); - while (!cancelled - && (count = in.read(buffer)) != -1) { - out.write(buffer, 0, count); - request.setSoFar(request.getSoFar() + count); - request.setProgressPercent((int) (((double) request - .getSoFar() / (double) request - .getSize()) * 100)); - } - if (cancelled) { - onCancelled(); - } else { - onSuccess(); - } - } else { - onFail(DownloadError.ERROR_NOT_ENOUGH_SPACE, null); - } - } else { - Log.w(TAG, "File already exists"); - onFail(DownloadError.ERROR_FILE_EXISTS, null); - } - } else { - onFail(DownloadError.ERROR_DEVICE_NOT_FOUND, null); - } - } else { - onFail(DownloadError.ERROR_HTTP_DATA_ERROR, - String.valueOf(responseCode)); - } - } catch (IllegalArgumentException e) { - e.printStackTrace(); - onFail(DownloadError.ERROR_MALFORMED_URL, e.getMessage()); - } catch (SocketTimeoutException e) { - e.printStackTrace(); - onFail(DownloadError.ERROR_CONNECTION_ERROR, e.getMessage()); - } catch (UnknownHostException e) { - e.printStackTrace(); - onFail(DownloadError.ERROR_UNKNOWN_HOST, e.getMessage()); - } catch (IOException e) { - e.printStackTrace(); - onFail(DownloadError.ERROR_IO_ERROR, e.getMessage()); - } catch (NullPointerException e) { - // might be thrown by connection.getInputStream() - e.printStackTrace(); - onFail(DownloadError.ERROR_CONNECTION_ERROR, request.getSource()); - } finally { - IOUtils.closeQuietly(out); - if (httpClient != null) { - httpClient.getConnectionManager().shutdown(); - } - } - } - - private void onSuccess() { - if (AppConfig.DEBUG) - Log.d(TAG, "Download was successful"); - result.setSuccessful(); - } - - private void onFail(DownloadError reason, String reasonDetailed) { - if (AppConfig.DEBUG) { - Log.d(TAG, "Download failed"); - } + private static final String TAG = "HttpDownloader"; + + private static final int MAX_REDIRECTS = 5; + + private static final int BUFFER_SIZE = 8 * 1024; + private static final int CONNECTION_TIMEOUT = 30000; + private static final int SOCKET_TIMEOUT = 30000; + + public HttpDownloader(DownloadRequest request) { + super(request); + } + + private DefaultHttpClient createHttpClient() { + DefaultHttpClient httpClient = new DefaultHttpClient(); + HttpParams params = httpClient.getParams(); + params.setIntParameter("http.protocol.max-redirects", MAX_REDIRECTS); + params.setBooleanParameter("http.protocol.reject-relative-redirect", + false); + HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT); + HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT); + HttpClientParams.setRedirecting(params, true); + + // Workaround for broken URLs in redirection + ((AbstractHttpClient) httpClient) + .setRedirectHandler(new APRedirectHandler()); + return httpClient; + } + + @Override + protected void download() { + DefaultHttpClient httpClient = null; + BufferedOutputStream out = null; + InputStream connection = null; + try { + HttpGet httpGet = new HttpGet(request.getSource()); + httpClient = createHttpClient(); + HttpResponse response = httpClient.execute(httpGet); + HttpEntity httpEntity = response.getEntity(); + int responseCode = response.getStatusLine().getStatusCode(); + Header contentEncodingHeader = response.getFirstHeader("Content-Encoding"); + + final boolean isGzip = contentEncodingHeader != null && + contentEncodingHeader.getValue().equalsIgnoreCase("gzip"); + + if (AppConfig.DEBUG) + Log.d(TAG, "Response code is " + responseCode); + + if (responseCode != HttpURLConnection.HTTP_OK || httpEntity == null) { + onFail(DownloadError.ERROR_HTTP_DATA_ERROR, + String.valueOf(responseCode)); + return; + } + + if (!StorageUtils.storageAvailable(PodcastApp.getInstance())) { + onFail(DownloadError.ERROR_DEVICE_NOT_FOUND, null); + return; + } + + File destination = new File(request.getDestination()); + if (destination.exists()) { + Log.w(TAG, "File already exists"); + onFail(DownloadError.ERROR_FILE_EXISTS, null); + return; + } + + connection = new BufferedInputStream(AndroidHttpClient + .getUngzippedContent(httpEntity)); + out = new BufferedOutputStream(new FileOutputStream( + destination)); + byte[] buffer = new byte[BUFFER_SIZE]; + int count = 0; + request.setStatusMsg(R.string.download_running); + if (AppConfig.DEBUG) + Log.d(TAG, "Getting size of download"); + request.setSize(httpEntity.getContentLength()); + if (AppConfig.DEBUG) + Log.d(TAG, "Size is " + request.getSize()); + if (request.getSize() < 0) { + request.setSize(DownloadStatus.SIZE_UNKNOWN); + } + + long freeSpace = StorageUtils.getFreeSpaceAvailable(); + if (AppConfig.DEBUG) + Log.d(TAG, "Free space is " + freeSpace); + + if (request.getSize() != DownloadStatus.SIZE_UNKNOWN + && request.getSize() > freeSpace) { + onFail(DownloadError.ERROR_NOT_ENOUGH_SPACE, null); + return; + } + + if (AppConfig.DEBUG) + Log.d(TAG, "Starting download"); + while (!cancelled + && (count = connection.read(buffer)) != -1) { + out.write(buffer, 0, count); + request.setSoFar(request.getSoFar() + count); + request.setProgressPercent((int) (((double) request + .getSoFar() / (double) request + .getSize()) * 100)); + } + if (cancelled) { + onCancelled(); + } else { + out.flush(); + // check if size specified in the response header is the same as the size of the + // written file. This check cannot be made if compression was used + if (!isGzip && request.getSize() != DownloadStatus.SIZE_UNKNOWN && + request.getSoFar() != request.getSize()) { + onFail(DownloadError.ERROR_IO_ERROR, + "Download completed but size: " + + request.getSoFar() + + " does not equal expected size " + + request.getSize()); + return; + } + onSuccess(); + } + + } catch (IllegalArgumentException e) { + e.printStackTrace(); + onFail(DownloadError.ERROR_MALFORMED_URL, e.getMessage()); + } catch (SocketTimeoutException e) { + e.printStackTrace(); + onFail(DownloadError.ERROR_CONNECTION_ERROR, e.getMessage()); + } catch (UnknownHostException e) { + e.printStackTrace(); + onFail(DownloadError.ERROR_UNKNOWN_HOST, e.getMessage()); + } catch (IOException e) { + e.printStackTrace(); + onFail(DownloadError.ERROR_IO_ERROR, e.getMessage()); + } catch (NullPointerException e) { + // might be thrown by connection.getInputStream() + e.printStackTrace(); + onFail(DownloadError.ERROR_CONNECTION_ERROR, request.getSource()); + } finally { + IOUtils.closeQuietly(out); + if (httpClient != null) { + httpClient.getConnectionManager().shutdown(); + } + } + } + + private void onSuccess() { + if (AppConfig.DEBUG) + Log.d(TAG, "Download was successful"); + result.setSuccessful(); + } + + private void onFail(DownloadError reason, String reasonDetailed) { + if (AppConfig.DEBUG) { + Log.d(TAG, "Download failed"); + } result.setFailed(reason, reasonDetailed); - cleanup(); - } + cleanup(); + } - private void onCancelled() { - if (AppConfig.DEBUG) - Log.d(TAG, "Download was cancelled"); + private void onCancelled() { + if (AppConfig.DEBUG) + Log.d(TAG, "Download was cancelled"); result.setCancelled(); - cleanup(); - } - - /** Deletes unfinished downloads. */ - private void cleanup() { - if (request.getDestination() != null) { - File dest = new File(request.getDestination()); - if (dest.exists()) { - boolean rc = dest.delete(); - if (AppConfig.DEBUG) - Log.d(TAG, "Deleted file " + dest.getName() + "; Result: " - + rc); - } else { - if (AppConfig.DEBUG) - Log.d(TAG, "cleanup() didn't delete file: does not exist."); - } - } - } + cleanup(); + } + + /** + * Deletes unfinished downloads. + */ + private void cleanup() { + if (request.getDestination() != null) { + File dest = new File(request.getDestination()); + if (dest.exists()) { + boolean rc = dest.delete(); + if (AppConfig.DEBUG) + Log.d(TAG, "Deleted file " + dest.getName() + "; Result: " + + rc); + } else { + if (AppConfig.DEBUG) + Log.d(TAG, "cleanup() didn't delete file: does not exist."); + } + } + } } diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index c96051874..28ab3d939 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -229,9 +229,11 @@ public final class DBReader { title, item, link); break; } - chapter.setId(chapterCursor - .getLong(PodDBAdapter.KEY_ID_INDEX)); - item.getChapters().add(chapter); + if (chapter != null) { + chapter.setId(chapterCursor + .getLong(PodDBAdapter.KEY_ID_INDEX)); + item.getChapters().add(chapter); + } } while (chapterCursor.moveToNext()); } chapterCursor.close(); diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java index b1efda658..ba2e743a8 100644 --- a/src/de/danoeh/antennapod/storage/DBTasks.java +++ b/src/de/danoeh/antennapod/storage/DBTasks.java @@ -28,6 +28,7 @@ import de.danoeh.antennapod.service.download.DownloadStatus; import de.danoeh.antennapod.util.DownloadError; import de.danoeh.antennapod.util.NetworkUtils; import de.danoeh.antennapod.util.QueueAccess; +import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator; import de.danoeh.antennapod.util.exception.MediaFileNotFoundException; /** @@ -406,12 +407,13 @@ public final class DBTasks { private static int performAutoCleanup(final Context context, final int episodeNumber) { - List<FeedItem> candidates = DBReader.getDownloadedItems(context); - List<FeedItem> queue = DBReader.getQueue(context); + List<FeedItem> candidates = new ArrayList<FeedItem>(); + List<FeedItem> downloadedItems = DBReader.getDownloadedItems(context); + QueueAccess queue = QueueAccess.IDListAccess(DBReader.getQueueIDList(context)); List<FeedItem> delete; - for (FeedItem item : candidates) { + for (FeedItem item : downloadedItems) { if (item.hasMedia() && item.getMedia().isDownloaded() - && !queue.contains(item) && item.isRead()) { + && !queue.contains(item.getId()) && item.isRead()) { candidates.add(item); } @@ -440,7 +442,13 @@ public final class DBTasks { } for (FeedItem item : delete) { - DBWriter.deleteFeedMediaOfItem(context, item.getId()); + try { + DBWriter.deleteFeedMediaOfItem(context, item.getId()).get(); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } } int counter = delete.size(); @@ -561,6 +569,7 @@ public final class DBTasks { Log.d(TAG, "Feed with title " + newFeed.getTitle() + " already exists. Syncing new with existing one."); + Collections.sort(newFeed.getItems(), new FeedItemPubdateComparator()); savedFeed.setItems(DBReader.getFeedItemList(context, savedFeed)); if (savedFeed.compareWithOther(newFeed)) { if (AppConfig.DEBUG) @@ -578,7 +587,7 @@ public final class DBTasks { final int i = idx; item.setFeed(savedFeed); savedFeed.getItems().add(i, item); - DBWriter.markItemRead(context, item.getId(), false); + item.setRead(false); } else { oldItem.updateFromOther(item); } diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java index 5cdd6aee4..d96299dbe 100644 --- a/src/de/danoeh/antennapod/storage/DBWriter.java +++ b/src/de/danoeh/antennapod/storage/DBWriter.java @@ -73,7 +73,10 @@ public class DBWriter { } media.setDownloaded(false); media.setFile_url(null); - setFeedMedia(context, media); + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setMedia(media); + adapter.close(); // If media is currently being played, change playback // type to 'stream' and shutdown playback service @@ -212,7 +215,7 @@ public class DBWriter { media.setPlaybackCompletionDate(new Date()); PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); - adapter.setMedia(media); + adapter.setFeedMediaPlaybackCompletionDate(media); adapter.close(); EventDistributor.getInstance().sendPlaybackHistoryUpdateBroadcast(); @@ -654,18 +657,18 @@ public class DBWriter { } /** - * Saves only value of the 'position'-attribute of a FeedMedia object. + * Saves the 'position' and 'duration' attributes of a FeedMedia object * * @param context A context that is used for opening a database connection. * @param media The FeedMedia object. */ - public static Future<?> setFeedMediaPosition(final Context context, final FeedMedia media) { + public static Future<?> setFeedMediaPlaybackInformation(final Context context, final FeedMedia media) { return dbExec.submit(new Runnable() { @Override public void run() { PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); - adapter.setFeedMediaPosition(media); + adapter.setFeedMediaPlaybackInformation(media); adapter.close(); } }); diff --git a/src/de/danoeh/antennapod/storage/DownloadRequester.java b/src/de/danoeh/antennapod/storage/DownloadRequester.java index 246b8bdfd..013162f0c 100644 --- a/src/de/danoeh/antennapod/storage/DownloadRequester.java +++ b/src/de/danoeh/antennapod/storage/DownloadRequester.java @@ -26,9 +26,9 @@ import de.danoeh.antennapod.util.URLChecker; public class DownloadRequester { private static final String TAG = "DownloadRequester"; - public static String IMAGE_DOWNLOADPATH = "images/"; - public static String FEED_DOWNLOADPATH = "cache/"; - public static String MEDIA_DOWNLOADPATH = "media/"; + public static final String IMAGE_DOWNLOADPATH = "images/"; + public static final String FEED_DOWNLOADPATH = "cache/"; + public static final String MEDIA_DOWNLOADPATH = "media/"; private static DownloadRequester downloader; @@ -38,7 +38,7 @@ public class DownloadRequester { downloads = new ConcurrentHashMap<String, DownloadRequest>(); } - public static DownloadRequester getInstance() { + public static synchronized DownloadRequester getInstance() { if (downloader == null) { downloader = new DownloadRequester(); } diff --git a/src/de/danoeh/antennapod/storage/FeedItemStatistics.java b/src/de/danoeh/antennapod/storage/FeedItemStatistics.java index 17e838761..6b79dd144 100644 --- a/src/de/danoeh/antennapod/storage/FeedItemStatistics.java +++ b/src/de/danoeh/antennapod/storage/FeedItemStatistics.java @@ -17,7 +17,7 @@ public class FeedItemStatistics { this.numberOfItems = numberOfItems; this.numberOfNewItems = numberOfNewItems; this.numberOfInProgressItems = numberOfInProgressItems; - this.lastUpdate = lastUpdate; + this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null; } public long getFeedID() { @@ -37,6 +37,6 @@ public class FeedItemStatistics { } public Date getLastUpdate() { - return lastUpdate; + return (lastUpdate != null) ? (Date) lastUpdate.clone() : null; } } diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index edcbe1cf4..d36d6184c 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -10,7 +10,6 @@ import android.database.DatabaseUtils; import android.database.MergeCursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteStatement; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; @@ -235,7 +234,7 @@ public class PodDBAdapter { /** * Select id, description and content-encoded column from feeditems. */ - public static final String[] SEL_FI_EXTRA = {KEY_ID, KEY_DESCRIPTION, + private static final String[] SEL_FI_EXTRA = {KEY_ID, KEY_DESCRIPTION, KEY_CONTENT_ENCODED, KEY_FEED}; // column indices for SEL_FI_EXTRA @@ -279,6 +278,13 @@ public class PodDBAdapter { //db.close(); } + public static boolean deleteDatabase(Context context) { + Log.w(TAG, "Deleting database"); + dbHelperSingleton.close(); + dbHelperSingleton = null; + return context.deleteDatabase(DATABASE_NAME); + } + /** * Inserts or updates a feed entry * @@ -361,6 +367,7 @@ public class PodDBAdapter { values.put(KEY_DOWNLOAD_URL, media.getDownload_url()); values.put(KEY_DOWNLOADED, media.isDownloaded()); values.put(KEY_FILE_URL, media.getFile_url()); + if (media.getPlaybackCompletionDate() != null) { values.put(KEY_PLAYBACK_COMPLETION_DATE, media .getPlaybackCompletionDate().getTime()); @@ -379,14 +386,26 @@ public class PodDBAdapter { return media.getId(); } - public void setFeedMediaPosition(FeedMedia media) { + public void setFeedMediaPlaybackInformation(FeedMedia media) { if (media.getId() != 0) { ContentValues values = new ContentValues(); values.put(KEY_POSITION, media.getPosition()); + values.put(KEY_DURATION, media.getDuration()); + db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", + new String[]{String.valueOf(media.getId())}); + } else { + Log.e(TAG, "setFeedMediaPlaybackInformation: ID of media was 0"); + } + } + + public void setFeedMediaPlaybackCompletionDate(FeedMedia media) { + if (media.getId() != 0) { + ContentValues values = new ContentValues(); + values.put(KEY_PLAYBACK_COMPLETION_DATE, media.getPlaybackCompletionDate().getTime()); db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", new String[]{String.valueOf(media.getId())}); } else { - Log.e(TAG, "setFeedMediaPosition: ID of media was 0"); + Log.e(TAG, "setFeedMediaPlaybackCompletionDate: ID of media was 0"); } } @@ -397,8 +416,10 @@ public class PodDBAdapter { public void setCompleteFeed(Feed feed) { db.beginTransaction(); setFeed(feed); - for (FeedItem item : feed.getItemsArray()) { - setFeedItem(item); + if (feed.getItems() != null) { + for (FeedItem item : feed.getItems()) { + setFeedItem(item, false); + } } db.setTransactionSuccessful(); db.endTransaction(); @@ -407,7 +428,7 @@ public class PodDBAdapter { public void setFeedItemlist(List<FeedItem> items) { db.beginTransaction(); for (FeedItem item : items) { - setFeedItem(item); + setFeedItem(item, true); } db.setTransactionSuccessful(); db.endTransaction(); @@ -415,7 +436,7 @@ public class PodDBAdapter { public long setSingleFeedItem(FeedItem item) { db.beginTransaction(); - long result = setFeedItem(item); + long result = setFeedItem(item, true); db.setTransactionSuccessful(); db.endTransaction(); return result; @@ -423,10 +444,12 @@ public class PodDBAdapter { /** * Inserts or updates a feeditem entry - * + * @param item The FeedItem + * @param saveFeed true if the Feed of the item should also be saved. This should be set to + * false if the method is executed on a list of FeedItems of the same Feed. * @return the id of the entry */ - private long setFeedItem(FeedItem item) { + private long setFeedItem(FeedItem item, boolean saveFeed) { ContentValues values = new ContentValues(); values.put(KEY_TITLE, item.getTitle()); values.put(KEY_LINK, item.getLink()); @@ -438,7 +461,7 @@ public class PodDBAdapter { } values.put(KEY_PUBDATE, item.getPubDate().getTime()); values.put(KEY_PAYMENT_LINK, item.getPaymentLink()); - if (item.getFeed() != null) { + if (saveFeed && item.getFeed() != null) { setFeed(item.getFeed()); } values.put(KEY_FEED, item.getFeed().getId()); @@ -603,8 +626,10 @@ public class PodDBAdapter { if (feed.getImage() != null) { removeFeedImage(feed.getImage()); } - for (FeedItem item : feed.getItemsArray()) { - removeFeedItem(item); + if (feed.getItems() != null) { + for (FeedItem item : feed.getItems()) { + removeFeedItem(item); + } } db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?", new String[]{String.valueOf(feed.getId())}); @@ -756,7 +781,7 @@ public class PodDBAdapter { final String query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS + " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" - + TABLE_NAME_FEED_MEDIA + "." + KEY_ID + " WHERE " + + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM + " WHERE " + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + ">0"; Cursor c = db.rawQuery(query, null); return c; @@ -985,7 +1010,7 @@ public class PodDBAdapter { " MAX(pubDate) AS latest_episode," + " COUNT(CASE WHEN position>0 THEN 1 END) AS in_progress," + " COUNT(CASE WHEN downloaded=1 THEN 1 END) AS episodes_downloaded " + - " FROM FeedItems INNER JOIN FeedMedia ON FeedItems.id=FeedMedia.feeditem GROUP BY FeedItems.feed)" + + " FROM FeedItems LEFT JOIN FeedMedia ON FeedItems.id=FeedMedia.feeditem GROUP BY FeedItems.feed)" + " INNER JOIN Feeds ON Feeds.id = feed ORDER BY Feeds.title;"; public Cursor getFeedStatisticsCursor() { diff --git a/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java b/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java index 1efaac359..bcb0422ce 100644 --- a/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java +++ b/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java @@ -121,7 +121,7 @@ public class NSAtom extends Namespace { if (state.getContentBuf() != null) { content = state.getContentBuf().toString(); } else { - content = new String(); + content = ""; } SyndElement topElement = state.getTagstack().peek(); String top = topElement.getName(); diff --git a/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java b/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java index 30835434f..a1ed01354 100644 --- a/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java +++ b/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java @@ -11,7 +11,7 @@ import android.util.Log; public class SyndDateUtils { private static final String TAG = "DateUtils"; - public static final String[] RFC822DATES = { "dd MMM yy HH:mm:ss Z", }; + private static final String[] RFC822DATES = { "dd MMM yy HH:mm:ss Z", }; /** RFC 3339 date format for UTC dates. */ public static final String RFC3339UTC = "yyyy-MM-dd'T'HH:mm:ss'Z'"; @@ -123,12 +123,12 @@ public class SyndDateUtils { int idx = 0; if (parts.length == 3) { // string has hours - result += Integer.valueOf(parts[idx]) * 3600000; + result += Integer.valueOf(parts[idx]) * 3600000L; idx++; } - result += Integer.valueOf(parts[idx]) * 60000; + result += Integer.valueOf(parts[idx]) * 60000L; idx++; - result += (Float.valueOf(parts[idx])) * 1000; + result += (Float.valueOf(parts[idx])) * 1000L; return result; } } diff --git a/src/de/danoeh/antennapod/util/ChapterUtils.java b/src/de/danoeh/antennapod/util/ChapterUtils.java index ac8149119..521bfebea 100644 --- a/src/de/danoeh/antennapod/util/ChapterUtils.java +++ b/src/de/danoeh/antennapod/util/ChapterUtils.java @@ -35,9 +35,9 @@ public class ChapterUtils { * chapters. */ public static void readID3ChaptersFromPlayableStreamUrl(Playable p) { - if (AppConfig.DEBUG) - Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle()); if (p != null && p.getStreamUrl() != null) { + if (AppConfig.DEBUG) + Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle()); InputStream in = null; try { URL url = new URL(p.getStreamUrl()); @@ -86,9 +86,9 @@ public class ChapterUtils { * chapters. */ public static void readID3ChaptersFromPlayableFileUrl(Playable p) { - if (AppConfig.DEBUG) - Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle()); if (p != null && p.localFileAvailable() && p.getLocalMediaUrl() != null) { + if (AppConfig.DEBUG) + Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle()); File source = new File(p.getLocalMediaUrl()); if (source.exists()) { ChapterReader reader = new ChapterReader(); diff --git a/src/de/danoeh/antennapod/util/DuckType.java b/src/de/danoeh/antennapod/util/DuckType.java new file mode 100644 index 000000000..0dfc01508 --- /dev/null +++ b/src/de/danoeh/antennapod/util/DuckType.java @@ -0,0 +1,115 @@ +/* Adapted from: http://thinking-in-code.blogspot.com/2008/11/duck-typing-in-java-using-dynamic.html */ + +package de.danoeh.antennapod.util; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * Allows "duck typing" or dynamic invocation based on method signature rather + * than type hierarchy. In other words, rather than checking whether something + * IS-a duck, check whether it WALKS-like-a duck or QUACKS-like a duck. + * + * To use first use the coerce static method to indicate the object you want to + * do Duck Typing for, then specify an interface to the to method which you want + * to coerce the type to, e.g: + * + * public interface Foo { void aMethod(); } class Bar { ... public void + * aMethod() { ... } ... } Bar bar = ...; Foo foo = + * DuckType.coerce(bar).to(Foo.class); foo.aMethod(); + * + * + */ +public class DuckType { + + private final Object objectToCoerce; + + private DuckType(Object objectToCoerce) { + this.objectToCoerce = objectToCoerce; + } + + private class CoercedProxy implements InvocationHandler { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Method delegateMethod = findMethodBySignature(method); + assert delegateMethod != null; + return delegateMethod.invoke(DuckType.this.objectToCoerce, args); + } + } + + /** + * Specify the duck typed object to coerce. + * + * @param object + * the object to coerce + * @return + */ + public static DuckType coerce(Object object) { + return new DuckType(object); + } + + /** + * Coerce the Duck Typed object to the given interface providing it + * implements all the necessary methods. + * + * @param + * @param iface + * @return an instance of the given interface that wraps the duck typed + * class + * @throws ClassCastException + * if the object being coerced does not implement all the + * methods in the given interface. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public <T> T to(Class iface) { + assert iface.isInterface() : "cannot coerce object to a class, must be an interface"; + if (isA(iface)) { + return (T) iface.cast(objectToCoerce); + } + if (quacksLikeA(iface)) { + return generateProxy(iface); + } + throw new ClassCastException("Could not coerce object of type " + objectToCoerce.getClass() + " to " + iface); + } + + @SuppressWarnings("rawtypes") + private boolean isA(Class iface) { + return objectToCoerce.getClass().isInstance(iface); + } + + /** + * Determine whether the duck typed object can be used with the given + * interface. + * + * @param Type + * of the interface to check. + * @param iface + * Interface class to check + * @return true if the object will support all the methods in the interface, + * false otherwise. + */ + @SuppressWarnings("rawtypes") + public boolean quacksLikeA(Class iface) { + for (Method method : iface.getMethods()) { + if (findMethodBySignature(method) == null) { + return false; + } + } + return true; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private <T> T generateProxy(Class iface) { + return (T) Proxy.newProxyInstance(iface.getClassLoader(), new Class[] { iface }, new CoercedProxy()); + } + + private Method findMethodBySignature(Method method) { + try { + return objectToCoerce.getClass().getMethod(method.getName(), method.getParameterTypes()); + } catch (NoSuchMethodException e) { + return null; + } + } + +}
\ No newline at end of file diff --git a/src/de/danoeh/antennapod/util/LangUtils.java b/src/de/danoeh/antennapod/util/LangUtils.java index 53f8de773..e6e1d8399 100644 --- a/src/de/danoeh/antennapod/util/LangUtils.java +++ b/src/de/danoeh/antennapod/util/LangUtils.java @@ -1,8 +1,11 @@ package de.danoeh.antennapod.util; +import java.nio.charset.Charset; import java.util.HashMap; public class LangUtils { + public static final Charset UTF_8 = Charset.forName("UTF-8"); + private static HashMap<String, String> languages; static { languages = new HashMap<String, String>(); diff --git a/src/de/danoeh/antennapod/util/QueueAccess.java b/src/de/danoeh/antennapod/util/QueueAccess.java index ce9d11429..7a1c7fef2 100644 --- a/src/de/danoeh/antennapod/util/QueueAccess.java +++ b/src/de/danoeh/antennapod/util/QueueAccess.java @@ -51,9 +51,8 @@ public abstract class QueueAccess { if (items == null) { return false; } - Iterator<FeedItem> it = items.iterator(); - for (FeedItem i = it.next(); it.hasNext(); i = it.next()) { - if (i.getId() == id) { + for (FeedItem item : items) { + if (item.getId() == id) { return true; } } @@ -63,8 +62,10 @@ public abstract class QueueAccess { @Override public boolean remove(long id) { Iterator<FeedItem> it = items.iterator(); - for (FeedItem i = it.next(); it.hasNext(); i = it.next()) { - if (i.getId() == id) { + FeedItem item; + while (it.hasNext()) { + item = it.next(); + if (item.getId() == id) { it.remove(); return true; } diff --git a/src/de/danoeh/antennapod/util/URLChecker.java b/src/de/danoeh/antennapod/util/URLChecker.java index 6d9b8ff03..13668d4a9 100644 --- a/src/de/danoeh/antennapod/util/URLChecker.java +++ b/src/de/danoeh/antennapod/util/URLChecker.java @@ -19,13 +19,12 @@ public final class URLChecker { * */ public static String prepareURL(String url) { StringBuilder builder = new StringBuilder(); - url = url.trim(); - if (!url.startsWith("http")) { + if (url.startsWith("feed://")) { + if (AppConfig.DEBUG) Log.d(TAG, "Replacing feed:// with http://"); + url = url.replace("feed://", "http://"); + } else if (!(url.startsWith("http://") || url.startsWith("https://"))) { + if (AppConfig.DEBUG) Log.d(TAG, "Adding http:// at the beginning of the URL"); builder.append("http://"); - if (AppConfig.DEBUG) Log.d(TAG, "Missing http; appending"); - } else if (url.startsWith("https")) { - if (AppConfig.DEBUG) Log.d(TAG, "Replacing https with http"); - url = url.replaceFirst("https", "http"); } builder.append(url); diff --git a/src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java b/src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java index 2cfe52364..d0561252f 100644 --- a/src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java +++ b/src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java @@ -9,8 +9,7 @@ public class DownloadStatusComparator implements Comparator<DownloadStatus> { @Override public int compare(DownloadStatus lhs, DownloadStatus rhs) { - return -lhs.getCompletionDate().compareTo(rhs.getCompletionDate()); - + return rhs.getCompletionDate().compareTo(lhs.getCompletionDate()); } } diff --git a/src/de/danoeh/antennapod/util/comparator/FeedItemPubdateComparator.java b/src/de/danoeh/antennapod/util/comparator/FeedItemPubdateComparator.java index b9ee6c07e..c95c0833c 100644 --- a/src/de/danoeh/antennapod/util/comparator/FeedItemPubdateComparator.java +++ b/src/de/danoeh/antennapod/util/comparator/FeedItemPubdateComparator.java @@ -13,7 +13,7 @@ public class FeedItemPubdateComparator implements Comparator<FeedItem> { }*/ @Override public int compare(FeedItem lhs, FeedItem rhs) { - return -lhs.getPubDate().compareTo(rhs.getPubDate()); + return rhs.getPubDate().compareTo(lhs.getPubDate()); } } diff --git a/src/de/danoeh/antennapod/util/comparator/PlaybackCompletionDateComparator.java b/src/de/danoeh/antennapod/util/comparator/PlaybackCompletionDateComparator.java index 2d0ce75ca..434a5a956 100644 --- a/src/de/danoeh/antennapod/util/comparator/PlaybackCompletionDateComparator.java +++ b/src/de/danoeh/antennapod/util/comparator/PlaybackCompletionDateComparator.java @@ -11,8 +11,8 @@ public class PlaybackCompletionDateComparator implements Comparator<FeedItem> { && lhs.getMedia().getPlaybackCompletionDate() != null && rhs.getMedia() != null && rhs.getMedia().getPlaybackCompletionDate() != null) { - return -lhs.getMedia().getPlaybackCompletionDate() - .compareTo(rhs.getMedia().getPlaybackCompletionDate()); + return rhs.getMedia().getPlaybackCompletionDate() + .compareTo(lhs.getMedia().getPlaybackCompletionDate()); } return 0; } diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java index aa029f672..fdd2011ae 100644 --- a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java +++ b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java @@ -51,10 +51,13 @@ public class FeedItemMenuHandler { * parameter should be set to false if the menu space is limited. * @param queueAccess * Used for testing if the queue contains the selected item - * @return Always returns true + * @return Returns true if selectedItem is not null. * */ public static boolean onPrepareMenu(MenuInterface mi, FeedItem selectedItem, boolean showExtendedMenu, QueueAccess queueAccess) { + if (selectedItem == null) { + return false; + } DownloadRequester requester = DownloadRequester.getInstance(); boolean hasMedia = selectedItem.getMedia() != null; boolean downloaded = hasMedia && selectedItem.getMedia().isDownloaded(); diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java index 843607617..446e024d9 100644 --- a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java +++ b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java @@ -30,6 +30,10 @@ public class FeedMenuHandler { } public static boolean onPrepareOptionsMenu(Menu menu, Feed selectedFeed) { + if (selectedFeed == null) { + return false; + } + if (AppConfig.DEBUG) Log.d(TAG, "Preparing options menu"); menu.findItem(R.id.mark_all_read_item).setVisible( diff --git a/src/de/danoeh/antennapod/util/playback/AudioPlayer.java b/src/de/danoeh/antennapod/util/playback/AudioPlayer.java new file mode 100644 index 000000000..68d31324d --- /dev/null +++ b/src/de/danoeh/antennapod/util/playback/AudioPlayer.java @@ -0,0 +1,30 @@ +package de.danoeh.antennapod.util.playback; + +import android.content.Context; +import android.util.Log; +import android.view.SurfaceHolder; + +import com.aocate.media.MediaPlayer; + +public class AudioPlayer extends MediaPlayer implements IPlayer { + private static final String TAG = "AudioPlayer"; + + public AudioPlayer(Context context) { + super(context); + } + + @Override + public void setScreenOnWhilePlaying(boolean screenOn) { + Log.e(TAG, "Setting screen on while playing not supported in Audio Player"); + throw new UnsupportedOperationException("Setting screen on while playing not supported in Audio Player"); + + } + + @Override + public void setDisplay(SurfaceHolder sh) { + if (sh != null) { + Log.e(TAG, "Setting display not supported in Audio Player"); + throw new UnsupportedOperationException("Setting display not supported in Audio Player"); + } + } +} diff --git a/src/de/danoeh/antennapod/util/playback/ExternalMedia.java b/src/de/danoeh/antennapod/util/playback/ExternalMedia.java index 1ada0ec03..e937ee437 100644 --- a/src/de/danoeh/antennapod/util/playback/ExternalMedia.java +++ b/src/de/danoeh/antennapod/util/playback/ExternalMedia.java @@ -25,7 +25,6 @@ public class ExternalMedia implements Playable { private String episodeTitle; private String feedTitle; - private String shownotes; private MediaType mediaType = MediaType.AUDIO; private List<Chapter> chapters; private int duration; @@ -80,8 +79,13 @@ public class ExternalMedia implements Playable { .extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE); feedTitle = mmr .extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM); - duration = Integer.parseInt(mmr + try { + duration = Integer.parseInt(mmr .extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)); + } catch (NumberFormatException e) { + e.printStackTrace(); + throw new PlayableException("NumberFormatException when reading duration of media file"); + } ChapterUtils.loadChaptersFromFileUrl(this); } diff --git a/src/de/danoeh/antennapod/util/playback/IPlayer.java b/src/de/danoeh/antennapod/util/playback/IPlayer.java new file mode 100644 index 000000000..ca9b36358 --- /dev/null +++ b/src/de/danoeh/antennapod/util/playback/IPlayer.java @@ -0,0 +1,64 @@ +package de.danoeh.antennapod.util.playback; + +import java.io.IOException; + +import android.view.SurfaceHolder; + +public interface IPlayer { + boolean canSetPitch(); + + boolean canSetSpeed(); + + float getCurrentPitchStepsAdjustment(); + + int getCurrentPosition(); + + float getCurrentSpeedMultiplier(); + + int getDuration(); + + float getMaxSpeedMultiplier(); + + float getMinSpeedMultiplier(); + + boolean isLooping(); + + boolean isPlaying(); + + void pause(); + + void prepare() throws IllegalStateException, IOException; + + void prepareAsync(); + + void release(); + + void reset(); + + void seekTo(int msec); + + void setAudioStreamType(int streamtype); + + void setScreenOnWhilePlaying(boolean screenOn); + + void setDataSource(String path) throws IllegalStateException, IOException, + IllegalArgumentException, SecurityException; + + void setDisplay(SurfaceHolder sh); + + void setEnableSpeedAdjustment(boolean enableSpeedAdjustment); + + void setLooping(boolean looping); + + void setPitchStepsAdjustment(float pitchSteps); + + void setPlaybackPitch(float f); + + void setPlaybackSpeed(float f); + + void setVolume(float left, float right); + + void start(); + + void stop(); +} diff --git a/src/de/danoeh/antennapod/util/playback/PlaybackController.java b/src/de/danoeh/antennapod/util/playback/PlaybackController.java index 5a5b43a6e..f5d1847b3 100644 --- a/src/de/danoeh/antennapod/util/playback/PlaybackController.java +++ b/src/de/danoeh/antennapod/util/playback/PlaybackController.java @@ -342,6 +342,9 @@ public abstract class PlaybackController { case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_END: onPlaybackEnd(); break; + case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE: + onPlaybackSpeedChange(); + break; } } else { @@ -369,6 +372,8 @@ public abstract class PlaybackController { } }; + public abstract void onPlaybackSpeedChange(); + public abstract void onShutdownNotification(); /** @@ -663,6 +668,24 @@ public abstract class PlaybackController { return status; } + public boolean canSetPlaybackSpeed() { + return playbackService != null && playbackService.canSetSpeed(); + } + + public void setPlaybackSpeed(float speed) { + if (playbackService != null) { + playbackService.setSpeed(speed); + } + } + + public float getCurrentPlaybackSpeedMultiplier() { + if (canSetPlaybackSpeed()) { + return playbackService.getCurrentPlaybackSpeed(); + } else { + return -1; + } + } + public boolean isPlayingVideo() { if (playbackService != null) { return PlaybackService.isPlayingVideo(); @@ -670,6 +693,7 @@ public abstract class PlaybackController { return false; } + /** * Returns true if PlaybackController can communicate with the playback * service. diff --git a/src/de/danoeh/antennapod/util/playback/VideoPlayer.java b/src/de/danoeh/antennapod/util/playback/VideoPlayer.java new file mode 100644 index 000000000..f0a50542c --- /dev/null +++ b/src/de/danoeh/antennapod/util/playback/VideoPlayer.java @@ -0,0 +1,62 @@ +package de.danoeh.antennapod.util.playback; + +import android.media.MediaPlayer; +import android.util.Log; + +public class VideoPlayer extends MediaPlayer implements IPlayer { + private static final String TAG = "VideoPlayer"; + + @Override + public boolean canSetPitch() { + return false; + } + + @Override + public boolean canSetSpeed() { + return false; + } + + @Override + public float getCurrentPitchStepsAdjustment() { + return 1; + } + + @Override + public float getCurrentSpeedMultiplier() { + return 1; + } + + @Override + public float getMaxSpeedMultiplier() { + return 1; + } + + @Override + public float getMinSpeedMultiplier() { + return 1; + } + + @Override + public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) throws UnsupportedOperationException { + Log.e(TAG, "Setting enable speed adjustment unsupported in video player"); + throw new UnsupportedOperationException("Setting enable speed adjustment unsupported in video player"); + } + + @Override + public void setPitchStepsAdjustment(float pitchSteps) { + Log.e(TAG, "Setting pitch steps adjustment unsupported in video player"); + throw new UnsupportedOperationException("Setting pitch steps adjustment unsupported in video player"); + } + + @Override + public void setPlaybackPitch(float f) { + Log.e(TAG, "Setting playback pitch unsupported in video player"); + throw new UnsupportedOperationException("Setting playback pitch unsupported in video player"); + } + + @Override + public void setPlaybackSpeed(float f) { + Log.e(TAG, "Setting playback speed unsupported in video player"); + throw new UnsupportedOperationException("Setting playback speed unsupported in video player"); + } +} |