summaryrefslogtreecommitdiff
path: root/app/src/play/java/de/danoeh/antennapod/activity
diff options
context:
space:
mode:
authorMartin Fietz <Martin.Fietz@gmail.com>2016-06-04 01:36:25 +0200
committerMartin Fietz <Martin.Fietz@gmail.com>2016-06-04 01:36:25 +0200
commit3c033cc0fbaa4b1418cda330ac0287d735c621f2 (patch)
tree44d563b702ef5ebe466ca3e4953e654e220ff328 /app/src/play/java/de/danoeh/antennapod/activity
parent0aaa14923c9b99c6bda8e4a72c0dd61202ae9680 (diff)
downloadAntennaPod-3c033cc0fbaa4b1418cda330ac0287d735c621f2.zip
Create one flavor with Google Cast support and one (free) without
Diffstat (limited to 'app/src/play/java/de/danoeh/antennapod/activity')
-rw-r--r--app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java237
-rw-r--r--app/src/play/java/de/danoeh/antennapod/activity/MainActivity.java773
-rw-r--r--app/src/play/java/de/danoeh/antennapod/activity/MediaplayerActivity.java876
3 files changed, 1886 insertions, 0 deletions
diff --git a/app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java b/app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java
new file mode 100644
index 000000000..b8856c295
--- /dev/null
+++ b/app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java
@@ -0,0 +1,237 @@
+package de.danoeh.antennapod.activity;
+
+import android.content.SharedPreferences;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.annotation.CallSuper;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.google.android.gms.cast.ApplicationMetadata;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.cast.CastConsumer;
+import de.danoeh.antennapod.core.cast.CastManager;
+import de.danoeh.antennapod.core.cast.DefaultCastConsumer;
+import de.danoeh.antennapod.core.cast.SwitchableMediaRouteActionProvider;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.service.playback.PlaybackService;
+
+/**
+ * Activity that allows for showing the MediaRouter button whenever there's a cast device in the
+ * network.
+ */
+public abstract class CastEnabledActivity extends AppCompatActivity
+ implements SharedPreferences.OnSharedPreferenceChangeListener {
+ public static final String TAG = "CastEnabledActivity";
+
+ protected CastManager castManager;
+ protected SwitchableMediaRouteActionProvider mediaRouteActionProvider;
+ private final CastButtonVisibilityManager castButtonVisibilityManager = new CastButtonVisibilityManager();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).
+ registerOnSharedPreferenceChangeListener(this);
+
+ castManager = CastManager.getInstance();
+ castManager.addCastConsumer(castConsumer);
+ castButtonVisibilityManager.setPrefEnabled(UserPreferences.isCastEnabled());
+ onCastConnectionChanged(castManager.isConnected());
+ }
+
+ @Override
+ protected void onDestroy() {
+ PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
+ .unregisterOnSharedPreferenceChangeListener(this);
+ castManager.removeCastConsumer(castConsumer);
+ super.onDestroy();
+ }
+
+ @Override
+ @CallSuper
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getMenuInflater().inflate(R.menu.cast_enabled, menu);
+ castButtonVisibilityManager.setMenu(menu);
+ return true;
+ }
+
+ @Override
+ @CallSuper
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ mediaRouteActionProvider = castManager
+ .addMediaRouterButton(menu.findItem(R.id.media_route_menu_item));
+ mediaRouteActionProvider.setEnabled(castButtonVisibilityManager.shouldEnable());
+ return true;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ castButtonVisibilityManager.setResumed(true);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ castButtonVisibilityManager.setResumed(false);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (UserPreferences.PREF_CAST_ENABLED.equals(key)) {
+ boolean newValue = UserPreferences.isCastEnabled();
+ Log.d(TAG, "onSharedPreferenceChanged(), isCastEnabled set to " + newValue);
+ castButtonVisibilityManager.setPrefEnabled(newValue);
+ // PlaybackService has its own listener, so if it's active we don't have to take action here.
+ if (!newValue && !PlaybackService.isRunning) {
+ CastManager.getInstance().disconnect();
+ }
+ }
+ }
+
+ CastConsumer castConsumer = new DefaultCastConsumer() {
+ @Override
+ public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) {
+ onCastConnectionChanged(true);
+ }
+
+ @Override
+ public void onDisconnected() {
+ onCastConnectionChanged(false);
+ }
+ };
+
+ private void onCastConnectionChanged(boolean connected) {
+ if (connected) {
+ castButtonVisibilityManager.onConnected();
+ setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE);
+ } else {
+ castButtonVisibilityManager.onDisconnected();
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ }
+ }
+
+ /**
+ * Should be called by any activity or fragment for which the cast button should be shown.
+ *
+ * @param showAsAction refer to {@link MenuItem#setShowAsAction(int)}
+ */
+ public final void requestCastButton(int showAsAction) {
+ castButtonVisibilityManager.requestCastButton(showAsAction);
+ }
+
+ private class CastButtonVisibilityManager {
+ private volatile boolean prefEnabled = false;
+ private volatile boolean viewRequested = false;
+ private volatile boolean resumed = false;
+ private volatile boolean connected = false;
+ private volatile int showAsAction = MenuItem.SHOW_AS_ACTION_IF_ROOM;
+ private Menu menu;
+
+ public synchronized void setPrefEnabled(boolean newValue) {
+ if (prefEnabled != newValue && resumed && (viewRequested || connected)) {
+ if (newValue) {
+ castManager.incrementUiCounter();
+ } else {
+ castManager.decrementUiCounter();
+ }
+ }
+ prefEnabled = newValue;
+ if (mediaRouteActionProvider != null) {
+ mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected));
+ }
+ }
+
+ public synchronized void setResumed(boolean newValue) {
+ if (resumed == newValue) {
+ Log.e(TAG, "resumed should never change to the same value");
+ return;
+ }
+ resumed = newValue;
+ if (prefEnabled && (viewRequested || connected)) {
+ if (resumed) {
+ castManager.incrementUiCounter();
+ } else {
+ castManager.decrementUiCounter();
+ }
+ }
+ }
+
+ public synchronized void setViewRequested(boolean newValue) {
+ if (viewRequested != newValue && resumed && prefEnabled && !connected) {
+ if (newValue) {
+ castManager.incrementUiCounter();
+ } else {
+ castManager.decrementUiCounter();
+ }
+ }
+ viewRequested = newValue;
+ if (mediaRouteActionProvider != null) {
+ mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected));
+ }
+ }
+
+ public synchronized void setConnected(boolean newValue) {
+ if (connected != newValue && resumed && prefEnabled && !prefEnabled) {
+ if (newValue) {
+ castManager.incrementUiCounter();
+ } else {
+ castManager.decrementUiCounter();
+ }
+ }
+ connected = newValue;
+ if (mediaRouteActionProvider != null) {
+ mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected));
+ }
+ }
+
+ public synchronized boolean shouldEnable() {
+ return prefEnabled && viewRequested;
+ }
+
+ public void setMenu(Menu menu) {
+ setViewRequested(false);
+ showAsAction = MenuItem.SHOW_AS_ACTION_IF_ROOM;
+ this.menu = menu;
+ setShowAsAction();
+ }
+
+ public void requestCastButton(int showAsAction) {
+ setViewRequested(true);
+ this.showAsAction = showAsAction;
+ setShowAsAction();
+ }
+
+ public void onConnected() {
+ setConnected(true);
+ setShowAsAction();
+ }
+
+ public void onDisconnected() {
+ setConnected(false);
+ setShowAsAction();
+ }
+
+ private void setShowAsAction() {
+ if (menu == null) {
+ Log.d(TAG, "setShowAsAction() without a menu");
+ return;
+ }
+ MenuItem item = menu.findItem(R.id.media_route_menu_item);
+ if (item == null) {
+ Log.e(TAG, "setShowAsAction(), but cast button not inflated");
+ return;
+ }
+ MenuItemCompat.setShowAsAction(item, connected? MenuItem.SHOW_AS_ACTION_ALWAYS : showAsAction);
+ }
+ }
+}
diff --git a/app/src/play/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/play/java/de/danoeh/antennapod/activity/MainActivity.java
new file mode 100644
index 000000000..b7c7d86c7
--- /dev/null
+++ b/app/src/play/java/de/danoeh/antennapod/activity/MainActivity.java
@@ -0,0 +1,773 @@
+package de.danoeh.antennapod.activity;
+
+import android.annotation.TargetApi;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.database.DataSetObserver;
+import android.os.Build;
+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.ActionBarDrawerToggle;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.widget.Toolbar;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.ContextMenu;
+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 com.bumptech.glide.Glide;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.Validate;
+
+import java.util.List;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.adapter.NavListAdapter;
+import de.danoeh.antennapod.core.asynctask.FeedRemover;
+import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
+import de.danoeh.antennapod.core.event.ProgressEvent;
+import de.danoeh.antennapod.core.event.QueueEvent;
+import de.danoeh.antennapod.core.feed.EventDistributor;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
+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.storage.DBWriter;
+import de.danoeh.antennapod.core.util.FeedItemUtil;
+import de.danoeh.antennapod.core.util.StorageUtils;
+import de.danoeh.antennapod.dialog.RatingDialog;
+import de.danoeh.antennapod.fragment.AddFeedFragment;
+import de.danoeh.antennapod.fragment.DownloadsFragment;
+import de.danoeh.antennapod.fragment.EpisodesFragment;
+import de.danoeh.antennapod.fragment.ExternalPlayerFragment;
+import de.danoeh.antennapod.fragment.ItemlistFragment;
+import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
+import de.danoeh.antennapod.fragment.QueueFragment;
+import de.danoeh.antennapod.fragment.SubscriptionFragment;
+import de.danoeh.antennapod.menuhandler.NavDrawerActivity;
+import de.danoeh.antennapod.preferences.PreferenceController;
+import de.greenrobot.event.EventBus;
+import rx.Observable;
+import rx.Subscription;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.schedulers.Schedulers;
+
+/**
+ * The activity that is shown when the user launches the app.
+ */
+public class MainActivity extends CastEnabledActivity implements NavDrawerActivity {
+
+ private static final String TAG = "MainActivity";
+
+ private static final int EVENTS = EventDistributor.FEED_LIST_UPDATE
+ | EventDistributor.UNREAD_ITEMS_UPDATE;
+
+ public static final String PREF_NAME = "MainActivityPrefs";
+ public static final String PREF_IS_FIRST_LAUNCH = "prefMainActivityIsFirstLaunch";
+ public static final String PREF_LAST_FRAGMENT_TAG = "prefMainActivityLastFragmentTag";
+
+ public static final String EXTRA_NAV_TYPE = "nav_type";
+ public static final String EXTRA_NAV_INDEX = "nav_index";
+ public static final String EXTRA_FRAGMENT_TAG = "fragment_tag";
+ public static final String EXTRA_FRAGMENT_ARGS = "fragment_args";
+ public static final String EXTRA_FEED_ID = "fragment_feed_id";
+
+ public static final String SAVE_BACKSTACK_COUNT = "backstackCount";
+ public static final String SAVE_TITLE = "title";
+
+ public static final String[] NAV_DRAWER_TAGS = {
+ QueueFragment.TAG,
+ EpisodesFragment.TAG,
+ SubscriptionFragment.TAG,
+ DownloadsFragment.TAG,
+ PlaybackHistoryFragment.TAG,
+ AddFeedFragment.TAG,
+ NavListAdapter.SUBSCRIPTION_LIST_TAG
+ };
+
+ private Toolbar toolbar;
+ private ExternalPlayerFragment externalPlayerFragment;
+ private DrawerLayout drawerLayout;
+
+ private View navDrawer;
+ private ListView navList;
+ private NavListAdapter navAdapter;
+ private int mPosition = -1;
+
+ private ActionBarDrawerToggle drawerToggle;
+
+ private CharSequence currentTitle;
+
+ private ProgressDialog pd;
+
+ private Subscription subscription;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getNoTitleTheme());
+ super.onCreate(savedInstanceState);
+ StorageUtils.checkStorageAvailability(this);
+ setContentView(R.layout.main);
+
+ toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ findViewById(R.id.shadow).setVisibility(View.GONE);
+ int elevation = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4,
+ getResources().getDisplayMetrics());
+ getSupportActionBar().setElevation(elevation);
+ }
+
+ currentTitle = getTitle();
+
+ drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+ navList = (ListView) findViewById(R.id.nav_list);
+ navDrawer = findViewById(R.id.nav_layout);
+
+ drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close);
+ if (savedInstanceState != null) {
+ int backstackCount = savedInstanceState.getInt(SAVE_BACKSTACK_COUNT, 0);
+ drawerToggle.setDrawerIndicatorEnabled(backstackCount == 0);
+ }
+ drawerLayout.setDrawerListener(drawerToggle);
+
+ final FragmentManager fm = getSupportFragmentManager();
+
+ fm.addOnBackStackChangedListener(() -> {
+ drawerToggle.setDrawerIndicatorEnabled(fm.getBackStackEntryCount() == 0);
+ });
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setHomeButtonEnabled(true);
+
+ navAdapter = new NavListAdapter(itemAccess, this);
+ navList.setAdapter(navAdapter);
+ navList.setOnItemClickListener(navListClickListener);
+ navList.setOnItemLongClickListener(newListLongClickListener);
+ registerForContextMenu(navList);
+
+ navAdapter.registerDataSetObserver(new DataSetObserver() {
+ @Override
+ public void onChanged() {
+ selectedNavListIndex = getSelectedNavListIndex();
+ }
+ });
+
+ findViewById(R.id.nav_settings).setOnClickListener(v -> {
+ drawerLayout.closeDrawer(navDrawer);
+ startActivity(new Intent(MainActivity.this, PreferenceController.getPreferenceActivity()));
+ });
+
+ FragmentTransaction transaction = fm.beginTransaction();
+
+ Fragment mainFragment = fm.findFragmentByTag("main");
+ if (mainFragment != null) {
+ transaction.replace(R.id.main_view, mainFragment);
+ } else {
+ String lastFragment = getLastNavFragment();
+ if (ArrayUtils.contains(NAV_DRAWER_TAGS, lastFragment)) {
+ loadFragment(lastFragment, null);
+ } else {
+ try {
+ loadFeedFragmentById(Integer.parseInt(lastFragment), null);
+ } catch (NumberFormatException e) {
+ // it's not a number, this happens if we removed
+ // a label from the NAV_DRAWER_TAGS
+ // give them a nice default...
+ loadFragment(QueueFragment.TAG, null);
+ }
+ }
+ }
+ externalPlayerFragment = new ExternalPlayerFragment();
+ transaction.replace(R.id.playerFragment, externalPlayerFragment, ExternalPlayerFragment.TAG);
+ transaction.commit();
+
+ checkFirstLaunch();
+ }
+
+ private void saveLastNavFragment(String tag) {
+ Log.d(TAG, "saveLastNavFragment(tag: " + tag +")");
+ SharedPreferences prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE);
+ SharedPreferences.Editor edit = prefs.edit();
+ if(tag != null) {
+ edit.putString(PREF_LAST_FRAGMENT_TAG, tag);
+ } else {
+ edit.remove(PREF_LAST_FRAGMENT_TAG);
+ }
+ edit.commit();
+ }
+
+ private String getLastNavFragment() {
+ SharedPreferences prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE);
+ String lastFragment = prefs.getString(PREF_LAST_FRAGMENT_TAG, QueueFragment.TAG);
+ Log.d(TAG, "getLastNavFragment() -> " + lastFragment);
+ return lastFragment;
+ }
+
+ private void checkFirstLaunch() {
+ SharedPreferences prefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE);
+ if (prefs.getBoolean(PREF_IS_FIRST_LAUNCH, true)) {
+ new Handler().postDelayed(() -> drawerLayout.openDrawer(navDrawer), 1500);
+
+ // for backward compatibility, we only change defaults for fresh installs
+ UserPreferences.setUpdateInterval(12);
+
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putBoolean(PREF_IS_FIRST_LAUNCH, false);
+ edit.commit();
+ }
+ }
+
+ public void showDrawerPreferencesDialog() {
+ final List<String> hiddenDrawerItems = UserPreferences.getHiddenDrawerItems();
+ String[] navLabels = new String[NAV_DRAWER_TAGS.length];
+ final boolean[] checked = new boolean[NAV_DRAWER_TAGS.length];
+ for (int i = 0; i < NAV_DRAWER_TAGS.length; i++) {
+ String tag = NAV_DRAWER_TAGS[i];
+ navLabels[i] = navAdapter.getLabel(tag);
+ if (!hiddenDrawerItems.contains(tag)) {
+ checked[i] = true;
+ }
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
+ builder.setTitle(R.string.drawer_preferences);
+ builder.setMultiChoiceItems(navLabels, checked, (dialog, which, isChecked) -> {
+ if (isChecked) {
+ hiddenDrawerItems.remove(NAV_DRAWER_TAGS[which]);
+ } else {
+ hiddenDrawerItems.add(NAV_DRAWER_TAGS[which]);
+ }
+ });
+ builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
+ UserPreferences.setHiddenDrawerItems(hiddenDrawerItems);
+ });
+ builder.setNegativeButton(R.string.cancel_label, null);
+ builder.create().show();
+ }
+
+ public boolean isDrawerOpen() {
+ return drawerLayout != null && navDrawer != null && drawerLayout.isDrawerOpen(navDrawer);
+ }
+
+ public List<Feed> getFeeds() {
+ return (navDrawerData != null) ? navDrawerData.feeds : null;
+ }
+
+ public void loadFragment(int index, Bundle args) {
+ Log.d(TAG, "loadFragment(index: " + index + ", args: " + args + ")");
+ if (index < navAdapter.getSubscriptionOffset()) {
+ String tag = navAdapter.getTags().get(index);
+ loadFragment(tag, args);
+ } else {
+ int pos = index - navAdapter.getSubscriptionOffset();
+ loadFeedFragmentByPosition(pos, args);
+ }
+ }
+
+ public void loadFragment(String tag, Bundle args) {
+ Log.d(TAG, "loadFragment(tag: " + tag + ", args: " + args + ")");
+ Fragment fragment = null;
+ switch (tag) {
+ case QueueFragment.TAG:
+ fragment = new QueueFragment();
+ break;
+ case EpisodesFragment.TAG:
+ fragment = new EpisodesFragment();
+ break;
+ case DownloadsFragment.TAG:
+ fragment = new DownloadsFragment();
+ break;
+ case PlaybackHistoryFragment.TAG:
+ fragment = new PlaybackHistoryFragment();
+ break;
+ case AddFeedFragment.TAG:
+ fragment = new AddFeedFragment();
+ break;
+ case SubscriptionFragment.TAG:
+ SubscriptionFragment subscriptionFragment = new SubscriptionFragment();
+ fragment = subscriptionFragment;
+ break;
+ default:
+ // default to the queue
+ tag = QueueFragment.TAG;
+ fragment = new QueueFragment();
+ args = null;
+ break;
+ }
+ currentTitle = navAdapter.getLabel(tag);
+ getSupportActionBar().setTitle(currentTitle);
+ saveLastNavFragment(tag);
+ if (args != null) {
+ fragment.setArguments(args);
+ }
+ loadFragment(fragment);
+ }
+
+ private void loadFeedFragmentByPosition(int relPos, Bundle args) {
+ if(relPos < 0) {
+ return;
+ }
+ Feed feed = itemAccess.getItem(relPos);
+ loadFeedFragmentById(feed.getId(), args);
+ }
+
+ public void loadFeedFragmentById(long feedId, Bundle args) {
+ Fragment fragment = ItemlistFragment.newInstance(feedId);
+ if(args != null) {
+ fragment.setArguments(args);
+ }
+ saveLastNavFragment(String.valueOf(feedId));
+ currentTitle = "";
+ getSupportActionBar().setTitle(currentTitle);
+ loadFragment(fragment);
+ }
+
+ private void loadFragment(Fragment fragment) {
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ // clear back stack
+ for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) {
+ fragmentManager.popBackStack();
+ }
+ FragmentTransaction t = fragmentManager.beginTransaction();
+ t.replace(R.id.main_view, fragment, "main");
+ fragmentManager.popBackStack();
+ // TODO: we have to allow state loss here
+ // since this function can get called from an AsyncTask which
+ // could be finishing after our app has already committed state
+ // and is about to get shutdown. What we *should* do is
+ // not commit anything in an AsyncTask, but that's a bigger
+ // change than we want now.
+ t.commitAllowingStateLoss();
+ if (navAdapter != null) {
+ navAdapter.notifyDataSetChanged();
+ }
+ }
+
+ public void loadChildFragment(Fragment fragment) {
+ Validate.notNull(fragment);
+ FragmentManager fm = getSupportFragmentManager();
+ fm.beginTransaction()
+ .replace(R.id.main_view, fragment, "main")
+ .addToBackStack(null)
+ .commit();
+ }
+
+ public void dismissChildFragment() {
+ getSupportFragmentManager().popBackStack();
+ }
+
+ private int getSelectedNavListIndex() {
+ String currentFragment = getLastNavFragment();
+ if(currentFragment == null) {
+ // should not happen, but better safe than sorry
+ return -1;
+ }
+ int tagIndex = navAdapter.getTags().indexOf(currentFragment);
+ if(tagIndex >= 0) {
+ return tagIndex;
+ } else if(ArrayUtils.contains(NAV_DRAWER_TAGS, currentFragment)) {
+ // the fragment was just hidden
+ return -1;
+ } else { // last fragment was not a list, but a feed
+ long feedId = Long.parseLong(currentFragment);
+ if (navDrawerData != null) {
+ List<Feed> feeds = navDrawerData.feeds;
+ for (int i = 0; i < feeds.size(); i++) {
+ if (feeds.get(i).getId() == feedId) {
+ return i + navAdapter.getSubscriptionOffset();
+ }
+ }
+ }
+ return -1;
+ }
+ }
+
+ 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) {
+ loadFragment(position, null);
+ }
+ drawerLayout.closeDrawer(navDrawer);
+ }
+ };
+
+ private AdapterView.OnItemLongClickListener newListLongClickListener = new AdapterView.OnItemLongClickListener() {
+ @Override
+ public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
+ if(position < navAdapter.getTags().size()) {
+ showDrawerPreferencesDialog();
+ return true;
+ } else {
+ mPosition = position;
+ return false;
+ }
+ }
+ };
+
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ drawerToggle.syncState();
+ if (savedInstanceState != null) {
+ currentTitle = savedInstanceState.getString(SAVE_TITLE);
+ if (!drawerLayout.isDrawerOpen(navDrawer)) {
+ getSupportActionBar().setTitle(currentTitle);
+ }
+ selectedNavListIndex = getSelectedNavListIndex();
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ drawerToggle.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putString(SAVE_TITLE, getSupportActionBar().getTitle().toString());
+ outState.putInt(SAVE_BACKSTACK_COUNT, getSupportFragmentManager().getBackStackEntryCount());
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ EventDistributor.getInstance().register(contentUpdate);
+ EventBus.getDefault().register(this);
+ RatingDialog.init(this);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ StorageUtils.checkStorageAvailability(this);
+
+ Intent intent = getIntent();
+ if (intent.hasExtra(EXTRA_FEED_ID) ||
+ (navDrawerData != null && intent.hasExtra(EXTRA_NAV_TYPE) &&
+ (intent.hasExtra(EXTRA_NAV_INDEX) || intent.hasExtra(EXTRA_FRAGMENT_TAG)))) {
+ handleNavIntent();
+ }
+ loadData();
+ RatingDialog.check();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ EventDistributor.getInstance().unregister(contentUpdate);
+ EventBus.getDefault().unregister(this);
+ if(subscription != null) {
+ subscription.unsubscribe();
+ }
+ if(pd != null) {
+ pd.dismiss();
+ }
+ }
+
+
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ @Override
+ public void onTrimMemory(int level) {
+ super.onTrimMemory(level);
+ Glide.get(this).trimMemory(level);
+ }
+
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ Glide.get(this).clearMemory();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ boolean retVal = super.onCreateOptionsMenu(menu);
+ switch (getLastNavFragment()) {
+ case QueueFragment.TAG:
+ case EpisodesFragment.TAG:
+ requestCastButton(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ return retVal;
+ case DownloadsFragment.TAG:
+ case PlaybackHistoryFragment.TAG:
+ case AddFeedFragment.TAG:
+ case SubscriptionFragment.TAG:
+ return retVal;
+ default:
+ requestCastButton(MenuItem.SHOW_AS_ACTION_NEVER);
+ return retVal;
+ }
+
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (drawerToggle.onOptionsItemSelected(item)) {
+ return true;
+ } else if (item.getItemId() == android.R.id.home) {
+ if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
+ dismissChildFragment();
+ }
+ return true;
+ } else {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ if(v.getId() != R.id.nav_list) {
+ return;
+ }
+ AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo;
+ int position = adapterInfo.position;
+ if(position < navAdapter.getSubscriptionOffset()) {
+ return;
+ }
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.nav_feed_context, menu);
+ Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset());
+ menu.setHeaderTitle(feed.getTitle());
+ // episodes are not loaded, so we cannot check if the podcast has new or unplayed ones!
+ }
+
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ final int position = mPosition;
+ mPosition = -1; // reset
+ if(position < 0) {
+ return false;
+ }
+ Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset());
+ switch(item.getItemId()) {
+ case R.id.mark_all_seen_item:
+ DBWriter.markFeedSeen(feed.getId());
+ return true;
+ case R.id.mark_all_read_item:
+ DBWriter.markFeedRead(feed.getId());
+ return true;
+ case R.id.remove_item:
+ final FeedRemover remover = new FeedRemover(this, feed) {
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ if(getSelectedNavListIndex() == position) {
+ loadFragment(EpisodesFragment.TAG, null);
+ }
+ }
+ };
+ ConfirmationDialog conDialog = new ConfirmationDialog(this,
+ R.string.remove_feed_label,
+ R.string.feed_delete_confirmation_msg) {
+ @Override
+ public void onConfirmButtonPressed(
+ DialogInterface dialog) {
+ dialog.dismiss();
+ long mediaId = PlaybackPreferences.getCurrentlyPlayingFeedMediaId();
+ if (mediaId > 0 &&
+ FeedItemUtil.indexOfItemWithMediaId(feed.getItems(), mediaId) >= 0) {
+ Log.d(TAG, "Currently playing episode is about to be deleted, skipping");
+ remover.skipOnCompletion = true;
+ int playerStatus = PlaybackPreferences.getCurrentPlayerStatus();
+ if(playerStatus == PlaybackPreferences.PLAYER_STATUS_PLAYING) {
+ sendBroadcast(new Intent(
+ PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE));
+ }
+ }
+ remover.executeAsync();
+ }
+ };
+ conDialog.createNewDialog().show();
+ return true;
+ default:
+ return super.onContextItemSelected(item);
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ if(isDrawerOpen()) {
+ drawerLayout.closeDrawer(navDrawer);
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ private DBReader.NavDrawerData navDrawerData;
+ 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 && 0 <= position && 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 getNumberOfNewItems() {
+ return (navDrawerData != null) ? navDrawerData.numNewItems : 0;
+ }
+
+ @Override
+ public int getNumberOfDownloadedItems() {
+ return (navDrawerData != null) ? navDrawerData.numDownloadedItems : 0;
+ }
+
+ @Override
+ public int getReclaimableItems() {
+ return (navDrawerData != null) ? navDrawerData.reclaimableSpace : 0;
+ }
+
+ @Override
+ public int getFeedCounter(long feedId) {
+ return navDrawerData != null ? navDrawerData.feedCounters.get(feedId) : 0;
+ }
+
+ @Override
+ public int getFeedCounterSum() {
+ if(navDrawerData == null) {
+ return 0;
+ }
+ int sum = 0;
+ for(int counter : navDrawerData.feedCounters.values()) {
+ sum += counter;
+ }
+ return sum;
+ }
+
+ };
+
+ private void loadData() {
+ subscription = Observable.fromCallable(DBReader::getNavDrawerData)
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(result -> {
+ boolean handleIntent = (navDrawerData == null);
+
+ navDrawerData = result;
+ navAdapter.notifyDataSetChanged();
+
+ if (handleIntent) {
+ handleNavIntent();
+ }
+ }, error -> {
+ Log.e(TAG, Log.getStackTraceString(error));
+ });
+ }
+
+ public void onEvent(QueueEvent event) {
+ Log.d(TAG, "onEvent(" + event + ")");
+ // we are only interested in the number of queue items, not download status or position
+ if(event.action == QueueEvent.Action.DELETED_MEDIA ||
+ event.action == QueueEvent.Action.SORTED ||
+ event.action == QueueEvent.Action.MOVED) {
+ return;
+ }
+ loadData();
+ }
+
+ public void onEventMainThread(ProgressEvent event) {
+ Log.d(TAG, "onEvent(" + event + ")");
+ switch(event.action) {
+ case START:
+ pd = new ProgressDialog(this);
+ pd.setMessage(event.message);
+ pd.setIndeterminate(true);
+ pd.setCancelable(false);
+ pd.show();
+ break;
+ case END:
+ if(pd != null) {
+ pd.dismiss();
+ }
+ break;
+ }
+ }
+
+ private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
+
+ @Override
+ public void update(EventDistributor eventDistributor, Integer arg) {
+ if ((EVENTS & arg) != 0) {
+ Log.d(TAG, "Received contentUpdate Intent.");
+ loadData();
+ }
+ }
+ };
+
+ private void handleNavIntent() {
+ Log.d(TAG, "handleNavIntent()");
+ Intent intent = getIntent();
+ if (intent.hasExtra(EXTRA_FEED_ID) ||
+ (intent.hasExtra(EXTRA_NAV_TYPE) &&
+ (intent.hasExtra(EXTRA_NAV_INDEX) || intent.hasExtra(EXTRA_FRAGMENT_TAG)))) {
+ int index = intent.getIntExtra(EXTRA_NAV_INDEX, -1);
+ String tag = intent.getStringExtra(EXTRA_FRAGMENT_TAG);
+ Bundle args = intent.getBundleExtra(EXTRA_FRAGMENT_ARGS);
+ long feedId = intent.getLongExtra(EXTRA_FEED_ID, 0);
+ if (index >= 0) {
+ loadFragment(index, args);
+ } else if (tag != null) {
+ loadFragment(tag, args);
+ } else if(feedId > 0) {
+ loadFeedFragmentById(feedId, 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/play/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/play/java/de/danoeh/antennapod/activity/MediaplayerActivity.java
new file mode 100644
index 000000000..71d288725
--- /dev/null
+++ b/app/src/play/java/de/danoeh/antennapod/activity/MediaplayerActivity.java
@@ -0,0 +1,876 @@
+package de.danoeh.antennapod.activity;
+
+import android.annotation.TargetApi;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v7.app.AlertDialog;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.ImageButton;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.afollestad.materialdialogs.MaterialDialog;
+import com.bumptech.glide.Glide;
+import com.joanzapata.iconify.IconDrawable;
+import com.joanzapata.iconify.fonts.FontAwesomeIcons;
+
+import java.util.Locale;
+
+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.DBReader;
+import de.danoeh.antennapod.core.storage.DBTasks;
+import de.danoeh.antennapod.core.storage.DBWriter;
+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.SleepTimerDialog;
+import de.danoeh.antennapod.dialog.VariableSpeedDialog;
+import rx.Observable;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.schedulers.Schedulers;
+
+
+/**
+ * Provides general features which are both needed for playing audio and video
+ * files.
+ */
+public abstract class MediaplayerActivity extends CastEnabledActivity implements OnSeekBarChangeListener {
+ private static final String TAG = "MediaplayerActivity";
+ private static final String PREFS = "MediaPlayerActivityPreferences";
+ private static final String PREF_SHOW_TIME_LEFT = "showTimeLeft";
+
+ protected PlaybackController controller;
+
+ protected TextView txtvPosition;
+ protected TextView txtvLength;
+ protected SeekBar sbPosition;
+ protected ImageButton butRev;
+ protected TextView txtvRev;
+ protected ImageButton butPlay;
+ protected ImageButton butFF;
+ protected TextView txtvFF;
+ protected ImageButton butSkip;
+
+ protected boolean showTimeLeft = false;
+
+ private boolean isFavorite = false;
+
+ 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, boolean showToast) {
+ MediaplayerActivity.this.postStatusMsg(msg, showToast);
+ }
+
+ @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);
+ }
+
+ @Override
+ public void onSetSpeedAbilityChanged() {
+ MediaplayerActivity.this.onSetSpeedAbilityChanged();
+ }
+ };
+ }
+
+ protected void onSetSpeedAbilityChanged() {
+ Log.d(TAG, "onSetSpeedAbilityChanged()");
+ updatePlaybackSpeedButton();
+ }
+
+ protected void onPlaybackSpeedChange() {
+ updatePlaybackSpeedButtonText();
+ }
+
+ 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);
+
+ Log.d(TAG, "onCreate()");
+ StorageUtils.checkStorageAvailability(this);
+
+ orientation = getResources().getConfiguration().orientation;
+ getWindow().setFormat(PixelFormat.TRANSPARENT);
+ }
+
+ @Override
+ protected void onPause() {
+ if(controller != null) {
+ controller.reinitServiceIfPaused();
+ controller.pause();
+ }
+ super.onPause();
+ }
+
+ /**
+ * 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() {
+ Log.d(TAG, "onStop()");
+ if (controller != null) {
+ controller.release();
+ controller = null; // prevent leak
+ }
+ super.onStop();
+ }
+
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ @Override
+ public void onTrimMemory(int level) {
+ super.onTrimMemory(level);
+ Glide.get(this).trimMemory(level);
+ }
+
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ Glide.get(this).clearMemory();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ requestCastButton(MenuItem.SHOW_AS_ACTION_ALWAYS);
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.mediaplayer, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ if (controller == null) {
+ return false;
+ }
+ Playable media = controller.getMedia();
+
+ menu.findItem(R.id.support_item).setVisible(
+ media != null && media.getPaymentLink() != null &&
+ (media instanceof FeedMedia) &&
+ ((FeedMedia) media).getItem() != null &&
+ ((FeedMedia) media).getItem().getFlattrStatus().flattrable()
+ );
+
+ boolean hasWebsiteLink = media != null && media.getWebsiteLink() != null;
+ menu.findItem(R.id.visit_website_item).setVisible(hasWebsiteLink);
+
+ boolean isItemAndHasLink = media != null && (media instanceof FeedMedia) &&
+ ((FeedMedia) media).getItem() != null && ((FeedMedia) media).getItem().getLink() != null;
+ menu.findItem(R.id.share_link_item).setVisible(isItemAndHasLink);
+ menu.findItem(R.id.share_link_with_position_item).setVisible(isItemAndHasLink);
+
+ boolean isItemHasDownloadLink = media != null && (media instanceof FeedMedia) && ((FeedMedia) media).getDownload_url() != null;
+ menu.findItem(R.id.share_download_url_item).setVisible(isItemHasDownloadLink);
+ menu.findItem(R.id.share_download_url_with_position_item).setVisible(isItemHasDownloadLink);
+
+ menu.findItem(R.id.share_item).setVisible(hasWebsiteLink || isItemAndHasLink || isItemHasDownloadLink);
+
+ menu.findItem(R.id.add_to_favorites_item).setVisible(false);
+ menu.findItem(R.id.remove_from_favorites_item).setVisible(false);
+ if(media != null && media instanceof FeedMedia) {
+ menu.findItem(R.id.add_to_favorites_item).setVisible(!isFavorite);
+ menu.findItem(R.id.remove_from_favorites_item).setVisible(isFavorite);
+ }
+
+ 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);
+
+ if (this instanceof AudioplayerActivity) {
+ int[] attrs = {R.attr.action_bar_icon_color};
+ TypedArray ta = obtainStyledAttributes(UserPreferences.getTheme(), attrs);
+ int textColor = ta.getColor(0, Color.GRAY);
+ ta.recycle();
+ menu.findItem(R.id.audio_controls).setIcon(new IconDrawable(this,
+ FontAwesomeIcons.fa_sliders).color(textColor).actionBarSize());
+ } else {
+ menu.findItem(R.id.audio_controls).setVisible(false);
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (controller == null) {
+ return false;
+ }
+ 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.add_to_favorites_item:
+ if(media instanceof FeedMedia) {
+ FeedItem feedItem = ((FeedMedia)media).getItem();
+ if(feedItem != null) {
+ DBWriter.addFavoriteItem(feedItem);
+ isFavorite = true;
+ invalidateOptionsMenu();
+ Toast.makeText(this, R.string.added_to_favorites, Toast.LENGTH_SHORT)
+ .show();
+ }
+ }
+ break;
+ case R.id.remove_from_favorites_item:
+ if(media instanceof FeedMedia) {
+ FeedItem feedItem = ((FeedMedia)media).getItem();
+ if(feedItem != null) {
+ DBWriter.removeFavoriteItem(feedItem);
+ isFavorite = false;
+ invalidateOptionsMenu();
+ Toast.makeText(this, R.string.removed_from_favorites, Toast.LENGTH_SHORT)
+ .show();
+ }
+ }
+ break;
+ case R.id.disable_sleeptimer_item:
+ if (controller.serviceAvailable()) {
+
+ MaterialDialog.Builder stDialog = new MaterialDialog.Builder(this);
+ stDialog.title(R.string.sleep_timer_label);
+ stDialog.content(getString(R.string.time_left_label)
+ + Converter.getDurationStringLong((int) controller
+ .getSleepTimerTimeLeft()));
+ stDialog.positiveText(R.string.disable_sleeptimer_label);
+ stDialog.negativeText(R.string.cancel_label);
+ stDialog.onPositive((dialog, which) -> {
+ dialog.dismiss();
+ controller.disableSleepTimer();
+ });
+ stDialog.onNegative((dialog, which) -> dialog.dismiss());
+ stDialog.build().show();
+ }
+ break;
+ case R.id.set_sleeptimer_item:
+ if (controller.serviceAvailable()) {
+ SleepTimerDialog td = new SleepTimerDialog(this) {
+ @Override
+ public void onTimerSet(long millis, boolean shakeToReset, boolean vibrate) {
+ controller.setSleepTimer(millis, shakeToReset, vibrate);
+ }
+ };
+ td.createNewDialog().show();
+ }
+ break;
+ case R.id.audio_controls:
+ MaterialDialog dialog = new MaterialDialog.Builder(this)
+ .title(R.string.audio_controls)
+ .customView(R.layout.audio_controls, true)
+ .neutralText(R.string.close_label)
+ .onNeutral((dialog1, which) -> {
+ final SeekBar left = (SeekBar) dialog1.findViewById(R.id.volume_left);
+ final SeekBar right = (SeekBar) dialog1.findViewById(R.id.volume_right);
+ UserPreferences.setVolume(left.getProgress(), right.getProgress());
+ })
+ .show();
+ final SeekBar barPlaybackSpeed = (SeekBar) dialog.findViewById(R.id.playback_speed);
+ final Button butDecSpeed = (Button) dialog.findViewById(R.id.butDecSpeed);
+ butDecSpeed.setOnClickListener(v -> {
+ if(controller != null && controller.canSetPlaybackSpeed()) {
+ barPlaybackSpeed.setProgress(barPlaybackSpeed.getProgress() - 2);
+ } else {
+ VariableSpeedDialog.showGetPluginDialog(this);
+ }
+ });
+ final Button butIncSpeed = (Button) dialog.findViewById(R.id.butIncSpeed);
+ butIncSpeed.setOnClickListener(v -> {
+ if(controller != null && controller.canSetPlaybackSpeed()) {
+ barPlaybackSpeed.setProgress(barPlaybackSpeed.getProgress() + 2);
+ } else {
+ VariableSpeedDialog.showGetPluginDialog(this);
+ }
+ });
+
+ final TextView txtvPlaybackSpeed = (TextView) dialog.findViewById(R.id.txtvPlaybackSpeed);
+ float currentSpeed = 1.0f;
+ try {
+ currentSpeed = Float.parseFloat(UserPreferences.getPlaybackSpeed());
+ } catch (NumberFormatException e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ UserPreferences.setPlaybackSpeed(String.valueOf(currentSpeed));
+ }
+
+ txtvPlaybackSpeed.setText(String.format("%.2fx", currentSpeed));
+ barPlaybackSpeed.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if(controller != null && controller.canSetPlaybackSpeed()) {
+ float playbackSpeed = (progress + 10) / 20.0f;
+ controller.setPlaybackSpeed(playbackSpeed);
+ String speedPref = String.format(Locale.US, "%.2f", playbackSpeed);
+ UserPreferences.setPlaybackSpeed(speedPref);
+ String speedStr = String.format("%.2fx", playbackSpeed);
+ txtvPlaybackSpeed.setText(speedStr);
+ } else if(fromUser) {
+ float speed = Float.valueOf(UserPreferences.getPlaybackSpeed());
+ barPlaybackSpeed.post(() -> barPlaybackSpeed.setProgress((int) (20 * speed) - 10));
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ if(controller != null && !controller.canSetPlaybackSpeed()) {
+ VariableSpeedDialog.showGetPluginDialog(MediaplayerActivity.this);
+ }
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ }
+ });
+ barPlaybackSpeed.setProgress((int) (20 * currentSpeed) - 10);
+
+ final SeekBar barLeftVolume = (SeekBar) dialog.findViewById(R.id.volume_left);
+ barLeftVolume.setProgress(UserPreferences.getLeftVolumePercentage());
+ final SeekBar barRightVolume = (SeekBar) dialog.findViewById(R.id.volume_right);
+ barRightVolume.setProgress(UserPreferences.getRightVolumePercentage());
+ final CheckBox stereoToMono = (CheckBox) dialog.findViewById(R.id.stereo_to_mono);
+ stereoToMono.setChecked(UserPreferences.stereoToMono());
+ if (controller != null && !controller.canDownmix()) {
+ stereoToMono.setEnabled(false);
+ String sonicOnly = getString(R.string.sonic_only);
+ stereoToMono.setText(stereoToMono.getText() + " [" + sonicOnly + "]");
+ }
+
+ barLeftVolume.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ controller.setVolume(
+ Converter.getVolumeFromPercentage(progress),
+ Converter.getVolumeFromPercentage(barRightVolume.getProgress()));
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ }
+ });
+ barRightVolume.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ controller.setVolume(
+ Converter.getVolumeFromPercentage(barLeftVolume.getProgress()),
+ Converter.getVolumeFromPercentage(progress));
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ }
+ });
+ stereoToMono.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ UserPreferences.stereoToMono(isChecked);
+ if (controller != null) {
+ controller.setDownmix(isChecked);
+ }
+ });
+ 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) {
+ DBTasks.flattrItemIfLoggedIn(this, ((FeedMedia) media).getItem());
+ }
+ break;
+ case R.id.share_link_item:
+ if (media instanceof FeedMedia) {
+ ShareUtils.shareFeedItemLink(this, ((FeedMedia) media).getItem());
+ }
+ break;
+ case R.id.share_download_url_item:
+ if (media instanceof FeedMedia) {
+ ShareUtils.shareFeedItemDownloadLink(this, ((FeedMedia) media).getItem());
+ }
+ break;
+ case R.id.share_link_with_position_item:
+ if (media instanceof FeedMedia) {
+ ShareUtils.shareFeedItemLink(this, ((FeedMedia) media).getItem(), true);
+ }
+ break;
+ case R.id.share_download_url_with_position_item:
+ if (media instanceof FeedMedia) {
+ ShareUtils.shareFeedItemDownloadLink(this, ((FeedMedia) media).getItem(), true);
+ }
+ break;
+ default:
+ return false;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Log.d(TAG, "onResume()");
+ StorageUtils.checkStorageAvailability(this);
+ if(controller != null) {
+ controller.init();
+ }
+ }
+
+ /**
+ * Called by 'handleStatus()' when the PlaybackService is waiting for
+ * a video surface.
+ */
+ protected abstract void onAwaitingVideoSurface();
+
+ protected abstract void postStatusMsg(int resId, boolean showToast);
+
+ protected abstract void clearStatusMsg();
+
+ protected void onPositionObserverUpdate() {
+ if (controller == null || txtvPosition == null || txtvLength == null) {
+ return;
+ }
+ int currentPosition = controller.getPosition();
+ int duration = controller.getDuration();
+ Log.d(TAG, "currentPosition " + Converter.getDurationStringLong(currentPosition));
+ if (currentPosition == PlaybackService.INVALID_TIME ||
+ duration == PlaybackService.INVALID_TIME) {
+ Log.w(TAG, "Could not react to position observer update because of invalid time");
+ return;
+ }
+ txtvPosition.setText(Converter.getDurationStringLong(currentPosition));
+ if (showTimeLeft) {
+ txtvLength.setText("-" + Converter.getDurationStringLong(duration - currentPosition));
+ } else {
+ txtvLength.setText(Converter.getDurationStringLong(duration));
+ }
+ updateProgressbarPosition(currentPosition, duration);
+ }
+
+ private void updateProgressbarPosition(int position, int duration) {
+ Log.d(TAG, "updateProgressbarPosition(" + position + ", " + duration + ")");
+ if(sbPosition == null) {
+ return;
+ }
+ 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() {
+ Log.d(TAG, "loadMediaInfo()");
+ Playable media = controller.getMedia();
+ SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
+ showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false);
+ if (media != null) {
+ onPositionObserverUpdate();
+ checkFavorite();
+ updatePlaybackSpeedButton();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ protected void updatePlaybackSpeedButton() {
+ // Only meaningful on AudioplayerActivity, where it is overridden.
+ }
+
+ protected void updatePlaybackSpeedButtonText() {
+ // Only meaningful on AudioplayerActivity, where it is overridden.
+ }
+
+
+ protected void setupGUI() {
+ setContentView(getContentViewResourceId());
+ sbPosition = (SeekBar) findViewById(R.id.sbPosition);
+ txtvPosition = (TextView) findViewById(R.id.txtvPosition);
+
+ SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
+ showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false);
+ Log.d("timeleft", showTimeLeft ? "true" : "false");
+ txtvLength = (TextView) findViewById(R.id.txtvLength);
+ if (txtvLength != null) {
+ txtvLength.setOnClickListener(v -> {
+ showTimeLeft = !showTimeLeft;
+ Playable media = controller.getMedia();
+ if (media == null) {
+ return;
+ }
+
+ String length;
+ if (showTimeLeft) {
+ length = "-" + Converter.getDurationStringLong(media.getDuration() - media.getPosition());
+ } else {
+ length = Converter.getDurationStringLong(media.getDuration());
+ }
+ txtvLength.setText(length);
+
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(PREF_SHOW_TIME_LEFT, showTimeLeft);
+ editor.apply();
+ Log.d("timeleft on click", showTimeLeft ? "true" : "false");
+ });
+ }
+
+ butRev = (ImageButton) findViewById(R.id.butRev);
+ txtvRev = (TextView) findViewById(R.id.txtvRev);
+ if (txtvRev != null) {
+ txtvRev.setText(String.valueOf(UserPreferences.getRewindSecs()));
+ }
+ butPlay = (ImageButton) findViewById(R.id.butPlay);
+ butFF = (ImageButton) findViewById(R.id.butFF);
+ txtvFF = (TextView) findViewById(R.id.txtvFF);
+ if (txtvFF != null) {
+ txtvFF.setText(String.valueOf(UserPreferences.getFastFowardSecs()));
+ }
+ butSkip = (ImageButton) findViewById(R.id.butSkip);
+
+ // SEEKBAR SETUP
+
+ sbPosition.setOnSeekBarChangeListener(this);
+
+ // BUTTON SETUP
+
+ if (butRev != null) {
+ butRev.setOnClickListener(v -> onRewind());
+ butRev.setOnLongClickListener(new View.OnLongClickListener() {
+
+ int choice;
+
+ @Override
+ public boolean onLongClick(View v) {
+ int checked = 0;
+ int rewindSecs = UserPreferences.getRewindSecs();
+ final int[] values = getResources().getIntArray(R.array.seek_delta_values);
+ final String[] choices = new String[values.length];
+ for (int i = 0; i < values.length; i++) {
+ if (rewindSecs == values[i]) {
+ checked = i;
+ }
+ choices[i] = String.valueOf(values[i]) + " " + getString(R.string.time_seconds);
+ }
+ choice = values[checked];
+ AlertDialog.Builder builder = new AlertDialog.Builder(MediaplayerActivity.this);
+ builder.setTitle(R.string.pref_rewind);
+ builder.setSingleChoiceItems(choices, checked,
+ (dialog, which) -> {
+ choice = values[which];
+ });
+ builder.setNegativeButton(R.string.cancel_label, null);
+ builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
+ UserPreferences.setPrefRewindSecs(choice);
+ if(txtvRev != null){
+ txtvRev.setText(String.valueOf(choice));
+ }
+ });
+ builder.create().show();
+ return true;
+ }
+ });
+ }
+
+ butPlay.setOnClickListener(v -> onPlayPause());
+
+ if (butFF != null) {
+ butFF.setOnClickListener(v -> onFastForward());
+ butFF.setOnLongClickListener(new View.OnLongClickListener() {
+
+ int choice;
+
+ @Override
+ public boolean onLongClick(View v) {
+ int checked = 0;
+ int rewindSecs = UserPreferences.getFastFowardSecs();
+ final int[] values = getResources().getIntArray(R.array.seek_delta_values);
+ final String[] choices = new String[values.length];
+ for (int i = 0; i < values.length; i++) {
+ if (rewindSecs == values[i]) {
+ checked = i;
+ }
+ choices[i] = String.valueOf(values[i]) + " " + getString(R.string.time_seconds);
+ }
+ choice = values[checked];
+ AlertDialog.Builder builder = new AlertDialog.Builder(MediaplayerActivity.this);
+ builder.setTitle(R.string.pref_fast_forward);
+ builder.setSingleChoiceItems(choices, checked,
+ (dialog, which) -> {
+ choice = values[which];
+ });
+ builder.setNegativeButton(R.string.cancel_label, null);
+ builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
+ UserPreferences.setPrefFastForwardSecs(choice);
+ if(txtvFF != null) {
+ txtvFF.setText(String.valueOf(choice));
+ }
+ });
+ builder.create().show();
+ return true;
+ }
+ });
+ }
+
+ if (butSkip != null) {
+ butSkip.setOnClickListener(v -> sendBroadcast(new Intent(PlaybackService.ACTION_SKIP_CURRENT_EPISODE)));
+ }
+ }
+
+ protected void onRewind() {
+ if (controller == null) {
+ return;
+ }
+ int curr = controller.getPosition();
+ controller.seekTo(curr - UserPreferences.getRewindSecs() * 1000);
+ }
+
+ protected void onPlayPause() {
+ if(controller == null) {
+ return;
+ }
+ controller.playPause();
+ }
+
+ protected void onFastForward() {
+ if (controller == null) {
+ return;
+ }
+ int curr = controller.getPosition();
+ controller.seekTo(curr + UserPreferences.getFastFowardSecs() * 1000);
+ }
+
+ 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",
+ (dialog, which) -> {
+ dialog.dismiss();
+ finish();
+ }
+ );
+ errorDialog.create().show();
+ }
+
+ float prog;
+
+ @Override
+ public void onProgressChanged (SeekBar seekBar,int progress, boolean fromUser) {
+ if (controller == null || txtvLength == null) {
+ return;
+ }
+ prog = controller.onSeekBarProgressChanged(seekBar, progress, fromUser, txtvPosition);
+ if (showTimeLeft && prog != 0) {
+ int duration = controller.getDuration();
+ String length = "-" + Converter.getDurationStringLong(duration - (int) (prog * duration));
+ txtvLength.setText(length);
+ }
+ }
+
+ @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);
+ }
+ }
+
+ private void checkFavorite() {
+ Playable playable = controller.getMedia();
+ if (playable != null && playable instanceof FeedMedia) {
+ FeedItem feedItem = ((FeedMedia) playable).getItem();
+ if (feedItem != null) {
+ Observable.fromCallable(() -> DBReader.getFeedItem(feedItem.getId()))
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ item -> {
+ boolean isFav = item.isTagged(FeedItem.TAG_FAVORITE);
+ if (isFavorite != isFav) {
+ isFavorite = isFav;
+ invalidateOptionsMenu();
+ }
+ }, error -> {
+ Log.e(TAG, Log.getStackTraceString(error));
+ }
+ );
+ }
+ }
+ }
+
+}