From 3c033cc0fbaa4b1418cda330ac0287d735c621f2 Mon Sep 17 00:00:00 2001 From: Martin Fietz Date: Sat, 4 Jun 2016 01:36:25 +0200 Subject: Create one flavor with Google Cast support and one (free) without --- .../antennapod/activity/CastEnabledActivity.java | 237 ++++++ .../danoeh/antennapod/activity/MainActivity.java | 773 ++++++++++++++++++ .../antennapod/activity/MediaplayerActivity.java | 876 +++++++++++++++++++++ 3 files changed, 1886 insertions(+) create mode 100644 app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java create mode 100644 app/src/play/java/de/danoeh/antennapod/activity/MainActivity.java create mode 100644 app/src/play/java/de/danoeh/antennapod/activity/MediaplayerActivity.java (limited to 'app/src/play/java/de/danoeh/antennapod/activity') 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 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 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 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)); + } + ); + } + } + } + +} -- cgit v1.2.3