diff options
Diffstat (limited to 'src/de')
20 files changed, 546 insertions, 155 deletions
diff --git a/src/de/danoeh/antennapod/activity/AddFeedActivity.java b/src/de/danoeh/antennapod/activity/AddFeedActivity.java index 39434fa87..7e702f28b 100644 --- a/src/de/danoeh/antennapod/activity/AddFeedActivity.java +++ b/src/de/danoeh/antennapod/activity/AddFeedActivity.java @@ -2,6 +2,8 @@ package de.danoeh.antennapod.activity; import java.util.Date; +import org.apache.commons.lang3.StringUtils; + import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; @@ -44,6 +46,9 @@ public class AddFeedActivity extends SherlockActivity { @Override protected void onCreate(Bundle savedInstanceState) { + if (AppConfig.DEBUG) + Log.d(TAG, "Was started with Intent " + getIntent().getAction() + + " and Data " + getIntent().getDataString()); setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -54,6 +59,10 @@ public class AddFeedActivity extends SherlockActivity { progDialog = new ProgressDialog(this); etxtFeedurl = (EditText) findViewById(R.id.etxtFeedurl); + if (StringUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) { + etxtFeedurl.setText(getIntent().getDataString()); + } + butBrowseMiroGuide = (Button) findViewById(R.id.butBrowseMiroguide); butOpmlImport = (Button) findViewById(R.id.butOpmlImport); butConfirm = (Button) findViewById(R.id.butConfirm); @@ -101,7 +110,7 @@ public class AddFeedActivity extends SherlockActivity { if (intent.getAction() != null && intent.getAction().equals(Intent.ACTION_SEND)) { if (AppConfig.DEBUG) - Log.d(TAG, "Was started with ACTION_SEND intent"); + Log.d(TAG, "Resuming with ACTION_SEND intent"); String text = intent.getStringExtra(Intent.EXTRA_TEXT); if (text != null) { etxtFeedurl.setText(text); diff --git a/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java b/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java index 89001f7f5..7269f7549 100644 --- a/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java +++ b/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java @@ -3,6 +3,8 @@ package de.danoeh.antennapod.activity; import android.content.Context; import android.content.res.TypedArray; import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -21,19 +23,23 @@ import de.danoeh.antennapod.feed.EventDistributor; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedManager; import de.danoeh.antennapod.preferences.UserPreferences; +import de.danoeh.antennapod.util.UndoBarController; -public class OrganizeQueueActivity extends SherlockListActivity { +public class OrganizeQueueActivity extends SherlockListActivity implements + UndoBarController.UndoListener { private static final String TAG = "OrganizeQueueActivity"; private static final int MENU_ID_ACCEPT = 2; private OrganizeAdapter adapter; + private UndoBarController undoBarController; @Override protected void onCreate(Bundle savedInstanceState) { setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); setContentView(R.layout.organize_queue); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); DragSortListView listView = (DragSortListView) getListView(); listView.setDropListener(dropListener); @@ -41,6 +47,9 @@ public class OrganizeQueueActivity extends SherlockListActivity { adapter = new OrganizeAdapter(this); setListAdapter(adapter); + + undoBarController = new UndoBarController(findViewById(R.id.undobar), + this); } @Override @@ -50,6 +59,13 @@ public class OrganizeQueueActivity extends SherlockListActivity { } @Override + protected void onStop() { + super.onStop(); + FeedManager.getInstance().autodownloadUndownloadedItems( + getApplicationContext()); + } + + @Override protected void onResume() { super.onResume(); EventDistributor.getInstance().register(contentUpdate); @@ -82,27 +98,24 @@ public class OrganizeQueueActivity extends SherlockListActivity { @Override public void remove(int which) { FeedManager manager = FeedManager.getInstance(); - manager.removeQueueItem(OrganizeQueueActivity.this, - (FeedItem) getListAdapter().getItem(which)); + FeedItem item = (FeedItem) getListAdapter().getItem(which); + manager.removeQueueItem(OrganizeQueueActivity.this, item, false); + undoBarController.showUndoBar(false, + getString(R.string.removed_from_queue), new UndoToken(item, + which)); } }; @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - TypedArray drawables = obtainStyledAttributes(new int[] { R.attr.navigation_accept }); - menu.add(Menu.NONE, MENU_ID_ACCEPT, Menu.NONE, R.string.confirm_label) - .setIcon(drawables.getDrawable(0)) - .setShowAsAction( - MenuItem.SHOW_AS_ACTION_IF_ROOM - | MenuItem.SHOW_AS_ACTION_WITH_TEXT); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case MENU_ID_ACCEPT: + case android.R.id.home: finish(); return true; default: @@ -110,11 +123,18 @@ public class OrganizeQueueActivity extends SherlockListActivity { } } - /** - * WARNING: If the PlaybackService is playing an episode from the queue, - * this list adapter will ignore the first item in the list to make sure - * that the position of the first queue item cannot be changed. - */ + @Override + public void onUndo(Parcelable token) { + // Perform the undo + UndoToken undoToken = (UndoToken) token; + FeedItem feedItem = undoToken.getFeedItem(); + int position = undoToken.getPosition(); + + FeedManager manager = FeedManager.getInstance(); + manager.addQueueItemAt(OrganizeQueueActivity.this, feedItem, position, + false); + } + private static class OrganizeAdapter extends BaseAdapter { private Context context; @@ -185,4 +205,52 @@ public class OrganizeQueueActivity extends SherlockListActivity { } + private static class UndoToken implements Parcelable { + private long itemId; + private long feedId; + private int position; + + public UndoToken(FeedItem item, int position) { + FeedManager manager = FeedManager.getInstance(); + this.itemId = item.getId(); + this.feedId = item.getFeed().getId(); + this.position = position; + } + + private UndoToken(Parcel in) { + itemId = in.readLong(); + feedId = in.readLong(); + position = in.readInt(); + } + + public static final Parcelable.Creator<UndoToken> CREATOR = new Parcelable.Creator<UndoToken>() { + public UndoToken createFromParcel(Parcel in) { + return new UndoToken(in); + } + + public UndoToken[] newArray(int size) { + return new UndoToken[size]; + } + }; + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeLong(itemId); + out.writeLong(feedId); + out.writeInt(position); + } + + public FeedItem getFeedItem() { + FeedManager manager = FeedManager.getInstance(); + return manager.getFeedItem(itemId, feedId); + } + + public int getPosition() { + return position; + } + } + } diff --git a/src/de/danoeh/antennapod/activity/PreferenceActivity.java b/src/de/danoeh/antennapod/activity/PreferenceActivity.java index 994cd2df6..9fcf57ac2 100644 --- a/src/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/src/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -12,6 +12,7 @@ import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Bundle; import android.preference.CheckBoxPreference; +import android.preference.ListPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; import android.preference.Preference.OnPreferenceClickListener; @@ -166,13 +167,40 @@ public class PreferenceActivity extends SherlockPreferenceActivity { return true; } }); - + buildUpdateIntervalPreference(); buildAutodownloadSelectedNetworsPreference(); setSelectedNetworksEnabled(UserPreferences .isEnableAutodownloadWifiFilter()); } + private void buildUpdateIntervalPreference() { + ListPreference pref = (ListPreference) findPreference(UserPreferences.PREF_UPDATE_INTERVAL); + String[] values = getResources().getStringArray( + R.array.update_intervall_values); + String[] entries = new String[values.length]; + for (int x = 0; x < values.length; x++) { + Integer v = Integer.parseInt(values[x]); + switch (v) { + case 0: + entries[x] = getString(R.string.pref_update_interval_hours_manual); + break; + case 1: + entries[x] = v + + " " + + getString(R.string.pref_update_interval_hours_singular); + break; + default: + entries[x] = v + " " + + getString(R.string.pref_update_interval_hours_plural); + break; + + } + } + pref.setEntries(entries); + + } + private void setSelectedNetworksEnabled(boolean b) { if (selectedNetworks != null) { for (Preference p : selectedNetworks) { @@ -205,9 +233,15 @@ public class PreferenceActivity extends SherlockPreferenceActivity { } private void setEpisodeCacheSizeText(int cacheSize) { - findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE).setSummary( - Integer.toString(cacheSize) - + getString(R.string.episodes_suffix)); + String s; + if (cacheSize == getResources().getInteger( + R.integer.episode_cache_size_unlimited)) { + s = getString(R.string.pref_episode_cache_unlimited); + } else { + s = Integer.toString(cacheSize) + + getString(R.string.episodes_suffix); + } + findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE).setSummary(s); } private void setDataFolderText() { diff --git a/src/de/danoeh/antennapod/adapter/DefaultFeedItemlistAdapter.java b/src/de/danoeh/antennapod/adapter/DefaultFeedItemlistAdapter.java index b603bb54f..2b49795c3 100644 --- a/src/de/danoeh/antennapod/adapter/DefaultFeedItemlistAdapter.java +++ b/src/de/danoeh/antennapod/adapter/DefaultFeedItemlistAdapter.java @@ -1,7 +1,5 @@ package de.danoeh.antennapod.adapter; -import java.text.DateFormat; - import android.content.Context; import android.content.res.TypedArray; import android.text.format.DateUtils; @@ -75,10 +73,9 @@ public class DefaultFeedItemlistAdapter extends BaseAdapter { holder.title.setText(item.getTitle()); holder.published.setText(convertView.getResources().getString( R.string.published_prefix) - + DateUtils.formatSameDayTime(item.getPubDate().getTime(), - System.currentTimeMillis(), DateFormat.MEDIUM, - DateFormat.SHORT)); - + + DateUtils.getRelativeTimeSpanString( + item.getPubDate().getTime(), + System.currentTimeMillis(), 0, 0)); if (item.getMedia() == null) { holder.type.setVisibility(View.GONE); holder.lenSize.setVisibility(View.GONE); diff --git a/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java b/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java index c0ccdc7fe..f97210cf3 100644 --- a/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java +++ b/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java @@ -1,7 +1,5 @@ package de.danoeh.antennapod.adapter; -import java.text.DateFormat; - import android.content.Context; import android.text.format.DateUtils; import android.view.LayoutInflater; @@ -60,9 +58,9 @@ public class DownloadLogAdapter extends BaseAdapter { } else { holder.title.setText(R.string.download_log_title_unknown); } - holder.date.setText(DateUtils.formatSameDayTime(status - .getCompletionDate().getTime(), System.currentTimeMillis(), - DateFormat.SHORT, DateFormat.SHORT)); + holder.date.setText(DateUtils.getRelativeTimeSpanString( + status.getCompletionDate().getTime(), + System.currentTimeMillis(), 0, 0)); if (status.isSuccessful()) { holder.successful.setTextColor(convertView.getResources().getColor( R.color.download_success_green)); diff --git a/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java b/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java index d7ea0c160..03b46cce5 100644 --- a/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java +++ b/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java @@ -1,7 +1,5 @@ package de.danoeh.antennapod.adapter; -import java.text.DateFormat; - import android.content.Context; import android.text.format.DateUtils; import android.view.LayoutInflater; @@ -77,16 +75,21 @@ public class FeedlistAdapter extends BaseAdapter { } holder.title.setText(feed.getTitle()); + int numOfItems = feed.getNumOfItems(true); if (DownloadRequester.getInstance().isDownloadingFile(feed)) { holder.lastUpdate.setText(R.string.refreshing_label); } else { - holder.lastUpdate.setText(convertView.getResources().getString( - R.string.last_update_prefix) - + DateUtils.formatSameDayTime(feed.getLastUpdate() - .getTime(), System.currentTimeMillis(), - DateFormat.MEDIUM, DateFormat.SHORT)); + if (numOfItems > 0) { + holder.lastUpdate.setText(convertView.getResources().getString( + R.string.most_recent_prefix) + + DateUtils.getRelativeTimeSpanString( + feed.getItemAtIndex(true, 0).getPubDate().getTime(), + System.currentTimeMillis(), 0, 0)); + } else { + holder.lastUpdate.setText(""); + } } - holder.numberOfEpisodes.setText(feed.getNumOfItems(true) + holder.numberOfEpisodes.setText(numOfItems + convertView.getResources() .getString(R.string.episodes_suffix)); diff --git a/src/de/danoeh/antennapod/adapter/InternalFeedItemlistAdapter.java b/src/de/danoeh/antennapod/adapter/InternalFeedItemlistAdapter.java index 7b898385e..e5c12f018 100644 --- a/src/de/danoeh/antennapod/adapter/InternalFeedItemlistAdapter.java +++ b/src/de/danoeh/antennapod/adapter/InternalFeedItemlistAdapter.java @@ -1,7 +1,5 @@ package de.danoeh.antennapod.adapter; -import java.text.DateFormat; - import android.content.Context; import android.content.res.TypedArray; import android.text.format.DateUtils; @@ -122,9 +120,9 @@ public class InternalFeedItemlistAdapter extends DefaultFeedItemlistAdapter { holder.published.setText(convertView.getResources().getString( R.string.published_prefix) - + DateUtils.formatSameDayTime(item.getPubDate().getTime(), - System.currentTimeMillis(), DateFormat.MEDIUM, - DateFormat.SHORT)); + + DateUtils.getRelativeTimeSpanString( + item.getPubDate().getTime(), + System.currentTimeMillis(), 0, 0)); FeedMedia media = item.getMedia(); if (media == null) { diff --git a/src/de/danoeh/antennapod/adapter/MiroGuideItemlistAdapter.java b/src/de/danoeh/antennapod/adapter/MiroGuideItemlistAdapter.java index 4cee0a64a..f12345f84 100644 --- a/src/de/danoeh/antennapod/adapter/MiroGuideItemlistAdapter.java +++ b/src/de/danoeh/antennapod/adapter/MiroGuideItemlistAdapter.java @@ -1,6 +1,5 @@ package de.danoeh.antennapod.adapter; -import java.text.DateFormat; import java.util.List; import android.content.Context; @@ -42,9 +41,8 @@ public class MiroGuideItemlistAdapter extends ArrayAdapter<MiroGuideItem> { holder.title.setText(item.getName()); if (item.getDate() != null) { - holder.date.setText(DateUtils.formatSameDayTime(item.getDate() - .getTime(), System.currentTimeMillis(), DateFormat.SHORT, - DateFormat.SHORT)); + holder.date.setText(DateUtils.getRelativeTimeSpanString( + item.getDate().getTime(), System.currentTimeMillis(), 0, 0)); holder.date.setVisibility(View.VISIBLE); } else { holder.date.setVisibility(View.GONE); diff --git a/src/de/danoeh/antennapod/feed/FeedManager.java b/src/de/danoeh/antennapod/feed/FeedManager.java index bdffdc667..a1a8c6c32 100644 --- a/src/de/danoeh/antennapod/feed/FeedManager.java +++ b/src/de/danoeh/antennapod/feed/FeedManager.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.TreeSet; +import java.util.Comparator; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; @@ -676,8 +677,12 @@ public class FeedManager { int deletedEpisodes = performAutoCleanup(context, getPerformAutoCleanupArgs(undownloadedEpisodes)); int episodeSpaceLeft = undownloadedEpisodes; - if (UserPreferences.getEpisodeCacheSize() < downloadedEpisodes - + undownloadedEpisodes) { + boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences + .getEpisodeCacheSizeUnlimited(); + + if (!cacheIsUnlimited + && UserPreferences.getEpisodeCacheSize() < downloadedEpisodes + + undownloadedEpisodes) { episodeSpaceLeft = UserPreferences.getEpisodeCacheSize() - (downloadedEpisodes - deletedEpisodes); } @@ -732,7 +737,9 @@ public class FeedManager { * that the number of episodes fits into the episode cache. * */ private int getPerformAutoCleanupArgs(final int episodeNumber) { - if (episodeNumber >= 0) { + if (episodeNumber >= 0 + && UserPreferences.getEpisodeCacheSize() != UserPreferences + .getEpisodeCacheSizeUnlimited()) { int downloadedEpisodes = getNumberOfDownloadedEpisodes(); if (downloadedEpisodes + episodeNumber >= UserPreferences .getEpisodeCacheSize()) { @@ -760,24 +767,45 @@ public class FeedManager { * @return The number of episodes that were actually deleted * */ private int performAutoCleanup(Context context, final int episodeNumber) { - int counter = 0; - if (episodeNumber > 0) { - int episodesLeft = episodeNumber; - feedloop: for (Feed feed : feeds) { - for (FeedItem item : feed.getItems()) { - if (item.hasMedia() && item.getMedia().isDownloaded()) { - if (!isInQueue(item) && item.isRead()) { - deleteFeedMedia(context, item.getMedia()); - counter++; - episodesLeft--; - if (episodesLeft == 0) { - break feedloop; - } - } - } + List<FeedItem> candidates = new ArrayList<FeedItem>(); + List<FeedItem> delete; + for (Feed feed : feeds) { + for (FeedItem item : feed.getItems()) { + if (item.hasMedia() && item.getMedia().isDownloaded() + && !isInQueue(item) && item.isRead()) { + candidates.add(item); } } } + + Collections.sort(candidates, new Comparator<FeedItem>() { + @Override + public int compare(FeedItem lhs, FeedItem rhs) { + Date l = lhs.getMedia().getPlaybackCompletionDate(); + Date r = rhs.getMedia().getPlaybackCompletionDate(); + + if (l == null) { + l = new Date(0); + } + if (r == null) { + r = new Date(0); + } + return l.compareTo(r); + } + }); + + if (candidates.size() > episodeNumber) { + delete = candidates.subList(0, episodeNumber); + } else { + delete = candidates; + } + + for (FeedItem item : delete) { + deleteFeedMedia(context, item.getMedia()); + } + + int counter = delete.size(); + if (AppConfig.DEBUG) Log.d(TAG, String.format( "Auto-delete deleted %d episodes (%d requested)", counter, @@ -970,7 +998,8 @@ public class FeedManager { } /** Removes a FeedItem from the queue. */ - public void removeQueueItem(final Context context, FeedItem item) { + public void removeQueueItem(final Context context, FeedItem item, + final boolean performAutoDownload) { boolean removed = queue.remove(item); if (removed) { dbExec.execute(new Runnable() { @@ -985,12 +1014,14 @@ public class FeedManager { }); } - new Thread() { - @Override - public void run() { - autodownloadUndownloadedItems(context); - } - }.start(); + if (performAutoDownload) { + new Thread() { + @Override + public void run() { + autodownloadUndownloadedItems(context); + } + }.start(); + } eventDist.sendQueueUpdateBroadcast(); } @@ -1980,4 +2011,4 @@ public class FeedManager { } } -}
\ No newline at end of file +} diff --git a/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java b/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java index a1dfa51d4..10f43718f 100644 --- a/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java +++ b/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java @@ -9,7 +9,6 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.TypedArray; -import android.graphics.Picture; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; @@ -23,10 +22,9 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.webkit.WebChromeClient; import android.webkit.WebSettings.LayoutAlgorithm; import android.webkit.WebView; -import android.webkit.WebView.PictureListener; +import android.webkit.WebViewClient; import android.widget.Toast; import com.actionbarsherlock.app.SherlockFragment; @@ -101,7 +99,6 @@ public class ItemDescriptionFragment extends SherlockFragment { if (AppConfig.DEBUG) Log.d(TAG, "Creating view"); webvDescription = new WebView(getActivity()); - 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) { @@ -115,6 +112,32 @@ public class ItemDescriptionFragment extends SherlockFragment { LayoutAlgorithm.NARROW_COLUMNS); webvDescription.getSettings().setLoadWithOverviewMode(true); webvDescription.setOnLongClickListener(webViewLongClickListener); + webvDescription.setWebViewClient(new WebViewClient() { + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + startActivity(intent); + return true; + } + + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + if (AppConfig.DEBUG) + Log.d(TAG, "Page finished"); + // Restoring the scroll position might not always work + view.postDelayed(new Runnable() { + + @Override + public void run() { + restoreFromPreference(); + } + + }, 50); + } + + }); registerForContextMenu(webvDescription); return webvDescription; } @@ -336,7 +359,6 @@ public class ItemDescriptionFragment extends SherlockFragment { String data; - @SuppressWarnings("deprecation") @Override protected void onPostExecute(Void result) { super.onPostExecute(result); @@ -350,16 +372,6 @@ public class ItemDescriptionFragment extends SherlockFragment { if (AppConfig.DEBUG) Log.d(TAG, "Webview loaded"); webViewLoader = null; - webvDescription.setPictureListener(new PictureListener() { - - @Override - @Deprecated - public void onNewPicture(WebView view, Picture picture) { - restoreFromPreference(); - - } - }); - } @Override @@ -434,17 +446,21 @@ public class ItemDescriptionFragment extends SherlockFragment { if (saveState) { if (AppConfig.DEBUG) Log.d(TAG, "Restoring from preferences"); - SharedPreferences prefs = getActivity().getSharedPreferences(PREF, - Activity.MODE_PRIVATE); - String id = prefs.getString(PREF_PLAYABLE_ID, ""); - int scrollY = prefs.getInt(PREF_SCROLL_Y, -1); - if (scrollY != -1 && media != null - && id.equals(media.getIdentifier().toString()) - && webvDescription != null) { - if (AppConfig.DEBUG) - Log.d(TAG, "Restored scroll Position: " + scrollY); - webvDescription.scrollTo(webvDescription.getScrollX(), scrollY); - return true; + Activity activity = getActivity(); + if (activity != null) { + SharedPreferences prefs = activity.getSharedPreferences( + PREF, Activity.MODE_PRIVATE); + String id = prefs.getString(PREF_PLAYABLE_ID, ""); + int scrollY = prefs.getInt(PREF_SCROLL_Y, -1); + if (scrollY != -1 && media != null + && id.equals(media.getIdentifier().toString()) + && webvDescription != null) { + if (AppConfig.DEBUG) + Log.d(TAG, "Restored scroll Position: " + scrollY); + webvDescription.scrollTo(webvDescription.getScrollX(), + scrollY); + return true; + } } } return false; diff --git a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java b/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java index 4637c7725..99bef4bd8 100644 --- a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java +++ b/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java @@ -20,7 +20,7 @@ import android.net.Uri; public class MiroGuideConnector { private HttpClient httpClient; - private static final String HOST_URL = "https://www.miroguide.com/api/"; + private static final String HOST_URL = "http://www.miroguide.com/api/"; private static final String PATH_GET_CHANNELS = "get_channels"; private static final String PATH_LIST_CATEGORIES = "list_categories"; private static final String PATH_GET_CHANNEL = "get_channel"; diff --git a/src/de/danoeh/antennapod/preferences/UserPreferences.java b/src/de/danoeh/antennapod/preferences/UserPreferences.java index bd491a5cf..f2f35f41d 100644 --- a/src/de/danoeh/antennapod/preferences/UserPreferences.java +++ b/src/de/danoeh/antennapod/preferences/UserPreferences.java @@ -42,6 +42,8 @@ public class UserPreferences implements private static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks"; public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize"; + private static int EPISODE_CACHE_SIZE_UNLIMITED = -1; + private static UserPreferences instance; private Context context; @@ -86,6 +88,8 @@ public class UserPreferences implements private void loadPreferences() { SharedPreferences sp = PreferenceManager .getDefaultSharedPreferences(context); + EPISODE_CACHE_SIZE_UNLIMITED = context.getResources().getInteger( + R.integer.episode_cache_size_unlimited); pauseOnHeadsetDisconnect = sp.getBoolean( PREF_PAUSE_ON_HEADSET_DISCONNECT, true); followQueue = sp.getBoolean(PREF_FOLLOW_QUEUE, false); @@ -101,7 +105,7 @@ public class UserPreferences implements PREF_ENABLE_AUTODL_WIFI_FILTER, false); autodownloadSelectedNetworks = StringUtils.split( sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ','); - episodeCacheSize = Integer.valueOf(sp.getString( + episodeCacheSize = readEpisodeCacheSize(sp.getString( PREF_EPISODE_CACHE_SIZE, "20")); enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false); } @@ -122,6 +126,15 @@ public class UserPreferences implements return TimeUnit.HOURS.toMillis(hours); } + private int readEpisodeCacheSize(String valueFromPrefs) { + if (valueFromPrefs.equals(context + .getString(R.string.pref_episode_cache_unlimited))) { + return EPISODE_CACHE_SIZE_UNLIMITED; + } else { + return Integer.valueOf(valueFromPrefs); + } + } + private static void instanceAvailable() { if (instance == null) { throw new IllegalStateException( @@ -179,6 +192,15 @@ public class UserPreferences implements return instance.autodownloadSelectedNetworks; } + public static int getEpisodeCacheSizeUnlimited() { + return EPISODE_CACHE_SIZE_UNLIMITED; + } + + /** + * Returns the capacity of the episode cache. This method will return the + * negative integer EPISODE_CACHE_SIZE_UNLIMITED if the cache size is set to + * 'unlimited'. + */ public static int getEpisodeCacheSize() { instanceAvailable(); return instance.episodeCacheSize; @@ -224,7 +246,7 @@ public class UserPreferences implements autodownloadSelectedNetworks = StringUtils.split( sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ','); } else if (key.equals(PREF_EPISODE_CACHE_SIZE)) { - episodeCacheSize = Integer.valueOf(sp.getString( + episodeCacheSize = readEpisodeCacheSize(sp.getString( PREF_EPISODE_CACHE_SIZE, "20")); } else if (key.equals(PREF_ENABLE_AUTODL)) { enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false); diff --git a/src/de/danoeh/antennapod/service/PlaybackService.java b/src/de/danoeh/antennapod/service/PlaybackService.java index 11da79015..409ac6b48 100644 --- a/src/de/danoeh/antennapod/service/PlaybackService.java +++ b/src/de/danoeh/antennapod/service/PlaybackService.java @@ -73,8 +73,8 @@ public class PlaybackService extends Service { public static final String EXTRA_PREPARE_IMMEDIATELY = "extra.de.danoeh.antennapod.service.prepareImmediately"; public static final String ACTION_PLAYER_STATUS_CHANGED = "action.de.danoeh.antennapod.service.playerStatusChanged"; - private static final String AVRCP_ACTION_PLAYER_STATUS_CHANGED= "com.android.music.playstatechanged"; - + private static final String AVRCP_ACTION_PLAYER_STATUS_CHANGED = "com.android.music.playstatechanged"; + public static final String ACTION_PLAYER_NOTIFICATION = "action.de.danoeh.antennapod.service.playerNotification"; public static final String EXTRA_NOTIFICATION_CODE = "extra.de.danoeh.antennapod.service.notificationCode"; public static final String EXTRA_NOTIFICATION_TYPE = "extra.de.danoeh.antennapod.service.notificationType"; @@ -363,13 +363,15 @@ public class PlaybackService extends Service { } // Intent values appear to be valid // check if already playing and playbackType is the same - } else if (media == null || playable != media + } else if (media == null + || !playable.getIdentifier().equals(media.getIdentifier()) || playbackType != shouldStream) { pause(true, false); player.reset(); sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); if (media == null - || playable.getIdentifier() != media.getIdentifier()) { + || !playable.getIdentifier().equals( + media.getIdentifier())) { media = playable; } @@ -730,7 +732,7 @@ public class PlaybackService extends Service { isInQueue = media instanceof FeedMedia && manager.isInQueue(((FeedMedia) media).getItem()); if (isInQueue) { - manager.removeQueueItem(PlaybackService.this, item); + manager.removeQueueItem(PlaybackService.this, item, true); } manager.addItemToPlaybackHistory(PlaybackService.this, item); manager.setFeedMedia(PlaybackService.this, (FeedMedia) media); @@ -1226,23 +1228,23 @@ public class PlaybackService extends Service { private void bluetoothNotifyChange() { boolean isPlaying = false; - + if (status == PlayerStatus.PLAYING) { isPlaying = true; } - - Intent i = new Intent(AVRCP_ACTION_PLAYER_STATUS_CHANGED); - i.putExtra("id", 1); - i.putExtra("artist", ""); - i.putExtra("album", media.getFeedTitle()); - i.putExtra("track", media.getEpisodeTitle()); - i.putExtra("playing", isPlaying); - i.putExtra("ListSize", manager.getQueueSize(false)); - i.putExtra("duration", media.getDuration()); - i.putExtra("position", media.getPosition()); - sendBroadcast(i); + + Intent i = new Intent(AVRCP_ACTION_PLAYER_STATUS_CHANGED); + i.putExtra("id", 1); + i.putExtra("artist", ""); + i.putExtra("album", media.getFeedTitle()); + i.putExtra("track", media.getEpisodeTitle()); + i.putExtra("playing", isPlaying); + i.putExtra("ListSize", manager.getQueueSize(false)); + i.putExtra("duration", media.getDuration()); + i.putExtra("position", media.getPosition()); + sendBroadcast(i); } - + /** * Pauses playback when the headset is disconnected and the preference is * set diff --git a/src/de/danoeh/antennapod/syndication/namespace/NSRSS20.java b/src/de/danoeh/antennapod/syndication/namespace/NSRSS20.java index 4d0b42132..5a2c6005e 100644 --- a/src/de/danoeh/antennapod/syndication/namespace/NSRSS20.java +++ b/src/de/danoeh/antennapod/syndication/namespace/NSRSS20.java @@ -78,6 +78,15 @@ public class NSRSS20 extends Namespace { @Override public void handleElementEnd(String localName, HandlerState state) { if (localName.equals(ITEM)) { + if (state.getCurrentItem() != null) { + // the title tag is optional in RSS 2.0. The description is used + // as a + // title if the item has no title-tag. + if (state.getCurrentItem().getTitle() == null) { + state.getCurrentItem().setTitle( + state.getCurrentItem().getDescription()); + } + } state.setCurrentItem(null); } else if (state.getTagstack().size() >= 2 && state.getContentBuf() != null) { @@ -98,7 +107,8 @@ public class NSRSS20 extends Namespace { state.getCurrentItem().setTitle(content); } else if (second.equals(CHANNEL)) { state.getFeed().setTitle(content); - } else if (second.equals(IMAGE) && third != null && third.equals(CHANNEL)) { + } else if (second.equals(IMAGE) && third != null + && third.equals(CHANNEL)) { state.getFeed().getImage().setTitle(content); } } else if (top.equals(LINK)) { @@ -110,7 +120,8 @@ public class NSRSS20 extends Namespace { } else if (top.equals(PUBDATE) && second.equals(ITEM)) { state.getCurrentItem().setPubDate( SyndDateUtils.parseRFC822Date(content)); - } else if (top.equals(URL) && second.equals(IMAGE) && third != null && third.equals(CHANNEL)) { + } else if (top.equals(URL) && second.equals(IMAGE) && third != null + && third.equals(CHANNEL)) { state.getFeed().getImage().setDownload_url(content); } else if (localName.equals(DESCR)) { if (second.equals(CHANNEL)) { diff --git a/src/de/danoeh/antennapod/util/Converter.java b/src/de/danoeh/antennapod/util/Converter.java index f02e8ea69..6ef47af31 100644 --- a/src/de/danoeh/antennapod/util/Converter.java +++ b/src/de/danoeh/antennapod/util/Converter.java @@ -78,4 +78,5 @@ public final class Converter { return String.format("%02d:%02d", h, m); } + } diff --git a/src/de/danoeh/antennapod/util/UndoBarController.java b/src/de/danoeh/antennapod/util/UndoBarController.java new file mode 100644 index 000000000..e726717a1 --- /dev/null +++ b/src/de/danoeh/antennapod/util/UndoBarController.java @@ -0,0 +1,135 @@ +/* + * Copyright 2012 Roman Nurik + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.danoeh.antennapod.util; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.os.Bundle; +import android.os.Handler; +import android.os.Parcelable; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewPropertyAnimator; +import android.widget.TextView; + +import de.danoeh.antennapod.R; + +public class UndoBarController { + private View mBarView; + private TextView mMessageView; + private ViewPropertyAnimator mBarAnimator; + private Handler mHideHandler = new Handler(); + + private UndoListener mUndoListener; + + // State objects + private Parcelable mUndoToken; + private CharSequence mUndoMessage; + + public interface UndoListener { + void onUndo(Parcelable token); + } + + public UndoBarController(View undoBarView, UndoListener undoListener) { + mBarView = undoBarView; + mBarAnimator = mBarView.animate(); + mUndoListener = undoListener; + + mMessageView = (TextView) mBarView.findViewById(R.id.undobar_message); + mBarView.findViewById(R.id.undobar_button) + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + hideUndoBar(false); + mUndoListener.onUndo(mUndoToken); + } + }); + + hideUndoBar(true); + } + + public void showUndoBar(boolean immediate, CharSequence message, Parcelable undoToken) { + mUndoToken = undoToken; + mUndoMessage = message; + mMessageView.setText(mUndoMessage); + + mHideHandler.removeCallbacks(mHideRunnable); + mHideHandler.postDelayed(mHideRunnable, + mBarView.getResources().getInteger(R.integer.undobar_hide_delay)); + + mBarView.setVisibility(View.VISIBLE); + if (immediate) { + mBarView.setAlpha(1); + } else { + mBarAnimator.cancel(); + mBarAnimator + .alpha(1) + .setDuration( + mBarView.getResources() + .getInteger(android.R.integer.config_shortAnimTime)) + .setListener(null); + } + } + + public void hideUndoBar(boolean immediate) { + mHideHandler.removeCallbacks(mHideRunnable); + if (immediate) { + mBarView.setVisibility(View.GONE); + mBarView.setAlpha(0); + mUndoMessage = null; + mUndoToken = null; + + } else { + mBarAnimator.cancel(); + mBarAnimator + .alpha(0) + .setDuration(mBarView.getResources() + .getInteger(android.R.integer.config_shortAnimTime)) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mBarView.setVisibility(View.GONE); + mUndoMessage = null; + mUndoToken = null; + } + }); + } + } + + public void onSaveInstanceState(Bundle outState) { + outState.putCharSequence("undo_message", mUndoMessage); + outState.putParcelable("undo_token", mUndoToken); + } + + public void onRestoreInstanceState(Bundle savedInstanceState) { + if (savedInstanceState != null) { + mUndoMessage = savedInstanceState.getCharSequence("undo_message"); + mUndoToken = savedInstanceState.getParcelable("undo_token"); + + if (mUndoToken != null || !TextUtils.isEmpty(mUndoMessage)) { + showUndoBar(true, mUndoMessage, mUndoToken); + } + } + } + + private Runnable mHideRunnable = new Runnable() { + @Override + public void run() { + hideUndoBar(false); + } + }; +} diff --git a/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java b/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java index e1cafe85d..f897f886c 100644 --- a/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java +++ b/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java @@ -2,18 +2,24 @@ package de.danoeh.antennapod.util.id3reader; import java.io.IOException; import java.io.InputStream; +import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; +import android.util.Log; + +import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.ID3Chapter; import de.danoeh.antennapod.util.id3reader.model.FrameHeader; import de.danoeh.antennapod.util.id3reader.model.TagHeader; public class ChapterReader extends ID3Reader { + private static final String TAG = "ID3ChapterReader"; private static final String FRAME_ID_CHAPTER = "CHAP"; private static final String FRAME_ID_TITLE = "TIT2"; + private static final String FRAME_ID_LINK = "WXXX"; private List<Chapter> chapters; private ID3Chapter currentChapter; @@ -33,27 +39,45 @@ public class ChapterReader extends ID3Reader { if (currentChapter != null) { if (!hasId3Chapter(currentChapter)) { chapters.add(currentChapter); - System.out.println("Found chapter: " + currentChapter); + if (AppConfig.DEBUG) Log.d(TAG, "Found chapter: " + currentChapter); currentChapter = null; } } - String elementId = readISOString(input, Integer.MAX_VALUE); + StringBuffer elementId = new StringBuffer(); + readISOString(elementId, input, Integer.MAX_VALUE); char[] startTimeSource = readBytes(input, 4); long startTime = ((int) startTimeSource[0] << 24) | ((int) startTimeSource[1] << 16) | ((int) startTimeSource[2] << 8) | startTimeSource[3]; - currentChapter = new ID3Chapter(elementId, startTime); + currentChapter = new ID3Chapter(elementId.toString(), startTime); skipBytes(input, 12); return ID3Reader.ACTION_DONT_SKIP; } else if (header.getId().equals(FRAME_ID_TITLE)) { if (currentChapter != null && currentChapter.getTitle() == null) { + StringBuffer title = new StringBuffer(); + readString(title, input, header.getSize()); currentChapter - .setTitle(readString(input, header.getSize())); - System.out.println("Found title: " + currentChapter.getTitle()); + .setTitle(title.toString()); + if (AppConfig.DEBUG) Log.d(TAG, "Found title: " + currentChapter.getTitle()); return ID3Reader.ACTION_DONT_SKIP; } - } + } else if (header.getId().equals(FRAME_ID_LINK)) { + if (currentChapter != null) { + // skip description + int descriptionLength = readString(null, input, header.getSize()); + StringBuffer link = new StringBuffer(); + readISOString(link, input, header.getSize() - descriptionLength); + String decodedLink = URLDecoder.decode(link.toString(), "UTF-8"); + + currentChapter.setLink(decodedLink); + + if (AppConfig.DEBUG) Log.d(TAG, "Found link: " + currentChapter.getLink()); + return ID3Reader.ACTION_DONT_SKIP; + } + } else if (header.getId().equals("APIC")) { + Log.d(TAG, header.toString()); + } return super.onStartFrameHeader(header, input); } diff --git a/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java b/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java index dff6d77e8..92f817363 100644 --- a/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java +++ b/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java @@ -24,7 +24,11 @@ public class ID3Reader { protected int readerPosition; - private static final byte ENCODING_UNICODE = 1; + private static final byte ENCODING_UTF16_WITH_BOM = 1; + private static final byte ENCODING_UTF16_WITHOUT_BOM = 2; + private static final byte ENCODING_UTF8 = 3; + + private TagHeader tagHeader; public ID3Reader() { } @@ -34,7 +38,7 @@ public class ID3Reader { int rc; readerPosition = 0; char[] tagHeaderSource = readBytes(input, HEADER_LENGTH); - TagHeader tagHeader = createTagHeader(tagHeaderSource); + tagHeader = createTagHeader(tagHeaderSource); if (tagHeader == null) { onNoTagHeaderFound(); } else { @@ -124,12 +128,12 @@ public class ID3Reader { + HEADER_LENGTH); } if (hasTag) { - String id = null; - id = new String(source, 0, ID3_LENGTH); + String id = new String(source, 0, ID3_LENGTH); char version = (char) ((source[3] << 8) | source[4]); byte flags = (byte) source[5]; int size = (source[6] << 24) | (source[7] << 16) | (source[8] << 8) | source[9]; + size = unsynchsafe(size); return new TagHeader(id, size, version, flags); } else { return null; @@ -142,48 +146,89 @@ public class ID3Reader { throw new ID3ReaderException("Length of header must be " + HEADER_LENGTH); } - String id = null; - id = new String(source, 0, FRAME_ID_LENGTH); - int size = (((int) source[4]) << 24) | (((int) source[5]) << 16) - | (((int) source[6]) << 8) | source[7]; + String id = new String(source, 0, FRAME_ID_LENGTH); + + int size = (((int) source[4]) << 24) | (((int) source[5]) << 16) + | (((int) source[6]) << 8) | source[7]; + if (tagHeader != null && tagHeader.getVersion() >= 0x0400) { + size = unsynchsafe(size); + } char flags = (char) ((source[8] << 8) | source[9]); return new FrameHeader(id, size, flags); } - protected String readString(InputStream input, int max) throws IOException, + private int unsynchsafe(int in) { + int out = 0; + int mask = 0x7F000000; + + while (mask != 0) { + out >>= 1; + out |= in & mask; + mask >>= 8; + } + + return out; + } + + protected int readString(StringBuffer buffer, InputStream input, int max) throws IOException, ID3ReaderException { if (max > 0) { char[] encoding = readBytes(input, 1); max--; - if (encoding[0] == ENCODING_UNICODE) { - return readUnicodeString(input, max); - } else { - return readISOString(input, max); + if (encoding[0] == ENCODING_UTF16_WITH_BOM || encoding[0] == ENCODING_UTF16_WITHOUT_BOM) { + return readUnicodeString(buffer, input, max, Charset.forName("UTF-16")) + 1; // take encoding byte into account + } else if (encoding[0] == ENCODING_UTF8) { + return readUnicodeString(buffer, input, max, Charset.forName("UTF-8")) + 1; // take encoding byte into account + } else { + return readISOString(buffer, input, max) + 1; // take encoding byte into account } } else { - return ""; + if (buffer != null) { + buffer.append(""); + } + return 0; } } - protected String readISOString(InputStream input, int max) + protected int readISOString(StringBuffer buffer, InputStream input, int max) throws IOException, ID3ReaderException { int bytesRead = 0; - StringBuilder builder = new StringBuilder(); char c; while (++bytesRead <= max && (c = (char) input.read()) > 0) { - builder.append(c); + if (buffer != null) { + buffer.append(c); + } } - return builder.toString(); + return bytesRead; } - private String readUnicodeString(InputStream input, int max) + private int readUnicodeString(StringBuffer strBuffer, InputStream input, int max, Charset charset) throws IOException, ID3ReaderException { byte[] buffer = new byte[max]; - IOUtils.readFully(input, buffer); - Charset charset = Charset.forName("UTF-16"); - return charset.newDecoder().decode(ByteBuffer.wrap(buffer)).toString(); + int c, cZero = -1; + int i = 0; + for (; i < max; i++) { + c = input.read(); + if (c == -1) { + break; + } else if (c == 0) { + if (cZero == 0) { + // termination character found + break; + } else { + cZero = 0; + } + } else { + buffer[i] = (byte) c; + cZero = -1; + } + } + if (strBuffer != null) { + strBuffer.append(charset.newDecoder().decode(ByteBuffer.wrap(buffer)).toString()); + } + return i; } public int onStartTagHeader(TagHeader header) { diff --git a/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java b/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java index 2c0d8e5ba..df73393a5 100644 --- a/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java +++ b/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java @@ -11,8 +11,7 @@ public class FrameHeader extends Header { @Override public String toString() { - return "FrameHeader [flags=" + Integer.toString(flags) + ", id=" + id + ", size=" + size - + "]"; - } + return String.format("FrameHeader [flags=%s, id=%s, size=%s]", Integer.toBinaryString(flags), id, Integer.toBinaryString(size)); + } } diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java index 64bcb1cf2..472124bf7 100644 --- a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java +++ b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java @@ -140,7 +140,7 @@ public class FeedItemMenuHandler { manager.addQueueItem(context, selectedItem); break; case R.id.remove_from_queue_item: - manager.removeQueueItem(context, selectedItem); + manager.removeQueueItem(context, selectedItem, true); break; case R.id.stream_item: manager.playMedia(context, selectedItem.getMedia(), true, true, |