diff options
author | daniel oeh <daniel.oeh@gmail.com> | 2013-03-06 21:04:05 +0100 |
---|---|---|
committer | daniel oeh <daniel.oeh@gmail.com> | 2013-03-06 21:04:05 +0100 |
commit | b3a250c766253da57a917814a6f60e44f616e938 (patch) | |
tree | d26aec2b75fa0fa0117499110a7650317930b64b | |
parent | ec1992f36c5bf1916ebaed8196705b2324486388 (diff) | |
parent | 7fe0e05304b5683d3a3cc0ba5a1938f73ffd54de (diff) | |
download | AntennaPod-b3a250c766253da57a917814a6f60e44f616e938.zip |
Merge branch 'apichanges' into develop
76 files changed, 2582 insertions, 1332 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 1455263d0..2e6a599f1 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -74,7 +74,17 @@ android:name=".activity.AudioplayerActivity" android:configChanges="keyboardHidden|orientation" android:launchMode="singleTask" - android:screenOrientation="portrait" android:label=" "/> + android:screenOrientation="portrait" > + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data android:scheme="file" /> + <data android:mimeType="audio/*" /> + </intent-filter> + </activity> <service android:name=".service.download.DownloadService" @@ -316,6 +326,15 @@ android:name=".activity.VideoplayerActivity" android:configChanges="keyboardHidden|orientation" android:screenOrientation="landscape" > + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data android:scheme="file" /> + <data android:mimeType="video/*" /> + </intent-filter> </activity> <activity android:name=".activity.PlaybackHistoryActivity" @@ -323,7 +342,11 @@ <activity android:name=".activity.DirectoryChooserActivity" android:label="@string/choose_data_directory" /> - <activity android:label="@string/organize_queue_label" android:name=".activity.OrganizeQueueActivity" android:configChanges="orientation"></activity> + <activity + android:name=".activity.OrganizeQueueActivity" + android:configChanges="orientation" + android:label="@string/organize_queue_label" > + </activity> </application> -</manifest>
\ No newline at end of file +</manifest> diff --git a/src/de/danoeh/antennapod/PodcastApp.java b/src/de/danoeh/antennapod/PodcastApp.java index 0e3fab80d..9a3332788 100644 --- a/src/de/danoeh/antennapod/PodcastApp.java +++ b/src/de/danoeh/antennapod/PodcastApp.java @@ -1,54 +1,24 @@ package de.danoeh.antennapod; -import java.io.File; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import android.app.AlarmManager; import android.app.Application; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; import android.content.res.Configuration; -import android.preference.PreferenceManager; import android.util.Log; -import de.danoeh.antennapod.activity.OpmlImportFromPathActivity; -import de.danoeh.antennapod.asynctask.FeedImageLoader; +import de.danoeh.antennapod.asynctask.ImageLoader; +import de.danoeh.antennapod.feed.EventDistributor; import de.danoeh.antennapod.feed.FeedManager; -import de.danoeh.antennapod.feed.FeedMedia; -import de.danoeh.antennapod.receiver.FeedUpdateReceiver; -import de.danoeh.antennapod.service.PlaybackService; +import de.danoeh.antennapod.preferences.PlaybackPreferences; +import de.danoeh.antennapod.preferences.UserPreferences; /** Main application class. */ -public class PodcastApp extends Application implements - SharedPreferences.OnSharedPreferenceChangeListener { +public class PodcastApp extends Application { private static final String TAG = "PodcastApp"; public static final String EXPORT_DIR = "export/"; - public static final String PREF_PAUSE_ON_HEADSET_DISCONNECT = "prefPauseOnHeadsetDisconnect"; - public static final String PREF_FOLLOW_QUEUE = "prefFollowQueue"; - public static final String PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY = "prefDownloadMediaOnWifiOnly"; - public static final String PREF_UPDATE_INTERVALL = "prefAutoUpdateIntervall"; - public static final String PREF_MOBILE_UPDATE = "prefMobileUpdate"; - public static final String PREF_AUTO_QUEUE = "prefAutoQueue"; - public static final String PREF_DISPLAY_ONLY_EPISODES = "prefDisplayOnlyEpisodes"; - public static final String PREF_AUTO_DELETE = "prefAutoDelete"; - public static final String PREF_THEME = "prefTheme"; - public static final String PREF_DATA_FOLDER = "prefDataFolder"; - private static float LOGICAL_DENSITY; private static PodcastApp singleton; - private boolean displayOnlyEpisodes; - - private static long currentlyPlayingMediaId; - - /** Resource id of the currently selected theme. */ - private static int theme; - public static PodcastApp getInstance() { return singleton; } @@ -58,225 +28,29 @@ public class PodcastApp extends Application implements super.onCreate(); singleton = this; LOGICAL_DENSITY = getResources().getDisplayMetrics().density; - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(this); - displayOnlyEpisodes = prefs.getBoolean(PREF_DISPLAY_ONLY_EPISODES, - false); - currentlyPlayingMediaId = prefs.getLong( - PlaybackService.PREF_CURRENTLY_PLAYING_MEDIA, - PlaybackService.NO_MEDIA_PLAYING); - readThemeValue(); - createImportDirectory(); - createNoMediaFile(); - prefs.registerOnSharedPreferenceChangeListener(this); + + UserPreferences.createInstance(this); + PlaybackPreferences.createInstance(this); + EventDistributor.getInstance(); FeedManager manager = FeedManager.getInstance(); manager.loadDBData(getApplicationContext()); } - /** Create a .nomedia file to prevent scanning by the media scanner. */ - private void createNoMediaFile() { - File f = new File(getExternalFilesDir(null), ".nomedia"); - if (!f.exists()) { - try { - f.createNewFile(); - } catch (IOException e) { - Log.e(TAG, "Could not create .nomedia file"); - e.printStackTrace(); - } - if (AppConfig.DEBUG) - Log.d(TAG, ".nomedia file created"); - } - } - - /** - * Creates the import directory if it doesn't exist and if storage is - * available - */ - private void createImportDirectory() { - File importDir = getDataFolder(this, OpmlImportFromPathActivity.IMPORT_DIR); - if (importDir != null) { - if (importDir.exists()) { - if (AppConfig.DEBUG) - Log.d(TAG, "Import directory already exists"); - } else { - if (AppConfig.DEBUG) - Log.d(TAG, "Creating import directory"); - importDir.mkdir(); - } - } else { - if (AppConfig.DEBUG) - Log.d(TAG, "Could not access external storage."); - } - } - @Override public void onLowMemory() { super.onLowMemory(); Log.w(TAG, "Received onLowOnMemory warning. Cleaning image cache..."); - FeedImageLoader.getInstance().wipeImageCache(); - } - - /** - * Listens for changes in the 'update intervall'-preference and changes the - * alarm if necessary. - */ - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, - String key) { - if (AppConfig.DEBUG) - Log.d(TAG, "Registered change of application preferences"); - if (key.equals(PREF_UPDATE_INTERVALL)) { - AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); - int hours = Integer.parseInt(sharedPreferences.getString( - PREF_UPDATE_INTERVALL, "0")); - PendingIntent updateIntent = PendingIntent.getBroadcast(this, 0, - new Intent(FeedUpdateReceiver.ACTION_REFRESH_FEEDS), 0); - alarmManager.cancel(updateIntent); - if (hours != 0) { - long newIntervall = TimeUnit.HOURS.toMillis(hours); - alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, - newIntervall, newIntervall, updateIntent); - if (AppConfig.DEBUG) - Log.d(TAG, "Changed alarm to new intervall"); - } else { - if (AppConfig.DEBUG) - Log.d(TAG, "Automatic update was deactivated"); - } - } else if (key.equals(PREF_DISPLAY_ONLY_EPISODES)) { - if (AppConfig.DEBUG) - Log.d(TAG, "PREF_DISPLAY_ONLY_EPISODES changed"); - displayOnlyEpisodes = sharedPreferences.getBoolean( - PREF_DISPLAY_ONLY_EPISODES, false); - } else if (key.equals(PlaybackService.PREF_LAST_PLAYED_ID)) { - if (AppConfig.DEBUG) - Log.d(TAG, "PREF_LAST_PLAYED_ID changed"); - long mediaId = sharedPreferences.getLong( - PlaybackService.PREF_AUTODELETE_MEDIA_ID, -1); - if (mediaId != -1) { - FeedManager manager = FeedManager.getInstance(); - FeedMedia media = manager.getFeedMedia(mediaId); - if (media != null) { - manager.autoDeleteIfPossible(this, media); - } - } - } else if (key.equals(PlaybackService.PREF_CURRENTLY_PLAYING_MEDIA)) { - long id = sharedPreferences.getLong( - PlaybackService.PREF_CURRENTLY_PLAYING_MEDIA, - PlaybackService.NO_MEDIA_PLAYING); - if (AppConfig.DEBUG) - Log.d(TAG, "Currently playing media set to " + id); - if (id != currentlyPlayingMediaId) { - currentlyPlayingMediaId = id; - } - } else if (key.equals(PREF_THEME)) { - readThemeValue(); - } + ImageLoader.getInstance().wipeImageCache(); } public static float getLogicalDensity() { return LOGICAL_DENSITY; } - public boolean displayOnlyEpisodes() { - return displayOnlyEpisodes; - } - - public static long getCurrentlyPlayingMediaId() { - return currentlyPlayingMediaId; - } - public boolean isLargeScreen() { return (getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_LARGE || (getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE; } - public static int getThemeResourceId() { - return theme; - } - - /** Read value of prefTheme and determine the correct resource id. */ - private void readThemeValue() { - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(this); - int prefTheme = Integer.parseInt(prefs.getString(PREF_THEME, "0")); - switch (prefTheme) { - case 0: - theme = R.style.Theme_AntennaPod_Light; - break; - case 1: - theme = R.style.Theme_AntennaPod_Dark; - break; - } - } - - /** - * Return the folder where the app stores all of its data. This method will - * return the standard data folder if none has been set by the user. - * - * @param type - * The name of the folder inside the data folder. May be null - * when accessing the root of the data folder. - * @return The data folder that has been requested or null if the folder - * could not be created. - */ - public static File getDataFolder(Context context, String type) { - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(context.getApplicationContext()); - String strDir = prefs.getString(PREF_DATA_FOLDER, null); - if (strDir == null) { - if (AppConfig.DEBUG) - Log.d(TAG, "Using default data folder"); - return context.getExternalFilesDir(type); - } else { - File dataDir = new File(strDir); - if (!dataDir.exists()) { - if (!dataDir.mkdir()) { - Log.w(TAG, "Could not create data folder"); - return null; - } - } - - if (type == null) { - return dataDir; - } else { - // handle path separators - String[] dirs = type.split("/"); - for (int i = 0; i < dirs.length; i++) { - if (dirs.length > 0) { - if (i < dirs.length - 1) { - dataDir = getDataFolder(context, dirs[i]); - if (dataDir == null) { - return null; - } - } - type = dirs[i]; - } - } - File typeDir = new File(dataDir, type); - if (!typeDir.exists()) { - if (dataDir.canWrite()) { - if (!typeDir.mkdir()) { - Log.e(TAG, "Could not create data folder named " - + type); - return null; - } - } - } - return typeDir; - } - - } - } - - public void setDataFolder(String dir) { - if (AppConfig.DEBUG) - Log.d(TAG, "Result from DirectoryChooser: " + dir); - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(this); - SharedPreferences.Editor editor = prefs.edit(); - editor.putString(PodcastApp.PREF_DATA_FOLDER, dir); - editor.commit(); - createImportDirectory(); - } } diff --git a/src/de/danoeh/antennapod/activity/AddFeedActivity.java b/src/de/danoeh/antennapod/activity/AddFeedActivity.java index 44486b5ef..39434fa87 100644 --- a/src/de/danoeh/antennapod/activity/AddFeedActivity.java +++ b/src/de/danoeh/antennapod/activity/AddFeedActivity.java @@ -18,9 +18,9 @@ import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.storage.DownloadRequestException; import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.ConnectionTester; @@ -44,7 +44,7 @@ public class AddFeedActivity extends SherlockActivity { @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); getSupportActionBar().setDisplayHomeAsUpEnabled(true); StorageUtils.checkStorageAvailability(this); diff --git a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java index 48b09b92b..3e38d7b5c 100644 --- a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java +++ b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java @@ -1,5 +1,7 @@ package de.danoeh.antennapod.activity; +import java.io.File; + import android.content.Intent; import android.content.res.TypedArray; import android.os.Bundle; @@ -20,13 +22,15 @@ import com.actionbarsherlock.view.Window; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.ChapterListAdapter; -import de.danoeh.antennapod.asynctask.FeedImageLoader; +import de.danoeh.antennapod.asynctask.ImageLoader; import de.danoeh.antennapod.feed.Chapter; -import de.danoeh.antennapod.feed.FeedMedia; +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.service.PlaybackService; +import de.danoeh.antennapod.util.playback.ExternalMedia; +import de.danoeh.antennapod.util.playback.Playable; /** Activity for playing audio files. */ public class AudioplayerActivity extends MediaplayerActivity { @@ -71,10 +75,32 @@ public class AudioplayerActivity extends MediaplayerActivity { protected void onCreate(Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); super.onCreate(savedInstanceState); + getSupportActionBar().setDisplayShowTitleEnabled(false); detachedFragments = new Fragment[NUM_CONTENT_FRAGMENTS]; } @Override + protected void onResume() { + super.onResume(); + if (getIntent().getAction() != null + && getIntent().getAction().equals(Intent.ACTION_VIEW)) { + Intent intent = getIntent(); + if (AppConfig.DEBUG) + Log.d(TAG, "Received VIEW intent: " + + intent.getData().getPath()); + ExternalMedia media = new ExternalMedia(intent.getData().getPath(), MediaType.AUDIO); + Intent launchIntent = new Intent(this, PlaybackService.class); + launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media); + launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED, + true); + launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, false); + launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, + true); + startService(launchIntent); + } + } + + @Override protected void onAwaitingVideoSurface() { startActivity(new Intent(this, VideoplayerActivity.class)); } @@ -101,7 +127,7 @@ public class AudioplayerActivity extends MediaplayerActivity { if (AppConfig.DEBUG) Log.d(TAG, "Switching contentView to position " + pos); if (currentlyShownPosition != pos) { - FeedMedia media = controller.getMedia(); + Playable media = controller.getMedia(); if (media != null) { FragmentTransaction ft = getSupportFragmentManager() .beginTransaction(); @@ -113,15 +139,14 @@ public class AudioplayerActivity extends MediaplayerActivity { case POS_COVER: if (coverFragment == null) { Log.i(TAG, "Using new coverfragment"); - coverFragment = CoverFragment.newInstance(media - .getItem()); + coverFragment = CoverFragment.newInstance(media); } currentlyShownFragment = coverFragment; break; case POS_DESCR: if (descriptionFragment == null) { descriptionFragment = ItemDescriptionFragment - .newInstance(media.getItem()); + .newInstance(media); } currentlyShownFragment = descriptionFragment; break; @@ -140,7 +165,7 @@ public class AudioplayerActivity extends MediaplayerActivity { }; chapterFragment.setListAdapter(new ChapterListAdapter( - AudioplayerActivity.this, 0, media.getItem() + AudioplayerActivity.this, 0, media .getChapters(), media)); } currentlyShownFragment = chapterFragment; @@ -167,7 +192,7 @@ public class AudioplayerActivity extends MediaplayerActivity { private void updateNavButtonDrawable() { TypedArray drawables = obtainStyledAttributes(new int[] { R.attr.navigation_shownotes, R.attr.navigation_chapters }); - final FeedMedia media = controller.getMedia(); + final Playable media = controller.getMedia(); if (butNavLeft != null && butNavRight != null && media != null) { switch (currentlyShownPosition) { case POS_COVER: @@ -181,9 +206,8 @@ public class AudioplayerActivity extends MediaplayerActivity { @Override public void run() { - FeedImageLoader.getInstance().loadThumbnailBitmap( - media.getItem().getFeed().getImage(), - butNavLeft); + ImageLoader.getInstance().loadThumbnailBitmap( + media.getImageFileUrl(), butNavLeft); } }); butNavRight.setImageDrawable(drawables.getDrawable(1)); @@ -194,9 +218,8 @@ public class AudioplayerActivity extends MediaplayerActivity { @Override public void run() { - FeedImageLoader.getInstance().loadThumbnailBitmap( - media.getItem().getFeed().getImage(), - butNavLeft); + ImageLoader.getInstance().loadThumbnailBitmap( + media.getImageFileUrl(), butNavLeft); } }); butNavRight.setImageDrawable(drawables.getDrawable(0)); @@ -251,11 +274,11 @@ public class AudioplayerActivity extends MediaplayerActivity { @Override protected void loadMediaInfo() { super.loadMediaInfo(); - final FeedMedia media = controller.getMedia(); + final Playable media = controller.getMedia(); if (media != null) { - txtvTitle.setText(media.getItem().getTitle()); - txtvFeed.setText(media.getItem().getFeed().getTitle()); - if (media.getItem().getChapters() != null) { + txtvTitle.setText(media.getEpisodeTitle()); + txtvFeed.setText(media.getFeedTitle()); + if (media.getChapters() != null) { butNavRight.setVisibility(View.VISIBLE); } else { butNavRight.setVisibility(View.GONE); @@ -302,7 +325,7 @@ public class AudioplayerActivity extends MediaplayerActivity { } public interface AudioplayerContentFragment { - public void onDataSetChanged(FeedMedia media); + public void onDataSetChanged(Playable media); } } diff --git a/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java b/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java index 6e1163508..54c4f0589 100644 --- a/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java +++ b/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java @@ -30,8 +30,8 @@ import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.preferences.UserPreferences; /** * Let's the user choose a directory on the storage device. The selected folder @@ -61,7 +61,7 @@ public class DirectoryChooserActivity extends SherlockActivity { @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); getSupportActionBar().setDisplayHomeAsUpEnabled(true); diff --git a/src/de/danoeh/antennapod/activity/DownloadActivity.java b/src/de/danoeh/antennapod/activity/DownloadActivity.java index 0c10abefd..10ebb1285 100644 --- a/src/de/danoeh/antennapod/activity/DownloadActivity.java +++ b/src/de/danoeh/antennapod/activity/DownloadActivity.java @@ -22,10 +22,10 @@ import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.DownloadlistAdapter; import de.danoeh.antennapod.asynctask.DownloadStatus; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.service.download.DownloadService; import de.danoeh.antennapod.storage.DownloadRequester; @@ -52,7 +52,7 @@ public class DownloadActivity extends SherlockListActivity implements @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); if (AppConfig.DEBUG) Log.d(TAG, "Creating Activity"); diff --git a/src/de/danoeh/antennapod/activity/DownloadLogActivity.java b/src/de/danoeh/antennapod/activity/DownloadLogActivity.java index 45d31cda9..232a7ba1d 100644 --- a/src/de/danoeh/antennapod/activity/DownloadLogActivity.java +++ b/src/de/danoeh/antennapod/activity/DownloadLogActivity.java @@ -1,18 +1,15 @@ package de.danoeh.antennapod.activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.os.Bundle; import com.actionbarsherlock.app.SherlockListActivity; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.adapter.DownloadLogAdapter; +import de.danoeh.antennapod.feed.EventDistributor; import de.danoeh.antennapod.feed.FeedManager; +import de.danoeh.antennapod.preferences.UserPreferences; /** * Displays completed and failed downloads in a list. The data comes from the @@ -26,11 +23,11 @@ public class DownloadLogActivity extends SherlockListActivity { @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); manager = FeedManager.getInstance(); - dla = new DownloadLogAdapter(this, 0, manager.getDownloadLog()); + dla = new DownloadLogAdapter(this); getSupportActionBar().setDisplayHomeAsUpEnabled(true); setListAdapter(dla); } @@ -38,14 +35,13 @@ public class DownloadLogActivity extends SherlockListActivity { @Override protected void onPause() { super.onPause(); - unregisterReceiver(contentUpdate); + EventDistributor.getInstance().unregister(contentUpdate); } @Override protected void onResume() { super.onResume(); - registerReceiver(contentUpdate, new IntentFilter( - FeedManager.ACTION_DOWNLOADLOG_UPDATE)); + EventDistributor.getInstance().register(contentUpdate); dla.notifyDataSetChanged(); } @@ -66,12 +62,11 @@ public class DownloadLogActivity extends SherlockListActivity { return true; } - private BroadcastReceiver contentUpdate = new BroadcastReceiver() { - + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction() - .equals(FeedManager.ACTION_DOWNLOADLOG_UPDATE)) { + public void update(EventDistributor eventDistributor, Integer arg) { + if ((arg & EventDistributor.DOWNLOADLOG_UPDATE) != 0) { dla.notifyDataSetChanged(); } } diff --git a/src/de/danoeh/antennapod/activity/FeedInfoActivity.java b/src/de/danoeh/antennapod/activity/FeedInfoActivity.java index 0ff1d7c1c..c57a5794b 100644 --- a/src/de/danoeh/antennapod/activity/FeedInfoActivity.java +++ b/src/de/danoeh/antennapod/activity/FeedInfoActivity.java @@ -11,12 +11,12 @@ import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.asynctask.FeedImageLoader; +import de.danoeh.antennapod.asynctask.ImageLoader; import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedManager; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.storage.DownloadRequestException; import de.danoeh.antennapod.util.LangUtils; import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler; @@ -37,7 +37,7 @@ public class FeedInfoActivity extends SherlockActivity { @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); setContentView(R.layout.feedinfo); getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -58,7 +58,7 @@ public class FeedInfoActivity extends SherlockActivity { @Override public void run() { - FeedImageLoader.getInstance().loadThumbnailBitmap( + ImageLoader.getInstance().loadThumbnailBitmap( feed.getImage(), imgvCover); } }); diff --git a/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java b/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java index 1be29ddbe..fdca48e8a 100644 --- a/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java +++ b/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java @@ -15,7 +15,6 @@ import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.actionbarsherlock.view.Window; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.asynctask.FeedRemover; import de.danoeh.antennapod.dialog.ConfirmationDialog; @@ -25,6 +24,7 @@ import de.danoeh.antennapod.feed.FeedManager; import de.danoeh.antennapod.fragment.ExternalPlayerFragment; import de.danoeh.antennapod.fragment.FeedlistFragment; import de.danoeh.antennapod.fragment.ItemlistFragment; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.storage.DownloadRequestException; import de.danoeh.antennapod.util.StorageUtils; import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler; @@ -42,7 +42,7 @@ public class FeedItemlistActivity extends SherlockFragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); StorageUtils.checkStorageAvailability(this); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); diff --git a/src/de/danoeh/antennapod/activity/FlattrAuthActivity.java b/src/de/danoeh/antennapod/activity/FlattrAuthActivity.java index b78b37afa..75e513816 100644 --- a/src/de/danoeh/antennapod/activity/FlattrAuthActivity.java +++ b/src/de/danoeh/antennapod/activity/FlattrAuthActivity.java @@ -17,8 +17,8 @@ import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.util.flattr.FlattrUtils; /** Guides the user through the authentication process */ @@ -36,7 +36,7 @@ public class FlattrAuthActivity extends SherlockActivity { @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); singleton = this; authSuccessful = false; diff --git a/src/de/danoeh/antennapod/activity/ItemviewActivity.java b/src/de/danoeh/antennapod/activity/ItemviewActivity.java index 668cb3a28..63dcb78f1 100644 --- a/src/de/danoeh/antennapod/activity/ItemviewActivity.java +++ b/src/de/danoeh/antennapod/activity/ItemviewActivity.java @@ -16,7 +16,6 @@ import com.actionbarsherlock.view.MenuItem; import com.actionbarsherlock.view.Window; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.feed.Feed; @@ -25,6 +24,7 @@ import de.danoeh.antennapod.feed.FeedManager; import de.danoeh.antennapod.fragment.FeedlistFragment; import de.danoeh.antennapod.fragment.ItemDescriptionFragment; import de.danoeh.antennapod.fragment.ItemlistFragment; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.storage.DownloadRequestException; import de.danoeh.antennapod.util.StorageUtils; import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler; @@ -42,7 +42,7 @@ public class ItemviewActivity extends SherlockFragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); StorageUtils.checkStorageAvailability(this); manager = FeedManager.getInstance(); diff --git a/src/de/danoeh/antennapod/activity/MainActivity.java b/src/de/danoeh/antennapod/activity/MainActivity.java index 67d7424b6..0ec66ff35 100644 --- a/src/de/danoeh/antennapod/activity/MainActivity.java +++ b/src/de/danoeh/antennapod/activity/MainActivity.java @@ -1,9 +1,7 @@ package de.danoeh.antennapod.activity; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; @@ -20,12 +18,13 @@ import com.actionbarsherlock.view.Window; import com.viewpagerindicator.TabPageIndicator; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.feed.EventDistributor; import de.danoeh.antennapod.feed.FeedManager; import de.danoeh.antennapod.fragment.EpisodesFragment; import de.danoeh.antennapod.fragment.ExternalPlayerFragment; import de.danoeh.antennapod.fragment.FeedlistFragment; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.service.PlaybackService; import de.danoeh.antennapod.service.download.DownloadService; import de.danoeh.antennapod.storage.DownloadRequester; @@ -35,6 +34,9 @@ import de.danoeh.antennapod.util.StorageUtils; public class MainActivity extends SherlockFragmentActivity { private static final String TAG = "MainActivity"; + private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED + | EventDistributor.DOWNLOAD_QUEUED; + private FeedManager manager; private ViewPager viewpager; private MainPagerAdapter pagerAdapter; @@ -45,7 +47,7 @@ public class MainActivity extends SherlockFragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); StorageUtils.checkStorageAvailability(this); manager = FeedManager.getInstance(); @@ -69,7 +71,7 @@ public class MainActivity extends SherlockFragmentActivity { if (!appLaunched && getIntent().getAction() != null && getIntent().getAction().equals(Intent.ACTION_MAIN)) { appLaunched = true; - if (manager.getUnreadItems().size() > 0) { + if (manager.getUnreadItemsSize(true) > 0) { viewpager.setCurrentItem(MainPagerAdapter.POS_EPISODES); } @@ -79,7 +81,7 @@ public class MainActivity extends SherlockFragmentActivity { @Override protected void onPause() { super.onPause(); - unregisterReceiver(contentUpdate); + EventDistributor.getInstance().unregister(contentUpdate); } @Override @@ -87,18 +89,19 @@ public class MainActivity extends SherlockFragmentActivity { super.onResume(); StorageUtils.checkStorageAvailability(this); updateProgressBarVisibility(); - IntentFilter filter = new IntentFilter(); - filter.addAction(DownloadService.ACTION_DOWNLOAD_HANDLED); - filter.addAction(DownloadRequester.ACTION_DOWNLOAD_QUEUED); - registerReceiver(contentUpdate, filter); + EventDistributor.getInstance().register(contentUpdate); + } - private BroadcastReceiver contentUpdate = new BroadcastReceiver() { + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + @Override - public void onReceive(Context context, Intent intent) { - if (AppConfig.DEBUG) - Log.d(TAG, "Received contentUpdate Intent."); - updateProgressBarVisibility(); + public void update(EventDistributor eventDistributor, Integer arg) { + if ((EVENTS & arg) != 0) { + if (AppConfig.DEBUG) + Log.d(TAG, "Received contentUpdate Intent."); + updateProgressBarVisibility(); + } } }; @@ -109,7 +112,7 @@ public class MainActivity extends SherlockFragmentActivity { } else { setSupportProgressBarIndeterminateVisibility(false); } - invalidateOptionsMenu(); + supportInvalidateOptionsMenu(); } @Override @@ -151,7 +154,7 @@ public class MainActivity extends SherlockFragmentActivity { refreshAll.setVisible(true); } - boolean hasFeeds = !manager.getFeeds().isEmpty(); + boolean hasFeeds = manager.getFeedsSize() > 0; menu.findItem(R.id.all_feed_refresh).setVisible(hasFeeds); return true; } diff --git a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java index 952cddf8b..c217a4628 100644 --- a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -5,6 +5,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.res.Configuration; import android.graphics.PixelFormat; +import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.widget.ImageButton; @@ -18,19 +19,18 @@ import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator; +import de.danoeh.antennapod.asynctask.FlattrClickWorker; import de.danoeh.antennapod.dialog.TimeDialog; import de.danoeh.antennapod.feed.FeedManager; -import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.service.PlaybackService; -import de.danoeh.antennapod.storage.DownloadRequestException; import de.danoeh.antennapod.util.Converter; -import de.danoeh.antennapod.util.MediaPlayerError; -import de.danoeh.antennapod.util.PlaybackController; +import de.danoeh.antennapod.util.ShareUtils; import de.danoeh.antennapod.util.StorageUtils; -import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler; +import de.danoeh.antennapod.util.playback.MediaPlayerError; +import de.danoeh.antennapod.util.playback.Playable; +import de.danoeh.antennapod.util.playback.PlaybackController; /** * Provides general features which are both needed for playing audio and video @@ -91,7 +91,7 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity @Override public void onSleepTimerUpdate() { - invalidateOptionsMenu(); + supportInvalidateOptionsMenu(); } @Override @@ -133,12 +133,12 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity } protected void onServiceQueried() { - invalidateOptionsMenu(); + supportInvalidateOptionsMenu(); } @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); if (AppConfig.DEBUG) Log.d(TAG, "Creating Activity"); @@ -219,14 +219,14 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity @Override public boolean onPrepareOptionsMenu(Menu menu) { - FeedMedia media = controller.getMedia(); + Playable media = controller.getMedia(); menu.findItem(R.id.support_item).setVisible( - media != null && media.getItem().getPaymentLink() != null); + media != null && media.getPaymentLink() != null); menu.findItem(R.id.share_link_item).setVisible( - media != null && media.getItem().getLink() != null); + media != null && media.getWebsiteLink() != null); menu.findItem(R.id.visit_website_item).setVisible( - media != null && media.getItem().getLink() != null); + media != null && media.getWebsiteLink() != null); boolean sleepTimerSet = controller.sleepTimerActive(); boolean sleepTimerNotSet = controller.sleepTimerNotActive(); @@ -237,69 +237,80 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity @Override public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: + Playable media = controller.getMedia(); + if (item.getItemId() == android.R.id.home) { Intent intent = new Intent(MediaplayerActivity.this, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); - break; - case R.id.disable_sleeptimer_item: - if (controller.serviceAvailable()) { - AlertDialog.Builder stDialog = new AlertDialog.Builder(this); - stDialog.setTitle(R.string.sleep_timer_label); - stDialog.setMessage(getString(R.string.time_left_label) - + Converter.getDurationStringLong((int) controller - .getSleepTimerTimeLeft())); - stDialog.setPositiveButton(R.string.disable_sleeptimer_label, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { - dialog.dismiss(); - controller.disableSleepTimer(); - } - }); - stDialog.setNegativeButton(R.string.cancel_label, - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, - int which) { - dialog.dismiss(); - } - }); - stDialog.create().show(); - } - break; - case R.id.set_sleeptimer_item: - if (controller.serviceAvailable()) { - TimeDialog td = new TimeDialog(this, - R.string.set_sleeptimer_label, - R.string.set_sleeptimer_label) { - - @Override - public void onTimeEntered(long millis) { - controller.setSleepTimer(millis); - } - }; - td.show(); + return true; + } else if (media != null) { + switch (item.getItemId()) { + case R.id.disable_sleeptimer_item: + if (controller.serviceAvailable()) { + AlertDialog.Builder stDialog = new AlertDialog.Builder(this); + stDialog.setTitle(R.string.sleep_timer_label); + stDialog.setMessage(getString(R.string.time_left_label) + + Converter.getDurationStringLong((int) controller + .getSleepTimerTimeLeft())); + stDialog.setPositiveButton( + R.string.disable_sleeptimer_label, + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { + dialog.dismiss(); + controller.disableSleepTimer(); + } + }); + stDialog.setNegativeButton(R.string.cancel_label, + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, + int which) { + dialog.dismiss(); + } + }); + stDialog.create().show(); + } + break; + case R.id.set_sleeptimer_item: + if (controller.serviceAvailable()) { + TimeDialog td = new TimeDialog(this, + R.string.set_sleeptimer_label, + R.string.set_sleeptimer_label) { + + @Override + public void onTimeEntered(long millis) { + controller.setSleepTimer(millis); + } + }; + td.show(); + break; + + } + case R.id.visit_website_item: + Uri uri = Uri.parse(media.getWebsiteLink()); + startActivity(new Intent(Intent.ACTION_VIEW, uri)); break; + case R.id.support_item: + new FlattrClickWorker(this, media.getPaymentLink()) + .executeAsync(); + break; + case R.id.share_link_item: + ShareUtils.shareLink(this, media.getWebsiteLink()); + break; + default: + return false; } - default: - try { - return FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), - controller.getMedia().getItem()); - } catch (DownloadRequestException e) { - e.printStackTrace(); - DownloadRequestErrorDialogCreator.newRequestErrorDialog(this, - e.getMessage()); - } + return true; + } else { + return false; } - return true; } @Override @@ -363,7 +374,7 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity protected void loadMediaInfo() { if (AppConfig.DEBUG) Log.d(TAG, "Loading media info"); - FeedMedia media = controller.getMedia(); + Playable media = controller.getMedia(); if (media != null) { txtvPosition.setText(Converter.getDurationStringLong((media .getPosition()))); diff --git a/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java b/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java index 7a765a893..bb50944cc 100644 --- a/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java +++ b/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java @@ -12,9 +12,9 @@ import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; import com.viewpagerindicator.TabPageIndicator; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.fragment.MiroGuideChannellistFragment; +import de.danoeh.antennapod.preferences.UserPreferences; /** * Shows channels of a category sorted by different criteria in lists. The @@ -34,7 +34,7 @@ public class MiroGuideCategoryActivity extends SherlockFragmentActivity { @Override protected void onCreate(Bundle arg0) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(arg0); getSupportActionBar().setDisplayHomeAsUpEnabled(true); setContentView(R.layout.miroguide_category); diff --git a/src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java b/src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java index 05d346bb4..f9fe912cd 100644 --- a/src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java +++ b/src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java @@ -21,7 +21,6 @@ import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.MiroGuideItemlistAdapter; import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator; @@ -30,6 +29,7 @@ import de.danoeh.antennapod.feed.FeedManager; import de.danoeh.antennapod.miroguide.conn.MiroGuideException; import de.danoeh.antennapod.miroguide.conn.MiroGuideService; import de.danoeh.antennapod.miroguide.model.MiroGuideChannel; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.storage.DownloadRequestException; import de.danoeh.antennapod.storage.DownloadRequester; @@ -62,7 +62,7 @@ public class MiroGuideChannelViewActivity extends SherlockActivity { @SuppressLint("NewApi") @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); getSupportActionBar().setDisplayHomeAsUpEnabled(true); setContentView(R.layout.miroguide_channelview); diff --git a/src/de/danoeh/antennapod/activity/MiroGuideMainActivity.java b/src/de/danoeh/antennapod/activity/MiroGuideMainActivity.java index 6dcb1bdca..8b33ef1da 100644 --- a/src/de/danoeh/antennapod/activity/MiroGuideMainActivity.java +++ b/src/de/danoeh/antennapod/activity/MiroGuideMainActivity.java @@ -15,10 +15,10 @@ import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.miroguide.conn.MiroGuideException; import de.danoeh.antennapod.miroguide.conn.MiroGuideService; +import de.danoeh.antennapod.preferences.UserPreferences; /** * Shows a list of available categories and offers a search button. If the user @@ -34,7 +34,7 @@ public class MiroGuideMainActivity extends SherlockListActivity { @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); getSupportActionBar().setDisplayHomeAsUpEnabled(true); setContentView(R.layout.miroguide_categorylist); diff --git a/src/de/danoeh/antennapod/activity/MiroGuideSearchActivity.java b/src/de/danoeh/antennapod/activity/MiroGuideSearchActivity.java index 2943339ad..a30777fb1 100644 --- a/src/de/danoeh/antennapod/activity/MiroGuideSearchActivity.java +++ b/src/de/danoeh/antennapod/activity/MiroGuideSearchActivity.java @@ -11,9 +11,9 @@ import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.fragment.MiroGuideChannellistFragment; +import de.danoeh.antennapod.preferences.UserPreferences; /** * Displays results when a search for miroguide channels has been performed. It @@ -26,7 +26,7 @@ public class MiroGuideSearchActivity extends SherlockFragmentActivity { @Override protected void onCreate(Bundle arg0) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(arg0); getSupportActionBar().setDisplayHomeAsUpEnabled(true); setContentView(R.layout.miroguidesearch); diff --git a/src/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java b/src/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java index 2301d2f0f..9ba355baf 100644 --- a/src/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java +++ b/src/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java @@ -16,9 +16,9 @@ import com.actionbarsherlock.app.SherlockActivity; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.opml.OpmlElement; +import de.danoeh.antennapod.preferences.UserPreferences; /** * Displays the feeds that the OPML-Importer has read and lets the user choose @@ -36,7 +36,7 @@ public class OpmlFeedChooserActivity extends SherlockActivity { @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); setContentView(R.layout.opml_selection); diff --git a/src/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java b/src/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java index cf3028307..f887fdd94 100644 --- a/src/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java +++ b/src/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java @@ -1,14 +1,17 @@ package de.danoeh.antennapod.activity; +import java.io.Reader; +import java.util.ArrayList; + import android.content.Intent; import android.util.Log; + import com.actionbarsherlock.app.SherlockActivity; + import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.asynctask.OpmlFeedQueuer; import de.danoeh.antennapod.asynctask.OpmlImportWorker; import de.danoeh.antennapod.opml.OpmlElement; -import java.io.Reader; -import java.util.ArrayList; /** * Base activity for Opml Import - e.g. with code what to do afterwards diff --git a/src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java b/src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java index cbe69d48c..dc698a851 100644 --- a/src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java +++ b/src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java @@ -1,17 +1,19 @@ package de.danoeh.antennapod.activity; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; + import android.app.AlertDialog; import android.os.Bundle; -import de.danoeh.antennapod.PodcastApp; -import java.io.*; -import java.net.URL; +import de.danoeh.antennapod.preferences.UserPreferences; /** Lets the user start the OPML-import process. */ public class OpmlImportFromIntentActivity extends OpmlImportBaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); getSupportActionBar().setDisplayHomeAsUpEnabled(true); diff --git a/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java b/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java index bb5734b57..b38e0c443 100644 --- a/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java +++ b/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java @@ -1,5 +1,10 @@ package de.danoeh.antennapod.activity; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.Reader; + import android.app.AlertDialog; import android.content.DialogInterface; import android.os.Bundle; @@ -9,18 +14,15 @@ import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; + import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; + import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.util.StorageUtils; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.Reader; - /** * Lets the user start the OPML-import process from a path */ @@ -33,7 +35,7 @@ public class OpmlImportFromPathActivity extends OpmlImportBaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -63,7 +65,7 @@ public class OpmlImportFromPathActivity extends OpmlImportBaseActivity { * directory. */ private void setImportPath() { - File importDir = PodcastApp.getDataFolder(this, IMPORT_DIR); + File importDir = UserPreferences.getDataFolder(this, IMPORT_DIR); boolean success = true; if (!importDir.exists()) { if (AppConfig.DEBUG) diff --git a/src/de/danoeh/antennapod/activity/OpmlImportHolder.java b/src/de/danoeh/antennapod/activity/OpmlImportHolder.java index ec53ed7b6..8d51eb3de 100644 --- a/src/de/danoeh/antennapod/activity/OpmlImportHolder.java +++ b/src/de/danoeh/antennapod/activity/OpmlImportHolder.java @@ -1,9 +1,9 @@ package de.danoeh.antennapod.activity; -import de.danoeh.antennapod.opml.OpmlElement; - import java.util.ArrayList; +import de.danoeh.antennapod.opml.OpmlElement; + /** * Hold infos gathered by Ompl-Import * <p/> diff --git a/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java b/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java index a7017d2fb..56e42f79f 100644 --- a/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java +++ b/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java @@ -1,17 +1,12 @@ package de.danoeh.antennapod.activity; -import java.util.List; - -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.res.TypedArray; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; @@ -20,11 +15,12 @@ import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; import com.mobeta.android.dslv.DragSortListView; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.asynctask.FeedImageLoader; +import de.danoeh.antennapod.asynctask.ImageLoader; +import de.danoeh.antennapod.feed.EventDistributor; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedManager; +import de.danoeh.antennapod.preferences.UserPreferences; public class OrganizeQueueActivity extends SherlockListActivity { private static final String TAG = "OrganizeQueueActivity"; @@ -35,7 +31,7 @@ public class OrganizeQueueActivity extends SherlockListActivity { @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); setContentView(R.layout.organize_queue); @@ -43,38 +39,32 @@ public class OrganizeQueueActivity extends SherlockListActivity { listView.setDropListener(dropListener); listView.setRemoveListener(removeListener); - adapter = new OrganizeAdapter(this, 0, FeedManager.getInstance() - .getQueue()); + adapter = new OrganizeAdapter(this); setListAdapter(adapter); } @Override protected void onPause() { super.onPause(); - try { - unregisterReceiver(contentUpdate); - } catch (IllegalArgumentException e) { - - } + EventDistributor.getInstance().unregister(contentUpdate); } @Override protected void onResume() { super.onResume(); - IntentFilter filter = new IntentFilter(FeedManager.ACTION_QUEUE_UPDATE); - filter.addAction(FeedManager.ACTION_FEED_LIST_UPDATE); - registerReceiver(contentUpdate, filter); + EventDistributor.getInstance().register(contentUpdate); } - private BroadcastReceiver contentUpdate = new BroadcastReceiver() { + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { @Override - public void onReceive(Context context, Intent intent) { - if (adapter != null) { - adapter.notifyDataSetChanged(); + public void update(EventDistributor eventDistributor, Integer arg) { + if (((EventDistributor.QUEUE_UPDATE | EventDistributor.FEED_LIST_UPDATE) & arg) != 0) { + if (adapter != null) { + adapter.notifyDataSetChanged(); + } } } - }; private DragSortListView.DropListener dropListener = new DragSortListView.DropListener() { @@ -120,13 +110,12 @@ public class OrganizeQueueActivity extends SherlockListActivity { } } - private static class OrganizeAdapter extends ArrayAdapter<FeedItem> { + private static class OrganizeAdapter extends BaseAdapter { private Context context; - public OrganizeAdapter(Context context, int textViewResourceId, - List<FeedItem> objects) { - super(context, textViewResourceId, objects); + public OrganizeAdapter(Context context) { + super(); this.context = context; } @@ -156,8 +145,9 @@ public class OrganizeQueueActivity extends SherlockListActivity { holder.title.setText(item.getTitle()); holder.feedTitle.setText(item.getFeed().getTitle()); - holder.feedImage.setTag(item.getFeed().getImage()); - FeedImageLoader.getInstance().loadThumbnailBitmap( + holder.feedImage.setTag((item.getFeed().getImage() != null) ? item + .getFeed().getImage().getFile_url() : null); + ImageLoader.getInstance().loadThumbnailBitmap( item.getFeed().getImage(), holder.feedImage, (int) convertView.getResources().getDimension( @@ -172,6 +162,22 @@ public class OrganizeQueueActivity extends SherlockListActivity { ImageView feedImage; } + @Override + public int getCount() { + return FeedManager.getInstance().getQueueSize(true); + } + + @Override + public FeedItem getItem(int position) { + return FeedManager.getInstance() + .getQueueItemAtIndex(position, true); + } + + @Override + public long getItemId(int position) { + return position; + } + } } diff --git a/src/de/danoeh/antennapod/activity/PlaybackHistoryActivity.java b/src/de/danoeh/antennapod/activity/PlaybackHistoryActivity.java index 22958b058..1a5a2cac9 100644 --- a/src/de/danoeh/antennapod/activity/PlaybackHistoryActivity.java +++ b/src/de/danoeh/antennapod/activity/PlaybackHistoryActivity.java @@ -10,10 +10,10 @@ import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.feed.FeedManager; import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; +import de.danoeh.antennapod.preferences.UserPreferences; public class PlaybackHistoryActivity extends SherlockFragmentActivity { private static final String TAG = "PlaybackHistoryActivity"; @@ -44,7 +44,7 @@ public class PlaybackHistoryActivity extends SherlockFragmentActivity { @Override protected void onCreate(Bundle arg0) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(arg0); if (AppConfig.DEBUG) diff --git a/src/de/danoeh/antennapod/activity/PreferenceActivity.java b/src/de/danoeh/antennapod/activity/PreferenceActivity.java index fa0c42b37..360ff610d 100644 --- a/src/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/src/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -15,11 +15,11 @@ import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.asynctask.FlattrClickWorker; import de.danoeh.antennapod.asynctask.OpmlExportWorker; import de.danoeh.antennapod.feed.FeedManager; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.util.flattr.FlattrUtils; /** The main preference activity */ @@ -36,7 +36,7 @@ public class PreferenceActivity extends SherlockPreferenceActivity { @SuppressWarnings("deprecation") @Override public void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -82,7 +82,7 @@ public class PreferenceActivity extends SherlockPreferenceActivity { @Override public boolean onPreferenceClick(Preference preference) { - if (!FeedManager.getInstance().getFeeds().isEmpty()) { + if (FeedManager.getInstance().getFeedsSize() > 0) { new OpmlExportWorker(PreferenceActivity.this) .executeAsync(); } @@ -102,7 +102,7 @@ public class PreferenceActivity extends SherlockPreferenceActivity { return true; } }); - findPreference(PodcastApp.PREF_THEME).setOnPreferenceChangeListener( + findPreference(UserPreferences.PREF_THEME).setOnPreferenceChangeListener( new OnPreferenceChangeListener() { @Override @@ -137,7 +137,7 @@ public class PreferenceActivity extends SherlockPreferenceActivity { } private void setDataFolderText() { - File f = PodcastApp.getDataFolder(this, null); + File f = UserPreferences.getDataFolder(this, null); if (f != null) { findPreference(PREF_CHOOSE_DATA_DIR) .setSummary(f.getAbsolutePath()); @@ -165,7 +165,7 @@ public class PreferenceActivity extends SherlockPreferenceActivity { @Override protected void onApplyThemeResource(Theme theme, int resid, boolean first) { - theme.applyStyle(PodcastApp.getThemeResourceId(), true); + theme.applyStyle(UserPreferences.getTheme(), true); } @Override @@ -176,7 +176,7 @@ public class PreferenceActivity extends SherlockPreferenceActivity { .getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR); if (AppConfig.DEBUG) Log.d(TAG, "Setting data folder"); - PodcastApp.getInstance().setDataFolder(dir); + UserPreferences.setDataFolder(dir); } } diff --git a/src/de/danoeh/antennapod/activity/SearchActivity.java b/src/de/danoeh/antennapod/activity/SearchActivity.java index bb2eb54cc..152710112 100644 --- a/src/de/danoeh/antennapod/activity/SearchActivity.java +++ b/src/de/danoeh/antennapod/activity/SearchActivity.java @@ -16,7 +16,6 @@ import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.SearchlistAdapter; import de.danoeh.antennapod.feed.Feed; @@ -26,6 +25,7 @@ import de.danoeh.antennapod.feed.FeedSearcher; import de.danoeh.antennapod.feed.SearchResult; import de.danoeh.antennapod.fragment.FeedlistFragment; import de.danoeh.antennapod.fragment.ItemlistFragment; +import de.danoeh.antennapod.preferences.UserPreferences; /** Displays the results when the user searches for FeedItems or Feeds. */ public class SearchActivity extends SherlockListActivity { @@ -43,7 +43,7 @@ public class SearchActivity extends SherlockListActivity { @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); getSupportActionBar().setDisplayHomeAsUpEnabled(true); diff --git a/src/de/danoeh/antennapod/activity/StorageErrorActivity.java b/src/de/danoeh/antennapod/activity/StorageErrorActivity.java index 514b1f69d..4d9184dcf 100644 --- a/src/de/danoeh/antennapod/activity/StorageErrorActivity.java +++ b/src/de/danoeh/antennapod/activity/StorageErrorActivity.java @@ -10,8 +10,8 @@ import android.util.Log; import com.actionbarsherlock.app.SherlockActivity; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.util.StorageUtils; /** Is show if there is now external storage available. */ @@ -20,7 +20,7 @@ public class StorageErrorActivity extends SherlockActivity { @Override protected void onCreate(Bundle savedInstanceState) { - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); setContentView(R.layout.storage_error); diff --git a/src/de/danoeh/antennapod/activity/VideoplayerActivity.java b/src/de/danoeh/antennapod/activity/VideoplayerActivity.java index f6ea4f69e..2d9834a3e 100644 --- a/src/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/src/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -18,11 +18,13 @@ import android.widget.VideoView; import com.actionbarsherlock.view.Window; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.feed.MediaType; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.service.PlaybackService; import de.danoeh.antennapod.service.PlayerStatus; +import de.danoeh.antennapod.util.playback.ExternalMedia; +import de.danoeh.antennapod.util.playback.Playable; /** Activity for playing audio files. */ public class VideoplayerActivity extends MediaplayerActivity implements @@ -41,7 +43,7 @@ public class VideoplayerActivity extends MediaplayerActivity implements @Override protected void onCreate(Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); - setTheme(PodcastApp.getThemeResourceId()); + setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); } @@ -55,13 +57,34 @@ public class VideoplayerActivity extends MediaplayerActivity implements } @Override + protected void onResume() { + super.onResume(); + if (getIntent().getAction() != null + && getIntent().getAction().equals(Intent.ACTION_VIEW)) { + Intent intent = getIntent(); + if (AppConfig.DEBUG) + Log.d(TAG, "Received VIEW intent: " + + intent.getData().getPath()); + ExternalMedia media = new ExternalMedia(intent.getData().getPath(), + MediaType.VIDEO); + Intent launchIntent = new Intent(this, PlaybackService.class); + launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media); + launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED, + true); + launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, false); + launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, + true); + startService(launchIntent); + } + } + + @Override protected void loadMediaInfo() { super.loadMediaInfo(); - FeedMedia media = controller.getMedia(); + Playable media = controller.getMedia(); if (media != null) { - getSupportActionBar().setSubtitle(media.getItem().getTitle()); - getSupportActionBar() - .setTitle(media.getItem().getFeed().getTitle()); + getSupportActionBar().setSubtitle(media.getEpisodeTitle()); + getSupportActionBar().setTitle(media.getFeedTitle()); } } diff --git a/src/de/danoeh/antennapod/adapter/AbstractFeedItemlistAdapter.java b/src/de/danoeh/antennapod/adapter/AbstractFeedItemlistAdapter.java index 5c720d652..6f4cf7c29 100644 --- a/src/de/danoeh/antennapod/adapter/AbstractFeedItemlistAdapter.java +++ b/src/de/danoeh/antennapod/adapter/AbstractFeedItemlistAdapter.java @@ -1,49 +1,39 @@ package de.danoeh.antennapod.adapter; -import java.util.List; - -import android.content.Context; -import android.widget.ArrayAdapter; -import de.danoeh.antennapod.PodcastApp; +import android.widget.BaseAdapter; import de.danoeh.antennapod.feed.FeedItem; -import de.danoeh.antennapod.util.EpisodeFilter; -public abstract class AbstractFeedItemlistAdapter extends - ArrayAdapter<FeedItem> { +public abstract class AbstractFeedItemlistAdapter extends BaseAdapter { - private List<FeedItem> objects; - private boolean isExpanded = true; + ItemAccess itemAccess; - public AbstractFeedItemlistAdapter(Context context, int textViewResourceId, - List<FeedItem> objects) { - super(context, textViewResourceId, objects); - this.objects = objects; + public AbstractFeedItemlistAdapter(ItemAccess itemAccess) { + super(); + if (itemAccess == null) { + throw new IllegalArgumentException("itemAccess must not be null"); + } + this.itemAccess = itemAccess; } @Override public int getCount() { - if (isExpanded) { - if (PodcastApp.getInstance().displayOnlyEpisodes()) { - return EpisodeFilter.countItemsWithEpisodes(objects); - } else { - return super.getCount(); - } - } else { - return 0; - } + return itemAccess.getCount(); + + } + + @Override + public long getItemId(int position) { + return position; } @Override public FeedItem getItem(int position) { - if (PodcastApp.getInstance().displayOnlyEpisodes()) { - return EpisodeFilter.accessEpisodeByIndex(objects, position); - } else { - return super.getItem(position); - } + return itemAccess.getItem(position); } - public void toggleExpandedState() { - isExpanded = !isExpanded; - notifyDataSetChanged(); + public static interface ItemAccess { + int getCount(); + + FeedItem getItem(int position); } } diff --git a/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java b/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java index 145ca4230..3e9b586ce 100644 --- a/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java +++ b/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java @@ -20,20 +20,21 @@ import android.widget.ArrayAdapter; import android.widget.TextView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.feed.Chapter; -import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.util.ChapterUtils; import de.danoeh.antennapod.util.Converter; +import de.danoeh.antennapod.util.playback.Playable; public class ChapterListAdapter extends ArrayAdapter<Chapter> { private static final String TAG = "ChapterListAdapter"; private List<Chapter> chapters; - private FeedMedia media; + private Playable media; private int defaultTextColor; public ChapterListAdapter(Context context, int textViewResourceId, - List<Chapter> objects, FeedMedia media) { + List<Chapter> objects, Playable media) { super(context, textViewResourceId, objects); this.chapters = objects; this.media = media; @@ -122,7 +123,7 @@ public class ChapterListAdapter extends ArrayAdapter<Chapter> { } }); - Chapter current = sc.getItem().getCurrentChapter(); + Chapter current = ChapterUtils.getCurrentChapter(media); if (current != null) { if (current == sc) { holder.title.setTextColor(convertView.getResources().getColor( diff --git a/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java b/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java index ce339be88..c0ccdc7fe 100644 --- a/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java +++ b/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java @@ -1,39 +1,40 @@ package de.danoeh.antennapod.adapter; import java.text.DateFormat; -import java.util.List; import android.content.Context; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; import android.widget.TextView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.asynctask.DownloadStatus; import de.danoeh.antennapod.feed.Feed; -import de.danoeh.antennapod.feed.FeedFile; import de.danoeh.antennapod.feed.FeedImage; +import de.danoeh.antennapod.feed.FeedManager; import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.util.DownloadError; /** Displays a list of DownloadStatus entries. */ -public class DownloadLogAdapter extends ArrayAdapter<DownloadStatus> { +public class DownloadLogAdapter extends BaseAdapter { - public DownloadLogAdapter(Context context, int textViewResourceId, - List<DownloadStatus> objects) { - super(context, textViewResourceId, objects); + private Context context; + private FeedManager manager = FeedManager.getInstance(); + + public DownloadLogAdapter(Context context) { + super(); + this.context = context; } @Override public View getView(int position, View convertView, ViewGroup parent) { Holder holder; DownloadStatus status = getItem(position); - FeedFile feedfile = status.getFeedFile(); if (convertView == null) { holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) getContext() + LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.downloadlog_item, null); holder.title = (TextView) convertView.findViewById(R.id.txtvTitle); @@ -71,7 +72,7 @@ public class DownloadLogAdapter extends ArrayAdapter<DownloadStatus> { holder.successful.setTextColor(convertView.getResources().getColor( R.color.download_failed_red)); holder.successful.setText(R.string.download_failed); - String reasonText = DownloadError.getErrorString(getContext(), + String reasonText = DownloadError.getErrorString(context, status.getReason()); if (status.getReasonDetailed() != null) { reasonText += ": " + status.getReasonDetailed(); @@ -91,4 +92,19 @@ public class DownloadLogAdapter extends ArrayAdapter<DownloadStatus> { TextView reason; } + @Override + public int getCount() { + return manager.getDownloadLogSize(); + } + + @Override + public DownloadStatus getItem(int position) { + return manager.getDownloadStatusFromLogAtIndex(position); + } + + @Override + public long getItemId(int position) { + return position; + } + } diff --git a/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java b/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java index 2670dfa44..db716c66e 100644 --- a/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java +++ b/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java @@ -1,7 +1,5 @@ package de.danoeh.antennapod.adapter; -import java.util.List; - import android.content.Context; import android.content.res.TypedArray; import android.view.LayoutInflater; @@ -12,14 +10,13 @@ import android.widget.BaseExpandableListAdapter; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.asynctask.FeedImageLoader; +import de.danoeh.antennapod.asynctask.ImageLoader; import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.FeedManager; import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.Converter; -import de.danoeh.antennapod.util.EpisodeFilter; /** * Displays unread items and items in the queue in one combined list. The @@ -32,21 +29,17 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter { public static final int GROUP_POS_UNREAD = 1; private Context context; - - private List<FeedItem> unreadItems; - private List<FeedItem> queueItems; + private FeedManager manager = FeedManager.getInstance(); private ActionButtonCallback feedItemActionCallback; private OnGroupActionClicked groupActionCallback; public ExternalEpisodesListAdapter(Context context, - List<FeedItem> unreadItems, List<FeedItem> queueItems, ActionButtonCallback callback, OnGroupActionClicked groupActionCallback) { super(); this.context = context; - this.unreadItems = unreadItems; - this.queueItems = queueItems; + this.feedItemActionCallback = callback; this.groupActionCallback = groupActionCallback; } @@ -58,22 +51,10 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter { @Override public FeedItem getChild(int groupPosition, int childPosition) { - final boolean displayOnlyEpisodes = PodcastApp.getInstance() - .displayOnlyEpisodes(); if (groupPosition == GROUP_POS_QUEUE) { - if (displayOnlyEpisodes) { - return EpisodeFilter.accessEpisodeByIndex(queueItems, - childPosition); - } else { - return queueItems.get(childPosition); - } + return manager.getQueueItemAtIndex(childPosition, true); } else if (groupPosition == GROUP_POS_UNREAD) { - if (displayOnlyEpisodes) { - return EpisodeFilter.accessEpisodeByIndex(unreadItems, - childPosition); - } else { - return unreadItems.get(childPosition); - } + return manager.getUnreadItemAtIndex(childPosition, true); } return null; } @@ -180,8 +161,9 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter { holder.lenSize.setVisibility(View.INVISIBLE); } - holder.feedImage.setTag(item.getFeed().getImage()); - FeedImageLoader.getInstance().loadThumbnailBitmap( + holder.feedImage.setTag((item.getFeed().getImage() != null) ? item + .getFeed().getImage().getFile_url() : null); + ImageLoader.getInstance().loadThumbnailBitmap( item.getFeed().getImage(), holder.feedImage, (int) convertView.getResources().getDimension( @@ -213,20 +195,10 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter { @Override public int getChildrenCount(int groupPosition) { - final boolean displayOnlyEpisodes = PodcastApp.getInstance() - .displayOnlyEpisodes(); if (groupPosition == GROUP_POS_QUEUE) { - if (displayOnlyEpisodes) { - return EpisodeFilter.countItemsWithEpisodes(queueItems); - } else { - return queueItems.size(); - } + return manager.getQueueSize(true); } else if (groupPosition == GROUP_POS_UNREAD) { - if (displayOnlyEpisodes) { - return EpisodeFilter.countItemsWithEpisodes(unreadItems); - } else { - return unreadItems.size(); - } + return manager.getUnreadItemsSize(true); } return 0; } @@ -254,12 +226,12 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter { String headerString = null; if (groupPosition == 0) { headerString = context.getString(R.string.queue_label); - if (!queueItems.isEmpty()) { + if (manager.getQueueSize(true) > 0) { headerString += " (" + getChildrenCount(GROUP_POS_QUEUE) + ")"; } } else { headerString = context.getString(R.string.new_label); - if (!unreadItems.isEmpty()) { + if (manager.getUnreadItemsSize(true) > 0) { headerString += " (" + getChildrenCount(GROUP_POS_UNREAD) + ")"; } } @@ -277,7 +249,8 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter { @Override public boolean isEmpty() { - return unreadItems.isEmpty() && queueItems.isEmpty(); + return manager.getUnreadItemsSize(true) == 0 + && manager.getQueueSize(true) == 0; } @Override diff --git a/src/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java b/src/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java index 5941d52ea..0dda5d035 100644 --- a/src/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java +++ b/src/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java @@ -1,11 +1,9 @@ package de.danoeh.antennapod.adapter; import java.text.DateFormat; -import java.util.List; import android.content.Context; import android.content.res.TypedArray; -import android.graphics.Typeface; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; @@ -24,16 +22,20 @@ import de.danoeh.antennapod.util.Converter; import de.danoeh.antennapod.util.ThemeUtils; public class FeedItemlistAdapter extends AbstractFeedItemlistAdapter { + + private Context context; + private ActionButtonCallback callback; private boolean showFeedtitle; private int selectedItemIndex; public static final int SELECTION_NONE = -1; - public FeedItemlistAdapter(Context context, int textViewResourceId, - List<FeedItem> objects, ActionButtonCallback callback, - boolean showFeedtitle) { - super(context, textViewResourceId, objects); + public FeedItemlistAdapter(Context context, + AbstractFeedItemlistAdapter.ItemAccess itemAccess, + ActionButtonCallback callback, boolean showFeedtitle) { + super(itemAccess); + this.context = context; this.callback = callback; this.showFeedtitle = showFeedtitle; this.selectedItemIndex = SELECTION_NONE; @@ -46,7 +48,7 @@ public class FeedItemlistAdapter extends AbstractFeedItemlistAdapter { if (convertView == null) { holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) getContext() + LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.feeditemlist_item, null); holder.title = (TextView) convertView @@ -161,7 +163,7 @@ public class FeedItemlistAdapter extends AbstractFeedItemlistAdapter { holder.downloading.setVisibility(View.GONE); } - TypedArray typeDrawables = getContext().obtainStyledAttributes( + TypedArray typeDrawables = context.obtainStyledAttributes( new int[] { R.attr.type_audio, R.attr.type_video }); MediaType mediaType = item.getMedia().getMediaType(); if (mediaType == MediaType.AUDIO) { diff --git a/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java b/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java index f89f2854a..d7ea0c160 100644 --- a/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java +++ b/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java @@ -1,34 +1,37 @@ package de.danoeh.antennapod.adapter; import java.text.DateFormat; -import java.util.List; import android.content.Context; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; +import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.asynctask.FeedImageLoader; +import de.danoeh.antennapod.asynctask.ImageLoader; import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.feed.FeedManager; import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.ThemeUtils; -public class FeedlistAdapter extends ArrayAdapter<Feed> { +public class FeedlistAdapter extends BaseAdapter { private static final String TAG = "FeedlistAdapter"; + private Context context; + private FeedManager manager = FeedManager.getInstance(); + private int selectedItemIndex; - private FeedImageLoader imageLoader; + private ImageLoader imageLoader; public static final int SELECTION_NONE = -1; - public FeedlistAdapter(Context context, int textViewResourceId, - List<Feed> objects) { - super(context, textViewResourceId, objects); + public FeedlistAdapter(Context context) { + super(); + this.context = context; selectedItemIndex = SELECTION_NONE; - imageLoader = FeedImageLoader.getInstance(); + imageLoader = ImageLoader.getInstance(); } @Override @@ -39,7 +42,7 @@ public class FeedlistAdapter extends ArrayAdapter<Feed> { // Inflate Layout if (convertView == null) { holder = new Holder(); - LayoutInflater inflater = (LayoutInflater) getContext() + LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.feedlist_item, null); @@ -83,7 +86,7 @@ public class FeedlistAdapter extends ArrayAdapter<Feed> { .getTime(), System.currentTimeMillis(), DateFormat.MEDIUM, DateFormat.SHORT)); } - holder.numberOfEpisodes.setText(feed.getNumOfItems() + holder.numberOfEpisodes.setText(feed.getNumOfItems(true) + convertView.getResources() .getString(R.string.episodes_suffix)); @@ -105,8 +108,9 @@ public class FeedlistAdapter extends ArrayAdapter<Feed> { holder.inProgressEpisodesLabel.setVisibility(View.INVISIBLE); } - holder.image.setTag(feed.getImage()); - + final String imageUrl = (feed.getImage() != null) ? feed.getImage() + .getFile_url() : null; + holder.image.setTag(imageUrl); imageLoader.loadThumbnailBitmap( feed.getImage(), holder.image, @@ -136,4 +140,19 @@ public class FeedlistAdapter extends ArrayAdapter<Feed> { notifyDataSetChanged(); } + @Override + public int getCount() { + return manager.getFeedsSize(); + } + + @Override + public Feed getItem(int position) { + return manager.getFeedAtIndex(position); + } + + @Override + public long getItemId(int position) { + return position; + } + } diff --git a/src/de/danoeh/antennapod/adapter/SearchlistAdapter.java b/src/de/danoeh/antennapod/adapter/SearchlistAdapter.java index c7f42e7cb..129289d30 100644 --- a/src/de/danoeh/antennapod/adapter/SearchlistAdapter.java +++ b/src/de/danoeh/antennapod/adapter/SearchlistAdapter.java @@ -10,7 +10,7 @@ import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.asynctask.FeedImageLoader; +import de.danoeh.antennapod.asynctask.ImageLoader; import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedComponent; import de.danoeh.antennapod.feed.FeedItem; @@ -51,7 +51,7 @@ public class SearchlistAdapter extends ArrayAdapter<SearchResult> { final Feed feed = (Feed) component; holder.title.setText(feed.getTitle()); holder.subtitle.setVisibility(View.GONE); - FeedImageLoader.getInstance().loadThumbnailBitmap(feed.getImage(), + ImageLoader.getInstance().loadThumbnailBitmap(feed.getImage(), holder.cover, (int) convertView.getResources().getDimension(R.dimen.thumbnail_length)); } else if (component.getClass() == FeedItem.class) { final FeedItem item = (FeedItem) component; @@ -61,7 +61,7 @@ public class SearchlistAdapter extends ArrayAdapter<SearchResult> { holder.subtitle.setText(result.getSubtitle()); } - FeedImageLoader.getInstance().loadThumbnailBitmap( + ImageLoader.getInstance().loadThumbnailBitmap( item.getFeed().getImage(), holder.cover, (int) convertView.getResources().getDimension( diff --git a/src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java b/src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java index c23c4c66a..810a17165 100644 --- a/src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java +++ b/src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java @@ -11,10 +11,10 @@ import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.util.BitmapDecoder; -public abstract class BitmapDecodeWorkerTask extends Thread { +public class BitmapDecodeWorkerTask extends Thread { protected int PREFERRED_LENGTH; - + /** Can be thumbnail or cover */ protected int imageType; @@ -25,7 +25,7 @@ public abstract class BitmapDecodeWorkerTask extends Thread { protected String fileUrl; private Handler handler; - + private final int defaultCoverResource; public BitmapDecodeWorkerTask(Handler handler, ImageView target, @@ -36,7 +36,8 @@ public abstract class BitmapDecodeWorkerTask extends Thread { this.fileUrl = fileUrl; this.PREFERRED_LENGTH = length; this.imageType = imageType; - TypedArray res = target.getContext().obtainStyledAttributes(new int[] {R.attr.default_cover}); + TypedArray res = target.getContext().obtainStyledAttributes( + new int[] { R.attr.default_cover }); this.defaultCoverResource = res.getResourceId(0, 0); res.recycle(); } @@ -45,7 +46,9 @@ public abstract class BitmapDecodeWorkerTask extends Thread { * Should return true if tag of the imageview is still the same it was * before the bitmap was decoded */ - abstract protected boolean tagsMatching(ImageView target); + protected boolean tagsMatching(ImageView target) { + return target.getTag() == null || target.getTag() == fileUrl; + } protected void onPostExecute() { // check if imageview is still supposed to display this image @@ -64,13 +67,15 @@ public abstract class BitmapDecodeWorkerTask extends Thread { f = new File(fileUrl); } if (fileUrl != null && f.exists()) { - cBitmap = new CachedBitmap(BitmapDecoder.decodeBitmap(PREFERRED_LENGTH, fileUrl), PREFERRED_LENGTH); + cBitmap = new CachedBitmap(BitmapDecoder.decodeBitmap( + PREFERRED_LENGTH, fileUrl), PREFERRED_LENGTH); if (cBitmap.getBitmap() != null) { storeBitmapInCache(cBitmap); } else { Log.w(TAG, "Could not load bitmap. Using default image."); - cBitmap = new CachedBitmap(BitmapFactory.decodeResource(target.getResources(), - defaultCoverResource), PREFERRED_LENGTH); + cBitmap = new CachedBitmap(BitmapFactory.decodeResource( + target.getResources(), defaultCoverResource), + PREFERRED_LENGTH); } if (AppConfig.DEBUG) Log.d(TAG, "Finished loading bitmaps"); @@ -98,15 +103,15 @@ public abstract class BitmapDecodeWorkerTask extends Thread { protected void onInvalidFileUrl() { Log.e(TAG, "FeedImage has no valid file url. Using default image"); - cBitmap = new CachedBitmap(BitmapFactory.decodeResource(target.getResources(), - defaultCoverResource), PREFERRED_LENGTH); + cBitmap = new CachedBitmap(BitmapFactory.decodeResource( + target.getResources(), defaultCoverResource), PREFERRED_LENGTH); } protected void storeBitmapInCache(CachedBitmap cb) { - FeedImageLoader loader = FeedImageLoader.getInstance(); - if (imageType == FeedImageLoader.IMAGE_TYPE_COVER) { + ImageLoader loader = ImageLoader.getInstance(); + if (imageType == ImageLoader.IMAGE_TYPE_COVER) { loader.addBitmapToCoverCache(fileUrl, cb); - } else if (imageType == FeedImageLoader.IMAGE_TYPE_THUMBNAIL) { + } else if (imageType == ImageLoader.IMAGE_TYPE_THUMBNAIL) { loader.addBitmapToThumbnailCache(fileUrl, cb); } } diff --git a/src/de/danoeh/antennapod/asynctask/FeedImageLoader.java b/src/de/danoeh/antennapod/asynctask/ImageLoader.java index 5e1994adb..d0f20d621 100644 --- a/src/de/danoeh/antennapod/asynctask/FeedImageLoader.java +++ b/src/de/danoeh/antennapod/asynctask/ImageLoader.java @@ -16,13 +16,11 @@ import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.feed.FeedImage; -import de.danoeh.antennapod.feed.FeedManager; -import de.danoeh.antennapod.storage.DownloadRequester; /** Caches and loads FeedImage bitmaps in the background */ -public class FeedImageLoader { - private static final String TAG = "FeedImageLoader"; - private static FeedImageLoader singleton; +public class ImageLoader { + private static final String TAG = "ImageLoader"; + private static ImageLoader singleton; public static final int IMAGE_TYPE_THUMBNAIL = 0; public static final int IMAGE_TYPE_COVER = 1; @@ -44,7 +42,7 @@ public class FeedImageLoader { private LruCache<String, CachedBitmap> coverCache; private LruCache<String, CachedBitmap> thumbnailCache; - private FeedImageLoader() { + private ImageLoader() { handler = new Handler(); executor = createExecutor(); @@ -79,9 +77,9 @@ public class FeedImageLoader { }); } - public static FeedImageLoader getInstance() { + public static ImageLoader getInstance() { if (singleton == null) { - singleton = new FeedImageLoader(); + singleton = new ImageLoader(); } return singleton; } @@ -92,8 +90,18 @@ public class FeedImageLoader { * ImageView's size has already been set or inside a Runnable which is * posted to the ImageView's message queue. */ + public void loadCoverBitmap(String fileUrl, ImageView target) { + loadCoverBitmap(fileUrl, target, target.getHeight()); + } + public void loadCoverBitmap(FeedImage image, ImageView target) { - loadCoverBitmap(image, target, target.getHeight()); + loadCoverBitmap((image != null) ? image.getFile_url() : null, target, + target.getHeight()); + } + + public void loadCoverBitmap(FeedImage image, ImageView target, int length) { + loadCoverBitmap((image != null) ? image.getFile_url() : null, target, + length); } /** @@ -102,17 +110,18 @@ public class FeedImageLoader { * ImageView's size has already been set or inside a Runnable which is * posted to the ImageView's message queue. */ - public void loadCoverBitmap(FeedImage image, ImageView target, int length) { - final int defaultCoverResource = getDefaultCoverResource(target.getContext()); - - if (image != null && image.getFile_url() != null) { - CachedBitmap cBitmap = getBitmapFromCoverCache(image.getFile_url()); + public void loadCoverBitmap(String fileUrl, ImageView target, int length) { + final int defaultCoverResource = getDefaultCoverResource(target + .getContext()); + + if (fileUrl != null) { + CachedBitmap cBitmap = getBitmapFromCoverCache(fileUrl); if (cBitmap != null && cBitmap.getLength() >= length) { target.setImageBitmap(cBitmap.getBitmap()); } else { target.setImageResource(defaultCoverResource); - FeedImageDecodeWorkerTask worker = new FeedImageDecodeWorkerTask( - handler, target, image, length, IMAGE_TYPE_COVER); + BitmapDecodeWorkerTask worker = new BitmapDecodeWorkerTask( + handler, target, fileUrl, length, IMAGE_TYPE_COVER); executor.submit(worker); } } else { @@ -126,8 +135,19 @@ public class FeedImageLoader { * called if the ImageView's size has already been set or inside a Runnable * which is posted to the ImageView's message queue. */ + public void loadThumbnailBitmap(String fileUrl, ImageView target) { + loadThumbnailBitmap(fileUrl, target, target.getHeight()); + } + public void loadThumbnailBitmap(FeedImage image, ImageView target) { - loadThumbnailBitmap(image, target, target.getHeight()); + loadThumbnailBitmap((image != null) ? image.getFile_url() : null, + target, target.getHeight()); + } + + public void loadThumbnailBitmap(FeedImage image, ImageView target, + int length) { + loadThumbnailBitmap((image != null) ? image.getFile_url() : null, + target, length); } /** @@ -136,18 +156,18 @@ public class FeedImageLoader { * called if the ImageView's size has already been set or inside a Runnable * which is posted to the ImageView's message queue. */ - public void loadThumbnailBitmap(FeedImage image, ImageView target, - int length) { - final int defaultCoverResource = getDefaultCoverResource(target.getContext()); - - if (image != null && image.getFile_url() != null) { - CachedBitmap cBitmap = getBitmapFromThumbnailCache(image.getFile_url()); + public void loadThumbnailBitmap(String fileUrl, ImageView target, int length) { + final int defaultCoverResource = getDefaultCoverResource(target + .getContext()); + + if (fileUrl != null) { + CachedBitmap cBitmap = getBitmapFromThumbnailCache(fileUrl); if (cBitmap != null && cBitmap.getLength() >= length) { target.setImageBitmap(cBitmap.getBitmap()); } else { target.setImageResource(defaultCoverResource); - FeedImageDecodeWorkerTask worker = new FeedImageDecodeWorkerTask( - handler, target, image, length, IMAGE_TYPE_THUMBNAIL); + BitmapDecodeWorkerTask worker = new BitmapDecodeWorkerTask( + handler, target, fileUrl, length, IMAGE_TYPE_THUMBNAIL); executor.submit(worker); } } else { @@ -168,8 +188,8 @@ public class FeedImageLoader { thumbnailCache.evictAll(); } - public boolean isInThumbnailCache(FeedImage image) { - return thumbnailCache.get(image.getFile_url()) != null; + public boolean isInThumbnailCache(String fileUrl) { + return thumbnailCache.get(fileUrl) != null; } private CachedBitmap getBitmapFromThumbnailCache(String key) { @@ -180,8 +200,8 @@ public class FeedImageLoader { thumbnailCache.put(key, bitmap); } - public boolean isInCoverCache(FeedImage image) { - return coverCache.get(image.getFile_url()) != null; + public boolean isInCoverCache(String fileUrl) { + return coverCache.get(fileUrl) != null; } private CachedBitmap getBitmapFromCoverCache(String key) { @@ -191,43 +211,13 @@ public class FeedImageLoader { public void addBitmapToCoverCache(String key, CachedBitmap bitmap) { coverCache.put(key, bitmap); } - + private int getDefaultCoverResource(Context context) { - TypedArray res = context.obtainStyledAttributes(new int[] {R.attr.default_cover}); + TypedArray res = context + .obtainStyledAttributes(new int[] { R.attr.default_cover }); final int defaultCoverResource = res.getResourceId(0, 0); res.recycle(); return defaultCoverResource; } - class FeedImageDecodeWorkerTask extends BitmapDecodeWorkerTask { - - private static final String TAG = "FeedImageDecodeWorkerTask"; - - protected FeedImage image; - - public FeedImageDecodeWorkerTask(Handler handler, ImageView target, - FeedImage image, int length, int imageType) { - super(handler, target, image.getFile_url(), length, imageType); - this.image = image; - } - - @Override - protected boolean tagsMatching(ImageView target) { - return target.getTag() == null || target.getTag() == image; - } - - @Override - protected void onInvalidFileUrl() { - super.onInvalidFileUrl(); - if (image.getFile_url() != null - && !DownloadRequester.getInstance() - .isDownloadingFile(image)) { - FeedManager.getInstance().notifyInvalidImageFile( - PodcastApp.getInstance(), image); - } - - } - - } - } diff --git a/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java b/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java index db9ae999c..978f53ac6 100644 --- a/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java +++ b/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java @@ -3,6 +3,7 @@ package de.danoeh.antennapod.asynctask; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.util.Arrays; import android.annotation.SuppressLint; import android.app.AlertDialog; @@ -15,6 +16,7 @@ import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.feed.FeedManager; import de.danoeh.antennapod.opml.OpmlWriter; +import de.danoeh.antennapod.preferences.UserPreferences; /** Writes an OPML file into the export directory in the background. */ public class OpmlExportWorker extends AsyncTask<Void, Void, Void> { @@ -40,7 +42,7 @@ public class OpmlExportWorker extends AsyncTask<Void, Void, Void> { OpmlWriter opmlWriter = new OpmlWriter(); if (output == null) { output = new File( - PodcastApp.getDataFolder(context, PodcastApp.EXPORT_DIR), + UserPreferences.getDataFolder(context, PodcastApp.EXPORT_DIR), DEFAULT_OUTPUT_NAME); if (output.exists()) { Log.w(TAG, "Overwriting previously exported file."); @@ -49,7 +51,7 @@ public class OpmlExportWorker extends AsyncTask<Void, Void, Void> { } try { FileWriter writer = new FileWriter(output); - opmlWriter.writeDocument(FeedManager.getInstance().getFeeds(), + opmlWriter.writeDocument(Arrays.asList(FeedManager.getInstance().getFeedsArray()), writer); writer.close(); } catch (IOException e) { diff --git a/src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java b/src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java index a3652e05d..4d9c9867e 100644 --- a/src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java +++ b/src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java @@ -7,7 +7,6 @@ import android.app.ProgressDialog; import android.content.Context; import android.os.AsyncTask; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.OpmlImportFromPathActivity; import de.danoeh.antennapod.activity.OpmlImportHolder; import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.opml.OpmlElement; diff --git a/src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java b/src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java index 2c1d30bdb..5af06895f 100644 --- a/src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java +++ b/src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java @@ -1,6 +1,7 @@ package de.danoeh.antennapod.asynctask; -import java.io.*; +import java.io.IOException; +import java.io.Reader; import java.util.ArrayList; import org.xmlpull.v1.XmlPullParserException; diff --git a/src/de/danoeh/antennapod/feed/Chapter.java b/src/de/danoeh/antennapod/feed/Chapter.java index 10575e03d..ebf8ed44f 100644 --- a/src/de/danoeh/antennapod/feed/Chapter.java +++ b/src/de/danoeh/antennapod/feed/Chapter.java @@ -5,7 +5,6 @@ public abstract class Chapter extends FeedComponent { /** Defines starting point in milliseconds. */ protected long start; protected String title; - protected FeedItem item; protected String link; public Chapter() { @@ -20,7 +19,6 @@ public abstract class Chapter extends FeedComponent { super(); this.start = start; this.title = title; - this.item = item; this.link = link; } @@ -34,10 +32,6 @@ public abstract class Chapter extends FeedComponent { return title; } - public FeedItem getItem() { - return item; - } - public String getLink() { return link; } @@ -50,10 +44,6 @@ public abstract class Chapter extends FeedComponent { this.title = title; } - public void setItem(FeedItem item) { - this.item = item; - } - public void setLink(String link) { this.link = link; } diff --git a/src/de/danoeh/antennapod/feed/EventDistributor.java b/src/de/danoeh/antennapod/feed/EventDistributor.java new file mode 100644 index 000000000..1fc7e2c35 --- /dev/null +++ b/src/de/danoeh/antennapod/feed/EventDistributor.java @@ -0,0 +1,140 @@ +package de.danoeh.antennapod.feed; + +import java.util.AbstractQueue; +import java.util.Observable; +import java.util.Observer; +import java.util.concurrent.ConcurrentLinkedQueue; + +import android.os.Handler; +import android.util.Log; +import de.danoeh.antennapod.AppConfig; + +/** + * Notifies its observers about changes in the feed database. Observers can + * register by retrieving an instance of this class and registering an + * EventListener. When new events arrive, the EventDistributor will process the + * event queue in a handler that runs on the main thread. The observers will only + * be notified once if the event queue contains multiple elements. + * + * Events can be sent with the send* methods. + */ +public class EventDistributor extends Observable { + private static final String TAG = "EventDistributor"; + + public static final int FEED_LIST_UPDATE = 1; + public static final int UNREAD_ITEMS_UPDATE = 2; + public static final int QUEUE_UPDATE = 4; + public static final int DOWNLOADLOG_UPDATE = 8; + public static final int PLAYBACK_HISTORY_UPDATE = 16; + public static final int DOWNLOAD_QUEUED = 32; + public static final int DOWNLOAD_HANDLED = 64; + + private Handler handler; + private AbstractQueue<Integer> events; + + private static EventDistributor instance; + + private EventDistributor() { + this.handler = new Handler(); + events = new ConcurrentLinkedQueue<Integer>(); + } + + public static EventDistributor getInstance() { + if (instance == null) { + instance = new EventDistributor(); + } + return instance; + } + + public void register(EventListener el) { + addObserver(el); + } + + public void unregister(EventListener el) { + deleteObserver(el); + } + + public void addEvent(Integer i) { + events.offer(i); + handler.post(new Runnable() { + + @Override + public void run() { + processEventQueue(); + } + }); + } + + private void processEventQueue() { + Integer result = 0; + if (AppConfig.DEBUG) + Log.d(TAG, + "Processing event queue. Number of events: " + + events.size()); + for (Integer current = events.poll(); current != null; current = events + .poll()) { + result |= current; + } + if (result != 0) { + if (AppConfig.DEBUG) + Log.d(TAG, "Notifying observers. Data: " + result); + setChanged(); + notifyObservers(result); + } else { + if (AppConfig.DEBUG) + Log.d(TAG, + "Event queue didn't contain any new events. Observers will not be notified."); + } + } + + @Override + public void addObserver(Observer observer) { + super.addObserver(observer); + if (!(observer instanceof EventListener)) { + throw new IllegalArgumentException( + "Observer must be instance of FeedManager.EventListener"); + } + } + + public void sendDownloadQueuedBroadcast() { + addEvent(DOWNLOAD_QUEUED); + } + + public void sendUnreadItemsUpdateBroadcast() { + addEvent(UNREAD_ITEMS_UPDATE); + } + + public void sendQueueUpdateBroadcast() { + addEvent(QUEUE_UPDATE); + } + + public void sendFeedUpdateBroadcast() { + addEvent(FEED_LIST_UPDATE); + } + + public void sendPlaybackHistoryUpdateBroadcast() { + addEvent(PLAYBACK_HISTORY_UPDATE); + } + + public void sendDownloadLogUpdateBroadcast() { + addEvent(DOWNLOADLOG_UPDATE); + } + + public void sendDownloadHandledBroadcast() { + addEvent(DOWNLOAD_HANDLED); + } + + public static abstract class EventListener implements Observer { + + @Override + public void update(Observable observable, Object data) { + if (observable instanceof EventDistributor + && data instanceof Integer) { + update((EventDistributor) observable, (Integer) data); + } + } + + public abstract void update(EventDistributor eventDistributor, + Integer arg); + } +} diff --git a/src/de/danoeh/antennapod/feed/Feed.java b/src/de/danoeh/antennapod/feed/Feed.java index 4df9fc43f..6220bde00 100644 --- a/src/de/danoeh/antennapod/feed/Feed.java +++ b/src/de/danoeh/antennapod/feed/Feed.java @@ -1,12 +1,12 @@ package de.danoeh.antennapod.feed; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; -import android.preference.PreferenceManager; -import de.danoeh.antennapod.PodcastApp; +import de.danoeh.antennapod.preferences.UserPreferences; +import de.danoeh.antennapod.util.EpisodeFilter; /** * Data Object for a whole feed @@ -39,7 +39,7 @@ public class Feed extends FeedFile { public Feed(Date lastUpdate) { super(); - items = new CopyOnWriteArrayList<FeedItem>(); + items = Collections.synchronizedList(new ArrayList<FeedItem>()); this.lastUpdate = lastUpdate; } @@ -68,13 +68,10 @@ public class Feed extends FeedFile { * */ public int getNumOfNewItems() { int count = 0; - boolean displayOnlyEpisodes = PreferenceManager - .getDefaultSharedPreferences(PodcastApp.getInstance()) - .getBoolean(PodcastApp.PREF_DISPLAY_ONLY_EPISODES, false); - for (FeedItem item : items) { if (item.getState() == FeedItem.State.NEW) { - if (!displayOnlyEpisodes || item.getMedia() != null) { + if (!UserPreferences.isDisplayOnlyEpisodes() + || item.getMedia() != null) { count++; } } @@ -100,17 +97,18 @@ public class Feed extends FeedFile { } /** - * Returns true if at least one item in the itemlist is unread.If the - * 'display only episodes' - preference is set to true, this method will - * only count items with episodes. + * Returns true if at least one item in the itemlist is unread. + * + * @param enableEpisodeFilter + * true if this method should only count items with episodes if + * the 'display only episodes' - preference is set to true by the + * user. */ - public boolean hasNewItems() { - boolean displayOnlyEpisodes = PreferenceManager - .getDefaultSharedPreferences(PodcastApp.getInstance()) - .getBoolean(PodcastApp.PREF_DISPLAY_ONLY_EPISODES, false); + public boolean hasNewItems(boolean enableEpisodeFilter) { for (FeedItem item : items) { if (item.getState() == FeedItem.State.NEW) { - if (!displayOnlyEpisodes || item.getMedia() != null) { + if (!(enableEpisodeFilter && UserPreferences + .isDisplayOnlyEpisodes()) || item.getMedia() != null) { return true; } } @@ -119,21 +117,34 @@ public class Feed extends FeedFile { } /** - * Returns the number of FeedItems. If the 'display only episodes' - - * preference is set to true, this method will only count items with - * episodes. + * Returns the number of FeedItems. + * + * @param enableEpisodeFilter + * true if this method should only count items with episodes if + * the 'display only episodes' - preference is set to true by the + * user. * */ - public int getNumOfItems() { - int count = 0; - boolean displayOnlyEpisodes = PreferenceManager - .getDefaultSharedPreferences(PodcastApp.getInstance()) - .getBoolean(PodcastApp.PREF_DISPLAY_ONLY_EPISODES, false); - for (FeedItem item : items) { - if (!displayOnlyEpisodes || item.getMedia() != null) { - count++; - } + public int getNumOfItems(boolean enableEpisodeFilter) { + if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) { + return EpisodeFilter.countItemsWithEpisodes(items); + } else { + return items.size(); + } + } + + /** + * Returns the item at the specified index. + * + * @param enableEpisodeFilter + * true if this method should ignore items without episdodes if + * the episodes filter has been enabled by the user. + */ + public FeedItem getItemAtIndex(boolean enableEpisodeFilter, int position) { + if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) { + return EpisodeFilter.accessEpisodeByIndex(items, position); + } else { + return items.get(position); } - return count; } /** @@ -273,12 +284,17 @@ public class Feed extends FeedFile { this.image = image; } - public List<FeedItem> getItems() { + List<FeedItem> getItems() { return items; } public void setItems(ArrayList<FeedItem> items) { - this.items = items; + this.items = Collections.synchronizedList(items); + } + + /** Returns an array that contains all the feeditems of this feed. */ + public FeedItem[] getItemsArray() { + return items.toArray(new FeedItem[items.size()]); } public Date getLastUpdate() { diff --git a/src/de/danoeh/antennapod/feed/FeedItem.java b/src/de/danoeh/antennapod/feed/FeedItem.java index 06fdc4292..bb176c411 100644 --- a/src/de/danoeh/antennapod/feed/FeedItem.java +++ b/src/de/danoeh/antennapod/feed/FeedItem.java @@ -4,7 +4,7 @@ import java.lang.ref.SoftReference; import java.util.Date; import java.util.List; -import de.danoeh.antennapod.PodcastApp; +import de.danoeh.antennapod.preferences.PlaybackPreferences; /** * Data Object for a XML message @@ -92,27 +92,6 @@ public class FeedItem extends FeedComponent { contentEncoded = null; } - /** Get the chapter that fits the position. */ - public Chapter getCurrentChapter(int position) { - Chapter current = null; - if (chapters != null) { - current = chapters.get(0); - for (Chapter sc : chapters) { - if (sc.getStart() > position) { - break; - } else { - current = sc; - } - } - } - return current; - } - - /** Calls getCurrentChapter with current position. */ - public Chapter getCurrentChapter() { - return getCurrentChapter(media.getPosition()); - } - /** * Returns the value that uniquely identifies this FeedItem. If the * itemIdentifier attribute is not null, it will be returned. Else it will @@ -234,7 +213,7 @@ public class FeedItem extends FeedComponent { private boolean isPlaying() { if (media != null) { - if (PodcastApp.getCurrentlyPlayingMediaId() == media.getId()) { + if (PlaybackPreferences.getCurrentlyPlayingFeedMediaId() == media.getId()) { return true; } } diff --git a/src/de/danoeh/antennapod/feed/FeedManager.java b/src/de/danoeh/antennapod/feed/FeedManager.java index 6c3471e5c..e984da46a 100644 --- a/src/de/danoeh/antennapod/feed/FeedManager.java +++ b/src/de/danoeh/antennapod/feed/FeedManager.java @@ -19,13 +19,15 @@ import android.os.Handler; import android.preference.PreferenceManager; import android.util.Log; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.asynctask.DownloadStatus; +import de.danoeh.antennapod.preferences.PlaybackPreferences; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.service.PlaybackService; import de.danoeh.antennapod.storage.DownloadRequestException; import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.storage.PodDBAdapter; import de.danoeh.antennapod.util.DownloadError; +import de.danoeh.antennapod.util.EpisodeFilter; import de.danoeh.antennapod.util.FeedtitleComparator; import de.danoeh.antennapod.util.comparator.DownloadStatusComparator; import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator; @@ -33,21 +35,16 @@ import de.danoeh.antennapod.util.comparator.PlaybackCompletionDateComparator; import de.danoeh.antennapod.util.exception.MediaFileNotFoundException; /** - * Singleton class Manages all feeds, categories and feeditems - * + * Singleton class that - provides access to all Feeds and FeedItems and to + * several lists of FeedItems. - provides methods for modifying the + * application's data - takes care of updating the information stored in the + * database when something is modified * + * An instance of this class can be retrieved via getInstance(). * */ public class FeedManager { private static final String TAG = "FeedManager"; - public static final String ACTION_FEED_LIST_UPDATE = "de.danoeh.antennapod.action.feed.feedlistUpdate"; - public static final String ACTION_UNREAD_ITEMS_UPDATE = "de.danoeh.antennapod.action.feed.unreadItemsUpdate"; - public static final String ACTION_QUEUE_UPDATE = "de.danoeh.antennapod.action.feed.queueUpdate"; - public static final String ACTION_DOWNLOADLOG_UPDATE = "de.danoeh.antennapod.action.feed.downloadLogUpdate"; - public static final String ACTION_PLAYBACK_HISTORY_UPDATE = "de.danoeh.antennapod.action.feed.playbackHistoryUpdate"; - public static final String EXTRA_FEED_ITEM_ID = "de.danoeh.antennapod.extra.feed.feedItemId"; - public static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feed.feedId"; - /** Number of completed Download status entries to store. */ private static final int DOWNLOAD_LOG_SIZE = 50; @@ -59,7 +56,7 @@ public class FeedManager { private List<FeedItem> unreadItems; /** Contains completed Download status entries */ - private ArrayList<DownloadStatus> downloadLog; + private List<DownloadStatus> downloadLog; /** Contains the queue of items to be played. */ private List<FeedItem> queue; @@ -70,10 +67,15 @@ public class FeedManager { /** Maximum number of items in the playback history. */ private static final int PLAYBACK_HISTORY_SIZE = 15; - private DownloadRequester requester; + private DownloadRequester requester = DownloadRequester.getInstance(); + private EventDistributor eventDist = EventDistributor.getInstance(); - /** Should be used to change the content of the arrays from another thread. */ + /** + * Should be used to change the content of the arrays from another thread to + * ensure that arrays are only modified on the main thread. + */ private Handler contentChanger; + /** Ensures that there are no parallel db operations. */ private Executor dbExec; @@ -83,7 +85,6 @@ public class FeedManager { private FeedManager() { feeds = Collections.synchronizedList(new ArrayList<Feed>()); unreadItems = Collections.synchronizedList(new ArrayList<FeedItem>()); - requester = DownloadRequester.getInstance(); downloadLog = new ArrayList<DownloadStatus>(); queue = Collections.synchronizedList(new ArrayList<FeedItem>()); playbackHistory = Collections @@ -100,6 +101,7 @@ public class FeedManager { }); } + /** Creates a new instance of this class if necessary and returns it. */ public static FeedManager getInstance() { if (singleton == null) { singleton = new FeedManager(); @@ -119,6 +121,8 @@ public class FeedManager { * if Mediaplayer activity shall be started * @param startWhenPrepared * if Mediaplayer shall be started after it has been prepared + * @param shouldStream + * if Mediaplayer should stream the file */ public void playMedia(Context context, FeedMedia media, boolean showPlayer, boolean startWhenPrepared, boolean shouldStream) { @@ -132,10 +136,7 @@ public class FeedManager { } // Start playback Service Intent launchIntent = new Intent(context, PlaybackService.class); - launchIntent - .putExtra(PlaybackService.EXTRA_MEDIA_ID, media.getId()); - launchIntent.putExtra(PlaybackService.EXTRA_FEED_ID, media - .getItem().getFeed().getId()); + launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media); launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED, startWhenPrepared); launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, @@ -150,11 +151,7 @@ public class FeedManager { } } catch (MediaFileNotFoundException e) { e.printStackTrace(); - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(context); - final long lastPlayedId = prefs.getLong( - PlaybackService.PREF_LAST_PLAYED_ID, -1); - if (lastPlayedId == media.getId()) { + if (PlaybackPreferences.getLastPlayedId() == media.getId()) { context.sendBroadcast(new Intent( PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); } @@ -176,14 +173,12 @@ public class FeedManager { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context); - final long lastPlayedId = prefs.getLong( - PlaybackService.PREF_LAST_PLAYED_ID, -1); - if (media.getId() == lastPlayedId) { + if (media.getId() == PlaybackPreferences.getLastPlayedId()) { SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(PlaybackService.PREF_LAST_IS_STREAM, true); + editor.putBoolean(PlaybackPreferences.PREF_LAST_IS_STREAM, true); editor.commit(); } - if (lastPlayedId == media.getId()) { + if (PlaybackPreferences.getLastPlayedId() == media.getId()) { context.sendBroadcast(new Intent( PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); } @@ -197,14 +192,12 @@ public class FeedManager { public void deleteFeed(final Context context, final Feed feed) { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context.getApplicationContext()); - long lastPlayedFeed = prefs.getLong( - PlaybackService.PREF_LAST_PLAYED_FEED_ID, -1); - if (lastPlayedFeed == feed.getId()) { + if (PlaybackPreferences.getLastPlayedFeedId() == feed.getId()) { context.sendBroadcast(new Intent( PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); SharedPreferences.Editor editor = prefs.edit(); - editor.putLong(PlaybackService.PREF_LAST_PLAYED_ID, -1); - editor.putLong(PlaybackService.PREF_LAST_PLAYED_FEED_ID, -1); + editor.putLong(PlaybackPreferences.PREF_LAST_PLAYED_ID, -1); + editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, -1); editor.commit(); } @@ -213,7 +206,7 @@ public class FeedManager { @Override public void run() { feeds.remove(feed); - sendFeedUpdateBroadcast(context); + eventDist.sendFeedUpdateBroadcast(); dbExec.execute(new Runnable() { @Override @@ -266,32 +259,6 @@ public class FeedManager { } - private void sendUnreadItemsUpdateBroadcast(Context context, FeedItem item) { - Intent update = new Intent(ACTION_UNREAD_ITEMS_UPDATE); - if (item != null) { - update.putExtra(EXTRA_FEED_ID, item.getFeed().getId()); - update.putExtra(EXTRA_FEED_ITEM_ID, item.getId()); - } - context.sendBroadcast(update); - } - - private void sendQueueUpdateBroadcast(Context context, FeedItem item) { - Intent update = new Intent(ACTION_QUEUE_UPDATE); - if (item != null) { - update.putExtra(EXTRA_FEED_ID, item.getFeed().getId()); - update.putExtra(EXTRA_FEED_ITEM_ID, item.getId()); - } - context.sendBroadcast(update); - } - - private void sendFeedUpdateBroadcast(Context context) { - context.sendBroadcast(new Intent(ACTION_FEED_LIST_UPDATE)); - } - - private void sendPlaybackHistoryUpdateBroadcast(Context context) { - context.sendBroadcast(new Intent(ACTION_PLAYBACK_HISTORY_UPDATE)); - } - /** * Makes sure that playback history is sorted and is not larger than * PLAYBACK_HISTORY_SIZE. @@ -346,6 +313,7 @@ public class FeedManager { } } + /** Removes all items from the playback history. */ public void clearPlaybackHistory(final Context context) { if (!playbackHistory.isEmpty()) { if (AppConfig.DEBUG) @@ -353,7 +321,7 @@ public class FeedManager { final FeedItem[] items = playbackHistory .toArray(new FeedItem[playbackHistory.size()]); playbackHistory.clear(); - sendPlaybackHistoryUpdateBroadcast(context); + eventDist.sendPlaybackHistoryUpdateBroadcast(); dbExec.execute(new Runnable() { @Override @@ -373,6 +341,7 @@ public class FeedManager { } } + /** Adds a FeedItem to the playback history. */ public void addItemToPlaybackHistory(Context context, FeedItem item) { if (item.getMedia() != null && item.getMedia().getPlaybackCompletionDate() != null) { @@ -382,13 +351,13 @@ public class FeedManager { playbackHistory.add(item); } cleanupPlaybackHistoryWithDBCleanup(context); - sendPlaybackHistoryUpdateBroadcast(context); + eventDist.sendPlaybackHistoryUpdateBroadcast(); } } private void removeItemFromPlaybackHistory(Context context, FeedItem item) { playbackHistory.remove(item); - sendPlaybackHistoryUpdateBroadcast(context); + eventDist.sendPlaybackHistoryUpdateBroadcast(); } /** @@ -420,7 +389,7 @@ public class FeedManager { Collections.sort(unreadItems, new FeedItemPubdateComparator()); } - sendUnreadItemsUpdateBroadcast(context, item); + eventDist.sendUnreadItemsUpdateBroadcast(); } }); @@ -428,8 +397,6 @@ public class FeedManager { /** * Sets the 'read' attribute of all FeedItems of a specific feed to true - * - * @param context */ public void markFeedRead(Context context, Feed feed) { for (FeedItem item : feed.getItems()) { @@ -449,7 +416,7 @@ public class FeedManager { final ArrayList<FeedItem> unreadItemsCopy = new ArrayList<FeedItem>( unreadItems); unreadItems.clear(); - sendUnreadItemsUpdateBroadcast(context, null); + eventDist.sendUnreadItemsUpdateBroadcast(); dbExec.execute(new Runnable() { @Override @@ -467,6 +434,7 @@ public class FeedManager { } + /** Updates all feeds in the feed list. */ @SuppressLint("NewApi") public void refreshAllFeeds(final Context context) { if (AppConfig.DEBUG) @@ -536,15 +504,17 @@ public class FeedManager { media.setDownloaded(false); media.setFile_url(null); setFeedMedia(context, media); - sendFeedUpdateBroadcast(context); + eventDist.sendFeedUpdateBroadcast(); } + /** Updates a specific feed. */ public void refreshFeed(Context context, Feed feed) throws DownloadRequestException { requester.downloadFeed(context, new Feed(feed.getDownload_url(), new Date(), feed.getTitle())); } + /** Adds a download status object to the download log. */ public void addDownloadStatus(final Context context, final DownloadStatus status) { contentChanger.post(new Runnable() { @@ -559,7 +529,7 @@ public class FeedManager { } else { removedStatus = null; } - context.sendBroadcast(new Intent(ACTION_DOWNLOADLOG_UPDATE)); + eventDist.sendDownloadLogUpdateBroadcast(); dbExec.execute(new Runnable() { @Override @@ -578,6 +548,7 @@ public class FeedManager { } + /** Downloads all items in the queue that have not been downloaded yet. */ public void downloadAllItemsInQueue(final Context context) { if (!queue.isEmpty()) { try { @@ -589,11 +560,9 @@ public class FeedManager { } } + /** Downloads FeedItems if they have not been downloaded yet. */ public void downloadFeedItem(final Context context, FeedItem... items) throws DownloadRequestException { - boolean autoQueue = PreferenceManager.getDefaultSharedPreferences( - context.getApplicationContext()).getBoolean( - PodcastApp.PREF_AUTO_QUEUE, true); List<FeedItem> addToQueue = new ArrayList<FeedItem>(); for (FeedItem item : items) { @@ -618,12 +587,16 @@ public class FeedManager { addToQueue.add(item); } } - if (autoQueue) { + if (UserPreferences.isAutoQueue()) { addQueueItem(context, addToQueue.toArray(new FeedItem[addToQueue.size()])); } } + /** + * Enqueues all items that are currently in the unreadItems list and marks + * them as 'read'. + */ public void enqueueAllNewItems(final Context context) { if (!unreadItems.isEmpty()) { addQueueItem(context, @@ -632,6 +605,7 @@ public class FeedManager { } } + /** Adds FeedItems to the queue if they are not in the queue yet. */ public void addQueueItem(final Context context, final FeedItem... items) { if (items.length > 0) { contentChanger.post(new Runnable() { @@ -643,7 +617,7 @@ public class FeedManager { queue.add(item); } } - sendQueueUpdateBroadcast(context, items[0]); + eventDist.sendQueueUpdateBroadcast(); dbExec.execute(new Runnable() { @Override @@ -679,7 +653,7 @@ public class FeedManager { if (AppConfig.DEBUG) Log.d(TAG, "Clearing queue"); queue.clear(); - sendQueueUpdateBroadcast(context, null); + eventDist.sendQueueUpdateBroadcast(); dbExec.execute(new Runnable() { @Override @@ -693,8 +667,8 @@ public class FeedManager { } - /** Uses external adapter. */ - public void removeQueueItem(FeedItem item, PodDBAdapter adapter) { + /** Removes a FeedItem from the queue. Uses external PodDBAdapter. */ + private void removeQueueItem(FeedItem item, PodDBAdapter adapter) { boolean removed = queue.remove(item); if (removed) { adapter.setQueue(queue); @@ -702,7 +676,7 @@ public class FeedManager { } - /** Uses its own adapter. */ + /** Removes a FeedItem from the queue. */ public void removeQueueItem(final Context context, FeedItem item) { boolean removed = queue.remove(item); if (removed) { @@ -719,7 +693,7 @@ public class FeedManager { }); } - sendQueueUpdateBroadcast(context, item); + eventDist.sendQueueUpdateBroadcast(); } /** @@ -732,25 +706,20 @@ public class FeedManager { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context .getApplicationContext()); - boolean autoDelete = prefs.getBoolean(PodcastApp.PREF_AUTO_DELETE, - false); - if (autoDelete) { - long lastPlayedId = prefs.getLong( - PlaybackService.PREF_LAST_PLAYED_ID, -1); - long autoDeleteId = prefs.getLong( - PlaybackService.PREF_AUTODELETE_MEDIA_ID, -1); - boolean playbackCompleted = prefs - .getBoolean( - PlaybackService.PREF_AUTO_DELETE_MEDIA_PLAYBACK_COMPLETED, - false); - if ((media.getId() != lastPlayedId) - && ((media.getId() != autoDeleteId) || (media.getId() == autoDeleteId && playbackCompleted))) { + if (UserPreferences.isAutoDelete()) { + + if ((media.getId() != PlaybackPreferences.getLastPlayedId()) + && ((media.getId() != PlaybackPreferences + .getAutoDeleteMediaId()) || (media.getId() == PlaybackPreferences + .getAutoDeleteMediaId() && PlaybackPreferences + .isAutoDeleteMediaPlaybackCompleted()))) { if (AppConfig.DEBUG) Log.d(TAG, "Performing auto-cleanup"); deleteFeedMedia(context, media); SharedPreferences.Editor editor = prefs.edit(); - editor.putLong(PlaybackService.PREF_AUTODELETE_MEDIA_ID, -1); + editor.putLong( + PlaybackPreferences.PREF_AUTODELETE_MEDIA_ID, -1); editor.commit(); } else { if (AppConfig.DEBUG) @@ -797,15 +766,20 @@ public class FeedManager { } }); if (broadcastUpdate) { - sendQueueUpdateBroadcast(context, item); + eventDist.sendQueueUpdateBroadcast(); } } } + /** Returns true if the specified item is in the queue. */ public boolean isInQueue(FeedItem item) { return queue.contains(item); } + /** + * Returns the FeedItem at the beginning of the queue or null if the queue + * is empty. + */ public FeedItem getFirstQueueItem() { if (queue.isEmpty()) { return null; @@ -821,7 +795,7 @@ public class FeedManager { public void run() { feeds.add(feed); Collections.sort(feeds, new FeedtitleComparator()); - sendFeedUpdateBroadcast(context); + eventDist.sendFeedUpdateBroadcast(); } }); setCompleteFeed(context, feed); @@ -884,7 +858,7 @@ public class FeedManager { } - /** Get a Feed by its link */ + /** Get a Feed by its identifying value. */ private Feed searchFeedByIdentifyingValue(String identifier) { for (Feed feed : feeds) { if (feed.getIdentifyingValue().equals(identifier)) { @@ -919,7 +893,7 @@ public class FeedManager { } /** Updates Information of an existing Feed. Uses external adapter. */ - public void setFeed(Feed feed, PodDBAdapter adapter) { + private void setFeed(Feed feed, PodDBAdapter adapter) { if (adapter != null) { adapter.setFeed(feed); feed.cacheDescriptionsOfItems(); @@ -929,7 +903,7 @@ public class FeedManager { } /** Updates Information of an existing Feeditem. Uses external adapter. */ - public void setFeedItem(FeedItem item, PodDBAdapter adapter) { + private void setFeedItem(FeedItem item, PodDBAdapter adapter) { if (adapter != null) { adapter.setSingleFeedItem(item); } else { @@ -938,7 +912,7 @@ public class FeedManager { } /** Updates Information of an existing Feedimage. Uses external adapter. */ - public void setFeedImage(FeedImage image, PodDBAdapter adapter) { + private void setFeedImage(FeedImage image, PodDBAdapter adapter) { if (adapter != null) { adapter.setImage(image); } else { @@ -950,7 +924,7 @@ public class FeedManager { * Updates Information of an existing Feedmedia object. Uses external * adapter. */ - public void setFeedImage(FeedMedia media, PodDBAdapter adapter) { + private void setFeedImage(FeedMedia media, PodDBAdapter adapter) { if (adapter != null) { adapter.setMedia(media); } else { @@ -1126,6 +1100,7 @@ public class FeedManager { return null; } + /** Get a download status object from the download log by its FeedFile. */ public DownloadStatus getDownloadStatus(FeedFile feedFile) { for (DownloadStatus status : downloadLog) { if (status.getFeedFile() == feedFile) { @@ -1137,10 +1112,6 @@ public class FeedManager { /** Reads the database */ public void loadDBData(Context context) { - updateArrays(context); - } - - public void updateArrays(Context context) { feeds.clear(); PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); @@ -1445,6 +1416,17 @@ public class FeedManager { }); } + /** + * Searches the descriptions of FeedItems of a specific feed for a given + * string. + * + * @param feed + * The feed whose items should be searched. + * @param query + * The search string + * @param callback + * A callback which will be used to return the search result + * */ public void searchFeedItemDescription(final Context context, final Feed feed, final String query, FeedManager.QueryTaskCallback callback) { @@ -1460,6 +1442,17 @@ public class FeedManager { }); } + /** + * Searches the 'contentEncoded' field of FeedItems of a specific feed for a + * given string. + * + * @param feed + * The feed whose items should be searched. + * @param query + * The search string + * @param callback + * A callback which will be used to return the search result + * */ public void searchFeedItemContentEncoded(final Context context, final Feed feed, final String query, FeedManager.QueryTaskCallback callback) { @@ -1475,24 +1468,116 @@ public class FeedManager { }); } - public List<Feed> getFeeds() { + /** Returns the number of feeds that are currently in the feeds list. */ + public int getFeedsSize() { + return feeds.size(); + } + + /** Returns the feed at the specified index of the feeds list. */ + public Feed getFeedAtIndex(int index) { + return feeds.get(index); + } + + /** Returns an array that contains all feeds of the feed manager. */ + public Feed[] getFeedsArray() { + return feeds.toArray(new Feed[feeds.size()]); + } + + List<Feed> getFeeds() { return feeds; } - public List<FeedItem> getUnreadItems() { - return unreadItems; + /** + * Returns the number of items that are currently in the queue. + * + * @param enableEpisodeFilter + * true if items without episodes should be ignored by this + * method if the episode filter was enabled by the user. + * */ + public int getQueueSize(boolean enableEpisodeFilter) { + if (UserPreferences.isDisplayOnlyEpisodes() && enableEpisodeFilter) { + return EpisodeFilter.countItemsWithEpisodes(queue); + } else { + return queue.size(); + } + } + + /** + * Returns the FeedItem at the specified index of the queue. + * + * @param enableEpisodeFilter + * true if items without episodes should be ignored by this + * method if the episode filter was enabled by the user. + * + * @throws IndexOutOfBoundsException + * if index is out of range + * */ + public FeedItem getQueueItemAtIndex(int index, boolean enableEpisodeFilter) { + if (UserPreferences.isDisplayOnlyEpisodes() && enableEpisodeFilter) { + return EpisodeFilter.accessEpisodeByIndex(queue, index); + } else { + return queue.get(index); + } + } + + /** + * Returns the number of unread items. + * + * @param enableEpisodeFilter + * true if items without episodes should be ignored by this + * method if the episode filter was enabled by the user. + * */ + public int getUnreadItemsSize(boolean enableEpisodeFilter) { + if (UserPreferences.isDisplayOnlyEpisodes() && enableEpisodeFilter) { + return EpisodeFilter.countItemsWithEpisodes(unreadItems); + } else { + return unreadItems.size(); + } + } + + /** + * Returns the FeedItem at the specified index of the unread items list. + * + * @param enableEpisodeFilter + * true if items without episodes should be ignored by this + * method if the episode filter was enabled by the user. + * + * @throws IndexOutOfBoundsException + * if index is out of range + * */ + public FeedItem getUnreadItemAtIndex(int index, boolean enableEpisodeFilter) { + if (UserPreferences.isDisplayOnlyEpisodes() && enableEpisodeFilter) { + return EpisodeFilter.accessEpisodeByIndex(unreadItems, index); + } else { + return unreadItems.get(index); + } + } + + /** + * Returns the number of items in the playback history. + * */ + public int getPlaybackHistorySize() { + return playbackHistory.size(); } - public ArrayList<DownloadStatus> getDownloadLog() { - return downloadLog; + /** + * Returns the FeedItem at the specified index of the playback history. + * + * @throws IndexOutOfBoundsException + * if index is out of range + * */ + public FeedItem getPlaybackHistoryItemIndex(int index) { + return playbackHistory.get(index); } - public List<FeedItem> getQueue() { - return queue; + /** Returns the number of items in the download log */ + public int getDownloadLogSize() { + return downloadLog.size(); } - public List<FeedItem> getPlaybackHistory() { - return playbackHistory; + /** Returns the download status at the specified index of the download log. */ + public DownloadStatus getDownloadStatusFromLogAtIndex(int index) { + return downloadLog.get(index); } /** Is called by a FeedManagerTask after completion. */ @@ -1600,4 +1685,4 @@ public class FeedManager { } } -} +}
\ No newline at end of file diff --git a/src/de/danoeh/antennapod/feed/FeedMedia.java b/src/de/danoeh/antennapod/feed/FeedMedia.java index a96649a62..de87c63a1 100644 --- a/src/de/danoeh/antennapod/feed/FeedMedia.java +++ b/src/de/danoeh/antennapod/feed/FeedMedia.java @@ -1,10 +1,23 @@ package de.danoeh.antennapod.feed; import java.util.Date; +import java.util.List; -public class FeedMedia extends FeedFile { +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.os.Parcel; +import android.os.Parcelable; +import de.danoeh.antennapod.PodcastApp; +import de.danoeh.antennapod.util.ChapterUtils; +import de.danoeh.antennapod.util.playback.Playable; + +public class FeedMedia extends FeedFile implements Playable { public static final int FEEDFILETYPE_FEEDMEDIA = 2; + public static final int PLAYABLE_TYPE_FEEDMEDIA = 1; + + public static final String PREF_MEDIA_ID = "FeedMedia.PrefMediaId"; + public static final String PREF_FEED_ID = "FeedMedia.PrefFeedId"; private int duration; private int position; // Current position in file @@ -146,7 +159,7 @@ public class FeedMedia extends FeedFile { public boolean isInProgress() { return (this.position > 0); } - + public FeedImage getImage() { if (item != null && item.getFeed() != null) { return item.getFeed().getImage(); @@ -154,4 +167,160 @@ public class FeedMedia extends FeedFile { return null; } + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(item.getFeed().getId()); + dest.writeLong(item.getId()); + } + + @Override + public void writeToPreferences(Editor prefEditor) { + prefEditor.putLong(PREF_FEED_ID, item.getFeed().getId()); + prefEditor.putLong(PREF_MEDIA_ID, id); + } + + @Override + public void loadMetadata() throws PlayableException { + if (getChapters() == null) { + ChapterUtils.loadChaptersFromStreamUrl(this); + } + } + + @Override + public String getEpisodeTitle() { + if (getItem().getTitle() != null) { + return getItem().getTitle(); + } else { + return getItem().getIdentifyingValue(); + } + } + + @Override + public List<Chapter> getChapters() { + return getItem().getChapters(); + } + + @Override + public String getWebsiteLink() { + return getItem().getLink(); + } + + @Override + public String getFeedTitle() { + return getItem().getFeed().getTitle(); + } + + @Override + public String getImageFileUrl() { + if (getItem().getFeed().getImage() != null) { + return getItem().getFeed().getImage().getFile_url(); + } else { + return null; + } + } + + @Override + public Object getIdentifier() { + return id; + } + + @Override + public String getLocalMediaUrl() { + return file_url; + } + + @Override + public String getStreamUrl() { + return download_url; + } + + @Override + public boolean localFileAvailable() { + return isDownloaded() && file_url != null; + } + + @Override + public boolean streamAvailable() { + return download_url != null; + } + + @Override + public void saveCurrentPosition(SharedPreferences pref, int newPosition) { + position = newPosition; + FeedManager.getInstance().setFeedMedia(PodcastApp.getInstance(), this); + } + + @Override + public void onPlaybackStart() { + if (getItem().isRead() == false) { + FeedManager.getInstance().markItemRead(PodcastApp.getInstance(), + getItem(), true, false); + } + } + + @Override + public void onPlaybackCompleted() { + + } + + @Override + public int getPlayableType() { + return PLAYABLE_TYPE_FEEDMEDIA; + } + + @Override + public void setChapters(List<Chapter> chapters) { + getItem().setChapters(chapters); + } + + @Override + public String getPaymentLink() { + return getItem().getPaymentLink(); + } + + @Override + public void loadShownotes(final ShownoteLoaderCallback callback) { + String contentEncoded = item.getContentEncoded(); + if (item.getDescription() == null || contentEncoded == null) { + FeedManager.getInstance().loadExtraInformationOfItem( + PodcastApp.getInstance(), item, + new FeedManager.TaskCallback<String[]>() { + @Override + public void onCompletion(String[] result) { + if (result[1] != null) { + callback.onShownotesLoaded(result[1]); + } else { + callback.onShownotesLoaded(result[0]); + + } + + } + }); + } else { + callback.onShownotesLoaded(contentEncoded); + } + } + + public static final Parcelable.Creator<FeedMedia> CREATOR = new Parcelable.Creator<FeedMedia>() { + public FeedMedia createFromParcel(Parcel in) { + long feedId = in.readLong(); + long itemId = in.readLong(); + FeedItem item = FeedManager.getInstance().getFeedItem(itemId, + feedId); + if (item != null) { + return item.getMedia(); + } else { + return null; + } + } + + public FeedMedia[] newArray(int size) { + return new FeedMedia[size]; + } + }; } diff --git a/src/de/danoeh/antennapod/fragment/CoverFragment.java b/src/de/danoeh/antennapod/fragment/CoverFragment.java index f1fde24d8..c477ea2b8 100644 --- a/src/de/danoeh/antennapod/fragment/CoverFragment.java +++ b/src/de/danoeh/antennapod/fragment/CoverFragment.java @@ -12,31 +12,26 @@ import com.actionbarsherlock.app.SherlockFragment; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.AudioplayerActivity.AudioplayerContentFragment; -import de.danoeh.antennapod.asynctask.FeedImageLoader; -import de.danoeh.antennapod.feed.Feed; -import de.danoeh.antennapod.feed.FeedItem; -import de.danoeh.antennapod.feed.FeedManager; -import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.asynctask.ImageLoader; +import de.danoeh.antennapod.util.playback.Playable; /** Displays the cover and the title of a FeedItem. */ public class CoverFragment extends SherlockFragment implements AudioplayerContentFragment { private static final String TAG = "CoverFragment"; - private static final String ARG_FEED_ID = "arg.feedId"; - private static final String ARG_FEEDITEM_ID = "arg.feedItem"; + private static final String ARG_PLAYABLE = "arg.playable"; - private FeedMedia media; + private Playable media; private ImageView imgvCover; private boolean viewCreated = false; - public static CoverFragment newInstance(FeedItem item) { + public static CoverFragment newInstance(Playable item) { CoverFragment f = new CoverFragment(); if (item != null) { Bundle args = new Bundle(); - args.putLong(ARG_FEED_ID, item.getFeed().getId()); - args.putLong(ARG_FEEDITEM_ID, item.getId()); + args.putParcelable(ARG_PLAYABLE, item); f.setArguments(args); } return f; @@ -46,21 +41,11 @@ public class CoverFragment extends SherlockFragment implements public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); - FeedManager manager = FeedManager.getInstance(); - FeedItem item = null; Bundle args = getArguments(); if (args != null) { - long feedId = args.getLong(ARG_FEED_ID, -1); - long itemId = args.getLong(ARG_FEEDITEM_ID, -1); - if (feedId != -1 && itemId != -1) { - Feed feed = manager.getFeed(feedId); - item = manager.getFeedItem(itemId, feed); - if (item != null) { - media = item.getMedia(); - } - } else { - Log.e(TAG, TAG + " was called with invalid arguments"); - } + media = args.getParcelable(ARG_PLAYABLE); + } else { + Log.e(TAG, TAG + " was called with invalid arguments"); } } @@ -79,8 +64,8 @@ public class CoverFragment extends SherlockFragment implements @Override public void run() { - FeedImageLoader.getInstance().loadCoverBitmap( - media.getItem().getFeed().getImage(), imgvCover); + ImageLoader.getInstance().loadCoverBitmap( + media.getImageFileUrl(), imgvCover); } }); } else { @@ -103,7 +88,7 @@ public class CoverFragment extends SherlockFragment implements } @Override - public void onDataSetChanged(FeedMedia media) { + public void onDataSetChanged(Playable media) { this.media = media; if (viewCreated) { loadMediaInfo(); diff --git a/src/de/danoeh/antennapod/fragment/EpisodesFragment.java b/src/de/danoeh/antennapod/fragment/EpisodesFragment.java index 843cf5af0..ac1fc402e 100644 --- a/src/de/danoeh/antennapod/fragment/EpisodesFragment.java +++ b/src/de/danoeh/antennapod/fragment/EpisodesFragment.java @@ -1,9 +1,6 @@ package de.danoeh.antennapod.fragment; -import android.content.BroadcastReceiver; -import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.os.Bundle; import android.util.Log; import android.view.ContextMenu; @@ -25,16 +22,21 @@ import de.danoeh.antennapod.activity.OrganizeQueueActivity; import de.danoeh.antennapod.adapter.ActionButtonCallback; import de.danoeh.antennapod.adapter.ExternalEpisodesListAdapter; import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator; +import de.danoeh.antennapod.feed.EventDistributor; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedManager; -import de.danoeh.antennapod.service.download.DownloadService; import de.danoeh.antennapod.storage.DownloadRequestException; -import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler; public class EpisodesFragment extends SherlockFragment { private static final String TAG = "EpisodesFragment"; + private static final int EVENTS = EventDistributor.QUEUE_UPDATE + | EventDistributor.UNREAD_ITEMS_UPDATE + | EventDistributor.FEED_LIST_UPDATE + | EventDistributor.DOWNLOAD_HANDLED + | EventDistributor.DOWNLOAD_QUEUED; + private ExpandableListView listView; private ExternalEpisodesListAdapter adapter; @@ -45,24 +47,14 @@ public class EpisodesFragment extends SherlockFragment { @Override public void onDestroy() { super.onDestroy(); - try { - getActivity().unregisterReceiver(contentUpdate); - } catch (IllegalArgumentException e) { - - } + EventDistributor.getInstance().unregister(contentUpdate); } @Override public void onResume() { super.onResume(); - IntentFilter filter = new IntentFilter(); - filter.addAction(DownloadRequester.ACTION_DOWNLOAD_QUEUED); - filter.addAction(DownloadService.ACTION_DOWNLOAD_HANDLED); - filter.addAction(FeedManager.ACTION_QUEUE_UPDATE); - filter.addAction(FeedManager.ACTION_UNREAD_ITEMS_UPDATE); - filter.addAction(FeedManager.ACTION_FEED_LIST_UPDATE); - - getActivity().registerReceiver(contentUpdate, filter); + + EventDistributor.getInstance().register(contentUpdate); if (adapter != null) { adapter.notifyDataSetChanged(); } @@ -99,10 +91,8 @@ public class EpisodesFragment extends SherlockFragment { @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - FeedManager manager = FeedManager.getInstance(); adapter = new ExternalEpisodesListAdapter(getActivity(), - manager.getUnreadItems(), manager.getQueue(), adapterCallback, - groupActionCallback); + adapterCallback, groupActionCallback); listView.setAdapter(adapter); listView.expandGroup(ExternalEpisodesListAdapter.GROUP_POS_QUEUE); listView.expandGroup(ExternalEpisodesListAdapter.GROUP_POS_UNREAD); @@ -130,12 +120,15 @@ public class EpisodesFragment extends SherlockFragment { registerForContextMenu(listView); } - private BroadcastReceiver contentUpdate = new BroadcastReceiver() { + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + @Override - public void onReceive(Context context, Intent intent) { - if (AppConfig.DEBUG) - Log.d(TAG, "Received contentUpdate Intent."); - adapter.notifyDataSetChanged(); + public void update(EventDistributor eventDistributor, Integer arg) { + if ((EVENTS & arg) != 0) { + if (AppConfig.DEBUG) + Log.d(TAG, "Received contentUpdate Intent."); + adapter.notifyDataSetChanged(); + } } }; diff --git a/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java b/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java index d79b7de10..94aa2b0fc 100644 --- a/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java +++ b/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java @@ -14,11 +14,11 @@ import com.actionbarsherlock.app.SherlockFragment; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.asynctask.FeedImageLoader; -import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.asynctask.ImageLoader; import de.danoeh.antennapod.service.PlaybackService; import de.danoeh.antennapod.util.Converter; -import de.danoeh.antennapod.util.PlaybackController; +import de.danoeh.antennapod.util.playback.Playable; +import de.danoeh.antennapod.util.playback.PlaybackController; /** * Fragment which is supposed to be displayed outside of the MediaplayerActivity @@ -193,11 +193,11 @@ public class ExternalPlayerFragment extends SherlockFragment { if (AppConfig.DEBUG) Log.d(TAG, "Loading media info"); if (controller.serviceAvailable()) { - FeedMedia media = controller.getMedia(); + Playable media = controller.getMedia(); if (media != null) { - txtvTitle.setText(media.getItem().getTitle()); - FeedImageLoader.getInstance().loadThumbnailBitmap( - media.getItem().getFeed().getImage(), + txtvTitle.setText(media.getEpisodeTitle()); + ImageLoader.getInstance().loadThumbnailBitmap( + media.getImageFileUrl(), imgvCover, (int) getActivity().getResources().getDimension( R.dimen.external_player_height)); diff --git a/src/de/danoeh/antennapod/fragment/FeedlistFragment.java b/src/de/danoeh/antennapod/fragment/FeedlistFragment.java index caf6c6a7f..c3034c2af 100644 --- a/src/de/danoeh/antennapod/fragment/FeedlistFragment.java +++ b/src/de/danoeh/antennapod/fragment/FeedlistFragment.java @@ -2,11 +2,8 @@ package de.danoeh.antennapod.fragment; import android.annotation.SuppressLint; import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.IntentFilter; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; @@ -18,7 +15,6 @@ import android.widget.ListView; import android.widget.TextView; import com.actionbarsherlock.app.SherlockFragment; -import com.actionbarsherlock.app.SherlockFragmentActivity; import com.actionbarsherlock.view.ActionMode; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; @@ -30,22 +26,26 @@ import de.danoeh.antennapod.adapter.FeedlistAdapter; import de.danoeh.antennapod.asynctask.FeedRemover; import de.danoeh.antennapod.dialog.ConfirmationDialog; import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator; +import de.danoeh.antennapod.feed.EventDistributor; import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedManager; -import de.danoeh.antennapod.service.download.DownloadService; import de.danoeh.antennapod.storage.DownloadRequestException; -import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler; public class FeedlistFragment extends SherlockFragment implements ActionMode.Callback, AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener { private static final String TAG = "FeedlistFragment"; + + private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED + | EventDistributor.DOWNLOAD_QUEUED + | EventDistributor.FEED_LIST_UPDATE + | EventDistributor.UNREAD_ITEMS_UPDATE; + public static final String EXTRA_SELECTED_FEED = "extra.de.danoeh.antennapod.activity.selected_feed"; private FeedManager manager; private FeedlistAdapter fla; - private SherlockFragmentActivity pActivity; private Feed selectedFeed; private ActionMode mActionMode; @@ -57,13 +57,11 @@ public class FeedlistFragment extends SherlockFragment implements @Override public void onAttach(Activity activity) { super.onAttach(activity); - pActivity = (SherlockFragmentActivity) activity; } @Override public void onDetach() { super.onDetach(); - pActivity = null; } @Override @@ -72,7 +70,7 @@ public class FeedlistFragment extends SherlockFragment implements if (AppConfig.DEBUG) Log.d(TAG, "Creating"); manager = FeedManager.getInstance(); - fla = new FeedlistAdapter(pActivity, 0, manager.getFeeds()); + fla = new FeedlistAdapter(getActivity()); } @@ -113,36 +111,28 @@ public class FeedlistFragment extends SherlockFragment implements super.onResume(); if (AppConfig.DEBUG) Log.d(TAG, "Resuming"); - IntentFilter filter = new IntentFilter(); - filter.addAction(DownloadRequester.ACTION_DOWNLOAD_QUEUED); - filter.addAction(FeedManager.ACTION_UNREAD_ITEMS_UPDATE); - filter.addAction(FeedManager.ACTION_FEED_LIST_UPDATE); - filter.addAction(DownloadService.ACTION_DOWNLOAD_HANDLED); - pActivity.registerReceiver(contentUpdate, filter); + EventDistributor.getInstance().register(contentUpdate); fla.notifyDataSetChanged(); } @Override public void onPause() { super.onPause(); - pActivity.unregisterReceiver(contentUpdate); + EventDistributor.getInstance().unregister(contentUpdate); if (mActionMode != null) { mActionMode.finish(); } } - private BroadcastReceiver contentUpdate = new BroadcastReceiver() { + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + @Override - public void onReceive(Context context, final Intent intent) { - if (AppConfig.DEBUG) - Log.d(TAG, "Received contentUpdate Intent."); - getActivity().runOnUiThread(new Runnable() { - - @Override - public void run() { - fla.notifyDataSetChanged(); - } - }); + public void update(EventDistributor eventDistributor, Integer arg) { + if ((EVENTS & arg) != 0) { + if (AppConfig.DEBUG) + Log.d(TAG, "Received contentUpdate Intent."); + fla.notifyDataSetChanged(); + } } }; @@ -211,10 +201,10 @@ public class FeedlistFragment extends SherlockFragment implements public void onItemClick(AdapterView<?> arg0, View arg1, int position, long id) { Feed selection = fla.getItem(position); - Intent showFeed = new Intent(pActivity, FeedItemlistActivity.class); + Intent showFeed = new Intent(getActivity(), FeedItemlistActivity.class); showFeed.putExtra(EXTRA_SELECTED_FEED, selection.getId()); - pActivity.startActivity(showFeed); + getActivity().startActivity(showFeed); } @Override diff --git a/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java b/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java index 8f5350c8e..02b74a4e5 100644 --- a/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java +++ b/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java @@ -23,8 +23,6 @@ import android.view.View; import android.view.ViewGroup; import android.webkit.WebSettings.LayoutAlgorithm; import android.webkit.WebView; -import android.widget.FrameLayout; -import android.widget.LinearLayout; import android.widget.Toast; import com.actionbarsherlock.app.SherlockFragment; @@ -32,34 +30,46 @@ import com.actionbarsherlock.app.SherlockFragment; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedManager; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.util.ShareUtils; +import de.danoeh.antennapod.util.playback.Playable; -/** Displays the description of a FeedItem in a Webview. */ +/** Displays the description of a Playable object in a Webview. */ public class ItemDescriptionFragment extends SherlockFragment { private static final String TAG = "ItemDescriptionFragment"; + private static final String ARG_PLAYABLE = "arg.playable"; + private static final String ARG_FEED_ID = "arg.feedId"; - private static final String ARG_FEEDITEM_ID = "arg.feedItemId"; + private static final String ARG_FEED_ITEM_ID = "arg.feeditemId"; private WebView webvDescription; + private Playable media; + private FeedItem item; private AsyncTask<Void, Void, Void> webViewLoader; - private String descriptionRef; - private String contentEncodedRef; + private String shownotes; /** URL that was selected via long-press. */ private String selectedURL; + public static ItemDescriptionFragment newInstance(Playable media) { + ItemDescriptionFragment f = new ItemDescriptionFragment(); + Bundle args = new Bundle(); + args.putParcelable(ARG_PLAYABLE, media); + f.setArguments(args); + return f; + } + public static ItemDescriptionFragment newInstance(FeedItem item) { ItemDescriptionFragment f = new ItemDescriptionFragment(); Bundle args = new Bundle(); args.putLong(ARG_FEED_ID, item.getFeed().getId()); - args.putLong(ARG_FEEDITEM_ID, item.getId()); + args.putLong(ARG_FEED_ITEM_ID, item.getId()); f.setArguments(args); return f; } @@ -72,12 +82,13 @@ public class ItemDescriptionFragment extends SherlockFragment { Log.d(TAG, "Creating view"); webvDescription = new WebView(getActivity()); - if (PodcastApp.getThemeResourceId() == R.style.Theme_AntennaPod_Dark) { + if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) { if (Build.VERSION.SDK_INT >= 11 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null); } - webvDescription.setBackgroundColor(getResources().getColor(R.color.black)); + webvDescription.setBackgroundColor(getResources().getColor( + R.color.black)); } webvDescription.getSettings().setUseWideViewPort(false); webvDescription.getSettings().setLayoutAlgorithm( @@ -126,50 +137,59 @@ public class ItemDescriptionFragment extends SherlockFragment { super.onCreate(savedInstanceState); if (AppConfig.DEBUG) Log.d(TAG, "Creating fragment"); - FeedManager manager = FeedManager.getInstance(); Bundle args = getArguments(); - long feedId = args.getLong(ARG_FEED_ID, -1); - long itemId = args.getLong(ARG_FEEDITEM_ID, -1); - if (feedId != -1 && itemId != -1) { - Feed feed = manager.getFeed(feedId); - item = manager.getFeedItem(itemId, feed); - - } else { - Log.e(TAG, TAG + " was called with invalid arguments"); + if (args.containsKey(ARG_PLAYABLE)) { + media = args.getParcelable(ARG_PLAYABLE); + } else if (args.containsKey(ARG_FEED_ID) + && args.containsKey(ARG_FEED_ITEM_ID)) { + long feedId = args.getLong(ARG_FEED_ID); + long itemId = args.getLong(ARG_FEED_ITEM_ID); + FeedItem f = FeedManager.getInstance().getFeedItem(itemId, feedId); + if (f != null) { + item = f; + } } } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - if (item != null) { + if (media != null) { + media.loadShownotes(new Playable.ShownoteLoaderCallback() { + + @Override + public void onShownotesLoaded(String shownotes) { + ItemDescriptionFragment.this.shownotes = shownotes; + if (ItemDescriptionFragment.this.shownotes != null) { + startLoader(); + } + } + }); + } else if (item != null) { if (item.getDescription() == null || item.getContentEncoded() == null) { - Log.i(TAG, "Loading data"); FeedManager.getInstance().loadExtraInformationOfItem( - getActivity(), item, + PodcastApp.getInstance(), item, new FeedManager.TaskCallback<String[]>() { @Override public void onCompletion(String[] result) { - if (result == null || result.length != 2) { - Log.e(TAG, "No description found"); + if (result[1] != null) { + shownotes = result[1]; } else { - descriptionRef = result[0]; - contentEncodedRef = result[1]; + shownotes = result[0]; + } + if (shownotes != null) { + startLoader(); } - startLoader(); } }); } else { - contentEncodedRef = item.getContentEncoded(); - descriptionRef = item.getDescription(); - if (AppConfig.DEBUG) - Log.d(TAG, "Using cached data"); + shownotes = item.getContentEncoded(); startLoader(); } } else { - Log.e(TAG, "Error in onViewCreated: Item was null"); + Log.e(TAG, "Error in onViewCreated: Item and media were null"); } } @@ -197,8 +217,11 @@ public class ItemDescriptionFragment extends SherlockFragment { * */ private String applyWebviewStyle(String textColor, String data) { final String WEBVIEW_STYLE = "<html><head><style type=\"text/css\"> * { color: %s; font-family: Helvetica; line-height: 1.5em; font-size: 11pt; } a { font-style: normal; text-decoration: none; font-weight: normal; color: #00A8DF; } img { display: block; margin: 10 auto; max-width: %s; height: auto; } body { margin: %dpx %dpx %dpx %dpx; }</style></head><body>%s</body></html>"; - final int pageMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics()); - return String.format(WEBVIEW_STYLE, textColor, "100%", pageMargin, pageMargin, pageMargin, pageMargin, data); + final int pageMargin = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 8, getResources() + .getDisplayMetrics()); + return String.format(WEBVIEW_STYLE, textColor, "100%", pageMargin, + pageMargin, pageMargin, pageMargin, data); } private View.OnLongClickListener webViewLongClickListener = new View.OnLongClickListener() { @@ -247,7 +270,8 @@ public class ItemDescriptionFragment extends SherlockFragment { .getSystemService(Context.CLIPBOARD_SERVICE); cm.setText(selectedURL); } - Toast t = Toast.makeText(getActivity(), R.string.copied_url_msg, Toast.LENGTH_SHORT); + Toast t = Toast.makeText(getActivity(), + R.string.copied_url_msg, Toast.LENGTH_SHORT); t.show(); break; default: @@ -319,11 +343,7 @@ public class ItemDescriptionFragment extends SherlockFragment { if (AppConfig.DEBUG) Log.d(TAG, "Loading Webview"); data = ""; - if (contentEncodedRef == null && descriptionRef != null) { - data = descriptionRef; - } else { - data = StringEscapeUtils.unescapeHtml4(contentEncodedRef); - } + data = StringEscapeUtils.unescapeHtml4(shownotes); Activity activity = getActivity(); if (activity != null) { TypedArray res = getActivity() diff --git a/src/de/danoeh/antennapod/fragment/ItemlistFragment.java b/src/de/danoeh/antennapod/fragment/ItemlistFragment.java index cdccdc338..9e9389838 100644 --- a/src/de/danoeh/antennapod/fragment/ItemlistFragment.java +++ b/src/de/danoeh/antennapod/fragment/ItemlistFragment.java @@ -1,11 +1,7 @@ package de.danoeh.antennapod.fragment; -import java.util.List; - -import android.content.BroadcastReceiver; -import android.content.Context; +import android.annotation.SuppressLint; import android.content.Intent; -import android.content.IntentFilter; import android.os.Bundle; import android.util.Log; import android.view.ContextMenu; @@ -25,6 +21,7 @@ import de.danoeh.antennapod.adapter.AbstractFeedItemlistAdapter; import de.danoeh.antennapod.adapter.ActionButtonCallback; import de.danoeh.antennapod.adapter.FeedItemlistAdapter; import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator; +import de.danoeh.antennapod.feed.EventDistributor; import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedManager; @@ -34,22 +31,24 @@ import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler; /** Displays a list of FeedItems. */ +@SuppressLint("ValidFragment") public class ItemlistFragment extends SherlockListFragment { - private static final String TAG = "ItemlistFragment"; + + private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED + | EventDistributor.DOWNLOAD_QUEUED + | EventDistributor.QUEUE_UPDATE + | EventDistributor.UNREAD_ITEMS_UPDATE; + 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 AbstractFeedItemlistAdapter fila; - protected FeedManager manager; - protected DownloadRequester requester; + protected FeedManager manager = FeedManager.getInstance(); + protected DownloadRequester requester = DownloadRequester.getInstance(); - /** The feed which the activity displays */ - protected List<FeedItem> items; - /** - * This is only not null if the fragment displays the items of a specific - * feed - */ - protected Feed feed; + private AbstractFeedItemlistAdapter.ItemAccess itemAccess; + + private Feed feed; protected FeedItem selectedItem = null; protected boolean contextMenuClosed = true; @@ -57,12 +56,11 @@ public class ItemlistFragment extends SherlockListFragment { /** Argument for FeeditemlistAdapter */ protected boolean showFeedtitle; - public ItemlistFragment(List<FeedItem> items, boolean showFeedtitle) { + public ItemlistFragment(AbstractFeedItemlistAdapter.ItemAccess itemAccess, + boolean showFeedtitle) { super(); - this.items = items; + this.itemAccess = itemAccess; this.showFeedtitle = showFeedtitle; - manager = FeedManager.getInstance(); - requester = DownloadRequester.getInstance(); } public ItemlistFragment() { @@ -94,15 +92,27 @@ public class ItemlistFragment extends SherlockListFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (items == null) { + if (itemAccess == null) { long feedId = getArguments().getLong(ARGUMENT_FEED_ID); - feed = FeedManager.getInstance().getFeed(feedId); - items = feed.getItems(); + final Feed feed = FeedManager.getInstance().getFeed(feedId); + this.feed = feed; + itemAccess = new AbstractFeedItemlistAdapter.ItemAccess() { + + @Override + public FeedItem getItem(int position) { + return feed.getItemAtIndex(true, position); + } + + @Override + public int getCount() { + return feed.getNumOfItems(true); + } + }; } } protected AbstractFeedItemlistAdapter createListAdapter() { - return new FeedItemlistAdapter(getActivity(), 0, items, + return new FeedItemlistAdapter(getActivity(), itemAccess, adapterCallback, showFeedtitle); } @@ -114,12 +124,7 @@ public class ItemlistFragment extends SherlockListFragment { @Override public void onDestroy() { super.onDestroy(); - try { - getActivity().unregisterReceiver(contentUpdate); - } catch (IllegalArgumentException e) { - Log.w(TAG, - "IllegalArgumentException when trying to unregister contentUpdate receiver."); - } + EventDistributor.getInstance().unregister(contentUpdate); } @Override @@ -133,13 +138,7 @@ public class ItemlistFragment extends SherlockListFragment { } }); updateProgressBarVisibility(); - IntentFilter filter = new IntentFilter(); - filter.addAction(DownloadRequester.ACTION_DOWNLOAD_QUEUED); - filter.addAction(DownloadService.ACTION_DOWNLOAD_HANDLED); - filter.addAction(FeedManager.ACTION_QUEUE_UPDATE); - filter.addAction(FeedManager.ACTION_UNREAD_ITEMS_UPDATE); - - getActivity().registerReceiver(contentUpdate, filter); + EventDistributor.getInstance().register(contentUpdate); } @Override @@ -153,17 +152,19 @@ public class ItemlistFragment extends SherlockListFragment { startActivity(showItem); } - private BroadcastReceiver contentUpdate = new BroadcastReceiver() { + private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { + @Override - public void onReceive(Context context, Intent intent) { - if (AppConfig.DEBUG) - Log.d(TAG, "Received contentUpdate Intent."); - if (intent.getAction().equals( - DownloadRequester.ACTION_DOWNLOAD_QUEUED)) { - updateProgressBarVisibility(); - } else { - fila.notifyDataSetChanged(); - updateProgressBarVisibility(); + public void update(EventDistributor eventDistributor, Integer arg) { + if ((EVENTS & arg) != 0) { + if (AppConfig.DEBUG) + Log.d(TAG, "Received contentUpdate Intent."); + if ((EventDistributor.DOWNLOAD_QUEUED & arg) != 0) { + updateProgressBarVisibility(); + } else { + fila.notifyDataSetChanged(); + updateProgressBarVisibility(); + } } } }; @@ -178,7 +179,7 @@ public class ItemlistFragment extends SherlockListFragment { getSherlockActivity() .setSupportProgressBarIndeterminateVisibility(false); } - getSherlockActivity().invalidateOptionsMenu(); + getSherlockActivity().supportInvalidateOptionsMenu(); } } diff --git a/src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java b/src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java index a2826c977..5c4750acd 100644 --- a/src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java +++ b/src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java @@ -1,47 +1,55 @@ package de.danoeh.antennapod.fragment; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.os.Bundle; import android.util.Log; import de.danoeh.antennapod.AppConfig; +import de.danoeh.antennapod.adapter.AbstractFeedItemlistAdapter; +import de.danoeh.antennapod.feed.EventDistributor; +import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedManager; public class PlaybackHistoryFragment extends ItemlistFragment { private static final String TAG = "PlaybackHistoryFragment"; public PlaybackHistoryFragment() { - super(FeedManager.getInstance().getPlaybackHistory(), true); + super(new AbstractFeedItemlistAdapter.ItemAccess() { + + @Override + public FeedItem getItem(int position) { + return FeedManager.getInstance().getPlaybackHistoryItemIndex( + position); + } + + @Override + public int getCount() { + return FeedManager.getInstance().getPlaybackHistorySize(); + } + }, true); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - getActivity().registerReceiver(historyUpdate, - new IntentFilter(FeedManager.ACTION_PLAYBACK_HISTORY_UPDATE)); + EventDistributor.getInstance().register(historyUpdate); } @Override public void onDestroy() { super.onDestroy(); - try { - getActivity().unregisterReceiver(historyUpdate); - } catch (IllegalArgumentException e) { - // ignore - } + EventDistributor.getInstance().unregister(historyUpdate); } - private BroadcastReceiver historyUpdate = new BroadcastReceiver() { - + private EventDistributor.EventListener historyUpdate = new EventDistributor.EventListener() { + @Override - public void onReceive(Context context, Intent intent) { - if (AppConfig.DEBUG) - Log.d(TAG, "Received content update"); - fila.notifyDataSetChanged(); + public void update(EventDistributor eventDistributor, Integer arg) { + if ((EventDistributor.PLAYBACK_HISTORY_UPDATE & arg) != 0) { + if (AppConfig.DEBUG) + Log.d(TAG, "Received content update"); + fila.notifyDataSetChanged(); + } + } - }; } diff --git a/src/de/danoeh/antennapod/preferences/PlaybackPreferences.java b/src/de/danoeh/antennapod/preferences/PlaybackPreferences.java new file mode 100644 index 000000000..b93b2e07c --- /dev/null +++ b/src/de/danoeh/antennapod/preferences/PlaybackPreferences.java @@ -0,0 +1,193 @@ +package de.danoeh.antennapod.preferences; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Log; +import de.danoeh.antennapod.AppConfig; +import de.danoeh.antennapod.feed.FeedManager; +import de.danoeh.antennapod.feed.FeedMedia; + +/** + * Provides access to preferences set by the playback service. A private + * instance of this class must first be instantiated via createInstance() or + * otherwise every public method will throw an Exception when called. + */ +public class PlaybackPreferences implements + SharedPreferences.OnSharedPreferenceChangeListener { + private static final String TAG = "PlaybackPreferences"; + + /** Contains the type of the media that was played last. */ + public static final String PREF_LAST_PLAYED_ID = "de.danoeh.antennapod.preferences.lastPlayedId"; + + /** + * Contains the feed id of the currently playing item if it is a FeedMedia + * object. + */ + public static final String PREF_CURRENTLY_PLAYING_FEED_ID = "de.danoeh.antennapod.preferences.lastPlayedFeedId"; + + /** + * Contains the id of the currently playing FeedMedia object or + * NO_MEDIA_PLAYING if the currently playing media is no FeedMedia object. + */ + public static final String PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID = "de.danoeh.antennapod.preferences.lastPlayedFeedMediaId"; + + /** + * Type of the media object that is currently being played. This preference + * is set to NO_MEDIA_PLAYING after playback has been completed and is set + * as soon as the 'play' button is pressed. + */ + public static final String PREF_CURRENTLY_PLAYING_MEDIA = "de.danoeh.antennapod.preferences.currentlyPlayingMedia"; + + /** True if last played media was streamed. */ + public static final String PREF_LAST_IS_STREAM = "de.danoeh.antennapod.preferences.lastIsStream"; + + /** True if last played media was a video. */ + public static final String PREF_LAST_IS_VIDEO = "de.danoeh.antennapod.preferences.lastIsVideo"; + + /** True if playback of last played media has been completed. */ + public static final String PREF_AUTO_DELETE_MEDIA_PLAYBACK_COMPLETED = "de.danoeh.antennapod.preferences.lastPlaybackCompleted"; + + /** + * ID of the last played media which should be auto-deleted as soon as + * PREF_LAST_PLAYED_ID changes. + */ + public static final String PREF_AUTODELETE_MEDIA_ID = "de.danoeh.antennapod.preferences.autoDeleteMediaId"; + + /** Value of PREF_CURRENTLY_PLAYING_MEDIA if no media is playing. */ + public static final long NO_MEDIA_PLAYING = -1; + + private long lastPlayedId; + private long currentlyPlayingFeedId; + private long currentlyPlayingFeedMediaId; + private long currentlyPlayingMedia; + private boolean lastIsStream; + private boolean lastIsVideo; + private boolean autoDeleteMediaPlaybackCompleted; + private long autoDeleteMediaId; + + private static PlaybackPreferences instance; + private Context context; + + private PlaybackPreferences(Context context) { + this.context = context; + loadPreferences(); + } + + /** + * Sets up the UserPreferences class. + * + * @throws IllegalArgumentException + * if context is null + * */ + public static void createInstance(Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Creating new instance of UserPreferences"); + if (context == null) + throw new IllegalArgumentException("Context must not be null"); + instance = new PlaybackPreferences(context); + + PreferenceManager.getDefaultSharedPreferences(context) + .registerOnSharedPreferenceChangeListener(instance); + } + + private void loadPreferences() { + SharedPreferences sp = PreferenceManager + .getDefaultSharedPreferences(context); + lastPlayedId = sp.getLong(PREF_LAST_PLAYED_ID, -1); + currentlyPlayingFeedId = sp.getLong(PREF_CURRENTLY_PLAYING_FEED_ID, -1); + currentlyPlayingFeedMediaId = sp.getLong( + PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING); + currentlyPlayingMedia = sp.getLong(PREF_CURRENTLY_PLAYING_MEDIA, + NO_MEDIA_PLAYING); + lastIsStream = sp.getBoolean(PREF_LAST_IS_STREAM, true); + lastIsVideo = sp.getBoolean(PREF_LAST_IS_VIDEO, false); + autoDeleteMediaPlaybackCompleted = sp.getBoolean( + PREF_AUTO_DELETE_MEDIA_PLAYBACK_COMPLETED, false); + autoDeleteMediaId = sp.getLong(PREF_AUTODELETE_MEDIA_ID, -1); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sp, String key) { + if (key.equals(PREF_LAST_PLAYED_ID)) { + lastPlayedId = sp.getLong(PREF_LAST_PLAYED_ID, -1); + long mediaId = sp.getLong( + PlaybackPreferences.PREF_AUTODELETE_MEDIA_ID, -1); + if (mediaId != -1) { + FeedManager manager = FeedManager.getInstance(); + FeedMedia media = manager.getFeedMedia(mediaId); + if (media != null) { + manager.autoDeleteIfPossible(context, media); + } + } + } else if (key.equals(PREF_CURRENTLY_PLAYING_FEED_ID)) { + currentlyPlayingFeedId = sp.getLong(PREF_CURRENTLY_PLAYING_FEED_ID, + -1); + + } else if (key.equals(PREF_CURRENTLY_PLAYING_MEDIA)) { + currentlyPlayingMedia = sp + .getLong(PREF_CURRENTLY_PLAYING_MEDIA, -1); + + } else if (key.equals(PREF_LAST_IS_STREAM)) { + lastIsStream = sp.getBoolean(PREF_LAST_IS_STREAM, true); + + } else if (key.equals(PREF_LAST_IS_VIDEO)) { + lastIsVideo = sp.getBoolean(PREF_LAST_IS_VIDEO, false); + + } else if (key.equals(PREF_AUTO_DELETE_MEDIA_PLAYBACK_COMPLETED)) { + autoDeleteMediaPlaybackCompleted = sp.getBoolean( + PREF_AUTODELETE_MEDIA_ID, false); + } else if (key.equals(PREF_AUTODELETE_MEDIA_ID)) { + autoDeleteMediaId = sp.getLong(PREF_AUTODELETE_MEDIA_ID, -1); + } else if (key.equals(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID)) { + currentlyPlayingFeedMediaId = sp.getLong( + PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING); + } + } + + private static void instanceAvailable() { + if (instance == null) { + throw new IllegalStateException( + "UserPreferences was used before being set up"); + } + } + + public static long getLastPlayedId() { + instanceAvailable(); + return instance.lastPlayedId; + } + + public static long getAutoDeleteMediaId() { + return instance.autoDeleteMediaId; + } + + public static long getLastPlayedFeedId() { + instanceAvailable(); + return instance.currentlyPlayingFeedId; + } + + public static long getCurrentlyPlayingMedia() { + instanceAvailable(); + return instance.currentlyPlayingMedia; + } + + public static long getCurrentlyPlayingFeedMediaId() { + return instance.currentlyPlayingFeedMediaId; + } + + public static boolean isLastIsStream() { + instanceAvailable(); + return instance.lastIsStream; + } + + public static boolean isLastIsVideo() { + instanceAvailable(); + return instance.lastIsVideo; + } + + public static boolean isAutoDeleteMediaPlaybackCompleted() { + instanceAvailable(); + return instance.autoDeleteMediaPlaybackCompleted; + } + +} diff --git a/src/de/danoeh/antennapod/preferences/UserPreferences.java b/src/de/danoeh/antennapod/preferences/UserPreferences.java new file mode 100644 index 000000000..f4c0b94b0 --- /dev/null +++ b/src/de/danoeh/antennapod/preferences/UserPreferences.java @@ -0,0 +1,320 @@ +package de.danoeh.antennapod.preferences; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Log; +import de.danoeh.antennapod.AppConfig; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.OpmlImportFromPathActivity; +import de.danoeh.antennapod.receiver.FeedUpdateReceiver; + +/** + * Provides access to preferences set by the user in the settings screen. A + * private instance of this class must first be instantiated via + * createInstance() or otherwise every public method will throw an Exception + * when called. + */ +public class UserPreferences implements + SharedPreferences.OnSharedPreferenceChangeListener { + private static final String TAG = "UserPreferences"; + + public static final String PREF_PAUSE_ON_HEADSET_DISCONNECT = "prefPauseOnHeadsetDisconnect"; + public static final String PREF_FOLLOW_QUEUE = "prefFollowQueue"; + public static final String PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY = "prefDownloadMediaOnWifiOnly"; + public static final String PREF_UPDATE_INTERVAL = "prefAutoUpdateIntervall"; + public static final String PREF_MOBILE_UPDATE = "prefMobileUpdate"; + public static final String PREF_AUTO_QUEUE = "prefAutoQueue"; + public static final String PREF_DISPLAY_ONLY_EPISODES = "prefDisplayOnlyEpisodes"; + public static final String PREF_AUTO_DELETE = "prefAutoDelete"; + public static final String PREF_THEME = "prefTheme"; + public static final String PREF_DATA_FOLDER = "prefDataFolder"; + + private static UserPreferences instance; + private Context context; + + // Preferences + private boolean pauseOnHeadsetDisconnect; + private boolean followQueue; + private boolean downloadMediaOnWifiOnly; + private long updateInterval; + private boolean allowMobileUpdate; + private boolean autoQueue; + private boolean displayOnlyEpisodes; + private boolean autoDelete; + private int theme; + + private UserPreferences(Context context) { + this.context = context; + loadPreferences(); + } + + /** + * Sets up the UserPreferences class. + * + * @throws IllegalArgumentException + * if context is null + * */ + public static void createInstance(Context context) { + if (AppConfig.DEBUG) + Log.d(TAG, "Creating new instance of UserPreferences"); + if (context == null) + throw new IllegalArgumentException("Context must not be null"); + instance = new UserPreferences(context); + + createImportDirectory(); + createNoMediaFile(); + PreferenceManager.getDefaultSharedPreferences(context) + .registerOnSharedPreferenceChangeListener(instance); + } + + private void loadPreferences() { + SharedPreferences sp = PreferenceManager + .getDefaultSharedPreferences(context); + pauseOnHeadsetDisconnect = sp.getBoolean( + PREF_PAUSE_ON_HEADSET_DISCONNECT, true); + followQueue = sp.getBoolean(PREF_FOLLOW_QUEUE, false); + downloadMediaOnWifiOnly = sp.getBoolean( + PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY, true); + updateInterval = readUpdateInterval(sp.getString(PREF_UPDATE_INTERVAL, + "0")); + allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false); + autoQueue = sp.getBoolean(PREF_AUTO_QUEUE, true); + displayOnlyEpisodes = sp.getBoolean(PREF_DISPLAY_ONLY_EPISODES, false); + autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false); + theme = readThemeValue(sp.getString(PREF_THEME, "0")); + } + + private int readThemeValue(String valueFromPrefs) { + switch (Integer.parseInt(valueFromPrefs)) { + case 0: + return R.style.Theme_AntennaPod_Light; + case 1: + return R.style.Theme_AntennaPod_Dark; + default: + return R.style.Theme_AntennaPod_Light; + } + } + + private long readUpdateInterval(String valueFromPrefs) { + int hours = Integer.parseInt(valueFromPrefs); + return TimeUnit.HOURS.toMillis(hours); + } + + private static void instanceAvailable() { + if (instance == null) { + throw new IllegalStateException( + "UserPreferences was used before being set up"); + } + } + + public static boolean isPauseOnHeadsetDisconnect() { + instanceAvailable(); + return instance.pauseOnHeadsetDisconnect; + } + + public static boolean isFollowQueue() { + instanceAvailable(); + return instance.followQueue; + } + + public static boolean isDownloadMediaOnWifiOnly() { + instanceAvailable(); + return instance.downloadMediaOnWifiOnly; + } + + public static long getUpdateInterval() { + instanceAvailable(); + return instance.updateInterval; + } + + public static boolean isAllowMobileUpdate() { + instanceAvailable(); + return instance.allowMobileUpdate; + } + + public static boolean isAutoQueue() { + instanceAvailable(); + return instance.autoQueue; + } + + public static boolean isDisplayOnlyEpisodes() { + instanceAvailable(); + return instance.displayOnlyEpisodes; + } + + public static boolean isAutoDelete() { + instanceAvailable(); + return instance.autoDelete; + } + + public static int getTheme() { + instanceAvailable(); + return instance.theme; + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sp, String key) { + if (AppConfig.DEBUG) + Log.d(TAG, "Registered change of user preferences. Key: " + key); + + if (key.equals(PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY)) { + downloadMediaOnWifiOnly = sp.getBoolean( + PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY, true); + + } else if (key.equals(PREF_MOBILE_UPDATE)) { + allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false); + + } else if (key.equals(PREF_FOLLOW_QUEUE)) { + followQueue = sp.getBoolean(PREF_FOLLOW_QUEUE, false); + + } else if (key.equals(PREF_UPDATE_INTERVAL)) { + AlarmManager alarmManager = (AlarmManager) context + .getSystemService(Context.ALARM_SERVICE); + updateInterval = readUpdateInterval(sp.getString( + PREF_UPDATE_INTERVAL, "0")); + PendingIntent updateIntent = PendingIntent.getBroadcast(context, 0, + new Intent(FeedUpdateReceiver.ACTION_REFRESH_FEEDS), 0); + alarmManager.cancel(updateIntent); + if (updateInterval != 0) { + alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, + updateInterval, updateInterval, updateIntent); + if (AppConfig.DEBUG) + Log.d(TAG, "Changed alarm to new intervall"); + } else { + if (AppConfig.DEBUG) + Log.d(TAG, "Automatic update was deactivated"); + } + + } else if (key.equals(PREF_AUTO_DELETE)) { + autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false); + + } else if (key.equals(PREF_AUTO_QUEUE)) { + autoQueue = sp.getBoolean(PREF_AUTO_QUEUE, true); + + } else if (key.equals(PREF_DISPLAY_ONLY_EPISODES)) { + displayOnlyEpisodes = sp.getBoolean(PREF_DISPLAY_ONLY_EPISODES, + false); + } else if (key.equals(PREF_THEME)) { + theme = readThemeValue(sp.getString(PREF_THEME, "")); + } + } + + /** + * Return the folder where the app stores all of its data. This method will + * return the standard data folder if none has been set by the user. + * + * @param type + * The name of the folder inside the data folder. May be null + * when accessing the root of the data folder. + * @return The data folder that has been requested or null if the folder + * could not be created. + */ + public static File getDataFolder(Context context, String type) { + instanceAvailable(); + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context.getApplicationContext()); + String strDir = prefs.getString(PREF_DATA_FOLDER, null); + if (strDir == null) { + if (AppConfig.DEBUG) + Log.d(TAG, "Using default data folder"); + return context.getExternalFilesDir(type); + } else { + File dataDir = new File(strDir); + if (!dataDir.exists()) { + if (!dataDir.mkdir()) { + Log.w(TAG, "Could not create data folder"); + return null; + } + } + + if (type == null) { + return dataDir; + } else { + // handle path separators + String[] dirs = type.split("/"); + for (int i = 0; i < dirs.length; i++) { + if (dirs.length > 0) { + if (i < dirs.length - 1) { + dataDir = getDataFolder(context, dirs[i]); + if (dataDir == null) { + return null; + } + } + type = dirs[i]; + } + } + File typeDir = new File(dataDir, type); + if (!typeDir.exists()) { + if (dataDir.canWrite()) { + if (!typeDir.mkdir()) { + Log.e(TAG, "Could not create data folder named " + + type); + return null; + } + } + } + return typeDir; + } + + } + } + + public static void setDataFolder(String dir) { + if (AppConfig.DEBUG) + Log.d(TAG, "Result from DirectoryChooser: " + dir); + instanceAvailable(); + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(instance.context); + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(PREF_DATA_FOLDER, dir); + editor.commit(); + createImportDirectory(); + } + + /** Create a .nomedia file to prevent scanning by the media scanner. */ + private static void createNoMediaFile() { + File f = new File(instance.context.getExternalFilesDir(null), + ".nomedia"); + if (!f.exists()) { + try { + f.createNewFile(); + } catch (IOException e) { + Log.e(TAG, "Could not create .nomedia file"); + e.printStackTrace(); + } + if (AppConfig.DEBUG) + Log.d(TAG, ".nomedia file created"); + } + } + + /** + * Creates the import directory if it doesn't exist and if storage is + * available + */ + private static void createImportDirectory() { + File importDir = getDataFolder(instance.context, + OpmlImportFromPathActivity.IMPORT_DIR); + if (importDir != null) { + if (importDir.exists()) { + if (AppConfig.DEBUG) + Log.d(TAG, "Import directory already exists"); + } else { + if (AppConfig.DEBUG) + Log.d(TAG, "Creating import directory"); + importDir.mkdir(); + } + } else { + if (AppConfig.DEBUG) + Log.d(TAG, "Could not access external storage."); + } + } + +} diff --git a/src/de/danoeh/antennapod/receiver/FeedUpdateReceiver.java b/src/de/danoeh/antennapod/receiver/FeedUpdateReceiver.java index ed6672ccb..651a62aed 100644 --- a/src/de/danoeh/antennapod/receiver/FeedUpdateReceiver.java +++ b/src/de/danoeh/antennapod/receiver/FeedUpdateReceiver.java @@ -5,11 +5,10 @@ import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.net.NetworkInfo; -import android.preference.PreferenceManager; import android.util.Log; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.feed.FeedManager; +import de.danoeh.antennapod.preferences.UserPreferences; /** Refreshes all feeds when it receives an intent */ public class FeedUpdateReceiver extends BroadcastReceiver { @@ -20,10 +19,7 @@ public class FeedUpdateReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(ACTION_REFRESH_FEEDS)) { if (AppConfig.DEBUG) Log.d(TAG, "Received intent"); - boolean mobileUpdate = PreferenceManager - .getDefaultSharedPreferences( - context.getApplicationContext()).getBoolean( - PodcastApp.PREF_MOBILE_UPDATE, false); + boolean mobileUpdate = UserPreferences.isAllowMobileUpdate(); if (mobileUpdate || connectedToWifi(context)) { FeedManager.getInstance().refreshAllFeeds(context); } else { diff --git a/src/de/danoeh/antennapod/service/PlaybackService.java b/src/de/danoeh/antennapod/service/PlaybackService.java index 62370ff6d..450f7f65d 100644 --- a/src/de/danoeh/antennapod/service/PlaybackService.java +++ b/src/de/danoeh/antennapod/service/PlaybackService.java @@ -28,6 +28,7 @@ import android.media.MediaMetadataRetriever; import android.media.MediaPlayer; import android.media.RemoteControlClient; import android.media.RemoteControlClient.MetadataEditor; +import android.os.AsyncTask; import android.os.Binder; import android.os.IBinder; import android.preference.PreferenceManager; @@ -36,52 +37,32 @@ import android.util.Log; import android.view.KeyEvent; import android.view.SurfaceHolder; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.AudioplayerActivity; import de.danoeh.antennapod.activity.VideoplayerActivity; import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.feed.FeedComponent; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedManager; import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.feed.MediaType; +import de.danoeh.antennapod.preferences.PlaybackPreferences; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.receiver.MediaButtonReceiver; import de.danoeh.antennapod.receiver.PlayerWidget; import de.danoeh.antennapod.util.BitmapDecoder; -import de.danoeh.antennapod.util.ChapterUtils; +import de.danoeh.antennapod.util.flattr.FlattrUtils; +import de.danoeh.antennapod.util.playback.Playable; +import de.danoeh.antennapod.util.playback.Playable.PlayableException; /** Controls the MediaPlayer that plays a FeedMedia-file */ public class PlaybackService extends Service { /** Logging tag */ private static final String TAG = "PlaybackService"; - /** Contains the id of the media that was played last. */ - public static final String PREF_LAST_PLAYED_ID = "de.danoeh.antennapod.preferences.lastPlayedId"; - /** Contains the feed id of the last played item. */ - public static final String PREF_LAST_PLAYED_FEED_ID = "de.danoeh.antennapod.preferences.lastPlayedFeedId"; - /** - * ID of the media object that is currently being played. This preference is - * set to NO_MEDIA_PLAYING after playback has been completed and is set as - * soon as the 'play' button is pressed. - */ - public static final String PREF_CURRENTLY_PLAYING_MEDIA = "de.danoeh.antennapod.preferences.currentlyPlayingMedia"; - /** True if last played media was streamed. */ - public static final String PREF_LAST_IS_STREAM = "de.danoeh.antennapod.preferences.lastIsStream"; - /** True if last played media was a video. */ - public static final String PREF_LAST_IS_VIDEO = "de.danoeh.antennapod.preferences.lastIsVideo"; - /** True if playback of last played media has been completed. */ - public static final String PREF_AUTO_DELETE_MEDIA_PLAYBACK_COMPLETED = "de.danoeh.antennapod.preferences.lastPlaybackCompleted"; - /** - * ID of the last played media which should be auto-deleted as soon as - * PREF_LAST_PLAYED_ID changes. - */ - public static final String PREF_AUTODELETE_MEDIA_ID = "de.danoeh.antennapod.preferences.autoDeleteMediaId"; - - /** Contains the id of the FeedMedia object. */ - public static final String EXTRA_MEDIA_ID = "extra.de.danoeh.antennapod.service.mediaId"; - /** Contains the id of the Feed object of the FeedMedia. */ - public static final String EXTRA_FEED_ID = "extra.de.danoeh.antennapod.service.feedId"; + /** Parcelable of type Playable. */ + public static final String EXTRA_PLAYABLE = "PlaybackService.PlayableExtra"; /** True if media should be streamed. */ public static final String EXTRA_SHOULD_STREAM = "extra.de.danoeh.antennapod.service.shouldStream"; /** @@ -134,8 +115,8 @@ public class PlaybackService extends Service { private MediaPlayer player; private RemoteControlClient remoteControlClient; - private FeedMedia media; - private Feed feed; + private Playable media; + /** True if media should be streamed (Extracted from Intent Extra) . */ private boolean shouldStream; @@ -154,8 +135,6 @@ public class PlaybackService extends Service { private SleepTimer sleepTimer; private Future sleepTimerFuture; - private Thread chapterLoader; - private static final int SCHED_EX_POOL_SIZE = 3; private ScheduledThreadPoolExecutor schedExecutor; @@ -166,9 +145,6 @@ public class PlaybackService extends Service { /** True if mediaplayer was paused because it lost audio focus temporarily */ private boolean pausedBecauseOfTransientAudiofocusLoss; - /** Value of PREF_CURRENTLY_PLAYING_MEDIA if no media is playing. */ - public static final long NO_MEDIA_PLAYING = -1; - private final IBinder mBinder = new LocalBinder(); public class LocalBinder extends Binder { @@ -197,11 +173,7 @@ public class PlaybackService extends Service { return new Intent(context, AudioplayerActivity.class); } } else { - SharedPreferences pref = PreferenceManager - .getDefaultSharedPreferences(context - .getApplicationContext()); - boolean isVideo = pref.getBoolean(PREF_LAST_IS_VIDEO, false); - if (isVideo) { + if (PlaybackPreferences.isLastIsVideo()) { return new Intent(context, VideoplayerActivity.class); } else { return new Intent(context, AudioplayerActivity.class); @@ -213,8 +185,7 @@ public class PlaybackService extends Service { * Same as getPlayerActivityIntent(context), but here the type of activity * depends on the FeedMedia that is provided as an argument. */ - public static Intent getPlayerActivityIntent(Context context, - FeedMedia media) { + public static Intent getPlayerActivityIntent(Context context, Playable media) { MediaType mt = media.getMediaType(); if (mt == MediaType.VIDEO) { return new Intent(context, VideoplayerActivity.class); @@ -227,9 +198,8 @@ public class PlaybackService extends Service { public static FeedMedia getLastPlayedMediaFromPreferences(Context context) { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context.getApplicationContext()); - long mediaId = prefs.getLong(PlaybackService.PREF_LAST_PLAYED_ID, -1); - long feedId = prefs.getLong(PlaybackService.PREF_LAST_PLAYED_FEED_ID, - -1); + long mediaId = PlaybackPreferences.getLastPlayedId(); + long feedId = PlaybackPreferences.getLastPlayedFeedId(); FeedManager manager = FeedManager.getInstance(); if (mediaId != -1 && feedId != -1) { Feed feed = manager.getFeed(feedId); @@ -243,12 +213,13 @@ public class PlaybackService extends Service { private void setLastPlayedMediaId(long mediaId) { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(getApplicationContext()); - long autoDeleteId = prefs.getLong(PREF_AUTODELETE_MEDIA_ID, -1); + long autoDeleteId = PlaybackPreferences.getAutoDeleteMediaId(); SharedPreferences.Editor editor = prefs.edit(); if (mediaId == autoDeleteId) { - editor.putBoolean(PREF_AUTO_DELETE_MEDIA_PLAYBACK_COMPLETED, false); + editor.putBoolean( + PlaybackPreferences.PREF_AUTO_DELETE_MEDIA_PLAYBACK_COMPLETED, + false); } - editor.putLong(PREF_LAST_PLAYED_ID, mediaId); editor.commit(); } @@ -350,7 +321,7 @@ public class PlaybackService extends Service { case AudioManager.AUDIOFOCUS_LOSS: if (AppConfig.DEBUG) Log.d(TAG, "Lost audio focus"); - pause(true, true); + pause(true, false); stopSelf(); break; case AudioManager.AUDIOFOCUS_GAIN: @@ -393,26 +364,24 @@ public class PlaybackService extends Service { handleKeycode(keycode); } else { - long mediaId = intent.getLongExtra(EXTRA_MEDIA_ID, -1); - long feedId = intent.getLongExtra(EXTRA_FEED_ID, -1); + Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE); boolean playbackType = intent.getBooleanExtra(EXTRA_SHOULD_STREAM, true); - if (mediaId == -1 || feedId == -1) { - Log.e(TAG, - "Media ID or Feed ID wasn't provided to the Service."); - if (media == null || feed == null) { + if (playable == null) { + Log.e(TAG, "Playable extra wasn't sent to the service"); + if (media == null) { stopSelf(); } // Intent values appear to be valid // check if already playing and playbackType is the same - } else if (media == null || mediaId != media.getId() + } else if (media == null || playable != media || playbackType != shouldStream) { pause(true, false); player.reset(); sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); - if (media == null || mediaId != media.getId()) { - feed = manager.getFeed(feedId); - media = manager.getFeedMedia(mediaId, feed); + if (media == null + || playable.getIdentifier() != media.getIdentifier()) { + media = playable; } if (media != null) { @@ -487,23 +456,45 @@ public class PlaybackService extends Service { if (status == PlayerStatus.STOPPED || status == PlayerStatus.AWAITING_VIDEO_SURFACE) { try { - if (shouldStream) { - player.setDataSource(media.getDownload_url()); - setStatus(PlayerStatus.PREPARING); - player.prepareAsync(); - } else { - player.setDataSource(media.getFile_url()); - setStatus(PlayerStatus.PREPARING); - player.prepare(); - } + InitTask initTask = new InitTask() { + + @Override + protected void onPostExecute(Playable result) { + if (result != null) { + try { + if (shouldStream) { + player.setDataSource(media.getStreamUrl()); + setStatus(PlayerStatus.PREPARING); + player.prepareAsync(); + } else { + player.setDataSource(media + .getLocalMediaUrl()); + setStatus(PlayerStatus.PREPARING); + player.prepareAsync(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } else { + setStatus(PlayerStatus.ERROR); + sendBroadcast(new Intent( + ACTION_SHUTDOWN_PLAYBACK_SERVICE)); + } + } + + @Override + protected void onPreExecute() { + setStatus(PlayerStatus.INITIALIZING); + } + + }; + initTask.executeAsync(media); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); } } @@ -535,18 +526,50 @@ public class PlaybackService extends Service { if (mediaType == MediaType.AUDIO) { if (AppConfig.DEBUG) Log.d(TAG, "Mime type is audio"); - playingVideo = false; - if (shouldStream) { - player.setDataSource(media.getDownload_url()); - } else if (media.getFile_url() != null) { - player.setDataSource(media.getFile_url()); - } - if (prepareImmediately) { - setStatus(PlayerStatus.PREPARING); - player.prepareAsync(); - } else { - setStatus(PlayerStatus.INITIALIZED); - } + + InitTask initTask = new InitTask() { + + @Override + protected void onPostExecute(Playable result) { + if (result != null) { + playingVideo = false; + try { + if (shouldStream) { + player.setDataSource(media.getStreamUrl()); + } else if (media.localFileAvailable()) { + player.setDataSource(media + .getLocalMediaUrl()); + } + + if (prepareImmediately) { + setStatus(PlayerStatus.PREPARING); + player.prepareAsync(); + } else { + setStatus(PlayerStatus.INITIALIZED); + } + } catch (IOException e) { + e.printStackTrace(); + media = null; + setStatus(PlayerStatus.ERROR); + sendBroadcast(new Intent( + ACTION_SHUTDOWN_PLAYBACK_SERVICE)); + } + } else { + Log.e(TAG, "InitTask could not load metadata"); + media = null; + setStatus(PlayerStatus.ERROR); + sendBroadcast(new Intent( + ACTION_SHUTDOWN_PLAYBACK_SERVICE)); + } + } + + @Override + protected void onPreExecute() { + setStatus(PlayerStatus.INITIALIZING); + } + + }; + initTask.executeAsync(media); } else if (mediaType == MediaType.VIDEO) { if (AppConfig.DEBUG) Log.d(TAG, "Mime type is video"); @@ -561,8 +584,6 @@ public class PlaybackService extends Service { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); } } @@ -601,39 +622,6 @@ public class PlaybackService extends Service { if (startWhenPrepared) { play(); } - if (shouldStream && media.getItem().getChapters() == null) { - // load chapters if available - if (chapterLoader != null) { - chapterLoader.interrupt(); - } - chapterLoader = new Thread() { - - @Override - public void run() { - if (AppConfig.DEBUG) - Log.d(TAG, "Starting chapterLoader thread"); - ChapterUtils - .readID3ChaptersFromFeedMediaDownloadUrl(media - .getItem()); - if (media.getItem().getChapters() == null) { - ChapterUtils - .readOggChaptersFromMediaDownloadUrl(media - .getItem()); - } - if (media.getItem().getChapters() != null - && !interrupted()) { - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, - 0); - manager.setFeedItem(PlaybackService.this, - media.getItem()); - } - if (AppConfig.DEBUG) - Log.d(TAG, "ChapterLoaderThread has finished"); - } - - }; - chapterLoader.start(); - } } }; @@ -675,7 +663,7 @@ public class PlaybackService extends Service { pause(true, true); } sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what); - setCurrentlyPlayingMedia(NO_MEDIA_PLAYING); + setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING); stopSelf(); return true; } @@ -690,42 +678,53 @@ public class PlaybackService extends Service { audioManager.abandonAudioFocus(audioFocusChangeListener); SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(getApplicationContext()); + SharedPreferences.Editor editor = prefs.edit(); + // Save state cancelPositionSaver(); - media.setPlaybackCompletionDate(new Date()); - manager.markItemRead(PlaybackService.this, media.getItem(), true, - true); - FeedItem nextItem = manager - .getQueueSuccessorOfItem(media.getItem()); - boolean isInQueue = manager.isInQueue(media.getItem()); - if (isInQueue) { - manager.removeQueueItem(PlaybackService.this, media.getItem()); - } - manager.addItemToPlaybackHistory(PlaybackService.this, - media.getItem()); - manager.setFeedMedia(PlaybackService.this, media); - - long autoDeleteMediaId = media.getId(); - if (shouldStream) { - autoDeleteMediaId = -1; + boolean isInQueue = false; + FeedItem nextItem = null; + + if (media instanceof FeedMedia) { + FeedItem item = ((FeedMedia) media).getItem(); + ((FeedMedia) media).setPlaybackCompletionDate(new Date()); + manager.markItemRead(PlaybackService.this, item, true, true); + nextItem = manager.getQueueSuccessorOfItem(item); + isInQueue = media instanceof FeedMedia + && manager.isInQueue(((FeedMedia) media).getItem()); + if (isInQueue) { + manager.removeQueueItem(PlaybackService.this, item); + } + manager.addItemToPlaybackHistory(PlaybackService.this, item); + manager.setFeedMedia(PlaybackService.this, (FeedMedia) media); + long autoDeleteMediaId = ((FeedComponent) media).getId(); + if (shouldStream) { + autoDeleteMediaId = -1; + } + editor.putLong(PlaybackPreferences.PREF_AUTODELETE_MEDIA_ID, + autoDeleteMediaId); } - SharedPreferences.Editor editor = prefs.edit(); - editor.putLong(PREF_CURRENTLY_PLAYING_MEDIA, NO_MEDIA_PLAYING); - editor.putLong(PREF_AUTODELETE_MEDIA_ID, autoDeleteMediaId); - editor.putBoolean(PREF_AUTO_DELETE_MEDIA_PLAYBACK_COMPLETED, true); + editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, + PlaybackPreferences.NO_MEDIA_PLAYING); + editor.putBoolean( + PlaybackPreferences.PREF_AUTO_DELETE_MEDIA_PLAYBACK_COMPLETED, + true); + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, + PlaybackPreferences.NO_MEDIA_PLAYING); + editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, + PlaybackPreferences.NO_MEDIA_PLAYING); editor.commit(); // Prepare for playing next item - boolean followQueue = prefs.getBoolean( - PodcastApp.PREF_FOLLOW_QUEUE, false); - boolean playNextItem = isInQueue && followQueue && nextItem != null; + boolean playNextItem = isInQueue && UserPreferences.isFollowQueue() + && nextItem != null; if (playNextItem) { if (AppConfig.DEBUG) Log.d(TAG, "Loading next item in queue"); media = nextItem.getMedia(); - feed = nextItem.getFeed(); - shouldStream = !media.isDownloaded(); + shouldStream = !media.localFileAvailable(); prepareImmediately = startWhenPrepared = true; } else { if (AppConfig.DEBUG) @@ -814,8 +813,12 @@ public class PlaybackService extends Service { public void stop() { if (AppConfig.DEBUG) Log.d(TAG, "Stopping playback"); - player.stop(); - setCurrentlyPlayingMedia(NO_MEDIA_PLAYING); + if (status == PlayerStatus.PREPARED || status == PlayerStatus.PAUSED + || status == PlayerStatus.STOPPED + || status == PlayerStatus.PLAYING) { + player.stop(); + } + setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING); stopSelf(); } @@ -856,12 +859,37 @@ public class PlaybackService extends Service { SharedPreferences.Editor editor = PreferenceManager .getDefaultSharedPreferences(getApplicationContext()) .edit(); - editor.putLong(PREF_CURRENTLY_PLAYING_MEDIA, media.getId()); - editor.putLong(PREF_LAST_PLAYED_FEED_ID, feed.getId()); - editor.putBoolean(PREF_LAST_IS_STREAM, shouldStream); - editor.putBoolean(PREF_LAST_IS_VIDEO, playingVideo); + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, + media.getPlayableType()); + editor.putBoolean(PlaybackPreferences.PREF_LAST_IS_STREAM, + shouldStream); + editor.putBoolean(PlaybackPreferences.PREF_LAST_IS_VIDEO, + playingVideo); + editor.putLong(PlaybackPreferences.PREF_LAST_PLAYED_ID, + media.getPlayableType()); + if (media instanceof FeedMedia) { + FeedMedia fMedia = (FeedMedia) media; + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, + fMedia.getItem().getFeed().getId()); + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, + fMedia.getId()); + } else { + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, + PlaybackPreferences.NO_MEDIA_PLAYING); + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, + PlaybackPreferences.NO_MEDIA_PLAYING); + } + media.writeToPreferences(editor); + editor.commit(); - setLastPlayedMediaId(media.getId()); + if (media instanceof FeedMedia) { + setLastPlayedMediaId(((FeedMedia) media).getId()); + } player.start(); if (status != PlayerStatus.PAUSED) { player.seekTo((int) media.getPosition()); @@ -877,9 +905,7 @@ public class PlaybackService extends Service { } audioManager .registerMediaButtonEventReceiver(mediaButtonReceiver); - if (media.getItem().isRead() == false) { - manager.markItemRead(this, media.getItem(), true, false); - } + media.onPlaybackStart(); } else { if (AppConfig.DEBUG) Log.d(TAG, "Failed to request Audiofocus"); @@ -917,12 +943,11 @@ public class PlaybackService extends Service { Bitmap icon = null; if (android.os.Build.VERSION.SDK_INT >= 11) { - if (media != null && media.getImage() != null - && media.getImage().getFile_url() != null) { + if (media != null && media.getImageFileUrl() != null) { int iconSize = getResources().getDimensionPixelSize( android.R.dimen.notification_large_icon_width); - icon = BitmapDecoder.decodeBitmap(iconSize, media.getImage() - .getFile_url()); + icon = BitmapDecoder.decodeBitmap(iconSize, + media.getImageFileUrl()); } } if (icon == null) { @@ -930,15 +955,16 @@ public class PlaybackService extends Service { R.drawable.ic_stat_antenna); } - String contentText = media.getItem().getFeed().getTitle(); - String contentTitle = media.getItem().getTitle(); + String contentText = media.getFeedTitle(); + String contentTitle = media.getEpisodeTitle(); Notification notification = null; if (android.os.Build.VERSION.SDK_INT >= 16) { Intent pauseButtonIntent = new Intent(this, PlaybackService.class); pauseButtonIntent.putExtra(MediaButtonReceiver.EXTRA_KEYCODE, KeyEvent.KEYCODE_MEDIA_PAUSE); - PendingIntent pauseButtonPendingIntent = PendingIntent - .getService(this, 0, pauseButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pauseButtonPendingIntent = PendingIntent.getService( + this, 0, pauseButtonIntent, + PendingIntent.FLAG_UPDATE_CURRENT); Notification.Builder notificationBuilder = new Notification.Builder( this) .setContentTitle(contentTitle) @@ -1006,8 +1032,9 @@ public class PlaybackService extends Service { if (position != INVALID_TIME) { if (AppConfig.DEBUG) Log.d(TAG, "Saving current position to " + position); - media.setPosition(position); - manager.setFeedMedia(this, media); + media.saveCurrentPosition(PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()), + position); } } @@ -1101,10 +1128,10 @@ public class PlaybackService extends Service { MetadataEditor editor = remoteControlClient .editMetadata(false); editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, - media.getItem().getTitle()); + media.getEpisodeTitle()); editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, - media.getItem().getFeed().getTitle()); + media.getFeedTitle()); editor.apply(); } @@ -1155,10 +1182,8 @@ public class PlaybackService extends Service { /** Pauses playback if PREF_PAUSE_ON_HEADSET_DISCONNECT was set to true. */ private void pauseIfPauseOnDisconnect() { - boolean pauseOnDisconnect = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()) - .getBoolean(PodcastApp.PREF_PAUSE_ON_HEADSET_DISCONNECT, false); - if (pauseOnDisconnect && status == PlayerStatus.PLAYING) { + if (UserPreferences.isPauseOnHeadsetDisconnect() + && status == PlayerStatus.PLAYING) { pause(true, true); } } @@ -1169,12 +1194,8 @@ public class PlaybackService extends Service { public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(ACTION_SHUTDOWN_PLAYBACK_SERVICE)) { schedExecutor.shutdownNow(); - if (chapterLoader != null) { - chapterLoader.interrupt(); - } stop(); media = null; - feed = null; } } @@ -1276,7 +1297,7 @@ public class PlaybackService extends Service { return status; } - public FeedMedia getMedia() { + public Playable getMedia() { return media; } @@ -1343,7 +1364,40 @@ public class PlaybackService extends Service { private void setCurrentlyPlayingMedia(long id) { SharedPreferences.Editor editor = PreferenceManager .getDefaultSharedPreferences(getApplicationContext()).edit(); - editor.putLong(PREF_CURRENTLY_PLAYING_MEDIA, id); + editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, id); editor.commit(); } + + private static class InitTask extends AsyncTask<Playable, Void, Playable> { + private Playable playable; + public PlayableException exception; + + @Override + protected Playable doInBackground(Playable... params) { + if (params[0] == null) { + throw new IllegalArgumentException("Playable must not be null"); + } + playable = params[0]; + + try { + playable.loadMetadata(); + } catch (PlayableException e) { + e.printStackTrace(); + exception = e; + return null; + } + return playable; + } + + @SuppressLint("NewApi") + public void executeAsync(Playable playable) { + FlattrUtils.hasToken(); + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + executeOnExecutor(THREAD_POOL_EXECUTOR, playable); + } else { + execute(playable); + } + } + + } } diff --git a/src/de/danoeh/antennapod/service/PlayerStatus.java b/src/de/danoeh/antennapod/service/PlayerStatus.java index 17e42f847..fbf5b1505 100644 --- a/src/de/danoeh/antennapod/service/PlayerStatus.java +++ b/src/de/danoeh/antennapod/service/PlayerStatus.java @@ -9,5 +9,6 @@ public enum PlayerStatus { PREPARED, SEEKING, AWAITING_VIDEO_SURFACE, // player has been initialized and the media type to be played is a video. + INITIALIZING, // playback service is loading the Playable's metadata INITIALIZED // playback service was started, data source of media player was set. } diff --git a/src/de/danoeh/antennapod/service/PlayerWidgetService.java b/src/de/danoeh/antennapod/service/PlayerWidgetService.java index dd4598e9c..475af9655 100644 --- a/src/de/danoeh/antennapod/service/PlayerWidgetService.java +++ b/src/de/danoeh/antennapod/service/PlayerWidgetService.java @@ -13,10 +13,10 @@ import android.view.View; import android.widget.RemoteViews; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.receiver.MediaButtonReceiver; import de.danoeh.antennapod.receiver.PlayerWidget; import de.danoeh.antennapod.util.Converter; +import de.danoeh.antennapod.util.playback.Playable; /** Updates the state of the player widget */ public class PlayerWidgetService extends Service { @@ -83,11 +83,11 @@ public class PlayerWidgetService extends Service { PlaybackService.getPlayerActivityIntent(this), 0); views.setOnClickPendingIntent(R.id.layout_left, startMediaplayer); - if (playbackService != null) { - FeedMedia media = playbackService.getMedia(); + if (playbackService != null && playbackService.getMedia() != null) { + Playable media = playbackService.getMedia(); PlayerStatus status = playbackService.getStatus(); - views.setTextViewText(R.id.txtvTitle, media.getItem().getTitle()); + views.setTextViewText(R.id.txtvTitle, media.getEpisodeTitle()); if (status == PlayerStatus.PLAYING) { String progressString = getProgressString(playbackService); diff --git a/src/de/danoeh/antennapod/service/download/DownloadService.java b/src/de/danoeh/antennapod/service/download/DownloadService.java index 59f0a5b97..986491fb5 100644 --- a/src/de/danoeh/antennapod/service/download/DownloadService.java +++ b/src/de/danoeh/antennapod/service/download/DownloadService.java @@ -30,7 +30,6 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; -import android.app.Notification.Builder; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -52,6 +51,7 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.DownloadActivity; import de.danoeh.antennapod.activity.DownloadLogActivity; import de.danoeh.antennapod.asynctask.DownloadStatus; +import de.danoeh.antennapod.feed.EventDistributor; import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedFile; import de.danoeh.antennapod.feed.FeedImage; @@ -78,7 +78,6 @@ public class DownloadService extends Service { /** Extra for ACTION_CANCEL_DOWNLOAD */ public static final String EXTRA_DOWNLOAD_URL = "downloadUrl"; - public static final String ACTION_DOWNLOAD_HANDLED = "action.de.danoeh.antennapod.service.download_handled"; /** * Sent by the DownloadService when the content of the downloads list * changes. @@ -90,12 +89,6 @@ public class DownloadService extends Service { /** Extra for ACTION_ENQUEUE_DOWNLOAD intent. */ public static final String EXTRA_REQUEST = "request"; - // Download types for ACTION_DOWNLOAD_HANDLED - public static final String EXTRA_DOWNLOAD_TYPE = "extra.de.danoeh.antennapod.service.downloadType"; - public static final int DOWNLOAD_TYPE_FEED = 1; - public static final int DOWNLOAD_TYPE_MEDIA = 2; - public static final int DOWNLOAD_TYPE_IMAGE = 3; - private CopyOnWriteArrayList<DownloadStatus> completedDownloads; private ExecutorService syncExecutor; @@ -463,7 +456,7 @@ public class DownloadService extends Service { Log.e(TAG, "Download failed"); saveDownloadStatus(status); } - sendDownloadHandledIntent(getDownloadType(download)); + sendDownloadHandledIntent(); downloadsBeingHandled -= 1; } } @@ -505,24 +498,8 @@ public class DownloadService extends Service { manager.addDownloadStatus(this, status); } - /** Returns correct value for EXTRA_DOWNLOAD_TYPE. */ - private int getDownloadType(FeedFile f) { - if (f.getClass() == Feed.class) { - return DOWNLOAD_TYPE_FEED; - } else if (f.getClass() == FeedImage.class) { - return DOWNLOAD_TYPE_IMAGE; - } else if (f.getClass() == FeedMedia.class) { - return DOWNLOAD_TYPE_MEDIA; - } else { - return 0; - } - } - - private void sendDownloadHandledIntent(int type) { - Intent intent = new Intent(ACTION_DOWNLOAD_HANDLED); - intent.putExtra(EXTRA_DOWNLOAD_TYPE, type); - - sendBroadcast(intent); + private void sendDownloadHandledIntent() { + EventDistributor.getInstance().sendDownloadHandledBroadcast(); } /** @@ -727,7 +704,7 @@ public class DownloadService extends Service { saveDownloadStatus(new DownloadStatus(savedFeed, savedFeed.getHumanReadableIdentifier(), reason, successful, reasonDetailed)); - sendDownloadHandledIntent(DOWNLOAD_TYPE_FEED); + sendDownloadHandledIntent(); downloadsBeingHandled -= 1; handler.post(new Runnable() { @@ -756,7 +733,7 @@ public class DownloadService extends Service { } private boolean hasValidFeedItems(Feed feed) { - for (FeedItem item : feed.getItems()) { + for (FeedItem item : feed.getItemsArray()) { if (item.getTitle() == null) { Log.e(TAG, "Item has no title"); return false; @@ -804,7 +781,7 @@ public class DownloadService extends Service { image.setDownloaded(true); saveDownloadStatus(status); - sendDownloadHandledIntent(DOWNLOAD_TYPE_IMAGE); + sendDownloadHandledIntent(); manager.setFeedImage(DownloadService.this, image); if (image.getFeed() != null) { manager.setFeed(DownloadService.this, image.getFeed()); @@ -854,21 +831,16 @@ public class DownloadService extends Service { } finally { mediaplayer.release(); } - + if (media.getItem().getChapters() == null) { - ChapterUtils.readID3ChaptersFromFeedMediaFileUrl(media - .getItem()); - if (media.getItem().getChapters() == null) { - ChapterUtils.readOggChaptersFromMediaFileUrl(media - .getItem()); - } + ChapterUtils.loadChaptersFromFileUrl(media); if (media.getItem().getChapters() != null) { chaptersRead = true; } } saveDownloadStatus(status); - sendDownloadHandledIntent(DOWNLOAD_TYPE_MEDIA); + sendDownloadHandledIntent(); if (chaptersRead) { manager.setFeedItem(DownloadService.this, media.getItem()); } else { diff --git a/src/de/danoeh/antennapod/storage/DownloadRequester.java b/src/de/danoeh/antennapod/storage/DownloadRequester.java index 38b143a7f..bebffe8f9 100644 --- a/src/de/danoeh/antennapod/storage/DownloadRequester.java +++ b/src/de/danoeh/antennapod/storage/DownloadRequester.java @@ -11,11 +11,12 @@ import android.content.Intent; import android.util.Log; import android.webkit.URLUtil; import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; +import de.danoeh.antennapod.feed.EventDistributor; import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedFile; import de.danoeh.antennapod.feed.FeedImage; import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.service.download.DownloadService; import de.danoeh.antennapod.util.FileNameGenerator; import de.danoeh.antennapod.util.URLChecker; @@ -23,11 +24,6 @@ import de.danoeh.antennapod.util.URLChecker; public class DownloadRequester { private static final String TAG = "DownloadRequester"; - public static String EXTRA_DOWNLOAD_ID = "extra.de.danoeh.antennapod.storage.download_id"; - public static String EXTRA_ITEM_ID = "extra.de.danoeh.antennapod.storage.item_id"; - - public static String ACTION_DOWNLOAD_QUEUED = "action.de.danoeh.antennapod.storage.downloadQueued"; - public static String IMAGE_DOWNLOADPATH = "images/"; public static String FEED_DOWNLOADPATH = "cache/"; public static String MEDIA_DOWNLOADPATH = "media/"; @@ -70,7 +66,8 @@ public class DownloadRequester { if (AppConfig.DEBUG) Log.d(TAG, "Testing filename " + newName); newDest = new File(dest.getParent(), newName); - if (!newDest.exists() && isFilenameAvailable(newDest.toString())) { + if (!newDest.exists() + && isFilenameAvailable(newDest.toString())) { if (AppConfig.DEBUG) Log.d(TAG, "File doesn't exist yet. Using " + newName); @@ -102,7 +99,7 @@ public class DownloadRequester { queueIntent.putExtra(DownloadService.EXTRA_REQUEST, request); context.sendBroadcast(queueIntent); } - context.sendBroadcast(new Intent(ACTION_DOWNLOAD_QUEUED)); + EventDistributor.getInstance().sendDownloadQueuedBroadcast(); } else { Log.e(TAG, "URL " + item.getDownload_url() + " is already being downloaded"); @@ -123,7 +120,8 @@ public class DownloadRequester { return false; } } - if (AppConfig.DEBUG) Log.d(TAG, path + " is available as a download destination"); + if (AppConfig.DEBUG) + Log.d(TAG, path + " is available as a download destination"); return true; } @@ -282,7 +280,7 @@ public class DownloadRequester { private File getExternalFilesDirOrThrowException(Context context, String type) throws DownloadRequestException { - File result = PodcastApp.getDataFolder(context, type); + File result = UserPreferences.getDataFolder(context, type); if (result == null) { throw new DownloadRequestException( "Failed to access external storage"); diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index 4045f8664..f1842800b 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -334,7 +334,7 @@ public class PodDBAdapter { public void setCompleteFeed(Feed feed) { db.beginTransaction(); setFeed(feed); - for (FeedItem item : feed.getItems()) { + for (FeedItem item : feed.getItemsArray()) { setFeedItem(item); } db.setTransactionSuccessful(); @@ -485,7 +485,7 @@ public class PodDBAdapter { if (feed.getImage() != null) { removeFeedImage(feed.getImage()); } - for (FeedItem item : feed.getItems()) { + for (FeedItem item : feed.getItemsArray()) { removeFeedItem(item); } db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?", diff --git a/src/de/danoeh/antennapod/syndication/handler/HandlerState.java b/src/de/danoeh/antennapod/syndication/handler/HandlerState.java index 6c206b8f3..e8687858b 100644 --- a/src/de/danoeh/antennapod/syndication/handler/HandlerState.java +++ b/src/de/danoeh/antennapod/syndication/handler/HandlerState.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.syndication.handler; +import java.util.ArrayList; import java.util.HashMap; import java.util.Stack; @@ -8,11 +9,15 @@ import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.syndication.namespace.Namespace; import de.danoeh.antennapod.syndication.namespace.SyndElement; -/** Contains all relevant information to describe the current state of a SyndHandler.*/ +/** + * Contains all relevant information to describe the current state of a + * SyndHandler. + */ public class HandlerState { - + /** Feed that the Handler is currently processing. */ protected Feed feed; + protected ArrayList<FeedItem> items; protected FeedItem currentItem; protected Stack<SyndElement> tagstack; /** Namespaces that have been defined so far. */ @@ -20,43 +25,49 @@ public class HandlerState { protected Stack<Namespace> defaultNamespaces; /** Buffer for saving characters. */ protected StringBuffer contentBuf; - + public HandlerState(Feed feed) { this.feed = feed; + items = new ArrayList<FeedItem>(); tagstack = new Stack<SyndElement>(); namespaces = new HashMap<String, Namespace>(); defaultNamespaces = new Stack<Namespace>(); } - - + public Feed getFeed() { return feed; } + + public ArrayList<FeedItem> getItems() { + return items; + } + public FeedItem getCurrentItem() { return currentItem; } + public Stack<SyndElement> getTagstack() { return tagstack; } - public void setFeed(Feed feed) { this.feed = feed; } - public void setCurrentItem(FeedItem currentItem) { this.currentItem = currentItem; } - /** Returns the SyndElement that comes after the top element of the tagstack. */ + /** + * Returns the SyndElement that comes after the top element of the tagstack. + */ public SyndElement getSecondTag() { SyndElement top = tagstack.pop(); SyndElement second = tagstack.peek(); tagstack.push(top); return second; } - + public SyndElement getThirdTag() { SyndElement top = tagstack.pop(); SyndElement second = tagstack.pop(); @@ -65,13 +76,9 @@ public class HandlerState { tagstack.push(top); return third; } - + public StringBuffer getContentBuf() { return contentBuf; } - - - - } diff --git a/src/de/danoeh/antennapod/syndication/handler/SyndHandler.java b/src/de/danoeh/antennapod/syndication/handler/SyndHandler.java index f830f0933..c51d054d4 100644 --- a/src/de/danoeh/antennapod/syndication/handler/SyndHandler.java +++ b/src/de/danoeh/antennapod/syndication/handler/SyndHandler.java @@ -120,6 +120,12 @@ public class SyndHandler extends DefaultHandler { return handler; } + @Override + public void endDocument() throws SAXException { + super.endDocument(); + state.getFeed().setItems(state.getItems()); + } + public HandlerState getState() { return state; } diff --git a/src/de/danoeh/antennapod/syndication/namespace/NSRSS20.java b/src/de/danoeh/antennapod/syndication/namespace/NSRSS20.java index a8c43800c..4d0b42132 100644 --- a/src/de/danoeh/antennapod/syndication/namespace/NSRSS20.java +++ b/src/de/danoeh/antennapod/syndication/namespace/NSRSS20.java @@ -43,7 +43,7 @@ public class NSRSS20 extends Namespace { Attributes attributes) { if (localName.equals(ITEM)) { state.setCurrentItem(new FeedItem()); - state.getFeed().getItems().add(state.getCurrentItem()); + state.getItems().add(state.getCurrentItem()); state.getCurrentItem().setFeed(state.getFeed()); } else if (localName.equals(ENCLOSURE)) { diff --git a/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java b/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java index e3cdce534..522b16efe 100644 --- a/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java +++ b/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java @@ -55,7 +55,7 @@ public class NSAtom extends Namespace { Attributes attributes) { if (localName.equals(ENTRY)) { state.setCurrentItem(new FeedItem()); - state.getFeed().getItems().add(state.getCurrentItem()); + state.getItems().add(state.getCurrentItem()); state.getCurrentItem().setFeed(state.getFeed()); } else if (localName.matches(isText)) { String type = attributes.getValue(TEXT_TYPE); diff --git a/src/de/danoeh/antennapod/util/BitmapDecoder.java b/src/de/danoeh/antennapod/util/BitmapDecoder.java index 8dd4953c6..e9ef2ad01 100644 --- a/src/de/danoeh/antennapod/util/BitmapDecoder.java +++ b/src/de/danoeh/antennapod/util/BitmapDecoder.java @@ -2,9 +2,6 @@ package de.danoeh.antennapod.util; import java.io.File; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.FilenameUtils; - import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.Log; diff --git a/src/de/danoeh/antennapod/util/ChapterUtils.java b/src/de/danoeh/antennapod/util/ChapterUtils.java index 3131b9e9a..ac8149119 100644 --- a/src/de/danoeh/antennapod/util/ChapterUtils.java +++ b/src/de/danoeh/antennapod/util/ChapterUtils.java @@ -16,11 +16,10 @@ import org.apache.commons.io.IOUtils; import android.util.Log; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.feed.Chapter; -import de.danoeh.antennapod.feed.FeedItem; -import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.util.comparator.ChapterStartTimeComparator; import de.danoeh.antennapod.util.id3reader.ChapterReader; import de.danoeh.antennapod.util.id3reader.ID3ReaderException; +import de.danoeh.antennapod.util.playback.Playable; import de.danoeh.antennapod.util.vorbiscommentreader.VorbisCommentChapterReader; import de.danoeh.antennapod.util.vorbiscommentreader.VorbisCommentReaderException; @@ -35,14 +34,13 @@ public class ChapterUtils { * Uses the download URL of a media object of a feeditem to read its ID3 * chapters. */ - public static void readID3ChaptersFromFeedMediaDownloadUrl(FeedItem item) { + public static void readID3ChaptersFromPlayableStreamUrl(Playable p) { if (AppConfig.DEBUG) - Log.d(TAG, "Reading id3 chapters from item " + item.getTitle()); - final FeedMedia media = item.getMedia(); - if (media != null && media.getDownload_url() != null) { + Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle()); + if (p != null && p.getStreamUrl() != null) { InputStream in = null; try { - URL url = new URL(media.getDownload_url()); + URL url = new URL(p.getStreamUrl()); ChapterReader reader = new ChapterReader(); in = url.openStream(); @@ -52,9 +50,9 @@ public class ChapterUtils { if (chapters != null) { Collections .sort(chapters, new ChapterStartTimeComparator()); - processChapters(chapters, item); + processChapters(chapters, p); if (chaptersValid(chapters)) { - item.setChapters(chapters); + p.setChapters(chapters); Log.i(TAG, "Chapters loaded"); } else { Log.e(TAG, "Chapter data was invalid"); @@ -87,13 +85,11 @@ public class ChapterUtils { * Uses the file URL of a media object of a feeditem to read its ID3 * chapters. */ - public static void readID3ChaptersFromFeedMediaFileUrl(FeedItem item) { + public static void readID3ChaptersFromPlayableFileUrl(Playable p) { if (AppConfig.DEBUG) - Log.d(TAG, "Reading id3 chapters from item " + item.getTitle()); - final FeedMedia media = item.getMedia(); - if (media != null && media.isDownloaded() - && media.getFile_url() != null) { - File source = new File(media.getFile_url()); + Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle()); + if (p != null && p.localFileAvailable() && p.getLocalMediaUrl() != null) { + File source = new File(p.getLocalMediaUrl()); if (source.exists()) { ChapterReader reader = new ChapterReader(); InputStream in = null; @@ -106,9 +102,9 @@ public class ChapterUtils { if (chapters != null) { Collections.sort(chapters, new ChapterStartTimeComparator()); - processChapters(chapters, item); + processChapters(chapters, p); if (chaptersValid(chapters)) { - item.setChapters(chapters); + p.setChapters(chapters); Log.i(TAG, "Chapters loaded"); } else { Log.e(TAG, "Chapter data was invalid"); @@ -136,15 +132,14 @@ public class ChapterUtils { } } - public static void readOggChaptersFromMediaDownloadUrl(FeedItem item) { - final FeedMedia media = item.getMedia(); - if (media != null && media.getDownload_url() != null) { + public static void readOggChaptersFromPlayableStreamUrl(Playable media) { + if (media != null && media.streamAvailable()) { InputStream input = null; try { - URL url = new URL(media.getDownload_url()); + URL url = new URL(media.getStreamUrl()); input = url.openStream(); if (input != null) { - readOggChaptersFromInputStream(item, input); + readOggChaptersFromInputStream(media, input); } } catch (MalformedURLException e) { e.printStackTrace(); @@ -156,15 +151,14 @@ public class ChapterUtils { } } - public static void readOggChaptersFromMediaFileUrl(FeedItem item) { - final FeedMedia media = item.getMedia(); - if (media != null && media.getFile_url() != null) { - File source = new File(media.getFile_url()); + public static void readOggChaptersFromPlayableFileUrl(Playable media) { + if (media != null && media.getLocalMediaUrl() != null) { + File source = new File(media.getLocalMediaUrl()); if (source.exists()) { InputStream input = null; try { input = new BufferedInputStream(new FileInputStream(source)); - readOggChaptersFromInputStream(item, input); + readOggChaptersFromInputStream(media, input); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { @@ -174,21 +168,21 @@ public class ChapterUtils { } } - private static void readOggChaptersFromInputStream(FeedItem item, + private static void readOggChaptersFromInputStream(Playable p, InputStream input) { if (AppConfig.DEBUG) Log.d(TAG, "Trying to read chapters from item with title " - + item.getTitle()); + + p.getEpisodeTitle()); try { VorbisCommentChapterReader reader = new VorbisCommentChapterReader(); reader.readInputStream(input); List<Chapter> chapters = reader.getChapters(); if (chapters != null) { Collections.sort(chapters, new ChapterStartTimeComparator()); - processChapters(chapters, item); + processChapters(chapters, p); if (chaptersValid(chapters)) { - item.setChapters(chapters); + p.setChapters(chapters); Log.i(TAG, "Chapters loaded"); } else { Log.e(TAG, "Chapter data was invalid"); @@ -203,13 +197,12 @@ public class ChapterUtils { } /** Makes sure that chapter does a title and an item attribute. */ - private static void processChapters(List<Chapter> chapters, FeedItem item) { + private static void processChapters(List<Chapter> chapters, Playable p) { for (int i = 0; i < chapters.size(); i++) { Chapter c = chapters.get(i); if (c.getTitle() == null) { c.setTitle(Integer.toString(i)); } - c.setItem(item); } } @@ -228,4 +221,47 @@ public class ChapterUtils { return true; } + /** Calls getCurrentChapter with current position. */ + public static Chapter getCurrentChapter(Playable media) { + if (media.getChapters() != null) { + List<Chapter> chapters = media.getChapters(); + Chapter current = null; + if (chapters != null) { + current = chapters.get(0); + for (Chapter sc : chapters) { + if (sc.getStart() > media.getPosition()) { + break; + } else { + current = sc; + } + } + } + return current; + } else { + return null; + } + } + + public static void loadChaptersFromStreamUrl(Playable media) { + if (AppConfig.DEBUG) + Log.d(TAG, "Starting chapterLoader thread"); + ChapterUtils.readID3ChaptersFromPlayableStreamUrl(media); + if (media.getChapters() == null) { + ChapterUtils.readOggChaptersFromPlayableStreamUrl(media); + } + + if (AppConfig.DEBUG) + Log.d(TAG, "ChapterLoaderThread has finished"); + } + + public static void loadChaptersFromFileUrl(Playable media) { + if (media.localFileAvailable()) { + ChapterUtils.readID3ChaptersFromPlayableFileUrl(media); + if (media.getChapters() == null) { + ChapterUtils.readOggChaptersFromPlayableFileUrl(media); + } + } else { + Log.e(TAG, "Could not load chapters from file url: local file not available"); + } + } } diff --git a/src/de/danoeh/antennapod/util/StorageUtils.java b/src/de/danoeh/antennapod/util/StorageUtils.java index cc0ba7136..9003ee907 100644 --- a/src/de/danoeh/antennapod/util/StorageUtils.java +++ b/src/de/danoeh/antennapod/util/StorageUtils.java @@ -10,13 +10,14 @@ import android.util.Log; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.activity.StorageErrorActivity; +import de.danoeh.antennapod.preferences.UserPreferences; /** Utility functions for handling storage errors */ public class StorageUtils { private static final String TAG = "StorageUtils"; public static boolean storageAvailable(Context context) { - File dir = PodcastApp.getDataFolder(context, null); + File dir = UserPreferences.getDataFolder(context, null); if (dir != null) { return dir.exists() && dir.canRead() && dir.canWrite(); } else { @@ -48,7 +49,7 @@ public class StorageUtils { /** Get the number of free bytes that are available on the external storage. */ public static long getFreeSpaceAvailable() { - StatFs stat = new StatFs(PodcastApp.getDataFolder( + StatFs stat = new StatFs(UserPreferences.getDataFolder( PodcastApp.getInstance(), null).getAbsolutePath()); long availableBlocks = stat.getAvailableBlocks(); long blockSize = stat.getBlockSize(); diff --git a/src/de/danoeh/antennapod/util/ThemeUtils.java b/src/de/danoeh/antennapod/util/ThemeUtils.java index ec47ed48e..8e593f3fb 100644 --- a/src/de/danoeh/antennapod/util/ThemeUtils.java +++ b/src/de/danoeh/antennapod/util/ThemeUtils.java @@ -1,14 +1,14 @@ package de.danoeh.antennapod.util; import android.util.Log; -import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.preferences.UserPreferences; public class ThemeUtils { private static final String TAG = "ThemeUtils"; public static int getSelectionBackgroundColor() { - switch (PodcastApp.getThemeResourceId()) { + switch (UserPreferences.getTheme()) { case R.style.Theme_AntennaPod_Dark: return R.color.selection_background_color_dark; case R.style.Theme_AntennaPod_Light: diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java index 37f5fd0d3..af8538e83 100644 --- a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java +++ b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java @@ -33,7 +33,7 @@ public class FeedMenuHandler { if (AppConfig.DEBUG) Log.d(TAG, "Preparing options menu"); menu.findItem(R.id.mark_all_read_item).setVisible( - selectedFeed.hasNewItems()); + selectedFeed.hasNewItems(true)); if (selectedFeed.getPaymentLink() != null) { menu.findItem(R.id.support_item).setVisible(true); } diff --git a/src/de/danoeh/antennapod/util/playback/ExternalMedia.java b/src/de/danoeh/antennapod/util/playback/ExternalMedia.java new file mode 100644 index 000000000..4574f088e --- /dev/null +++ b/src/de/danoeh/antennapod/util/playback/ExternalMedia.java @@ -0,0 +1,238 @@ +package de.danoeh.antennapod.util.playback; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +import org.apache.commons.io.IOUtils; + +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.media.MediaMetadataRetriever; +import android.os.Parcel; +import android.os.Parcelable; +import de.danoeh.antennapod.PodcastApp; +import de.danoeh.antennapod.feed.Chapter; +import de.danoeh.antennapod.feed.MediaType; +import de.danoeh.antennapod.util.ChapterUtils; +import de.danoeh.antennapod.util.FileNameGenerator; + +/** Represents a media file that is stored on the local storage device. */ +public class ExternalMedia implements Playable { + + public static final int PLAYABLE_TYPE_EXTERNAL_MEDIA = 2; + public static final String PREF_SOURCE_URL = "ExternalMedia.PrefSourceUrl"; + public static final String PREF_POSITION = "ExternalMedia.PrefPosition"; + public static final String PREF_MEDIA_TYPE = "ExternalMedia.PrefMediaType"; + + private String source; + + private String episodeTitle; + private String feedTitle; + private String shownotes; + private MediaType mediaType = MediaType.AUDIO; + private List<Chapter> chapters; + private String imageUrl; + private int duration; + private int position; + + public ExternalMedia(String source, MediaType mediaType) { + super(); + this.source = source; + this.mediaType = mediaType; + } + + public ExternalMedia(String source, MediaType mediaType, int position) { + this(source, mediaType); + this.position = position; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(source); + dest.writeString(mediaType.toString()); + dest.writeInt(position); + } + + @Override + public void writeToPreferences(Editor prefEditor) { + prefEditor.putString(PREF_SOURCE_URL, source); + prefEditor.putString(PREF_MEDIA_TYPE, mediaType.toString()); + prefEditor.putInt(PREF_POSITION, position); + } + + @Override + public void loadMetadata() throws PlayableException { + MediaMetadataRetriever mmr = new MediaMetadataRetriever(); + try { + mmr.setDataSource(source); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + throw new PlayableException("IllegalArgumentException when setting up MediaMetadataReceiver"); + } + episodeTitle = mmr + .extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE); + feedTitle = mmr + .extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM); + duration = Integer.parseInt(mmr + .extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)); + ChapterUtils.loadChaptersFromFileUrl(this); + byte[] imgData = mmr.getEmbeddedPicture(); + File cacheDir = PodcastApp.getInstance().getExternalCacheDir(); + if (cacheDir != null) { + OutputStream out = null; + try { + File tmpFile = File.createTempFile( + FileNameGenerator.generateFileName(source) + "-img", + null, cacheDir); + out = new BufferedOutputStream(new FileOutputStream(tmpFile)); + IOUtils.write(imgData, out); + imageUrl = tmpFile.getAbsolutePath(); + } catch (IOException e) { + e.printStackTrace(); + throw new PlayableException("IOException during loadMetadata()"); + } finally { + IOUtils.closeQuietly(out); + } + } + } + + @Override + public String getEpisodeTitle() { + return episodeTitle; + } + + @Override + public void loadShownotes(ShownoteLoaderCallback callback) { + callback.onShownotesLoaded(null); + } + + @Override + public List<Chapter> getChapters() { + return chapters; + } + + @Override + public String getWebsiteLink() { + return null; + } + + @Override + public String getPaymentLink() { + return null; + } + + @Override + public String getFeedTitle() { + return feedTitle; + } + + @Override + public String getImageFileUrl() { + return imageUrl; + } + + @Override + public Object getIdentifier() { + return source; + } + + @Override + public int getDuration() { + return duration; + } + + @Override + public int getPosition() { + return position; + } + + @Override + public MediaType getMediaType() { + return mediaType; + } + + @Override + public String getLocalMediaUrl() { + return source; + } + + @Override + public String getStreamUrl() { + return null; + } + + @Override + public boolean localFileAvailable() { + return true; + } + + @Override + public boolean streamAvailable() { + return false; + } + + @Override + public void saveCurrentPosition(SharedPreferences pref, int newPosition) { + SharedPreferences.Editor editor = pref.edit(); + editor.putInt(PREF_POSITION, newPosition); + position = newPosition; + editor.commit(); + } + + @Override + public void setPosition(int newPosition) { + position = newPosition; + } + + @Override + public void setDuration(int newDuration) { + duration = newDuration; + } + + @Override + public void onPlaybackStart() { + + } + + @Override + public void onPlaybackCompleted() { + + } + + @Override + public int getPlayableType() { + return PLAYABLE_TYPE_EXTERNAL_MEDIA; + } + + @Override + public void setChapters(List<Chapter> chapters) { + this.chapters = chapters; + } + + public static final Parcelable.Creator<ExternalMedia> CREATOR = new Parcelable.Creator<ExternalMedia>() { + public ExternalMedia createFromParcel(Parcel in) { + String source = in.readString(); + MediaType type = MediaType.valueOf(in.readString()); + int position = 0; + if (in.dataAvail() > 0) { + position = in.readInt(); + } + ExternalMedia extMedia = new ExternalMedia(source, type, position); + return extMedia; + } + + public ExternalMedia[] newArray(int size) { + return new ExternalMedia[size]; + } + }; + +} diff --git a/src/de/danoeh/antennapod/util/MediaPlayerError.java b/src/de/danoeh/antennapod/util/playback/MediaPlayerError.java index 93922c747..23ead581f 100644 --- a/src/de/danoeh/antennapod/util/MediaPlayerError.java +++ b/src/de/danoeh/antennapod/util/playback/MediaPlayerError.java @@ -1,4 +1,4 @@ -package de.danoeh.antennapod.util; +package de.danoeh.antennapod.util.playback; import android.content.Context; import android.media.MediaPlayer; diff --git a/src/de/danoeh/antennapod/util/playback/Playable.java b/src/de/danoeh/antennapod/util/playback/Playable.java new file mode 100644 index 000000000..bf38b9518 --- /dev/null +++ b/src/de/danoeh/antennapod/util/playback/Playable.java @@ -0,0 +1,197 @@ +package de.danoeh.antennapod.util.playback; + +import java.util.List; + +import android.content.SharedPreferences; +import android.os.Parcelable; +import android.util.Log; +import de.danoeh.antennapod.feed.Chapter; +import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.feed.FeedManager; +import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.feed.MediaType; + +/** Interface for objects that can be played by the PlaybackService. */ +public interface Playable extends Parcelable { + + /** + * Save information about the playable in a preference so that it can be + * restored later via PlayableUtils.createInstanceFromPreferences. + * Implementations must NOT call commit() after they have written the values + * to the preferences file. + */ + public void writeToPreferences(SharedPreferences.Editor prefEditor); + + /** + * This method is called from a separate thread by the PlaybackService. + * Playable objects should load their metadata in this method (for example: + * chapter marks). + */ + public void loadMetadata() throws PlayableException; + + /** Returns the title of the episode that this playable represents */ + public String getEpisodeTitle(); + + /** + * Loads shownotes. If the shownotes have to be loaded from a file or from a + * database, it should be done in a separate thread. After the shownotes + * have been loaded, callback.onShownotesLoaded should be called. + */ + public void loadShownotes(ShownoteLoaderCallback callback); + + /** + * Returns a list of chapter marks or null if this Playable has no chapters. + */ + public List<Chapter> getChapters(); + + /** Returns a link to a website that is meant to be shown in a browser */ + public String getWebsiteLink(); + + public String getPaymentLink(); + + /** Returns the title of the feed this Playable belongs to. */ + public String getFeedTitle(); + + /** Returns a file url to an image or null if no such image exists. */ + public String getImageFileUrl(); + + /** + * Returns a unique identifier, for example a file url or an ID from a + * database. + */ + public Object getIdentifier(); + + /** Return duration of object or 0 if duration is unknown. */ + public int getDuration(); + + /** Return position of object or 0 if position is unknown. */ + public int getPosition(); + + /** + * Returns the type of media. This method should return the correct value + * BEFORE loadMetadata() is called. + */ + public MediaType getMediaType(); + + /** + * Returns an url to a local file that can be played or null if this file + * does not exist. + */ + public String getLocalMediaUrl(); + + /** + * Returns an url to a file that can be streamed by the player or null if + * this url is not known. + */ + public String getStreamUrl(); + + /** + * Returns true if a local file that can be played is available. getFileUrl + * MUST return a non-null string if this method returns true. + */ + public boolean localFileAvailable(); + + /** + * Returns true if a streamable file is available. getStreamUrl MUST return + * a non-null string if this method returns true. + */ + public boolean streamAvailable(); + + /** + * Saves the current position of this object. Implementations can use the + * provided SharedPreference to save this information and retrieve it later + * via PlayableUtils.createInstanceFromPreferences. + */ + public void saveCurrentPosition(SharedPreferences pref, int newPosition); + + public void setPosition(int newPosition); + + public void setDuration(int newDuration); + + /** Is called by the PlaybackService when playback starts. */ + public void onPlaybackStart(); + + /** Is called by the PlaybackService when playback is completed. */ + public void onPlaybackCompleted(); + + /** + * Returns an integer that must be unique among all Playable classes. The + * return value is later used by PlayableUtils to determine the type of the + * Playable object that is restored. + */ + public int getPlayableType(); + + public void setChapters(List<Chapter> chapters); + + /** Provides utility methods for Playable objects. */ + public static class PlayableUtils { + private static final String TAG = "PlayableUtils"; + + /** + * Restores a playable object from a sharedPreferences file. + * + * @param type + * An integer that represents the type of the Playable object + * that is restored. + * @param pref + * The SharedPreferences file from which the Playable object + * is restored + * @return The restored Playable object + */ + public static Playable createInstanceFromPreferences(int type, + SharedPreferences pref) { + // ADD new Playable types here: + switch (type) { + case FeedMedia.PLAYABLE_TYPE_FEEDMEDIA: + long feedId = pref.getLong(FeedMedia.PREF_FEED_ID, -1); + long mediaId = pref.getLong(FeedMedia.PREF_MEDIA_ID, -1); + if (feedId != -1 && mediaId != -1) { + Feed feed = FeedManager.getInstance().getFeed(feedId); + if (feed != null) { + return FeedManager.getInstance().getFeedMedia(mediaId, + feed); + } + } + break; + case ExternalMedia.PLAYABLE_TYPE_EXTERNAL_MEDIA: + String source = pref.getString(ExternalMedia.PREF_SOURCE_URL, + null); + String mediaType = pref.getString( + ExternalMedia.PREF_MEDIA_TYPE, null); + if (source != null && mediaType != null) { + int position = pref.getInt(ExternalMedia.PREF_POSITION, 0); + return new ExternalMedia(source, + MediaType.valueOf(mediaType), position); + } + break; + } + Log.e(TAG, "Could not restore Playable object from preferences"); + return null; + } + } + + public static class PlayableException extends Exception { + private static final long serialVersionUID = 1L; + + public PlayableException() { + super(); + } + + public PlayableException(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + public PlayableException(String detailMessage) { + super(detailMessage); + } + + public PlayableException(Throwable throwable) { + super(throwable); + } + + } + + public static interface ShownoteLoaderCallback { + void onShownotesLoaded(String shownotes); + } +} diff --git a/src/de/danoeh/antennapod/util/PlaybackController.java b/src/de/danoeh/antennapod/util/playback/PlaybackController.java index 1a61555ff..699ff6699 100644 --- a/src/de/danoeh/antennapod/util/PlaybackController.java +++ b/src/de/danoeh/antennapod/util/playback/PlaybackController.java @@ -1,4 +1,4 @@ -package de.danoeh.antennapod.util; +package de.danoeh.antennapod.util.playback; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ScheduledFuture; @@ -30,8 +30,11 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.FeedManager; import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.preferences.PlaybackPreferences; import de.danoeh.antennapod.service.PlaybackService; import de.danoeh.antennapod.service.PlayerStatus; +import de.danoeh.antennapod.util.Converter; +import de.danoeh.antennapod.util.playback.Playable.PlayableUtils; /** * Communicates with the playback service. GUI classes should use this class to @@ -46,7 +49,7 @@ public abstract class PlaybackController { private Activity activity; private PlaybackService playbackService; - private FeedMedia media; + private Playable media; private PlayerStatus status; private ScheduledThreadPoolExecutor schedExecutor; @@ -185,26 +188,22 @@ public abstract class PlaybackController { Log.d(TAG, "Trying to restore last played media"); SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(activity.getApplicationContext()); - long mediaId = prefs.getLong(PlaybackService.PREF_LAST_PLAYED_ID, -1); - long feedId = prefs.getLong(PlaybackService.PREF_LAST_PLAYED_FEED_ID, - -1); - if (mediaId != -1 && feedId != -1) { - FeedMedia media = FeedManager.getInstance().getFeedMedia(mediaId); + long lastPlayedId = PlaybackPreferences.getLastPlayedId(); + if (lastPlayedId != PlaybackPreferences.NO_MEDIA_PLAYING) { + Playable media = PlayableUtils.createInstanceFromPreferences((int) lastPlayedId, prefs); if (media != null) { Intent serviceIntent = new Intent(activity, PlaybackService.class); - serviceIntent.putExtra(PlaybackService.EXTRA_FEED_ID, feedId); - serviceIntent.putExtra(PlaybackService.EXTRA_MEDIA_ID, mediaId); + serviceIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media); serviceIntent.putExtra( PlaybackService.EXTRA_START_WHEN_PREPARED, false); serviceIntent.putExtra( PlaybackService.EXTRA_PREPARE_IMMEDIATELY, false); - boolean fileExists = media.fileExists(); - boolean lastIsStream = prefs.getBoolean( - PlaybackService.PREF_LAST_IS_STREAM, true); - if (!fileExists && !lastIsStream) { + boolean fileExists = media.localFileAvailable(); + boolean lastIsStream = PlaybackPreferences.isLastIsStream(); + if (!fileExists && !lastIsStream && media instanceof FeedMedia) { FeedManager.getInstance().notifyMissingFeedMediaFile( - activity, media); + activity, (FeedMedia) media); } serviceIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, lastIsStream || !fileExists); @@ -586,7 +585,7 @@ public abstract class PlaybackController { } } - public FeedMedia getMedia() { + public Playable getMedia() { return media; } diff --git a/tests/src/de/danoeh/antennapod/test/FeedHandlerTest.java b/tests/src/de/danoeh/antennapod/test/FeedHandlerTest.java index d95fe69c3..132d40eba 100644 --- a/tests/src/de/danoeh/antennapod/test/FeedHandlerTest.java +++ b/tests/src/de/danoeh/antennapod/test/FeedHandlerTest.java @@ -93,10 +93,6 @@ public class FeedHandlerTest extends AndroidTestCase { Log.e(TAG, "Feed has no title"); return false; } - if (feed.getItems() == null) { - Log.e(TAG, "Feed has no items"); - return false; - } if (!hasValidFeedItems(feed)) { Log.e(TAG, "Feed has invalid items"); return false; @@ -122,7 +118,7 @@ public class FeedHandlerTest extends AndroidTestCase { } private boolean hasValidFeedItems(Feed feed) { - for (FeedItem item : feed.getItems()) { + for (FeedItem item : feed.getItemsArray()) { if (item.getTitle() == null) { Log.e(TAG, "Item has no title"); return false; |