summaryrefslogtreecommitdiff
path: root/app/src/main/java/de/danoeh/antennapod/activity
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/de/danoeh/antennapod/activity')
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/AboutActivity.java32
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java749
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java249
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/DirectoryChooserActivity.java370
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java110
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java198
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/FlattrAuthActivity.java127
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java438
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java500
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java428
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java134
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java90
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java38
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java171
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/OpmlImportHolder.java29
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java120
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivityGingerbread.java96
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java75
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java360
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java372
20 files changed, 4686 insertions, 0 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/AboutActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/AboutActivity.java
new file mode 100644
index 000000000..cf7de1709
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/AboutActivity.java
@@ -0,0 +1,32 @@
+package de.danoeh.antennapod.activity;
+
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import de.danoeh.antennapod.R;
+
+/** Displays the 'about' screen */
+public class AboutActivity extends ActionBarActivity {
+
+ private WebView webview;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getSupportActionBar().hide();
+ setContentView(R.layout.about);
+ webview = (WebView) findViewById(R.id.webvAbout);
+ webview.setWebViewClient(new WebViewClient() {
+
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ view.loadUrl(url);
+ return false;
+ }
+
+ });
+ webview.loadUrl("file:///android_asset/about.html");
+ }
+
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java
new file mode 100644
index 000000000..eb7a844db
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java
@@ -0,0 +1,749 @@
+package de.danoeh.antennapod.activity;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.app.ListFragment;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView.ScaleType;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.squareup.picasso.Picasso;
+
+import org.apache.commons.lang3.StringUtils;
+
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.adapter.ChapterListAdapter;
+import de.danoeh.antennapod.adapter.NavListAdapter;
+import de.danoeh.antennapod.core.feed.Chapter;
+import de.danoeh.antennapod.core.feed.EventDistributor;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.MediaType;
+import de.danoeh.antennapod.core.feed.SimpleChapter;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.service.playback.PlaybackService;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.util.playback.ExternalMedia;
+import de.danoeh.antennapod.core.util.playback.Playable;
+import de.danoeh.antennapod.core.util.playback.PlaybackController;
+import de.danoeh.antennapod.dialog.VariableSpeedDialog;
+import de.danoeh.antennapod.fragment.CoverFragment;
+import de.danoeh.antennapod.fragment.ItemDescriptionFragment;
+import de.danoeh.antennapod.menuhandler.MenuItemUtils;
+import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
+
+/**
+ * Activity for playing audio files.
+ */
+public class AudioplayerActivity extends MediaplayerActivity implements ItemDescriptionFragment.ItemDescriptionFragmentCallback,
+ NavDrawerActivity {
+ private static final int POS_COVER = 0;
+ private static final int POS_DESCR = 1;
+ private static final int POS_CHAPTERS = 2;
+ private static final int NUM_CONTENT_FRAGMENTS = 3;
+
+ final String TAG = "AudioplayerActivity";
+ private static final String PREFS = "AudioPlayerActivityPreferences";
+ private static final String PREF_KEY_SELECTED_FRAGMENT_POSITION = "selectedFragmentPosition";
+ private static final String PREF_PLAYABLE_ID = "playableId";
+
+ private DrawerLayout drawerLayout;
+ private NavListAdapter navAdapter;
+ private ListView navList;
+ private ActionBarDrawerToggle drawerToggle;
+
+ private Fragment[] detachedFragments;
+
+ private CoverFragment coverFragment;
+ private ItemDescriptionFragment descriptionFragment;
+ private ListFragment chapterFragment;
+
+ private Fragment currentlyShownFragment;
+ private int currentlyShownPosition = -1;
+ /**
+ * Used if onResume was called without loadMediaInfo.
+ */
+ private int savedPosition = -1;
+
+ private TextView txtvTitle;
+ private Button butPlaybackSpeed;
+ private ImageButton butNavLeft;
+ private ImageButton butNavRight;
+
+ private void resetFragmentView() {
+ FragmentTransaction fT = getSupportFragmentManager().beginTransaction();
+
+ if (coverFragment != null) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Removing cover fragment");
+ fT.remove(coverFragment);
+ }
+ if (descriptionFragment != null) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Removing description fragment");
+ fT.remove(descriptionFragment);
+ }
+ if (chapterFragment != null) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Removing chapter fragment");
+ fT.remove(chapterFragment);
+ }
+ if (currentlyShownFragment != null) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Removing currently shown fragment");
+ fT.remove(currentlyShownFragment);
+ }
+ for (int i = 0; i < detachedFragments.length; i++) {
+ Fragment f = detachedFragments[i];
+ if (f != null) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Removing detached fragment");
+ fT.remove(f);
+ }
+ }
+ fT.commit();
+ currentlyShownFragment = null;
+ coverFragment = null;
+ descriptionFragment = null;
+ chapterFragment = null;
+ currentlyShownPosition = -1;
+ detachedFragments = new Fragment[NUM_CONTENT_FRAGMENTS];
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "onStop");
+ cancelLoadTask();
+ EventDistributor.getInstance().unregister(contentUpdate);
+
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getSupportActionBar().setDisplayShowTitleEnabled(false);
+ detachedFragments = new Fragment[NUM_CONTENT_FRAGMENTS];
+ }
+
+ private void savePreferences() {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Saving preferences");
+ SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ if (currentlyShownPosition >= 0 && controller != null
+ && controller.getMedia() != null) {
+ editor.putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION,
+ currentlyShownPosition);
+ editor.putString(PREF_PLAYABLE_ID, controller.getMedia()
+ .getIdentifier().toString());
+ } else {
+ editor.putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, -1);
+ editor.putString(PREF_PLAYABLE_ID, "");
+ }
+ editor.commit();
+
+ savedPosition = currentlyShownPosition;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ drawerToggle.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ // super.onSaveInstanceState(outState); would cause crash
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "onSaveInstanceState");
+
+ }
+
+ @Override
+ protected void onPause() {
+ savePreferences();
+ resetFragmentView();
+ super.onPause();
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ restoreFromPreferences();
+ }
+
+ /**
+ * Tries to restore the selected fragment position from the Activity's
+ * preferences.
+ *
+ * @return true if restoreFromPrefernces changed the activity's state
+ */
+ private boolean restoreFromPreferences() {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Restoring instance state");
+ SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
+ int savedPosition = prefs.getInt(PREF_KEY_SELECTED_FRAGMENT_POSITION,
+ -1);
+ String playableId = prefs.getString(PREF_PLAYABLE_ID, "");
+
+ if (savedPosition != -1
+ && controller != null
+ && controller.getMedia() != null
+ && controller.getMedia().getIdentifier().toString()
+ .equals(playableId)) {
+ switchToFragment(savedPosition);
+ return true;
+ } else if (controller == null || controller.getMedia() == null) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG,
+ "Couldn't restore from preferences: controller or media was null");
+ } else {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG,
+ "Couldn't restore from preferences: savedPosition was -1 or saved identifier and playable identifier didn't match.\nsavedPosition: "
+ + savedPosition + ", id: " + playableId
+ );
+
+ }
+ return false;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (StringUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) {
+ Intent intent = getIntent();
+ if (BuildConfig.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);
+ }
+ if (savedPosition != -1) {
+ switchToFragment(savedPosition);
+ }
+
+ EventDistributor.getInstance().register(contentUpdate);
+ loadData();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent);
+ }
+
+ @Override
+ protected void onAwaitingVideoSurface() {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "onAwaitingVideoSurface was called in audio player -> switching to video player");
+ startActivity(new Intent(this, VideoplayerActivity.class));
+ }
+
+ @Override
+ protected void postStatusMsg(int resId) {
+ if (resId == R.string.player_preparing_msg
+ || resId == R.string.player_seeking_msg
+ || resId == R.string.player_buffering_msg) {
+ // TODO Show progress bar here
+ }
+ }
+
+ @Override
+ protected void clearStatusMsg() {
+ // TODO Hide progress bar here
+
+ }
+
+ /**
+ * Changes the currently displayed fragment.
+ *
+ * @param pos Must be POS_COVER, POS_DESCR, or POS_CHAPTERS
+ */
+ private void switchToFragment(int pos) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Switching contentView to position " + pos);
+ if (currentlyShownPosition != pos && controller != null) {
+ Playable media = controller.getMedia();
+ if (media != null) {
+ FragmentTransaction ft = getSupportFragmentManager()
+ .beginTransaction();
+ if (currentlyShownFragment != null) {
+ detachedFragments[currentlyShownPosition] = currentlyShownFragment;
+ ft.detach(currentlyShownFragment);
+ }
+ switch (pos) {
+ case POS_COVER:
+ if (coverFragment == null) {
+ Log.i(TAG, "Using new coverfragment");
+ coverFragment = CoverFragment.newInstance(media);
+ }
+ currentlyShownFragment = coverFragment;
+ break;
+ case POS_DESCR:
+ if (descriptionFragment == null) {
+ descriptionFragment = ItemDescriptionFragment
+ .newInstance(media, true, true);
+ }
+ currentlyShownFragment = descriptionFragment;
+ break;
+ case POS_CHAPTERS:
+ if (chapterFragment == null) {
+ chapterFragment = new ListFragment() {
+
+ @Override
+ public void onListItemClick(ListView l, View v,
+ int position, long id) {
+ super.onListItemClick(l, v, position, id);
+ Chapter chapter = (Chapter) this
+ .getListAdapter().getItem(position);
+ controller.seekToChapter(chapter);
+ }
+
+ };
+ chapterFragment.setListAdapter(new ChapterListAdapter(
+ AudioplayerActivity.this, 0, media
+ .getChapters(), media
+ ));
+ }
+ currentlyShownFragment = chapterFragment;
+ break;
+ }
+ if (currentlyShownFragment != null) {
+ currentlyShownPosition = pos;
+ if (detachedFragments[pos] != null) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Reattaching fragment at position "
+ + pos);
+ ft.attach(detachedFragments[pos]);
+ } else {
+ ft.add(R.id.contentView, currentlyShownFragment);
+ }
+ ft.disallowAddToBackStack();
+ ft.commit();
+ updateNavButtonDrawable();
+ }
+ }
+ }
+ }
+
+ private void updateNavButtonDrawable() {
+
+ final int[] buttonTexts = new int[]{R.string.show_shownotes_label,
+ R.string.show_chapters_label, R.string.show_cover_label};
+
+ final TypedArray drawables = obtainStyledAttributes(new int[]{
+ R.attr.navigation_shownotes, R.attr.navigation_chapters});
+ final Playable media = controller.getMedia();
+ if (butNavLeft != null && butNavRight != null && media != null) {
+
+ butNavRight.setTag(R.id.imageloader_key, null);
+ butNavLeft.setTag(R.id.imageloader_key, null);
+
+ switch (currentlyShownPosition) {
+ case POS_COVER:
+ butNavLeft.setScaleType(ScaleType.CENTER);
+ butNavLeft.setImageDrawable(drawables.getDrawable(0));
+ butNavLeft.setContentDescription(getString(buttonTexts[0]));
+
+ butNavRight.setImageDrawable(drawables.getDrawable(1));
+ butNavRight.setContentDescription(getString(buttonTexts[1]));
+
+ break;
+ case POS_DESCR:
+ butNavLeft.setScaleType(ScaleType.CENTER_CROP);
+ butNavLeft.post(new Runnable() {
+
+ @Override
+ public void run() {
+ Picasso.with(AudioplayerActivity.this)
+ .load(media.getImageUri())
+ .fit()
+ .into(butNavLeft);
+ }
+ });
+ butNavLeft.setContentDescription(getString(buttonTexts[2]));
+
+ butNavRight.setImageDrawable(drawables.getDrawable(1));
+ butNavRight.setContentDescription(getString(buttonTexts[1]));
+ break;
+ case POS_CHAPTERS:
+ butNavLeft.setScaleType(ScaleType.CENTER_CROP);
+ butNavLeft.post(new Runnable() {
+
+ @Override
+ public void run() {
+ Picasso.with(AudioplayerActivity.this)
+ .load(media.getImageUri())
+ .fit()
+ .into(butNavLeft);
+ }
+
+ });
+ butNavLeft.setContentDescription(getString(buttonTexts[2]));
+
+ butNavRight.setImageDrawable(drawables.getDrawable(0));
+ butNavRight.setContentDescription(getString(buttonTexts[0]));
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected void setupGUI() {
+ super.setupGUI();
+ resetFragmentView();
+ drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+ navList = (ListView) findViewById(R.id.nav_list);
+ txtvTitle = (TextView) findViewById(R.id.txtvTitle);
+ butNavLeft = (ImageButton) findViewById(R.id.butNavLeft);
+ butNavRight = (ImageButton) findViewById(R.id.butNavRight);
+ butPlaybackSpeed = (Button) findViewById(R.id.butPlaybackSpeed);
+
+ drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close) {
+ CharSequence currentTitle = getSupportActionBar().getTitle();
+
+ @Override
+ public void onDrawerOpened(View drawerView) {
+ super.onDrawerOpened(drawerView);
+ currentTitle = getSupportActionBar().getTitle();
+ getSupportActionBar().setTitle(R.string.app_name);
+ supportInvalidateOptionsMenu();
+ }
+
+ @Override
+ public void onDrawerClosed(View drawerView) {
+ super.onDrawerClosed(drawerView);
+ getSupportActionBar().setTitle(currentTitle);
+ supportInvalidateOptionsMenu();
+ }
+ };
+
+ drawerToggle.setDrawerIndicatorEnabled(false);
+ drawerLayout.setDrawerListener(drawerToggle);
+
+ navAdapter = new NavListAdapter(itemAccess, this);
+ navList.setAdapter(navAdapter);
+ navList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ int viewType = parent.getAdapter().getItemViewType(position);
+ if (viewType != NavListAdapter.VIEW_TYPE_SECTION_DIVIDER) {
+ int relPos = (viewType == NavListAdapter.VIEW_TYPE_NAV) ? position : position - NavListAdapter.SUBSCRIPTION_OFFSET;
+ Intent intent = new Intent(AudioplayerActivity.this, MainActivity.class);
+ intent.putExtra(MainActivity.EXTRA_NAV_TYPE, viewType);
+ intent.putExtra(MainActivity.EXTRA_NAV_INDEX, relPos);
+ startActivity(intent);
+ }
+ drawerLayout.closeDrawer(navList);
+ }
+ });
+ drawerToggle.syncState();
+
+ butNavLeft.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (currentlyShownFragment == null
+ || currentlyShownPosition == POS_DESCR) {
+ switchToFragment(POS_COVER);
+ } else if (currentlyShownPosition == POS_COVER) {
+ switchToFragment(POS_DESCR);
+ } else if (currentlyShownPosition == POS_CHAPTERS) {
+ switchToFragment(POS_COVER);
+ }
+ }
+ });
+
+ butNavRight.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (currentlyShownPosition == POS_CHAPTERS) {
+ switchToFragment(POS_DESCR);
+ } else {
+ switchToFragment(POS_CHAPTERS);
+ }
+ }
+ });
+
+ butPlaybackSpeed.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (controller != null && controller.canSetPlaybackSpeed()) {
+ String[] availableSpeeds = UserPreferences
+ .getPlaybackSpeedArray();
+ String currentSpeed = UserPreferences.getPlaybackSpeed();
+
+ // Provide initial value in case the speed list has changed
+ // out from under us
+ // and our current speed isn't in the new list
+ String newSpeed;
+ if (availableSpeeds.length > 0) {
+ newSpeed = availableSpeeds[0];
+ } else {
+ newSpeed = "1.0";
+ }
+
+ for (int i = 0; i < availableSpeeds.length; i++) {
+ if (availableSpeeds[i].equals(currentSpeed)) {
+ if (i == availableSpeeds.length - 1) {
+ newSpeed = availableSpeeds[0];
+ } else {
+ newSpeed = availableSpeeds[i + 1];
+ }
+ break;
+ }
+ }
+ UserPreferences.setPlaybackSpeed(newSpeed);
+ controller.setPlaybackSpeed(Float.parseFloat(newSpeed));
+ }
+ }
+ });
+
+ butPlaybackSpeed.setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ VariableSpeedDialog.showDialog(AudioplayerActivity.this);
+ return true;
+ }
+ });
+ }
+
+ @Override
+ protected void onPlaybackSpeedChange() {
+ super.onPlaybackSpeedChange();
+ updateButPlaybackSpeed();
+ }
+
+ private void updateButPlaybackSpeed() {
+ if (controller != null && controller.canSetPlaybackSpeed()) {
+ butPlaybackSpeed.setText(UserPreferences.getPlaybackSpeed());
+ }
+ }
+
+ @Override
+ protected void onPositionObserverUpdate() {
+ super.onPositionObserverUpdate();
+ notifyMediaPositionChanged();
+ }
+
+ @Override
+ protected boolean loadMediaInfo() {
+ if (!super.loadMediaInfo()) {
+ return false;
+ }
+ final Playable media = controller.getMedia();
+ if (media == null) {
+ return false;
+ }
+ txtvTitle.setText(media.getEpisodeTitle());
+ if (media.getChapters() != null) {
+ butNavRight.setVisibility(View.VISIBLE);
+ } else {
+ butNavRight.setVisibility(View.INVISIBLE);
+ }
+
+
+ if (currentlyShownPosition == -1) {
+ if (!restoreFromPreferences()) {
+ switchToFragment(POS_COVER);
+ }
+ }
+ if (currentlyShownFragment instanceof AudioplayerContentFragment) {
+ ((AudioplayerContentFragment) currentlyShownFragment)
+ .onDataSetChanged(media);
+ }
+
+ if (controller == null
+ || !controller.canSetPlaybackSpeed()) {
+ butPlaybackSpeed.setVisibility(View.GONE);
+ } else {
+ butPlaybackSpeed.setVisibility(View.VISIBLE);
+ }
+
+ updateButPlaybackSpeed();
+ return true;
+ }
+
+ public void notifyMediaPositionChanged() {
+ if (chapterFragment != null) {
+ ArrayAdapter<SimpleChapter> adapter = (ArrayAdapter<SimpleChapter>) chapterFragment
+ .getListAdapter();
+ adapter.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ protected void onReloadNotification(int notificationCode) {
+ if (notificationCode == PlaybackService.EXTRA_CODE_VIDEO) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG,
+ "ReloadNotification received, switching to Videoplayer now");
+ finish();
+ startActivity(new Intent(this, VideoplayerActivity.class));
+
+ }
+ }
+
+ @Override
+ protected void onBufferStart() {
+ postStatusMsg(R.string.player_buffering_msg);
+ }
+
+ @Override
+ protected void onBufferEnd() {
+ clearStatusMsg();
+ }
+
+ @Override
+ public PlaybackController getPlaybackController() {
+ return controller;
+ }
+
+ @Override
+ public boolean isDrawerOpen() {
+ return drawerLayout != null && navList != null && drawerLayout.isDrawerOpen(navList);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (!MenuItemUtils.isActivityDrawerOpen(this)) {
+ return super.onCreateOptionsMenu(menu);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ if (!MenuItemUtils.isActivityDrawerOpen(this)) {
+ return super.onPrepareOptionsMenu(menu);
+ } else {
+ return false;
+ }
+ }
+
+ public interface AudioplayerContentFragment {
+ public void onDataSetChanged(Playable media);
+ }
+
+ @Override
+ protected int getContentViewResourceId() {
+ return R.layout.audioplayer_activity;
+ }
+
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (drawerToggle != null && drawerToggle.onOptionsItemSelected(item)) {
+ return true;
+ } else {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private DBReader.NavDrawerData navDrawerData;
+ private AsyncTask<Void, Void, DBReader.NavDrawerData> loadTask;
+
+ private void loadData() {
+ loadTask = new AsyncTask<Void, Void, DBReader.NavDrawerData>() {
+ @Override
+ protected DBReader.NavDrawerData doInBackground(Void... params) {
+ return DBReader.getNavDrawerData(AudioplayerActivity.this);
+ }
+
+ @Override
+ protected void onPostExecute(DBReader.NavDrawerData result) {
+ super.onPostExecute(result);
+ navDrawerData = result;
+ if (navAdapter != null) {
+ navAdapter.notifyDataSetChanged();
+ }
+ }
+ };
+ loadTask.execute();
+ }
+
+ private void cancelLoadTask() {
+ if (loadTask != null) {
+ loadTask.cancel(true);
+ }
+ }
+
+ private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
+
+ @Override
+ public void update(EventDistributor eventDistributor, Integer arg) {
+ if ((EventDistributor.FEED_LIST_UPDATE & arg) != 0) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Received contentUpdate Intent.");
+ loadData();
+ }
+ }
+ };
+
+ private final NavListAdapter.ItemAccess itemAccess = new NavListAdapter.ItemAccess() {
+ @Override
+ public int getCount() {
+ if (navDrawerData != null) {
+ return navDrawerData.feeds.size();
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public Feed getItem(int position) {
+ if (navDrawerData != null && position < navDrawerData.feeds.size()) {
+ return navDrawerData.feeds.get(position);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public int getSelectedItemIndex() {
+ return -1;
+ }
+
+ @Override
+ public int getQueueSize() {
+ return (navDrawerData != null) ? navDrawerData.queueSize : 0;
+ }
+
+ @Override
+ public int getNumberOfUnreadItems() {
+ return (navDrawerData != null) ? navDrawerData.numUnreadItems : 0;
+ }
+ };
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java
new file mode 100644
index 000000000..d265c05b1
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java
@@ -0,0 +1,249 @@
+package de.danoeh.antennapod.activity;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v4.app.NavUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.squareup.picasso.Picasso;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jsoup.Jsoup;
+import org.jsoup.examples.HtmlToPlainText;
+import org.jsoup.nodes.Document;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.adapter.FeedItemlistDescriptionAdapter;
+import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
+import de.danoeh.antennapod.core.feed.EventDistributor;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.DownloadRequestException;
+import de.danoeh.antennapod.core.storage.DownloadRequester;
+
+/**
+ * Default implementation of OnlineFeedViewActivity. Shows the downloaded feed's items with their descriptions,
+ * a subscribe button and a spinner for choosing alternate feed URLs.
+ */
+public class DefaultOnlineFeedViewActivity extends OnlineFeedViewActivity {
+ private static final String TAG = "DefaultOnlineFeedViewActivity";
+
+ private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | EventDistributor.DOWNLOAD_QUEUED | EventDistributor.FEED_LIST_UPDATE;
+ private volatile List<Feed> feeds;
+ private Feed feed;
+ private String selectedDownloadUrl;
+
+ private Button subscribeButton;
+
+ @Override
+ protected void onCreate(Bundle arg0) {
+ super.onCreate(arg0);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ Intent destIntent = new Intent(this, MainActivity.class);
+ if (NavUtils.shouldUpRecreateTask(this, destIntent)) {
+ startActivity(destIntent);
+ } else {
+ NavUtils.navigateUpFromSameTask(this);
+ }
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void loadData() {
+ super.loadData();
+ feeds = DBReader.getFeedList(this);
+ }
+
+ @Override
+ protected void beforeShowFeedInformation(Feed feed, Map<String, String> alternateFeedUrls) {
+ super.beforeShowFeedInformation(feed, alternateFeedUrls);
+
+ // remove HTML tags from descriptions
+
+ if (BuildConfig.DEBUG) Log.d(TAG, "Removing HTML from shownotes");
+ if (feed.getItems() != null) {
+ HtmlToPlainText formatter = new HtmlToPlainText();
+ for (FeedItem item : feed.getItems()) {
+ if (item.getDescription() != null) {
+ Document description = Jsoup.parse(item.getDescription());
+ item.setDescription(StringUtils.trim(formatter.getPlainText(description)));
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void showFeedInformation(final Feed feed, final Map<String, String> alternateFeedUrls) {
+ super.showFeedInformation(feed, alternateFeedUrls);
+ setContentView(R.layout.listview_activity);
+
+ this.feed = feed;
+ this.selectedDownloadUrl = feed.getDownload_url();
+ EventDistributor.getInstance().register(listener);
+ ListView listView = (ListView) findViewById(R.id.listview);
+ LayoutInflater inflater = (LayoutInflater)
+ getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View header = inflater.inflate(R.layout.onlinefeedview_header, listView, false);
+ listView.addHeaderView(header);
+
+ listView.setAdapter(new FeedItemlistDescriptionAdapter(this, 0, feed.getItems()));
+
+ ImageView cover = (ImageView) header.findViewById(R.id.imgvCover);
+ TextView title = (TextView) header.findViewById(R.id.txtvTitle);
+ TextView author = (TextView) header.findViewById(R.id.txtvAuthor);
+ TextView description = (TextView) header.findViewById(R.id.txtvDescription);
+ Spinner spAlternateUrls = (Spinner) header.findViewById(R.id.spinnerAlternateUrls);
+
+ subscribeButton = (Button) header.findViewById(R.id.butSubscribe);
+
+ if (feed.getImage() != null && StringUtils.isNoneBlank(feed.getImage().getDownload_url())) {
+ Picasso.with(this)
+ .load(feed.getImage().getDownload_url())
+ .fit()
+ .into(cover);
+ }
+
+ title.setText(feed.getTitle());
+ author.setText(feed.getAuthor());
+ description.setText(feed.getDescription());
+
+ subscribeButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ try {
+ Feed f = new Feed(selectedDownloadUrl, new Date(), feed.getTitle());
+ f.setPreferences(feed.getPreferences());
+ DefaultOnlineFeedViewActivity.this.feed = f;
+
+ DownloadRequester.getInstance().downloadFeed(
+ DefaultOnlineFeedViewActivity.this,
+ f);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DownloadRequestErrorDialogCreator.newRequestErrorDialog(DefaultOnlineFeedViewActivity.this,
+ e.getMessage());
+ }
+ setSubscribeButtonState(feed);
+ }
+ });
+
+ if (alternateFeedUrls.isEmpty()) {
+ spAlternateUrls.setVisibility(View.GONE);
+ } else {
+ spAlternateUrls.setVisibility(View.VISIBLE);
+
+ final List<String> alternateUrlsList = new ArrayList<String>();
+ final List<String> alternateUrlsTitleList = new ArrayList<String>();
+
+ alternateUrlsList.add(feed.getDownload_url());
+ alternateUrlsTitleList.add(feed.getTitle());
+
+
+ alternateUrlsList.addAll(alternateFeedUrls.keySet());
+ for (String url : alternateFeedUrls.keySet()) {
+ alternateUrlsTitleList.add(alternateFeedUrls.get(url));
+ }
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, alternateUrlsTitleList);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spAlternateUrls.setAdapter(adapter);
+ spAlternateUrls.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ selectedDownloadUrl = alternateUrlsList.get(position);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+
+ }
+ });
+
+
+ }
+ setSubscribeButtonState(feed);
+
+ }
+
+ private boolean feedInFeedlist(Feed feed) {
+ if (feeds == null || feed == null)
+ return false;
+ for (Feed f : feeds) {
+ if (f.getIdentifyingValue().equals(feed.getIdentifyingValue())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void setSubscribeButtonState(Feed feed) {
+ if (subscribeButton != null && feed != null) {
+ if (DownloadRequester.getInstance().isDownloadingFile(feed.getDownload_url())) {
+ subscribeButton.setEnabled(false);
+ subscribeButton.setText(R.string.downloading_label);
+ } else if (feedInFeedlist(feed)) {
+ subscribeButton.setEnabled(false);
+ subscribeButton.setText(R.string.subscribed_label);
+ } else {
+ subscribeButton.setEnabled(true);
+ subscribeButton.setText(R.string.subscribe_label);
+ }
+ }
+ }
+
+ EventDistributor.EventListener listener = new EventDistributor.EventListener() {
+ @Override
+ public void update(EventDistributor eventDistributor, Integer arg) {
+ if ((arg & EventDistributor.FEED_LIST_UPDATE) != 0) {
+ new AsyncTask<Void, Void, List<Feed>>() {
+ @Override
+ protected List<Feed> doInBackground(Void... params) {
+ return DBReader.getFeedList(DefaultOnlineFeedViewActivity.this);
+ }
+
+ @Override
+ protected void onPostExecute(List<Feed> feeds) {
+ super.onPostExecute(feeds);
+ DefaultOnlineFeedViewActivity.this.feeds = feeds;
+ setSubscribeButtonState(feed);
+ }
+ }.execute();
+ } else if ((arg & EVENTS) != 0) {
+ setSubscribeButtonState(feed);
+ }
+ }
+ };
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ EventDistributor.getInstance().unregister(listener);
+ }
+}
+
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/DirectoryChooserActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/DirectoryChooserActivity.java
new file mode 100644
index 000000000..559fa0574
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/DirectoryChooserActivity.java
@@ -0,0 +1,370 @@
+package de.danoeh.antennapod.activity;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.FileObserver;
+import android.support.v4.app.NavUtils;
+import android.support.v7.app.ActionBarActivity;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.*;
+import android.widget.AdapterView.OnItemClickListener;
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * Let's the user choose a directory on the storage device. The selected folder
+ * will be sent back to the starting activity as an activity result.
+ */
+public class DirectoryChooserActivity extends ActionBarActivity {
+ private static final String TAG = "DirectoryChooserActivity";
+
+ private static final String CREATE_DIRECTORY_NAME = "AntennaPod";
+
+ public static final String RESULT_SELECTED_DIR = "selected_dir";
+ public static final int RESULT_CODE_DIR_SELECTED = 1;
+
+ private Button butConfirm;
+ private Button butCancel;
+ private ImageButton butNavUp;
+ private TextView txtvSelectedFolder;
+ private ListView listDirectories;
+
+ private ArrayAdapter<String> listDirectoriesAdapter;
+ private ArrayList<String> filenames;
+ /** The directory that is currently being shown. */
+ private File selectedDir;
+ private File[] filesInDir;
+
+ private FileObserver fileObserver;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ setContentView(R.layout.directory_chooser);
+ butConfirm = (Button) findViewById(R.id.butConfirm);
+ butCancel = (Button) findViewById(R.id.butCancel);
+ butNavUp = (ImageButton) findViewById(R.id.butNavUp);
+ txtvSelectedFolder = (TextView) findViewById(R.id.txtvSelectedFolder);
+ listDirectories = (ListView) findViewById(R.id.directory_list);
+
+ butConfirm.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (isValidFile(selectedDir)) {
+ if (selectedDir.list().length == 0) {
+ returnSelectedFolder();
+ } else {
+ showNonEmptyDirectoryWarning();
+ }
+ }
+ }
+
+ private void showNonEmptyDirectoryWarning() {
+ AlertDialog.Builder adb = new AlertDialog.Builder(
+ DirectoryChooserActivity.this);
+ adb.setTitle(R.string.folder_not_empty_dialog_title);
+ adb.setMessage(R.string.folder_not_empty_dialog_msg);
+ adb.setNegativeButton(R.string.cancel_label,
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog,
+ int which) {
+ dialog.dismiss();
+ }
+ });
+ adb.setPositiveButton(R.string.confirm_label,
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog,
+ int which) {
+ dialog.dismiss();
+ returnSelectedFolder();
+ }
+ });
+ adb.create().show();
+ }
+ });
+
+ butCancel.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ }
+ });
+
+ listDirectories.setOnItemClickListener(new OnItemClickListener() {
+
+ @Override
+ public void onItemClick(AdapterView<?> adapter, View view,
+ int position, long id) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Selected index: " + position);
+ if (filesInDir != null && position >= 0
+ && position < filesInDir.length) {
+ changeDirectory(filesInDir[position]);
+ }
+ }
+ });
+
+ butNavUp.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ File parent = null;
+ if (selectedDir != null
+ && (parent = selectedDir.getParentFile()) != null) {
+ changeDirectory(parent);
+ }
+ }
+ });
+
+ filenames = new ArrayList<String>();
+ listDirectoriesAdapter = new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_1, filenames);
+ listDirectories.setAdapter(listDirectoriesAdapter);
+ changeDirectory(Environment.getExternalStorageDirectory());
+ }
+
+ /**
+ * Finishes the activity and returns the selected folder as a result. The
+ * selected folder can also be null.
+ */
+ private void returnSelectedFolder() {
+ if (selectedDir != null && BuildConfig.DEBUG)
+ Log.d(TAG, "Returning " + selectedDir.getAbsolutePath()
+ + " as result");
+ Intent resultData = new Intent();
+ if (selectedDir != null) {
+ resultData.putExtra(RESULT_SELECTED_DIR,
+ selectedDir.getAbsolutePath());
+ }
+ setResult(RESULT_CODE_DIR_SELECTED, resultData);
+ finish();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (fileObserver != null) {
+ fileObserver.stopWatching();
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (fileObserver != null) {
+ fileObserver.startWatching();
+ }
+ }
+
+ /**
+ * Change the directory that is currently being displayed.
+ *
+ * @param dir
+ * The file the activity should switch to. This File must be
+ * non-null and a directory, otherwise the displayed directory
+ * will not be changed
+ */
+ private void changeDirectory(File dir) {
+ if (dir != null && dir.isDirectory()) {
+ File[] contents = dir.listFiles();
+ if (contents != null) {
+ int numDirectories = 0;
+ for (File f : contents) {
+ if (f.isDirectory()) {
+ numDirectories++;
+ }
+ }
+ filesInDir = new File[numDirectories];
+ filenames.clear();
+ for (int i = 0, counter = 0; i < numDirectories; counter++) {
+ if (contents[counter].isDirectory()) {
+ filesInDir[i] = contents[counter];
+ filenames.add(contents[counter].getName());
+ i++;
+ }
+ }
+ Arrays.sort(filesInDir);
+ Collections.sort(filenames);
+ selectedDir = dir;
+ txtvSelectedFolder.setText(dir.getAbsolutePath());
+ listDirectoriesAdapter.notifyDataSetChanged();
+ fileObserver = createFileObserver(dir.getAbsolutePath());
+ fileObserver.startWatching();
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Changed directory to " + dir.getAbsolutePath());
+ } else {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG,
+ "Could not change folder: contents of dir were null");
+ }
+ } else {
+ if (dir == null) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Could not change folder: dir was null");
+ } else {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Could not change folder: dir is no directory");
+ }
+ }
+ refreshButtonState();
+ }
+
+ /**
+ * Changes the state of the buttons depending on the currently selected file
+ * or folder.
+ */
+ private void refreshButtonState() {
+ if (selectedDir != null) {
+ butConfirm.setEnabled(isValidFile(selectedDir));
+ supportInvalidateOptionsMenu();
+ }
+ }
+
+ /** Refresh the contents of the directory that is currently shown. */
+ private void refreshDirectory() {
+ if (selectedDir != null) {
+ changeDirectory(selectedDir);
+ }
+ }
+
+ /** Sets up a FileObserver to watch the current directory. */
+ private FileObserver createFileObserver(String path) {
+ return new FileObserver(path, FileObserver.CREATE | FileObserver.DELETE
+ | FileObserver.MOVED_FROM | FileObserver.MOVED_TO) {
+
+ @Override
+ public void onEvent(int event, String path) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "FileObserver received event " + event);
+ runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ refreshDirectory();
+ }
+ });
+ }
+ };
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ menu.findItem(R.id.new_folder_item)
+ .setVisible(isValidFile(selectedDir));
+ return true;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.directory_chooser, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
+ case R.id.new_folder_item:
+ openNewFolderDialog();
+ return true;
+ case R.id.set_to_default_folder_item:
+ selectedDir = null;
+ returnSelectedFolder();
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Shows a confirmation dialog that asks the user if he wants to create a
+ * new folder.
+ */
+ private void openNewFolderDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.create_folder_label);
+ builder.setMessage(String.format(getString(R.string.create_folder_msg),
+ CREATE_DIRECTORY_NAME));
+ builder.setNegativeButton(R.string.cancel_label,
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ });
+ builder.setPositiveButton(R.string.confirm_label,
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ int msg = createFolder();
+ Toast t = Toast.makeText(DirectoryChooserActivity.this,
+ msg, Toast.LENGTH_SHORT);
+ t.show();
+ }
+ });
+ builder.create().show();
+ }
+
+ /**
+ * Creates a new folder in the current directory with the name
+ * CREATE_DIRECTORY_NAME.
+ */
+ private int createFolder() {
+ if (selectedDir == null) {
+ return R.string.create_folder_error;
+ } else if (selectedDir.canWrite()) {
+ File newDir = new File(selectedDir, CREATE_DIRECTORY_NAME);
+ if (!newDir.exists()) {
+ boolean result = newDir.mkdir();
+ if (result) {
+ return R.string.create_folder_success;
+ } else {
+ return R.string.create_folder_error;
+ }
+ } else {
+ return R.string.create_folder_error_already_exists;
+ }
+ } else {
+ return R.string.create_folder_error_no_write_access;
+ }
+ }
+
+ /** Returns true if the selected file or directory would be valid selection. */
+ private boolean isValidFile(File file) {
+ return (file != null && file.isDirectory() && file.canRead() && file
+ .canWrite());
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java
new file mode 100644
index 000000000..365c4216d
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java
@@ -0,0 +1,110 @@
+package de.danoeh.antennapod.activity;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import org.apache.commons.lang3.Validate;
+
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.service.download.DownloadRequest;
+import de.danoeh.antennapod.core.storage.DownloadRequester;
+
+/**
+ * Shows a username and a password text field.
+ * The activity MUST be started with the ARG_DOWNlOAD_REQUEST argument set to a non-null value.
+ * Other arguments are optional.
+ * The activity's result will be the same DownloadRequest with the entered username and password.
+ */
+public class DownloadAuthenticationActivity extends ActionBarActivity {
+ private static final String TAG = "DownloadAuthenticationActivity";
+
+ /**
+ * The download request object that contains information about the resource that requires a username and a password
+ */
+ public static final String ARG_DOWNLOAD_REQUEST = "request";
+ /**
+ * True if the request should be sent to the DownloadRequester when this activity is finished, false otherwise.
+ * The default value is false.
+ */
+ public static final String ARG_SEND_TO_DOWNLOAD_REQUESTER_BOOL = "send_to_downloadrequester";
+
+ public static final String RESULT_REQUEST = "request";
+
+ private EditText etxtUsername;
+ private EditText etxtPassword;
+ private Button butConfirm;
+ private Button butCancel;
+ private TextView txtvDescription;
+
+ private DownloadRequest request;
+ private boolean sendToDownloadRequester;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+ getSupportActionBar().hide();
+ setContentView(R.layout.download_authentication_activity);
+
+ etxtUsername = (EditText) findViewById(R.id.etxtUsername);
+ etxtPassword = (EditText) findViewById(R.id.etxtPassword);
+ butConfirm = (Button) findViewById(R.id.butConfirm);
+ butCancel = (Button) findViewById(R.id.butCancel);
+ txtvDescription = (TextView) findViewById(R.id.txtvDescription);
+
+ Validate.isTrue(getIntent().hasExtra(ARG_DOWNLOAD_REQUEST), "Download request missing");
+
+ request = getIntent().getParcelableExtra(ARG_DOWNLOAD_REQUEST);
+ sendToDownloadRequester = getIntent().getBooleanExtra(ARG_SEND_TO_DOWNLOAD_REQUESTER_BOOL, false);
+
+ if (savedInstanceState != null) {
+ etxtUsername.setText(savedInstanceState.getString("username"));
+ etxtPassword.setText(savedInstanceState.getString("password"));
+ }
+
+ txtvDescription.setText(txtvDescription.getText() + ":\n\n" + request.getTitle());
+
+ butCancel.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setResult(Activity.RESULT_CANCELED);
+ finish();
+ }
+ });
+
+ butConfirm.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String username = etxtUsername.getText().toString();
+ String password = etxtPassword.getText().toString();
+ request.setUsername(username);
+ request.setPassword(password);
+ Intent result = new Intent();
+ result.putExtra(RESULT_REQUEST, request);
+ setResult(Activity.RESULT_OK, result);
+
+ if (sendToDownloadRequester) {
+ if (BuildConfig.DEBUG) Log.d(TAG, "Sending request to DownloadRequester");
+ DownloadRequester.getInstance().download(DownloadAuthenticationActivity.this, request);
+ }
+ finish();
+ }
+ });
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putString("username", etxtUsername.getText().toString());
+ outState.putString("password", etxtPassword.getText().toString());
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java
new file mode 100644
index 000000000..93c71a868
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/FeedInfoActivity.java
@@ -0,0 +1,198 @@
+package de.danoeh.antennapod.activity;
+
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.squareup.picasso.Picasso;
+
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedPreferences;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.core.storage.DownloadRequestException;
+import de.danoeh.antennapod.core.util.LangUtils;
+import de.danoeh.antennapod.menuhandler.FeedMenuHandler;
+
+/**
+ * Displays information about a feed.
+ */
+public class FeedInfoActivity extends ActionBarActivity {
+ private static final String TAG = "FeedInfoActivity";
+
+ public static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId";
+
+ private Feed feed;
+
+ private ImageView imgvCover;
+ private TextView txtvTitle;
+ private TextView txtvDescription;
+ private TextView txtvLanguage;
+ private TextView txtvAuthor;
+ private EditText etxtUsername;
+ private EditText etxtPassword;
+ private CheckBox cbxAutoDownload;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.feedinfo);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ long feedId = getIntent().getLongExtra(EXTRA_FEED_ID, -1);
+
+ imgvCover = (ImageView) findViewById(R.id.imgvCover);
+ txtvTitle = (TextView) findViewById(R.id.txtvTitle);
+ txtvDescription = (TextView) findViewById(R.id.txtvDescription);
+ txtvLanguage = (TextView) findViewById(R.id.txtvLanguage);
+ txtvAuthor = (TextView) findViewById(R.id.txtvAuthor);
+ cbxAutoDownload = (CheckBox) findViewById(R.id.cbxAutoDownload);
+ etxtUsername = (EditText) findViewById(R.id.etxtUsername);
+ etxtPassword = (EditText) findViewById(R.id.etxtPassword);
+
+ AsyncTask<Long, Void, Feed> loadTask = new AsyncTask<Long, Void, Feed>() {
+
+ @Override
+ protected Feed doInBackground(Long... params) {
+ return DBReader.getFeed(FeedInfoActivity.this, params[0]);
+ }
+
+ @Override
+ protected void onPostExecute(Feed result) {
+ if (result != null) {
+ feed = result;
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Language is " + feed.getLanguage());
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Author is " + feed.getAuthor());
+ imgvCover.post(new Runnable() {
+
+ @Override
+ public void run() {
+ Picasso.with(FeedInfoActivity.this)
+ .load(feed.getImageUri())
+ .fit()
+ .into(imgvCover);
+ }
+ });
+
+ txtvTitle.setText(feed.getTitle());
+ txtvDescription.setText(feed.getDescription());
+ if (feed.getAuthor() != null) {
+ txtvAuthor.setText(feed.getAuthor());
+ }
+ if (feed.getLanguage() != null) {
+ txtvLanguage.setText(LangUtils
+ .getLanguageString(feed.getLanguage()));
+ }
+
+ cbxAutoDownload.setEnabled(UserPreferences.isEnableAutodownload());
+ cbxAutoDownload.setChecked(feed.getPreferences().getAutoDownload());
+ cbxAutoDownload.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
+ feed.getPreferences().setAutoDownload(checked);
+ feed.savePreferences(FeedInfoActivity.this);
+ }
+ });
+
+ etxtUsername.setText(feed.getPreferences().getUsername());
+ etxtPassword.setText(feed.getPreferences().getPassword());
+
+ etxtUsername.addTextChangedListener(authTextWatcher);
+ etxtPassword.addTextChangedListener(authTextWatcher);
+
+ supportInvalidateOptionsMenu();
+
+ } else {
+ Log.e(TAG, "Activity was started with invalid arguments");
+ }
+ }
+ };
+ loadTask.execute(feedId);
+ }
+
+
+ private boolean authInfoChanged = false;
+
+ private TextWatcher authTextWatcher = new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ authInfoChanged = true;
+ }
+ };
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (feed != null && authInfoChanged) {
+ Log.d(TAG, "Auth info changed, saving credentials");
+ FeedPreferences prefs = feed.getPreferences();
+ prefs.setUsername(etxtUsername.getText().toString());
+ prefs.setPassword(etxtPassword.getText().toString());
+ DBWriter.setFeedPreferences(this, prefs);
+ authInfoChanged = false;
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.feedinfo, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ menu.findItem(R.id.support_item).setVisible(
+ feed != null && feed.getPaymentLink() != null);
+ menu.findItem(R.id.share_link_item).setVisible(feed != null && feed.getLink() != null);
+ menu.findItem(R.id.visit_website_item).setVisible(feed != null && feed.getLink() != null);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ default:
+ try {
+ return FeedMenuHandler.onOptionsItemClicked(this, item, feed);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DownloadRequestErrorDialogCreator.newRequestErrorDialog(this,
+ e.getMessage());
+ }
+ return super.onOptionsItemSelected(item);
+ }
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/FlattrAuthActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/FlattrAuthActivity.java
new file mode 100644
index 000000000..3b10ba4c3
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/FlattrAuthActivity.java
@@ -0,0 +1,127 @@
+package de.danoeh.antennapod.activity;
+
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.TextView;
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
+import de.danoeh.antennapod.preferences.PreferenceController;
+
+import org.shredzone.flattr4j.exception.FlattrException;
+
+/** Guides the user through the authentication process */
+
+public class FlattrAuthActivity extends ActionBarActivity {
+ private static final String TAG = "FlattrAuthActivity";
+
+ private TextView txtvExplanation;
+ private Button butAuthenticate;
+ private Button butReturn;
+
+ private boolean authSuccessful;
+
+ private static FlattrAuthActivity singleton;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+ singleton = this;
+ authSuccessful = false;
+ if (BuildConfig.DEBUG) Log.d(TAG, "Activity created");
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ setContentView(R.layout.flattr_auth);
+ txtvExplanation = (TextView) findViewById(R.id.txtvExplanation);
+ butAuthenticate = (Button) findViewById(R.id.but_authenticate);
+ butReturn = (Button) findViewById(R.id.but_return_home);
+
+ butReturn.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(FlattrAuthActivity.this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ }
+ });
+
+ butAuthenticate.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ try {
+ FlattrUtils.startAuthProcess(FlattrAuthActivity.this);
+ } catch (FlattrException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ public static FlattrAuthActivity getInstance() {
+ return singleton;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (BuildConfig.DEBUG) Log.d(TAG, "Activity resumed");
+ Uri uri = getIntent().getData();
+ if (uri != null) {
+ if (BuildConfig.DEBUG) Log.d(TAG, "Received uri");
+ FlattrUtils.handleCallback(this, uri);
+ }
+ }
+
+ public void handleAuthenticationSuccess() {
+ authSuccessful = true;
+ txtvExplanation.setText(R.string.flattr_auth_success);
+ butAuthenticate.setEnabled(false);
+ butReturn.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ return true;
+ }
+
+
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (authSuccessful) {
+ finish();
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ if (authSuccessful) {
+ Intent intent = new Intent(this, PreferenceController.getPreferenceActivity());
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ } else {
+ finish();
+ }
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+
+
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java
new file mode 100644
index 000000000..df6ff1046
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java
@@ -0,0 +1,438 @@
+package de.danoeh.antennapod.activity;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.media.AudioManager;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import org.apache.commons.lang3.Validate;
+
+import java.util.List;
+
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.adapter.NavListAdapter;
+import de.danoeh.antennapod.core.feed.EventDistributor;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.util.StorageUtils;
+import de.danoeh.antennapod.fragment.AddFeedFragment;
+import de.danoeh.antennapod.fragment.DownloadsFragment;
+import de.danoeh.antennapod.fragment.ExternalPlayerFragment;
+import de.danoeh.antennapod.fragment.ItemlistFragment;
+import de.danoeh.antennapod.fragment.NewEpisodesFragment;
+import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
+import de.danoeh.antennapod.fragment.QueueFragment;
+import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
+import de.danoeh.antennapod.preferences.PreferenceController;
+
+/**
+ * The activity that is shown when the user launches the app.
+ */
+public class MainActivity extends ActionBarActivity implements NavDrawerActivity {
+ private static final String TAG = "MainActivity";
+ private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED
+ | EventDistributor.DOWNLOAD_QUEUED
+ | EventDistributor.FEED_LIST_UPDATE
+ | EventDistributor.UNREAD_ITEMS_UPDATE
+ | EventDistributor.QUEUE_UPDATE;
+
+ public static final String PREF_NAME = "MainActivityPrefs";
+ public static final String PREF_IS_FIRST_LAUNCH = "prefMainActivityIsFirstLaunch";
+
+ public static final String EXTRA_NAV_INDEX = "nav_index";
+ public static final String EXTRA_NAV_TYPE = "nav_type";
+ public static final String EXTRA_FRAGMENT_ARGS = "fragment_args";
+
+ public static final int POS_NEW = 0,
+ POS_QUEUE = 1,
+ POS_DOWNLOADS = 2,
+ POS_HISTORY = 3,
+ POS_ADD = 4;
+
+ private ExternalPlayerFragment externalPlayerFragment;
+ private DrawerLayout drawerLayout;
+
+ private ListView navList;
+ private NavListAdapter navAdapter;
+
+ private ActionBarDrawerToggle drawerToggle;
+
+ private CharSequence drawerTitle;
+ private CharSequence currentTitle;
+
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+ StorageUtils.checkStorageAvailability(this);
+ setContentView(R.layout.main);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ drawerTitle = currentTitle = getTitle();
+
+ drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+ navList = (ListView) findViewById(R.id.nav_list);
+
+ drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close) {
+ @Override
+ public void onDrawerOpened(View drawerView) {
+ super.onDrawerOpened(drawerView);
+ currentTitle = getSupportActionBar().getTitle();
+ getSupportActionBar().setTitle(drawerTitle);
+ supportInvalidateOptionsMenu();
+ }
+
+ @Override
+ public void onDrawerClosed(View drawerView) {
+ super.onDrawerClosed(drawerView);
+ getSupportActionBar().setTitle(currentTitle);
+ supportInvalidateOptionsMenu();
+
+ }
+ };
+
+ drawerLayout.setDrawerListener(drawerToggle);
+ FragmentManager fm = getSupportFragmentManager();
+
+ FragmentTransaction transaction = fm.beginTransaction();
+
+ Fragment mainFragment = fm.findFragmentByTag("main");
+ if (mainFragment != null) {
+ transaction.replace(R.id.main_view, mainFragment);
+ } else {
+ loadFragment(NavListAdapter.VIEW_TYPE_NAV, POS_NEW, null);
+ }
+
+ externalPlayerFragment = new ExternalPlayerFragment();
+ transaction.replace(R.id.playerFragment, externalPlayerFragment);
+ transaction.commit();
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setHomeButtonEnabled(true);
+
+ navAdapter = new NavListAdapter(itemAccess, this);
+ navList.setAdapter(navAdapter);
+ navList.setOnItemClickListener(navListClickListener);
+
+ checkFirstLaunch();
+ }
+
+ private void checkFirstLaunch() {
+ SharedPreferences prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE);
+ if (prefs.getBoolean(PREF_IS_FIRST_LAUNCH, true)) {
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ drawerLayout.openDrawer(navList);
+ }
+ }, 1500);
+
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putBoolean(PREF_IS_FIRST_LAUNCH, false);
+ edit.commit();
+ }
+ }
+
+ public ActionBar getMainActivtyActionBar() {
+ return getSupportActionBar();
+ }
+
+ public boolean isDrawerOpen() {
+ return drawerLayout != null && navList != null && drawerLayout.isDrawerOpen(navList);
+ }
+
+ public List<Feed> getFeeds() {
+ return (navDrawerData != null) ? navDrawerData.feeds : null;
+ }
+
+ private void loadFragment(int viewType, int relPos, Bundle args) {
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ // clear back stack
+ for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) {
+ fragmentManager.popBackStack();
+ }
+
+ FragmentTransaction fT = fragmentManager.beginTransaction();
+ Fragment fragment = null;
+ if (viewType == NavListAdapter.VIEW_TYPE_NAV) {
+ switch (relPos) {
+ case POS_NEW:
+ fragment = new NewEpisodesFragment();
+ break;
+ case POS_QUEUE:
+ fragment = new QueueFragment();
+ break;
+ case POS_DOWNLOADS:
+ fragment = new DownloadsFragment();
+ break;
+ case POS_HISTORY:
+ fragment = new PlaybackHistoryFragment();
+ break;
+ case POS_ADD:
+ fragment = new AddFeedFragment();
+ break;
+
+ }
+ currentTitle = getString(NavListAdapter.NAV_TITLES[relPos]);
+ selectedNavListIndex = relPos;
+
+ } else if (viewType == NavListAdapter.VIEW_TYPE_SUBSCRIPTION) {
+ Feed feed = itemAccess.getItem(relPos);
+ currentTitle = "";
+ fragment = ItemlistFragment.newInstance(feed.getId());
+ selectedNavListIndex = NavListAdapter.SUBSCRIPTION_OFFSET + relPos;
+
+ }
+ if (fragment != null) {
+ if (args != null) {
+ fragment.setArguments(args);
+ }
+ fT.replace(R.id.main_view, fragment, "main");
+ fragmentManager.popBackStack();
+ }
+ fT.commit();
+ getSupportActionBar().setTitle(currentTitle);
+ if (navAdapter != null) {
+ navAdapter.notifyDataSetChanged();
+ }
+ }
+
+ public void loadNavFragment(int position, Bundle args) {
+ loadFragment(NavListAdapter.VIEW_TYPE_NAV, position, args);
+ }
+
+ public void loadFeedFragment(long feedID) {
+ if (navDrawerData != null) {
+ for (int i = 0; i < navDrawerData.feeds.size(); i++) {
+ if (navDrawerData.feeds.get(i).getId() == feedID) {
+ loadFragment(NavListAdapter.VIEW_TYPE_SUBSCRIPTION, i, null);
+ break;
+ }
+ }
+ }
+ }
+
+ public void loadChildFragment(Fragment fragment) {
+ Validate.notNull(fragment);
+ FragmentManager fm = getSupportFragmentManager();
+ fm.beginTransaction()
+ .replace(R.id.main_view, fragment, "main")
+ .addToBackStack(null)
+ .commit();
+ }
+
+ private AdapterView.OnItemClickListener navListClickListener = new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ int viewType = parent.getAdapter().getItemViewType(position);
+ if (viewType != NavListAdapter.VIEW_TYPE_SECTION_DIVIDER && position != selectedNavListIndex) {
+ int relPos = (viewType == NavListAdapter.VIEW_TYPE_NAV) ? position : position - NavListAdapter.SUBSCRIPTION_OFFSET;
+ loadFragment(viewType, relPos, null);
+ selectedNavListIndex = position;
+ navAdapter.notifyDataSetChanged();
+ }
+ drawerLayout.closeDrawer(navList);
+ }
+ };
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ drawerToggle.syncState();
+ if (savedInstanceState != null) {
+ currentTitle = savedInstanceState.getString("title");
+ if (!drawerLayout.isDrawerOpen(navList)) {
+ getSupportActionBar().setTitle(currentTitle);
+ }
+ selectedNavListIndex = savedInstanceState.getInt("selectedNavIndex");
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ drawerToggle.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putString("title", getSupportActionBar().getTitle().toString());
+ outState.putInt("selectedNavIndex", selectedNavListIndex);
+
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ StorageUtils.checkStorageAvailability(this);
+ EventDistributor.getInstance().register(contentUpdate);
+
+ Intent intent = getIntent();
+ if (navDrawerData != null && intent.hasExtra(EXTRA_NAV_INDEX) && intent.hasExtra(EXTRA_NAV_TYPE)) {
+ handleNavIntent();
+ }
+
+ loadData();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ cancelLoadTask();
+ EventDistributor.getInstance().unregister(contentUpdate);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (drawerToggle.onOptionsItemSelected(item)) {
+ return true;
+ }
+ switch (item.getItemId()) {
+ case R.id.show_preferences:
+ startActivity(new Intent(this, PreferenceController.getPreferenceActivity()));
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ return true;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.main, menu);
+ return true;
+ }
+
+ private DBReader.NavDrawerData navDrawerData;
+ private AsyncTask<Void, Void, DBReader.NavDrawerData> loadTask;
+ private int selectedNavListIndex = 0;
+
+ private NavListAdapter.ItemAccess itemAccess = new NavListAdapter.ItemAccess() {
+ @Override
+ public int getCount() {
+ if (navDrawerData != null) {
+ return navDrawerData.feeds.size();
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public Feed getItem(int position) {
+ if (navDrawerData != null && position < navDrawerData.feeds.size()) {
+ return navDrawerData.feeds.get(position);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public int getSelectedItemIndex() {
+ return selectedNavListIndex;
+ }
+
+ @Override
+ public int getQueueSize() {
+ return (navDrawerData != null) ? navDrawerData.queueSize : 0;
+ }
+
+ @Override
+ public int getNumberOfUnreadItems() {
+ return (navDrawerData != null) ? navDrawerData.numUnreadItems : 0;
+ }
+
+
+ };
+
+ private void loadData() {
+ cancelLoadTask();
+ loadTask = new AsyncTask<Void, Void, DBReader.NavDrawerData>() {
+ @Override
+ protected DBReader.NavDrawerData doInBackground(Void... params) {
+ return DBReader.getNavDrawerData(MainActivity.this);
+ }
+
+ @Override
+ protected void onPostExecute(DBReader.NavDrawerData result) {
+ super.onPostExecute(navDrawerData);
+ boolean handleIntent = (navDrawerData == null);
+
+ navDrawerData = result;
+ navAdapter.notifyDataSetChanged();
+
+ if (handleIntent) {
+ handleNavIntent();
+ }
+ }
+ };
+ loadTask.execute();
+ }
+
+ private void cancelLoadTask() {
+ if (loadTask != null) {
+ loadTask.cancel(true);
+ }
+ }
+
+ private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
+
+ @Override
+ public void update(EventDistributor eventDistributor, Integer arg) {
+ if ((EVENTS & arg) != 0) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Received contentUpdate Intent.");
+ loadData();
+ }
+ }
+ };
+
+ private void handleNavIntent() {
+ Intent intent = getIntent();
+ if (intent.hasExtra(EXTRA_NAV_INDEX) && intent.hasExtra(EXTRA_NAV_TYPE)) {
+ int index = intent.getIntExtra(EXTRA_NAV_INDEX, 0);
+ int type = intent.getIntExtra(EXTRA_NAV_TYPE, NavListAdapter.VIEW_TYPE_NAV);
+ Bundle args = intent.getBundleExtra(EXTRA_FRAGMENT_ARGS);
+ loadFragment(type, index, args);
+ }
+ setIntent(new Intent(MainActivity.this, MainActivity.class)); // to avoid handling the intent twice when the configuration changes
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent);
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java
new file mode 100644
index 000000000..561188291
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java
@@ -0,0 +1,500 @@
+package de.danoeh.antennapod.activity;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.Window;
+import android.widget.ImageButton;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.service.playback.PlaybackService;
+import de.danoeh.antennapod.core.storage.DBTasks;
+import de.danoeh.antennapod.core.util.Converter;
+import de.danoeh.antennapod.core.util.ShareUtils;
+import de.danoeh.antennapod.core.util.StorageUtils;
+import de.danoeh.antennapod.core.util.playback.MediaPlayerError;
+import de.danoeh.antennapod.core.util.playback.Playable;
+import de.danoeh.antennapod.core.util.playback.PlaybackController;
+import de.danoeh.antennapod.dialog.TimeDialog;
+
+/**
+ * Provides general features which are both needed for playing audio and video
+ * files.
+ */
+public abstract class MediaplayerActivity extends ActionBarActivity
+ implements OnSeekBarChangeListener {
+ private static final String TAG = "MediaplayerActivity";
+
+ protected PlaybackController controller;
+
+ protected TextView txtvPosition;
+ protected TextView txtvLength;
+ protected SeekBar sbPosition;
+ protected ImageButton butPlay;
+ protected ImageButton butRev;
+ protected ImageButton butFF;
+
+ private PlaybackController newPlaybackController() {
+ return new PlaybackController(this, false) {
+
+ @Override
+ public void setupGUI() {
+ MediaplayerActivity.this.setupGUI();
+ }
+
+ @Override
+ public void onPositionObserverUpdate() {
+ MediaplayerActivity.this.onPositionObserverUpdate();
+ }
+
+ @Override
+ public void onBufferStart() {
+ MediaplayerActivity.this.onBufferStart();
+ }
+
+ @Override
+ public void onBufferEnd() {
+ MediaplayerActivity.this.onBufferEnd();
+ }
+
+ @Override
+ public void onBufferUpdate(float progress) {
+ MediaplayerActivity.this.onBufferUpdate(progress);
+ }
+
+ @Override
+ public void handleError(int code) {
+ MediaplayerActivity.this.handleError(code);
+ }
+
+ @Override
+ public void onReloadNotification(int code) {
+ MediaplayerActivity.this.onReloadNotification(code);
+ }
+
+ @Override
+ public void onSleepTimerUpdate() {
+ supportInvalidateOptionsMenu();
+ }
+
+ @Override
+ public ImageButton getPlayButton() {
+ return butPlay;
+ }
+
+ @Override
+ public void postStatusMsg(int msg) {
+ MediaplayerActivity.this.postStatusMsg(msg);
+ }
+
+ @Override
+ public void clearStatusMsg() {
+ MediaplayerActivity.this.clearStatusMsg();
+ }
+
+ @Override
+ public boolean loadMediaInfo() {
+ return MediaplayerActivity.this.loadMediaInfo();
+ }
+
+ @Override
+ public void onAwaitingVideoSurface() {
+ MediaplayerActivity.this.onAwaitingVideoSurface();
+ }
+
+ @Override
+ public void onServiceQueried() {
+ MediaplayerActivity.this.onServiceQueried();
+ }
+
+ @Override
+ public void onShutdownNotification() {
+ finish();
+ }
+
+ @Override
+ public void onPlaybackEnd() {
+ finish();
+ }
+
+ @Override
+ public void onPlaybackSpeedChange() {
+ MediaplayerActivity.this.onPlaybackSpeedChange();
+ }
+
+ @Override
+ protected void setScreenOn(boolean enable) {
+ super.setScreenOn(enable);
+ MediaplayerActivity.this.setScreenOn(enable);
+ }
+ };
+
+ }
+
+ protected void onPlaybackSpeedChange() {
+
+ }
+
+ protected void onServiceQueried() {
+ supportInvalidateOptionsMenu();
+ }
+
+ protected void chooseTheme() {
+ setTheme(UserPreferences.getTheme());
+ }
+
+ protected void setScreenOn(boolean enable) {
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ chooseTheme();
+ super.onCreate(savedInstanceState);
+
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Creating Activity");
+ StorageUtils.checkStorageAvailability(this);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ orientation = getResources().getConfiguration().orientation;
+ getWindow().setFormat(PixelFormat.TRANSPARENT);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ controller.reinitServiceIfPaused();
+ controller.pause();
+ }
+
+ /**
+ * Should be used to switch to another player activity if the mime type is
+ * not the correct one for the current activity.
+ */
+ protected abstract void onReloadNotification(int notificationCode);
+
+ /**
+ * Should be used to inform the user that the PlaybackService is currently
+ * buffering.
+ */
+ protected abstract void onBufferStart();
+
+ /**
+ * Should be used to hide the view that was showing the 'buffering'-message.
+ */
+ protected abstract void onBufferEnd();
+
+ protected void onBufferUpdate(float progress) {
+ if (sbPosition != null) {
+ sbPosition.setSecondaryProgress((int) progress
+ * sbPosition.getMax());
+ }
+ }
+
+ /**
+ * Current screen orientation.
+ */
+ protected int orientation;
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if (controller != null) {
+ controller.release();
+ }
+ controller = newPlaybackController();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Activity stopped");
+ if (controller != null) {
+ controller.release();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Activity destroyed");
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.mediaplayer, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ Playable media = controller.getMedia();
+
+ menu.findItem(R.id.support_item).setVisible(
+ media != null && media.getPaymentLink() != null &&
+ (media instanceof FeedMedia) &&
+ ((FeedMedia) media).getItem().getFlattrStatus().flattrable()
+ );
+ menu.findItem(R.id.share_link_item).setVisible(
+ media != null && media.getWebsiteLink() != null);
+ menu.findItem(R.id.visit_website_item).setVisible(
+ media != null && media.getWebsiteLink() != null);
+ menu.findItem(R.id.skip_episode_item).setVisible(media != null);
+ boolean sleepTimerSet = controller.sleepTimerActive();
+ boolean sleepTimerNotSet = controller.sleepTimerNotActive();
+ menu.findItem(R.id.set_sleeptimer_item).setVisible(sleepTimerNotSet);
+ menu.findItem(R.id.disable_sleeptimer_item).setVisible(sleepTimerSet);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ 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);
+ 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:
+ if (media instanceof FeedMedia) {
+ FeedItem feedItem = ((FeedMedia) media).getItem();
+ DBTasks.flattrItemIfLoggedIn(this, feedItem);
+ }
+ break;
+ case R.id.share_link_item:
+ ShareUtils.shareLink(this, media.getWebsiteLink());
+ break;
+ case R.id.skip_episode_item:
+ sendBroadcast(new Intent(
+ PlaybackService.ACTION_SKIP_CURRENT_EPISODE));
+ break;
+ default:
+ return false;
+
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Resuming Activity");
+ StorageUtils.checkStorageAvailability(this);
+ controller.init();
+ }
+
+ /**
+ * Called by 'handleStatus()' when the PlaybackService is waiting for
+ * a video surface.
+ */
+ protected abstract void onAwaitingVideoSurface();
+
+ protected abstract void postStatusMsg(int resId);
+
+ protected abstract void clearStatusMsg();
+
+ protected void onPositionObserverUpdate() {
+ if (controller != null) {
+ int currentPosition = controller.getPosition();
+ int duration = controller.getDuration();
+ if (currentPosition != PlaybackService.INVALID_TIME
+ && duration != PlaybackService.INVALID_TIME
+ && controller.getMedia() != null) {
+ txtvPosition.setText(Converter
+ .getDurationStringLong(currentPosition));
+ txtvLength.setText(Converter.getDurationStringLong(duration));
+ updateProgressbarPosition(currentPosition, duration);
+ } else {
+ Log.w(TAG,
+ "Could not react to position observer update because of invalid time");
+ }
+ }
+ }
+
+ private void updateProgressbarPosition(int position, int duration) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Updating progressbar info");
+ float progress = ((float) position) / duration;
+ sbPosition.setProgress((int) (progress * sbPosition.getMax()));
+ }
+
+ /**
+ * Load information about the media that is going to be played or currently
+ * being played. This method will be called when the activity is connected
+ * to the PlaybackService to ensure that the activity has the right
+ * FeedMedia object.
+ */
+ protected boolean loadMediaInfo() {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Loading media info");
+ Playable media = controller.getMedia();
+ if (media != null) {
+ txtvPosition.setText(Converter.getDurationStringLong((media
+ .getPosition())));
+
+ if (media.getDuration() != 0) {
+ txtvLength.setText(Converter.getDurationStringLong(media
+ .getDuration()));
+ float progress = ((float) media.getPosition())
+ / media.getDuration();
+ sbPosition.setProgress((int) (progress * sbPosition.getMax()));
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected void setupGUI() {
+ setContentView(getContentViewResourceId());
+ sbPosition = (SeekBar) findViewById(R.id.sbPosition);
+ txtvPosition = (TextView) findViewById(R.id.txtvPosition);
+ txtvLength = (TextView) findViewById(R.id.txtvLength);
+ butPlay = (ImageButton) findViewById(R.id.butPlay);
+ butRev = (ImageButton) findViewById(R.id.butRev);
+ butFF = (ImageButton) findViewById(R.id.butFF);
+
+ // SEEKBAR SETUP
+
+ sbPosition.setOnSeekBarChangeListener(this);
+
+ // BUTTON SETUP
+
+ butPlay.setOnClickListener(controller.newOnPlayButtonClickListener());
+
+ if (butFF != null) {
+ butFF.setOnClickListener(controller.newOnFFButtonClickListener());
+ }
+ if (butRev != null) {
+ butRev.setOnClickListener(controller.newOnRevButtonClickListener());
+ }
+
+ }
+
+ protected abstract int getContentViewResourceId();
+
+ void handleError(int errorCode) {
+ final AlertDialog.Builder errorDialog = new AlertDialog.Builder(this);
+ errorDialog.setTitle(R.string.error_label);
+ errorDialog
+ .setMessage(MediaPlayerError.getErrorString(this, errorCode));
+ errorDialog.setNeutralButton("OK",
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ finish();
+ }
+ }
+ );
+ errorDialog.create().show();
+ }
+
+ float prog;
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress,
+ boolean fromUser) {
+ if (controller != null) {
+ prog = controller.onSeekBarProgressChanged(seekBar, progress, fromUser,
+ txtvPosition);
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ if (controller != null) {
+ controller.onSeekBarStartTrackingTouch(seekBar);
+ }
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ if (controller != null) {
+ controller.onSeekBarStopTrackingTouch(seekBar, prog);
+ }
+ }
+
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
new file mode 100644
index 000000000..9f028000e
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
@@ -0,0 +1,428 @@
+package de.danoeh.antennapod.activity;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.util.Log;
+import android.widget.ArrayAdapter;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.dialog.AuthenticationDialog;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedPreferences;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.service.download.DownloadRequest;
+import de.danoeh.antennapod.core.service.download.DownloadStatus;
+import de.danoeh.antennapod.core.service.download.Downloader;
+import de.danoeh.antennapod.core.service.download.HttpDownloader;
+import de.danoeh.antennapod.core.syndication.handler.FeedHandler;
+import de.danoeh.antennapod.core.syndication.handler.FeedHandlerResult;
+import de.danoeh.antennapod.core.syndication.handler.UnsupportedFeedtypeException;
+import de.danoeh.antennapod.core.util.DownloadError;
+import de.danoeh.antennapod.core.util.FileNameGenerator;
+import de.danoeh.antennapod.core.util.StorageUtils;
+import de.danoeh.antennapod.core.util.URLChecker;
+import de.danoeh.antennapod.core.util.syndication.FeedDiscoverer;
+import org.apache.commons.lang3.StringUtils;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Downloads a feed from a feed URL and parses it. Subclasses can display the
+ * feed object that was parsed. This activity MUST be started with a given URL
+ * or an Exception will be thrown.
+ * <p/>
+ * If the feed cannot be downloaded or parsed, an error dialog will be displayed
+ * and the activity will finish as soon as the error dialog is closed.
+ */
+public abstract class OnlineFeedViewActivity extends ActionBarActivity {
+ private static final String TAG = "OnlineFeedViewActivity";
+ public static final String ARG_FEEDURL = "arg.feedurl";
+
+ /**
+ * Optional argument: specify a title for the actionbar.
+ */
+ public static final String ARG_TITLE = "title";
+
+ public static final int RESULT_ERROR = 2;
+
+ private Feed feed;
+ private Map<String, String> alternateFeedUrls;
+ private Downloader downloader;
+
+ private boolean isPaused;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+
+ if (getIntent() != null && getIntent().hasExtra(ARG_TITLE)) {
+ getSupportActionBar().setTitle(getIntent().getStringExtra(ARG_TITLE));
+ }
+
+ StorageUtils.checkStorageAvailability(this);
+
+ final String feedUrl;
+ if (getIntent().hasExtra(ARG_FEEDURL)) {
+ feedUrl = getIntent().getStringExtra(ARG_FEEDURL);
+ } else if (StringUtils.equals(getIntent().getAction(), Intent.ACTION_SEND)
+ || StringUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) {
+ feedUrl = (StringUtils.equals(getIntent().getAction(), Intent.ACTION_SEND))
+ ? getIntent().getStringExtra(Intent.EXTRA_TEXT) : getIntent().getDataString();
+
+ getSupportActionBar().setTitle(R.string.add_new_feed_label);
+ } else {
+ throw new IllegalArgumentException(
+ "Activity must be started with feedurl argument!");
+ }
+
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Activity was started with url " + feedUrl);
+ setLoadingLayout();
+ if (savedInstanceState == null) {
+ startFeedDownload(feedUrl, null, null);
+ } else {
+ startFeedDownload(feedUrl, savedInstanceState.getString("username"), savedInstanceState.getString("password"));
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ isPaused = false;
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ isPaused = true;
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (feed != null && feed.getPreferences() != null) {
+ outState.putString("username", feed.getPreferences().getUsername());
+ outState.putString("password", feed.getPreferences().getPassword());
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (downloader != null && !downloader.isFinished()) {
+ downloader.cancel();
+ }
+ }
+
+ private void resetIntent(String url, String title) {
+ Intent intent = new Intent();
+ intent.putExtra(ARG_FEEDURL, url);
+ intent.putExtra(ARG_TITLE, title);
+ setIntent(intent);
+ }
+
+
+ private void onDownloadCompleted(final Downloader downloader) {
+ runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ if (BuildConfig.DEBUG) Log.d(TAG, "Download was completed");
+ DownloadStatus status = downloader.getResult();
+ if (status != null) {
+ if (!status.isCancelled()) {
+ if (status.isSuccessful()) {
+ parseFeed();
+ } else if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) {
+ if (!isFinishing() && !isPaused) {
+ Dialog dialog = new FeedViewAuthenticationDialog(OnlineFeedViewActivity.this,
+ R.string.authentication_notification_title, downloader.getDownloadRequest().getSource());
+ dialog.show();
+ }
+ } else {
+ String errorMsg = status.getReason().getErrorString(
+ OnlineFeedViewActivity.this);
+ if (errorMsg != null
+ && status.getReasonDetailed() != null) {
+ errorMsg += " ("
+ + status.getReasonDetailed() + ")";
+ }
+ showErrorDialog(errorMsg);
+ }
+ }
+ } else {
+ Log.wtf(TAG,
+ "DownloadStatus returned by Downloader was null");
+ finish();
+ }
+ }
+ });
+
+ }
+
+ private void startFeedDownload(String url, String username, String password) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Starting feed download");
+ url = URLChecker.prepareURL(url);
+ feed = new Feed(url, new Date());
+ if (username != null && password != null) {
+ feed.setPreferences(new FeedPreferences(0, false, username, password));
+ }
+ String fileUrl = new File(getExternalCacheDir(),
+ FileNameGenerator.generateFileName(feed.getDownload_url()))
+ .toString();
+ feed.setFile_url(fileUrl);
+ final DownloadRequest request = new DownloadRequest(feed.getFile_url(),
+ feed.getDownload_url(), "OnlineFeed", 0, Feed.FEEDFILETYPE_FEED, username, password, true, null);
+ downloader = new HttpDownloader(
+ request);
+ new Thread() {
+ @Override
+ public void run() {
+ loadData();
+ downloader.call();
+ onDownloadCompleted(downloader);
+ }
+ }.start();
+
+
+ }
+
+ /**
+ * Displays a progress indicator.
+ */
+ private void setLoadingLayout() {
+ RelativeLayout rl = new RelativeLayout(this);
+ RelativeLayout.LayoutParams rlLayoutParams = new RelativeLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.MATCH_PARENT);
+
+ ProgressBar pb = new ProgressBar(this);
+ pb.setIndeterminate(true);
+ RelativeLayout.LayoutParams pbLayoutParams = new RelativeLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT);
+ pbLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
+ rl.addView(pb, pbLayoutParams);
+ addContentView(rl, rlLayoutParams);
+ }
+
+ private void parseFeed() {
+ if (feed == null || feed.getFile_url() == null && feed.isDownloaded()) {
+ throw new IllegalStateException(
+ "feed must be non-null and downloaded when parseFeed is called");
+ }
+
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Parsing feed");
+
+ Thread thread = new Thread() {
+
+ @Override
+ public void run() {
+ String reasonDetailed = "";
+ boolean successful = false;
+ FeedHandler handler = new FeedHandler();
+ try {
+ FeedHandlerResult result = handler.parseFeed(feed);
+ feed = result.feed;
+ alternateFeedUrls = result.alternateFeedUrls;
+ successful = true;
+ } catch (SAXException e) {
+ e.printStackTrace();
+ reasonDetailed = e.getMessage();
+ } catch (IOException e) {
+ e.printStackTrace();
+ reasonDetailed = e.getMessage();
+ } catch (ParserConfigurationException e) {
+ e.printStackTrace();
+ reasonDetailed = e.getMessage();
+ } catch (UnsupportedFeedtypeException e) {
+ if (BuildConfig.DEBUG) Log.d(TAG, "Unsupported feed type detected");
+ if (StringUtils.equalsIgnoreCase("html", e.getRootElement())) {
+ if (showFeedDiscoveryDialog(new File(feed.getFile_url()), feed.getDownload_url())) {
+ return;
+ }
+ } else {
+ e.printStackTrace();
+ reasonDetailed = e.getMessage();
+ }
+ } finally {
+ boolean rc = new File(feed.getFile_url()).delete();
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Deleted feed source file. Result: " + rc);
+ }
+
+ if (successful) {
+ beforeShowFeedInformation(feed, alternateFeedUrls);
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ showFeedInformation(feed, alternateFeedUrls);
+ }
+ });
+ } else {
+ final String errorMsg =
+ DownloadError.ERROR_PARSER_EXCEPTION.getErrorString(
+ OnlineFeedViewActivity.this)
+ + " (" + reasonDetailed + ")";
+ runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ showErrorDialog(errorMsg);
+ }
+ });
+ }
+ }
+ };
+ thread.start();
+ }
+
+ /**
+ * Can be used to load data asynchronously.
+ */
+ protected void loadData() {
+
+ }
+
+ /**
+ * Called after the feed has been downloaded and parsed and before showFeedInformation is called.
+ * This method is executed on a background thread
+ */
+ protected void beforeShowFeedInformation(Feed feed, Map<String, String> alternateFeedUrls) {
+
+ }
+
+ /**
+ * Called when feed parsed successfully.
+ * This method is executed on the GUI thread.
+ */
+ protected void showFeedInformation(Feed feed, Map<String, String> alternateFeedUrls) {
+
+ }
+
+ private void showErrorDialog(String errorMsg) {
+ if (!isFinishing() && !isPaused) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.error_label);
+ if (errorMsg != null) {
+ builder.setMessage(getString(R.string.error_msg_prefix) + errorMsg);
+ } else {
+ builder.setMessage(R.string.error_msg_prefix);
+ }
+ builder.setNeutralButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ }
+ );
+ builder.setOnCancelListener(new OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ setResult(RESULT_ERROR);
+ finish();
+ }
+ });
+ builder.show();
+ }
+ }
+
+ private boolean showFeedDiscoveryDialog(File feedFile, String baseUrl) {
+ FeedDiscoverer fd = new FeedDiscoverer();
+ final Map<String, String> urlsMap;
+ try {
+ urlsMap = fd.findLinks(feedFile, baseUrl);
+ if (urlsMap == null || urlsMap.isEmpty()) {
+ return false;
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (isPaused || isFinishing()) {
+ return;
+ }
+
+ final List<String> titles = new ArrayList<String>();
+ final List<String> urls = new ArrayList<String>();
+
+ urls.addAll(urlsMap.keySet());
+ for (String url : urls) {
+ titles.add(urlsMap.get(url));
+ }
+
+ final ArrayAdapter<String> adapter = new ArrayAdapter<String>(OnlineFeedViewActivity.this, R.layout.ellipsize_start_listitem, R.id.txtvTitle, titles);
+ DialogInterface.OnClickListener onClickListener = new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ String selectedUrl = urls.get(which);
+ dialog.dismiss();
+ resetIntent(selectedUrl, titles.get(which));
+ startFeedDownload(selectedUrl, null, null);
+ }
+ };
+
+ AlertDialog.Builder ab = new AlertDialog.Builder(OnlineFeedViewActivity.this)
+ .setTitle(R.string.feeds_label)
+ .setCancelable(true)
+ .setOnCancelListener(new OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ finish();
+ }
+ })
+ .setAdapter(adapter, onClickListener);
+ ab.show();
+ }
+ });
+
+
+ return true;
+ }
+
+ private class FeedViewAuthenticationDialog extends AuthenticationDialog {
+
+ private String feedUrl;
+
+ public FeedViewAuthenticationDialog(Context context, int titleRes, String feedUrl) {
+ super(context, titleRes, true, false, null, null);
+ this.feedUrl = feedUrl;
+ }
+
+ @Override
+ protected void onCancelled() {
+ super.onCancelled();
+ finish();
+ }
+
+ @Override
+ protected void onConfirmed(String username, String password, boolean saveUsernamePassword) {
+ startFeedDownload(feedUrl, username, password);
+ }
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java
new file mode 100644
index 000000000..8a9ec58d3
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java
@@ -0,0 +1,134 @@
+package de.danoeh.antennapod.activity;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
+import android.util.SparseBooleanArray;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.opml.OpmlElement;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Displays the feeds that the OPML-Importer has read and lets the user choose
+ * which feeds he wants to import.
+ */
+public class OpmlFeedChooserActivity extends ActionBarActivity {
+ private static final String TAG = "OpmlFeedChooserActivity";
+
+ public static final String EXTRA_SELECTED_ITEMS = "de.danoeh.antennapod.selectedItems";
+
+ private Button butConfirm;
+ private Button butCancel;
+ private ListView feedlist;
+ private ArrayAdapter<String> listAdapter;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.opml_selection);
+ butConfirm = (Button) findViewById(R.id.butConfirm);
+ butCancel = (Button) findViewById(R.id.butCancel);
+ feedlist = (ListView) findViewById(R.id.feedlist);
+
+ feedlist.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+ listAdapter = new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_multiple_choice,
+ getTitleList());
+
+ feedlist.setAdapter(listAdapter);
+
+ butCancel.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ });
+
+ butConfirm.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent();
+ SparseBooleanArray checked = feedlist.getCheckedItemPositions();
+
+ int checkedCount = 0;
+ // Get number of checked items
+ for (int i = 0; i < checked.size(); i++) {
+ if (checked.valueAt(i)) {
+ checkedCount++;
+ }
+ }
+ int[] selection = new int[checkedCount];
+ for (int i = 0, collected = 0; collected < checkedCount; i++) {
+ if (checked.valueAt(i)) {
+ selection[collected] = checked.keyAt(i);
+ collected++;
+ }
+ }
+ intent.putExtra(EXTRA_SELECTED_ITEMS, selection);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+ });
+
+ }
+
+ private List<String> getTitleList() {
+ List<String> result = new ArrayList<String>();
+ if (OpmlImportHolder.getReadElements() != null) {
+ for (OpmlElement element : OpmlImportHolder.getReadElements()) {
+ result.add(element.getText());
+ }
+
+ }
+ return result;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.select_all_item, Menu.NONE,
+ R.string.select_all_label),
+ MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
+
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.deselect_all_item, Menu.NONE,
+ R.string.deselect_all_label),
+ MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.select_all_item:
+ selectAllItems(true);
+ return true;
+ case R.id.deselect_all_item:
+ selectAllItems(false);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private void selectAllItems(boolean b) {
+ for (int i = 0; i < feedlist.getCount(); i++) {
+ feedlist.setItemChecked(i, b);
+ }
+ }
+
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java
new file mode 100644
index 000000000..d974e0e1b
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java
@@ -0,0 +1,90 @@
+package de.danoeh.antennapod.activity;
+
+import android.content.Intent;
+import android.support.v7.app.ActionBarActivity;
+import android.util.Log;
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.asynctask.OpmlFeedQueuer;
+import de.danoeh.antennapod.asynctask.OpmlImportWorker;
+import de.danoeh.antennapod.core.opml.OpmlElement;
+
+import java.io.Reader;
+import java.util.ArrayList;
+
+/**
+ * Base activity for Opml Import - e.g. with code what to do afterwards
+ * */
+public class OpmlImportBaseActivity extends ActionBarActivity {
+
+ private static final String TAG = "OpmlImportBaseActivity";
+ private OpmlImportWorker importWorker;
+
+ /**
+ * Handles the choices made by the user in the OpmlFeedChooserActivity and
+ * starts the OpmlFeedQueuer if necessary.
+ */
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Received result");
+ if (resultCode == RESULT_CANCELED) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Activity was cancelled");
+ if (finishWhenCanceled())
+ finish();
+ } else {
+ int[] selected = data
+ .getIntArrayExtra(OpmlFeedChooserActivity.EXTRA_SELECTED_ITEMS);
+ if (selected != null && selected.length > 0) {
+ OpmlFeedQueuer queuer = new OpmlFeedQueuer(this, selected) {
+
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ Intent intent = new Intent(OpmlImportBaseActivity.this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
+ | Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ }
+
+ };
+ queuer.executeAsync();
+ } else {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "No items were selected");
+ }
+ }
+ }
+
+ /** Starts the import process. */
+ protected void startImport(Reader reader) {
+
+ if (reader != null) {
+ importWorker = new OpmlImportWorker(this, reader) {
+
+ @Override
+ protected void onPostExecute(ArrayList<OpmlElement> result) {
+ super.onPostExecute(result);
+ if (result != null) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Parsing was successful");
+ OpmlImportHolder.setReadElements(result);
+ startActivityForResult(new Intent(
+ OpmlImportBaseActivity.this,
+ OpmlFeedChooserActivity.class), 0);
+ } else {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Parser error occurred");
+ }
+ }
+ };
+ importWorker.executeAsync();
+ }
+ }
+
+ protected boolean finishWhenCanceled() {
+ return false;
+ }
+
+
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java
new file mode 100644
index 000000000..e42072ead
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java
@@ -0,0 +1,38 @@
+package de.danoeh.antennapod.activity;
+
+import android.app.AlertDialog;
+import android.os.Bundle;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.util.LangUtils;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URL;
+
+/** Lets the user start the OPML-import process. */
+public class OpmlImportFromIntentActivity extends OpmlImportBaseActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ try {
+ URL mOpmlURL = new URL(getIntent().getData().toString());
+ BufferedReader in = new BufferedReader(new InputStreamReader(mOpmlURL.openStream(),
+ LangUtils.UTF_8));
+ startImport(in);
+ } catch (Exception e) {
+ new AlertDialog.Builder(this).setMessage("Cannot open XML - Reason: " + e.getMessage()).show();
+ }
+
+ }
+
+ @Override
+ protected boolean finishWhenCanceled() {
+ return true;
+ }
+
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java
new file mode 100644
index 000000000..162a8f2e5
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java
@@ -0,0 +1,171 @@
+package de.danoeh.antennapod.activity;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.util.LangUtils;
+import de.danoeh.antennapod.core.util.StorageUtils;
+
+import java.io.*;
+
+/**
+ * Lets the user start the OPML-import process from a path
+ */
+public class OpmlImportFromPathActivity extends OpmlImportBaseActivity {
+ private static final String TAG = "OpmlImportFromPathActivity";
+ private TextView txtvPath;
+ private Button butStart;
+ private String importPath;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ setContentView(R.layout.opml_import);
+
+ txtvPath = (TextView) findViewById(R.id.txtvPath);
+ butStart = (Button) findViewById(R.id.butStartImport);
+
+ butStart.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ checkFolderForFiles();
+ }
+
+ });
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ StorageUtils.checkStorageAvailability(this);
+ setImportPath();
+ }
+
+ /**
+ * Sets the importPath variable and makes txtvPath display the import
+ * directory.
+ */
+ private void setImportPath() {
+ File importDir = UserPreferences.getDataFolder(this, UserPreferences.IMPORT_DIR);
+ boolean success = true;
+ if (!importDir.exists()) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Import directory doesn't exist. Creating...");
+ success = importDir.mkdir();
+ if (!success) {
+ Log.e(TAG, "Could not create directory");
+ }
+ }
+ if (success) {
+ txtvPath.setText(importDir.toString());
+ importPath = importDir.toString();
+ } else {
+ txtvPath.setText(R.string.opml_directory_error);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Looks at the contents of the import directory and decides what to do. If
+ * more than one file is in the directory, a dialog will be created to let
+ * the user choose which item to import
+ */
+ private void checkFolderForFiles() {
+ File dir = new File(importPath);
+ if (dir.isDirectory()) {
+ File[] fileList = dir.listFiles();
+ if (fileList.length == 1) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Found one file, choosing that one.");
+ startImport(fileList[0]);
+ } else if (fileList.length > 1) {
+ Log.w(TAG, "Import directory contains more than one file.");
+ askForFile(dir);
+ } else {
+ Log.e(TAG, "Import directory is empty");
+ Toast toast = Toast
+ .makeText(this, R.string.opml_import_error_dir_empty,
+ Toast.LENGTH_LONG);
+ toast.show();
+ }
+ }
+ }
+
+ private void startImport(File file) {
+ Reader mReader = null;
+ try {
+ mReader = new InputStreamReader(new FileInputStream(file),
+ LangUtils.UTF_8);
+ if (BuildConfig.DEBUG) Log.d(TAG, "Parsing " + file.toString());
+ startImport(mReader);
+ } catch (FileNotFoundException e) {
+ Log.d(TAG, "File not found which really should be there");
+ // this should never happen as it is a file we have just chosen
+ }
+ }
+
+ /**
+ * Asks the user to choose from a list of files in a directory and returns
+ * his choice.
+ */
+ private void askForFile(File dir) {
+ final File[] fileList = dir.listFiles();
+ String[] fileNames = dir.list();
+
+ AlertDialog.Builder dialog = new AlertDialog.Builder(this);
+ dialog.setTitle(R.string.choose_file_to_import_label);
+ dialog.setNeutralButton(android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Dialog was cancelled");
+ dialog.dismiss();
+ }
+ });
+ dialog.setItems(fileNames, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "File at index " + which + " was chosen");
+ dialog.dismiss();
+ startImport(fileList[which]);
+ }
+ });
+ dialog.create().show();
+ }
+
+
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportHolder.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportHolder.java
new file mode 100644
index 000000000..7afa270cc
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportHolder.java
@@ -0,0 +1,29 @@
+package de.danoeh.antennapod.activity;
+
+import de.danoeh.antennapod.core.opml.OpmlElement;
+
+import java.util.ArrayList;
+
+/**
+ * Hold infos gathered by Ompl-Import
+ * <p/>
+ * Created with IntelliJ IDEA.
+ * User: ligi
+ * Date: 1/23/13
+ * Time: 2:15 PM
+ */
+public class OpmlImportHolder {
+
+ private static ArrayList<OpmlElement> readElements;
+
+ public static ArrayList<OpmlElement> getReadElements() {
+ return readElements;
+ }
+
+ public static void setReadElements(ArrayList<OpmlElement> _readElements) {
+ readElements = _readElements;
+ }
+
+
+}
+
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java
new file mode 100644
index 000000000..c40489374
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java
@@ -0,0 +1,120 @@
+package de.danoeh.antennapod.activity;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.preferences.PreferenceController;
+
+/**
+ * PreferenceActivity for API 11+. In order to change the behavior of the preference UI, see
+ * PreferenceController.
+ */
+public class PreferenceActivity extends ActionBarActivity {
+
+ private PreferenceController preferenceController;
+ private MainFragment prefFragment;
+ private static PreferenceActivity instance;
+
+
+ private final PreferenceController.PreferenceUI preferenceUI = new PreferenceController.PreferenceUI() {
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ @Override
+ public Preference findPreference(CharSequence key) {
+ return prefFragment.findPreference(key);
+ }
+
+ @Override
+ public Activity getActivity() {
+ return PreferenceActivity.this;
+ }
+ };
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+ instance = this;
+
+ ActionBar ab = getSupportActionBar();
+ if (ab != null) {
+ ab.setDisplayHomeAsUpEnabled(true);
+ }
+
+ // set up layout
+ FrameLayout root = new FrameLayout(this);
+ root.setId(R.id.content);
+ root.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ setContentView(root);
+ prefFragment = new MainFragment();
+ getFragmentManager().beginTransaction().replace(R.id.content, prefFragment).commit();
+
+ preferenceController = new PreferenceController(preferenceUI);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ preferenceController.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public void onBackPressed() {
+ // The default back button behavior has to be overwritten because changing the theme clears the back stack
+ Intent destIntent = new Intent(this, MainActivity.class);
+ destIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(destIntent);
+ finish();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ Intent destIntent = new Intent(this, MainActivity.class);
+ destIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(destIntent);
+ finish();
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public static class MainFragment extends PreferenceFragment {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.preferences);
+ instance.preferenceController.onCreate();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ instance.preferenceController.onResume();
+ }
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivityGingerbread.java b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivityGingerbread.java
new file mode 100644
index 000000000..c58593f77
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivityGingerbread.java
@@ -0,0 +1,96 @@
+package de.danoeh.antennapod.activity;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Resources.Theme;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.preferences.PreferenceController;
+
+/**
+ * PreferenceActivity for API 10. In order to change the behavior of the preference UI, see
+ * PreferenceController.
+ */
+public class PreferenceActivityGingerbread extends android.preference.PreferenceActivity {
+ private static final String TAG = "PreferenceActivity";
+
+ private PreferenceController preferenceController;
+
+ private final PreferenceController.PreferenceUI preferenceUI = new PreferenceController.PreferenceUI() {
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public Preference findPreference(CharSequence key) {
+ return PreferenceActivityGingerbread.this.findPreference(key);
+ }
+
+ @Override
+ public Activity getActivity() {
+ return PreferenceActivityGingerbread.this;
+ }
+ };
+
+ @SuppressLint("NewApi")
+ @SuppressWarnings("deprecation")
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+
+ addPreferencesFromResource(R.xml.preferences);
+ preferenceController = new PreferenceController(preferenceUI);
+ preferenceController.onCreate();
+ }
+
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ preferenceController.onResume();
+ }
+
+ @Override
+ protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
+ theme.applyStyle(UserPreferences.getTheme(), true);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ preferenceController.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
+ Preference preference) {
+ super.onPreferenceTreeClick(preferenceScreen, preference);
+ if (preference != null)
+ if (preference instanceof PreferenceScreen)
+ if (((PreferenceScreen) preference).getDialog() != null)
+ ((PreferenceScreen) preference)
+ .getDialog()
+ .getWindow()
+ .getDecorView()
+ .setBackgroundDrawable(
+ this.getWindow().getDecorView()
+ .getBackground().getConstantState()
+ .newDrawable()
+ );
+ return false;
+ }
+
+ @Override
+ public void onBackPressed() {
+ // The default back button behavior has to be overwritten because changing the theme clears the back stack
+ Intent destIntent = new Intent(this, MainActivity.class);
+ destIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(destIntent);
+ finish();
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java
new file mode 100644
index 000000000..173bec6b2
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/StorageErrorActivity.java
@@ -0,0 +1,75 @@
+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.v7.app.ActionBarActivity;
+import android.util.Log;
+
+import org.apache.commons.lang3.StringUtils;
+
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.util.StorageUtils;
+
+/** Is show if there is now external storage available. */
+public class StorageErrorActivity extends ActionBarActivity {
+ private static final String TAG = "StorageErrorActivity";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.storage_error);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ try {
+ unregisterReceiver(mediaUpdate);
+ } catch (IllegalArgumentException e) {
+
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (StorageUtils.storageAvailable(this)) {
+ leaveErrorState();
+ } else {
+ registerReceiver(mediaUpdate, new IntentFilter(
+ Intent.ACTION_MEDIA_MOUNTED));
+ }
+ }
+
+ private void leaveErrorState() {
+ finish();
+ startActivity(new Intent(this, MainActivity.class));
+ }
+
+ private BroadcastReceiver mediaUpdate = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (StringUtils.equals(intent.getAction(), Intent.ACTION_MEDIA_MOUNTED)) {
+ if (intent.getBooleanExtra("read-only", true)) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Media was mounted; Finishing activity");
+ leaveErrorState();
+ } else {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG,
+ "Media seemed to have been mounted read only");
+ }
+ }
+ }
+
+ };
+
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java
new file mode 100644
index 000000000..d625f21da
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java
@@ -0,0 +1,360 @@
+package de.danoeh.antennapod.activity;
+
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.graphics.drawable.ColorDrawable;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.view.WindowCompat;
+import android.util.Log;
+import android.util.Pair;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
+
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.feed.MediaType;
+import de.danoeh.antennapod.core.service.playback.PlaybackService;
+import de.danoeh.antennapod.core.service.playback.PlayerStatus;
+import de.danoeh.antennapod.core.util.playback.ExternalMedia;
+import de.danoeh.antennapod.core.util.playback.Playable;
+import de.danoeh.antennapod.view.AspectRatioVideoView;
+
+/**
+ * Activity for playing video files.
+ */
+public class VideoplayerActivity extends MediaplayerActivity {
+ private static final String TAG = "VideoplayerActivity";
+
+ /**
+ * True if video controls are currently visible.
+ */
+ private boolean videoControlsShowing = true;
+ private boolean videoSurfaceCreated = false;
+ private VideoControlsHider videoControlsToggler;
+
+ private LinearLayout videoOverlay;
+ private AspectRatioVideoView videoview;
+ private ProgressBar progressIndicator;
+
+ @Override
+ protected void chooseTheme() {
+ setTheme(R.style.Theme_AntennaPod_VideoPlayer);
+ }
+
+ @SuppressLint("AppCompatMethod")
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ supportRequestWindowFeature(WindowCompat.FEATURE_ACTION_BAR_OVERLAY); // has to be called before setting layout content
+ super.onCreate(savedInstanceState);
+ getSupportActionBar().setBackgroundDrawable(new ColorDrawable(0x80000000));
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ if (videoControlsToggler != null) {
+ videoControlsToggler.cancel(true);
+ }
+ if (controller != null && controller.getStatus() == PlayerStatus.PLAYING) {
+ controller.pause();
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (getIntent().getAction() != null
+ && getIntent().getAction().equals(Intent.ACTION_VIEW)) {
+ Intent intent = getIntent();
+ if (BuildConfig.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 boolean loadMediaInfo() {
+ if (!super.loadMediaInfo()) {
+ return false;
+ }
+ Playable media = controller.getMedia();
+ if (media != null) {
+ getSupportActionBar().setSubtitle(media.getEpisodeTitle());
+ getSupportActionBar().setTitle(media.getFeedTitle());
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ protected void setupGUI() {
+ super.setupGUI();
+ videoOverlay = (LinearLayout) findViewById(R.id.overlay);
+ videoview = (AspectRatioVideoView) findViewById(R.id.videoview);
+ progressIndicator = (ProgressBar) findViewById(R.id.progressIndicator);
+ videoview.getHolder().addCallback(surfaceHolderCallback);
+ videoview.setOnTouchListener(onVideoviewTouched);
+
+ setupVideoControlsToggler();
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ }
+
+ @Override
+ protected void onAwaitingVideoSurface() {
+ if (videoSurfaceCreated) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG,
+ "Videosurface already created, setting videosurface now");
+
+ Pair<Integer, Integer> videoSize = controller.getVideoSize();
+ if (videoSize != null && videoSize.first > 0 && videoSize.second > 0) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Width,height of video: " + videoSize.first + ", " + videoSize.second);
+ videoview.setVideoSize(videoSize.first, videoSize.second);
+ } else {
+ Log.e(TAG, "Could not determine video size");
+ }
+ controller.setVideoSurface(videoview.getHolder());
+ }
+ }
+
+ @Override
+ protected void postStatusMsg(int resId) {
+ if (resId == R.string.player_preparing_msg) {
+ progressIndicator.setVisibility(View.VISIBLE);
+ } else {
+ progressIndicator.setVisibility(View.INVISIBLE);
+ }
+
+ }
+
+ @Override
+ protected void clearStatusMsg() {
+ progressIndicator.setVisibility(View.INVISIBLE);
+ }
+
+ View.OnTouchListener onVideoviewTouched = new View.OnTouchListener() {
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ if (videoControlsToggler != null) {
+ videoControlsToggler.cancel(true);
+ }
+ toggleVideoControlsVisibility();
+ if (videoControlsShowing) {
+ setupVideoControlsToggler();
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+ };
+
+ @SuppressLint("NewApi")
+ void setupVideoControlsToggler() {
+ if (videoControlsToggler != null) {
+ videoControlsToggler.cancel(true);
+ }
+ videoControlsToggler = new VideoControlsHider();
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ videoControlsToggler
+ .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ videoControlsToggler.execute();
+ }
+ }
+
+ private void toggleVideoControlsVisibility() {
+ if (videoControlsShowing) {
+ getSupportActionBar().hide();
+ hideVideoControls();
+ } else {
+ getSupportActionBar().show();
+ showVideoControls();
+ }
+ videoControlsShowing = !videoControlsShowing;
+ }
+
+ /**
+ * Hides the videocontrols after a certain period of time.
+ */
+ public class VideoControlsHider extends AsyncTask<Void, Void, Void> {
+ @Override
+ protected void onCancelled() {
+ videoControlsToggler = null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ videoControlsToggler = null;
+ }
+
+ private static final int WAITING_INTERVALL = 5000;
+ private static final String TAG = "VideoControlsToggler";
+
+ @Override
+ protected void onProgressUpdate(Void... values) {
+ if (videoControlsShowing) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Hiding video controls");
+ getSupportActionBar().hide();
+ hideVideoControls();
+ videoControlsShowing = false;
+ }
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ Thread.sleep(WAITING_INTERVALL);
+ } catch (InterruptedException e) {
+ return null;
+ }
+ publishProgress();
+ return null;
+ }
+
+ }
+
+ private final SurfaceHolder.Callback surfaceHolderCallback = new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width,
+ int height) {
+ holder.setFixedSize(width, height);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Videoview holder created");
+ videoSurfaceCreated = true;
+ if (controller.getStatus() == PlayerStatus.PLAYING) {
+ if (controller.serviceAvailable()) {
+ controller.setVideoSurface(holder);
+ } else {
+ Log.e(TAG,
+ "Could'nt attach surface to mediaplayer - reference to service was null");
+ }
+ }
+
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG, "Videosurface was destroyed");
+ videoSurfaceCreated = false;
+ controller.notifyVideoSurfaceAbandoned();
+ }
+ };
+
+
+ @Override
+ protected void onReloadNotification(int notificationCode) {
+ if (notificationCode == PlaybackService.EXTRA_CODE_AUDIO) {
+ if (BuildConfig.DEBUG)
+ Log.d(TAG,
+ "ReloadNotification received, switching to Audioplayer now");
+ finish();
+ startActivity(new Intent(this, AudioplayerActivity.class));
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ super.onStartTrackingTouch(seekBar);
+ if (videoControlsToggler != null) {
+ videoControlsToggler.cancel(true);
+ }
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ super.onStopTrackingTouch(seekBar);
+ setupVideoControlsToggler();
+ }
+
+ @Override
+ protected void onBufferStart() {
+ progressIndicator.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ protected void onBufferEnd() {
+ progressIndicator.setVisibility(View.INVISIBLE);
+ }
+
+ @SuppressLint("NewApi")
+ private void showVideoControls() {
+ videoOverlay.setVisibility(View.VISIBLE);
+ butPlay.setVisibility(View.VISIBLE);
+ final Animation animation = AnimationUtils.loadAnimation(this,
+ R.anim.fade_in);
+ if (animation != null) {
+ videoOverlay.startAnimation(animation);
+ butPlay.startAnimation(animation);
+ }
+ if (Build.VERSION.SDK_INT >= 14) {
+ videoview.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
+ }
+ }
+
+ @SuppressLint("NewApi")
+ private void hideVideoControls() {
+ final Animation animation = AnimationUtils.loadAnimation(this,
+ R.anim.fade_out);
+ if (animation != null) {
+ videoOverlay.startAnimation(animation);
+ butPlay.startAnimation(animation);
+ }
+ if (Build.VERSION.SDK_INT >= 14) {
+ videoview.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
+ getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+
+ }
+ videoOverlay.setVisibility(View.GONE);
+ butPlay.setVisibility(View.GONE);
+ }
+
+ @Override
+ protected int getContentViewResourceId() {
+ return R.layout.videoplayer_activity;
+ }
+
+
+ @Override
+ protected void setScreenOn(boolean enable) {
+ super.setScreenOn(enable);
+ if (enable) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ } else {
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java
new file mode 100644
index 000000000..d7b069b19
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java
@@ -0,0 +1,372 @@
+package de.danoeh.antennapod.activity.gpoddernet;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v4.app.NavUtils;
+import android.support.v7.app.ActionBarActivity;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.*;
+import de.danoeh.antennapod.BuildConfig;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.activity.MainActivity;
+import de.danoeh.antennapod.core.gpoddernet.GpodnetService;
+import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException;
+import de.danoeh.antennapod.core.gpoddernet.model.GpodnetDevice;
+import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.service.GpodnetSyncService;
+
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Guides the user through the authentication process
+ * Step 1: Request username and password from user
+ * Step 2: Choose device from a list of available devices or create a new one
+ * Step 3: Choose from a list of actions
+ */
+public class GpodnetAuthenticationActivity extends ActionBarActivity {
+ private static final String TAG = "GpodnetAuthenticationActivity";
+
+ private static final String CURRENT_STEP = "current_step";
+
+ private ViewFlipper viewFlipper;
+
+ private static final int STEP_DEFAULT = -1;
+ private static final int STEP_LOGIN = 0;
+ private static final int STEP_DEVICE = 1;
+ private static final int STEP_FINISH = 2;
+
+ private int currentStep = -1;
+
+ private GpodnetService service;
+ private volatile String username;
+ private volatile String password;
+ private volatile GpodnetDevice selectedDevice;
+
+ View[] views;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ setContentView(R.layout.gpodnetauth_activity);
+ service = new GpodnetService();
+
+ viewFlipper = (ViewFlipper) findViewById(R.id.viewflipper);
+ LayoutInflater inflater = (LayoutInflater)
+ getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ views = new View[]{
+ inflater.inflate(R.layout.gpodnetauth_credentials, viewFlipper, false),
+ inflater.inflate(R.layout.gpodnetauth_device, viewFlipper, false),
+ inflater.inflate(R.layout.gpodnetauth_finish, viewFlipper, false)
+ };
+ for (View view : views) {
+ viewFlipper.addView(view);
+ }
+ advance();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (service != null) {
+ service.shutdown();
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ }
+
+ private void setupLoginView(View view) {
+ final EditText username = (EditText) view.findViewById(R.id.etxtUsername);
+ final EditText password = (EditText) view.findViewById(R.id.etxtPassword);
+ final Button login = (Button) view.findViewById(R.id.butLogin);
+ final TextView txtvError = (TextView) view.findViewById(R.id.txtvError);
+ final ProgressBar progressBar = (ProgressBar) view.findViewById(R.id.progBarLogin);
+
+ login.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+
+ final String usernameStr = username.getText().toString();
+ final String passwordStr = password.getText().toString();
+
+ if (BuildConfig.DEBUG) Log.d(TAG, "Checking login credentials");
+ new AsyncTask<GpodnetService, Void, Void>() {
+
+ volatile Exception exception;
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ login.setEnabled(false);
+ progressBar.setVisibility(View.VISIBLE);
+ txtvError.setVisibility(View.GONE);
+
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ super.onPostExecute(aVoid);
+ login.setEnabled(true);
+ progressBar.setVisibility(View.GONE);
+
+ if (exception == null) {
+ advance();
+ } else {
+ txtvError.setText(exception.getMessage());
+ txtvError.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ protected Void doInBackground(GpodnetService... params) {
+ try {
+ params[0].authenticate(usernameStr, passwordStr);
+ GpodnetAuthenticationActivity.this.username = usernameStr;
+ GpodnetAuthenticationActivity.this.password = passwordStr;
+ } catch (GpodnetServiceException e) {
+ e.printStackTrace();
+ exception = e;
+ }
+ return null;
+ }
+ }.execute(service);
+ }
+ });
+ }
+
+ private void setupDeviceView(View view) {
+ final EditText deviceID = (EditText) view.findViewById(R.id.etxtDeviceID);
+ final EditText caption = (EditText) view.findViewById(R.id.etxtCaption);
+ final Button createNewDevice = (Button) view.findViewById(R.id.butCreateNewDevice);
+ final Button chooseDevice = (Button) view.findViewById(R.id.butChooseExistingDevice);
+ final TextView txtvError = (TextView) view.findViewById(R.id.txtvError);
+ final ProgressBar progBarCreateDevice = (ProgressBar) view.findViewById(R.id.progbarCreateDevice);
+ final Spinner spinnerDevices = (Spinner) view.findViewById(R.id.spinnerChooseDevice);
+
+
+ // load device list
+ final AtomicReference<List<GpodnetDevice>> devices = new AtomicReference<List<GpodnetDevice>>();
+ new AsyncTask<GpodnetService, Void, List<GpodnetDevice>>() {
+
+ private volatile Exception exception;
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ chooseDevice.setEnabled(false);
+ spinnerDevices.setEnabled(false);
+ createNewDevice.setEnabled(false);
+ }
+
+ @Override
+ protected void onPostExecute(List<GpodnetDevice> gpodnetDevices) {
+ super.onPostExecute(gpodnetDevices);
+ if (gpodnetDevices != null) {
+ List<String> deviceNames = new ArrayList<String>();
+ for (GpodnetDevice device : gpodnetDevices) {
+ deviceNames.add(device.getCaption());
+ }
+ spinnerDevices.setAdapter(new ArrayAdapter<String>(GpodnetAuthenticationActivity.this,
+ android.R.layout.simple_spinner_dropdown_item, deviceNames));
+ spinnerDevices.setEnabled(true);
+ if (!deviceNames.isEmpty()) {
+ chooseDevice.setEnabled(true);
+ }
+ devices.set(gpodnetDevices);
+ createNewDevice.setEnabled(true);
+ }
+ }
+
+ @Override
+ protected List<GpodnetDevice> doInBackground(GpodnetService... params) {
+ try {
+ return params[0].getDevices(username);
+ } catch (GpodnetServiceException e) {
+ e.printStackTrace();
+ exception = e;
+ return null;
+ }
+ }
+ }.execute(service);
+
+
+ createNewDevice.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (checkDeviceIDText(deviceID, txtvError, devices.get())) {
+ final String deviceStr = deviceID.getText().toString();
+ final String captionStr = caption.getText().toString();
+
+ new AsyncTask<GpodnetService, Void, GpodnetDevice>() {
+
+ private volatile Exception exception;
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ createNewDevice.setEnabled(false);
+ chooseDevice.setEnabled(false);
+ progBarCreateDevice.setVisibility(View.VISIBLE);
+ txtvError.setVisibility(View.GONE);
+ }
+
+ @Override
+ protected void onPostExecute(GpodnetDevice result) {
+ super.onPostExecute(result);
+ createNewDevice.setEnabled(true);
+ chooseDevice.setEnabled(true);
+ progBarCreateDevice.setVisibility(View.GONE);
+ if (exception == null) {
+ selectedDevice = result;
+ advance();
+ } else {
+ txtvError.setText(exception.getMessage());
+ txtvError.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ protected GpodnetDevice doInBackground(GpodnetService... params) {
+ try {
+ params[0].configureDevice(username, deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE);
+ return new GpodnetDevice(deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0);
+ } catch (GpodnetServiceException e) {
+ e.printStackTrace();
+ exception = e;
+ }
+ return null;
+ }
+ }.execute(service);
+ }
+ }
+ });
+
+ deviceID.setText(generateDeviceID());
+ chooseDevice.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final int position = spinnerDevices.getSelectedItemPosition();
+ if (position != AdapterView.INVALID_POSITION) {
+ selectedDevice = devices.get().get(position);
+ advance();
+ }
+ }
+ });
+ }
+
+
+ private String generateDeviceID() {
+ final int DEVICE_ID_LENGTH = 10;
+ StringBuilder buffer = new StringBuilder(DEVICE_ID_LENGTH);
+ SecureRandom random = new SecureRandom();
+ for (int i = 0; i < DEVICE_ID_LENGTH; i++) {
+ buffer.append(random.nextInt(10));
+
+ }
+ return buffer.toString();
+ }
+
+ private boolean checkDeviceIDText(EditText deviceID, TextView txtvError, List<GpodnetDevice> devices) {
+ String text = deviceID.getText().toString();
+ if (text.length() == 0) {
+ txtvError.setText(R.string.gpodnetauth_device_errorEmpty);
+ txtvError.setVisibility(View.VISIBLE);
+ return false;
+ } else {
+ if (devices != null) {
+ for (GpodnetDevice device : devices) {
+ if (device.getId().equals(text)) {
+ txtvError.setText(R.string.gpodnetauth_device_errorAlreadyUsed);
+ txtvError.setVisibility(View.VISIBLE);
+ return false;
+ }
+ }
+ txtvError.setVisibility(View.GONE);
+ return true;
+ }
+ return true;
+ }
+
+ }
+
+ private void setupFinishView(View view) {
+ final Button sync = (Button) view.findViewById(R.id.butSyncNow);
+ final Button back = (Button) view.findViewById(R.id.butGoMainscreen);
+
+ sync.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ GpodnetSyncService.sendSyncIntent(GpodnetAuthenticationActivity.this);
+ finish();
+ }
+ });
+ back.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(GpodnetAuthenticationActivity.this, MainActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ }
+ });
+ }
+
+ private void writeLoginCredentials() {
+ if (BuildConfig.DEBUG) Log.d(TAG, "Writing login credentials");
+ GpodnetPreferences.setUsername(username);
+ GpodnetPreferences.setPassword(password);
+ GpodnetPreferences.setDeviceID(selectedDevice.getId());
+ }
+
+ private void advance() {
+ if (currentStep < STEP_FINISH) {
+
+ View view = views[currentStep + 1];
+ if (currentStep == STEP_DEFAULT) {
+ setupLoginView(view);
+ } else if (currentStep == STEP_LOGIN) {
+ if (username == null || password == null) {
+ throw new IllegalStateException("Username and password must not be null here");
+ } else {
+ setupDeviceView(view);
+ }
+ } else if (currentStep == STEP_DEVICE) {
+ if (selectedDevice == null) {
+ throw new IllegalStateException("Device must not be null here");
+ } else {
+ writeLoginCredentials();
+ setupFinishView(view);
+ }
+ }
+ if (currentStep != STEP_DEFAULT) {
+ viewFlipper.showNext();
+ }
+ currentStep++;
+ } else {
+ finish();
+ }
+ }
+}