diff --git a/app/src/free/java/de/danoeh/antennapod/activity/CastEnabledActivity.java b/app/src/free/java/de/danoeh/antennapod/activity/CastEnabledActivity.java index d82b199db..79d7e02f2 100644 --- a/app/src/free/java/de/danoeh/antennapod/activity/CastEnabledActivity.java +++ b/app/src/free/java/de/danoeh/antennapod/activity/CastEnabledActivity.java @@ -106,10 +106,10 @@ public abstract class CastEnabledActivity extends AppCompatActivity { // * // * @param showAsAction refer to {@link MenuItem#setShowAsAction(int)} // */ -// public final void requestCastButton(int showAsAction) { -// castButtonVisibilityManager.requestCastButton(showAsAction); -// } -// + public final void requestCastButton(int showAsAction) { + // no-op + } + // private class CastButtonVisibilityManager { // private volatile boolean prefEnabled = false; // private volatile boolean viewRequested = false; diff --git a/app/src/free/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/free/java/de/danoeh/antennapod/activity/MainActivity.java deleted file mode 100644 index c4771a583..000000000 --- a/app/src/free/java/de/danoeh/antennapod/activity/MainActivity.java +++ /dev/null @@ -1,773 +0,0 @@ -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/free/java/de/danoeh/antennapod/fragment/ItemFragment.java b/app/src/free/java/de/danoeh/antennapod/fragment/ItemFragment.java deleted file mode 100644 index 0e11a5a17..000000000 --- a/app/src/free/java/de/danoeh/antennapod/fragment/ItemFragment.java +++ /dev/null @@ -1,592 +0,0 @@ -package de.danoeh.antennapod.fragment; - -import android.content.ClipData; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.support.v4.content.ContextCompat; -import android.support.v4.view.GestureDetectorCompat; -import android.text.Layout; -import android.text.TextUtils; -import android.util.Log; -import android.view.ContextMenu; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import com.bumptech.glide.Glide; -import com.joanzapata.iconify.Iconify; -import com.joanzapata.iconify.widget.IconButton; - -import org.apache.commons.lang3.ArrayUtils; - -import java.util.List; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.adapter.DefaultActionButtonCallback; -import de.danoeh.antennapod.core.event.DownloadEvent; -import de.danoeh.antennapod.core.event.DownloaderUpdate; -import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.feed.EventDistributor; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.glide.ApGlideSettings; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.service.download.Downloader; -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.storage.DownloadRequestException; -import de.danoeh.antennapod.core.storage.DownloadRequester; -import de.danoeh.antennapod.core.util.Converter; -import de.danoeh.antennapod.core.util.DateUtils; -import de.danoeh.antennapod.core.util.IntentUtils; -import de.danoeh.antennapod.core.util.LongList; -import de.danoeh.antennapod.core.util.ShareUtils; -import de.danoeh.antennapod.core.util.playback.Timeline; -import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; -import de.danoeh.antennapod.view.OnSwipeGesture; -import de.danoeh.antennapod.view.SwipeGestureDetector; -import de.greenrobot.event.EventBus; -import rx.Observable; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.schedulers.Schedulers; - -/** - * Displays information about a FeedItem and actions. - */ -public class ItemFragment extends Fragment implements OnSwipeGesture { - - private static final String TAG = "ItemFragment"; - - private static final int EVENTS = EventDistributor.UNREAD_ITEMS_UPDATE; - - private static final String ARG_FEEDITEMS = "feeditems"; - private static final String ARG_FEEDITEM_POS = "feeditem_pos"; - - private GestureDetectorCompat headerGestureDetector; - private GestureDetectorCompat webviewGestureDetector; - - /** - * Creates a new instance of an ItemFragment - * - * @param feeditem The ID of the FeedItem that should be displayed. - * @return The ItemFragment instance - */ - public static ItemFragment newInstance(long feeditem) { - return newInstance(new long[] { feeditem }, 0); - } - - /** - * Creates a new instance of an ItemFragment - * - * @param feeditems The IDs of the FeedItems that belong to the same list - * @param feedItemPos The position of the FeedItem that is currently shown - * @return The ItemFragment instance - */ - public static ItemFragment newInstance(long[] feeditems, int feedItemPos) { - ItemFragment fragment = new ItemFragment(); - Bundle args = new Bundle(); - args.putLongArray(ARG_FEEDITEMS, feeditems); - args.putInt(ARG_FEEDITEM_POS, feedItemPos); - fragment.setArguments(args); - return fragment; - } - - private boolean itemsLoaded = false; - private long[] feedItems; - private int feedItemPos; - private FeedItem item; - private String webviewData; - private List downloaderList; - - private ViewGroup root; - private WebView webvDescription; - private TextView txtvPodcast; - private TextView txtvTitle; - private TextView txtvDuration; - private TextView txtvPublished; - private ImageView imgvCover; - private ProgressBar progbarDownload; - private ProgressBar progbarLoading; - private IconButton butAction1; - private IconButton butAction2; - private Menu popupMenu; - - private Subscription subscription; - - /** - * URL that was selected via long-press. - */ - private String selectedURL; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setRetainInstance(true); - setHasOptionsMenu(true); - - feedItems = getArguments().getLongArray(ARG_FEEDITEMS); - feedItemPos = getArguments().getInt(ARG_FEEDITEM_POS); - - headerGestureDetector = new GestureDetectorCompat(getActivity(), new SwipeGestureDetector(this)); - webviewGestureDetector = new GestureDetectorCompat(getActivity(), new SwipeGestureDetector(this) { - // necessary for the longclick context menu to work properly - @Override - public boolean onDown(MotionEvent e) { - return false; - } - }); - } - - @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - super.onCreateView(inflater, container, savedInstanceState); - View layout = inflater.inflate(R.layout.feeditem_fragment, container, false); - - root = (ViewGroup) layout.findViewById(R.id.content_root); - - LinearLayout header = (LinearLayout) root.findViewById(R.id.header); - if(feedItems.length > 0) { - header.setOnTouchListener((v, event) -> headerGestureDetector.onTouchEvent(event)); - } - - txtvPodcast = (TextView) layout.findViewById(R.id.txtvPodcast); - txtvPodcast.setOnClickListener(v -> openPodcast()); - txtvTitle = (TextView) layout.findViewById(R.id.txtvTitle); - if(Build.VERSION.SDK_INT >= 23) { - txtvTitle.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); - } - txtvDuration = (TextView) layout.findViewById(R.id.txtvDuration); - txtvPublished = (TextView) layout.findViewById(R.id.txtvPublished); - if (Build.VERSION.SDK_INT >= 14) { // ellipsize is causing problems on old versions, see #448 - txtvTitle.setEllipsize(TextUtils.TruncateAt.END); - } - webvDescription = (WebView) layout.findViewById(R.id.webvDescription); - if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) { - if (Build.VERSION.SDK_INT >= 11 - && Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { - webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - } - webvDescription.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.black)); - } - webvDescription.getSettings().setUseWideViewPort(false); - webvDescription.getSettings().setLayoutAlgorithm( - WebSettings.LayoutAlgorithm.NARROW_COLUMNS); - webvDescription.getSettings().setLoadWithOverviewMode(true); - if(feedItems.length > 0) { - webvDescription.setOnLongClickListener(webViewLongClickListener); - } - webvDescription.setOnTouchListener((v, event) -> webviewGestureDetector.onTouchEvent(event)); - webvDescription.setWebViewClient(new WebViewClient() { - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - if(IntentUtils.isCallable(getActivity(), intent)) { - startActivity(intent); - } - return true; - } - }); - registerForContextMenu(webvDescription); - - imgvCover = (ImageView) layout.findViewById(R.id.imgvCover); - imgvCover.setOnClickListener(v -> openPodcast()); - progbarDownload = (ProgressBar) layout.findViewById(R.id.progbarDownload); - progbarLoading = (ProgressBar) layout.findViewById(R.id.progbarLoading); - butAction1 = (IconButton) layout.findViewById(R.id.butAction1); - butAction2 = (IconButton) layout.findViewById(R.id.butAction2); - - butAction1.setOnClickListener(v -> { - if (item == null) { - return; - } - DefaultActionButtonCallback actionButtonCallback = new DefaultActionButtonCallback(getActivity()); - actionButtonCallback.onActionButtonPressed(item, item.isTagged(FeedItem.TAG_QUEUE) ? - LongList.of(item.getId()) : new LongList(0)); - FeedMedia media = item.getMedia(); - if (media != null && media.isDownloaded()) { - // playback was started, dialog should close itself - ((MainActivity) getActivity()).dismissChildFragment(); - } - }); - - butAction2.setOnClickListener(v -> { - if (item == null) { - return; - } - - if (item.hasMedia()) { - FeedMedia media = item.getMedia(); - if (!media.isDownloaded()) { - DBTasks.playMedia(getActivity(), media, true, true, true); - ((MainActivity) getActivity()).dismissChildFragment(); - } else { - DBWriter.deleteFeedMediaOfItem(getActivity(), media.getId()); - } - } else if (item.getLink() != null) { - Uri uri = Uri.parse(item.getLink()); - getActivity().startActivity(new Intent(Intent.ACTION_VIEW, uri)); - } - }); - - return layout; - } - - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - load(); - } - - @Override - public void onResume() { - super.onResume(); - EventDistributor.getInstance().register(contentUpdate); - EventBus.getDefault().registerSticky(this); - if(itemsLoaded) { - progbarLoading.setVisibility(View.GONE); - updateAppearance(); - } - } - - @Override - public void onPause() { - super.onPause(); - EventDistributor.getInstance().unregister(contentUpdate); - EventBus.getDefault().unregister(this); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - if(subscription != null) { - subscription.unsubscribe(); - } - if (webvDescription != null && root != null) { - root.removeView(webvDescription); - webvDescription.destroy(); - } - } - - @Override - public boolean onSwipeLeftToRight() { - Log.d(TAG, "onSwipeLeftToRight()"); - feedItemPos = feedItemPos - 1; - if(feedItemPos < 0) { - feedItemPos = feedItems.length - 1; - } - load(); - return true; - } - - @Override - public boolean onSwipeRightToLeft() { - Log.d(TAG, "onSwipeRightToLeft()"); - feedItemPos = (feedItemPos + 1) % feedItems.length; - load(); - return true; - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if(!isAdded() || item == null) { - return; - } -// ((CastEnabledActivity) getActivity()).requestCastButton(MenuItem.SHOW_AS_ACTION_ALWAYS); - inflater.inflate(R.menu.feeditem_options, menu); - popupMenu = menu; - if (item.hasMedia()) { - FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, null); - } else { - // these are already available via button1 and button2 - FeedItemMenuHandler.onPrepareMenu(popupMenuInterface, item, true, null, - R.id.mark_read_item, R.id.visit_website_item); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem menuItem) { - switch(menuItem.getItemId()) { - case R.id.open_podcast: - openPodcast(); - return true; - default: - try { - return FeedItemMenuHandler.onMenuItemClicked(getActivity(), menuItem.getItemId(), item); - } catch (DownloadRequestException e) { - e.printStackTrace(); - Toast.makeText(getActivity(), e.getMessage(), Toast.LENGTH_LONG).show(); - return true; - } - } - } - - private final FeedItemMenuHandler.MenuInterface popupMenuInterface = new FeedItemMenuHandler.MenuInterface() { - @Override - public void setItemVisibility(int id, boolean visible) { - MenuItem item = popupMenu.findItem(id); - if (item != null) { - item.setVisible(visible); - } - } - }; - - - private void onFragmentLoaded() { - if (webviewData != null) { - webvDescription.loadDataWithBaseURL(null, webviewData, "text/html", "utf-8", "about:blank"); - } - updateAppearance(); - } - - private void updateAppearance() { - if (item == null) { - Log.d(TAG, "updateAppearance item is null"); - return; - } - getActivity().supportInvalidateOptionsMenu(); - txtvPodcast.setText(item.getFeed().getTitle()); - txtvTitle.setText(item.getTitle()); - - if (item.getPubDate() != null) { - String pubDateStr = DateUtils.formatAbbrev(getActivity(), item.getPubDate()); - txtvPublished.setText(pubDateStr); - } - - Glide.with(getActivity()) - .load(item.getImageLocation()) - .placeholder(R.color.light_gray) - .error(R.color.light_gray) - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .fitCenter() - .dontAnimate() - .into(imgvCover); - - progbarDownload.setVisibility(View.GONE); - if (item.hasMedia() && downloaderList != null) { - for (Downloader downloader : downloaderList) { - if (downloader.getDownloadRequest().getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA - && downloader.getDownloadRequest().getFeedfileId() == item.getMedia().getId()) { - progbarDownload.setVisibility(View.VISIBLE); - progbarDownload.setProgress(downloader.getDownloadRequest().getProgressPercent()); - } - } - } - - FeedMedia media = item.getMedia(); - String butAction1Icon = null; - int butAction1Text = 0; - String butAction2Icon = null; - int butAction2Text = 0; - if (media == null) { - if (!item.isPlayed()) { - butAction1Icon = "{fa-check 24sp}"; - butAction1Text = R.string.mark_read_label; - } - if (item.getLink() != null) { - butAction2Icon = "{md-web 24sp}"; - butAction2Text = R.string.visit_website_label; - } - } else { - if(media.getDuration() > 0) { - txtvDuration.setText(Converter.getDurationStringLong(media.getDuration())); - } - boolean isDownloading = DownloadRequester.getInstance().isDownloadingFile(media); - if (!media.isDownloaded()) { - butAction2Icon = "{md-settings-input-antenna 24sp}"; - butAction2Text = R.string.stream_label; - } else { - butAction2Icon = "{md-delete 24sp}"; - butAction2Text = R.string.remove_label; - } - if (isDownloading) { - butAction1Icon = "{md-cancel 24sp}"; - butAction1Text = R.string.cancel_label; - } else if (media.isDownloaded()) { - butAction1Icon = "{md-play-arrow 24sp}"; - butAction1Text = R.string.play_label; - } else { - butAction1Icon = "{md-file-download 24sp}"; - butAction1Text = R.string.download_label; - } - } - if(butAction1Icon != null && butAction1Text != 0) { - butAction1.setText(butAction1Icon +"\u0020\u0020" + getActivity().getString(butAction1Text)); - Iconify.addIcons(butAction1); - butAction1.setVisibility(View.VISIBLE); - } else { - butAction1.setVisibility(View.INVISIBLE); - } - if(butAction2Icon != null && butAction2Text != 0) { - butAction2.setText(butAction2Icon +"\u0020\u0020" + getActivity().getString(butAction2Text)); - Iconify.addIcons(butAction2); - butAction2.setVisibility(View.VISIBLE); - } else { - butAction2.setVisibility(View.INVISIBLE); - } - } - - private View.OnLongClickListener webViewLongClickListener = new View.OnLongClickListener() { - - @Override - public boolean onLongClick(View v) { - WebView.HitTestResult r = webvDescription.getHitTestResult(); - if (r != null - && r.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) { - Log.d(TAG, "Link of webview was long-pressed. Extra: " + r.getExtra()); - selectedURL = r.getExtra(); - webvDescription.showContextMenu(); - return true; - } - selectedURL = null; - return false; - } - }; - - @Override - public boolean onContextItemSelected(MenuItem item) { - boolean handled = selectedURL != null; - if (selectedURL != null) { - switch (item.getItemId()) { - case R.id.open_in_browser_item: - Uri uri = Uri.parse(selectedURL); - final Intent intent = new Intent(Intent.ACTION_VIEW, uri); - if(IntentUtils.isCallable(getActivity(), intent)) { - getActivity().startActivity(intent); - } - break; - case R.id.share_url_item: - ShareUtils.shareLink(getActivity(), selectedURL); - break; - case R.id.copy_url_item: - if (android.os.Build.VERSION.SDK_INT >= 11) { - ClipData clipData = ClipData.newPlainText(selectedURL, - selectedURL); - android.content.ClipboardManager cm = (android.content.ClipboardManager) getActivity() - .getSystemService(Context.CLIPBOARD_SERVICE); - cm.setPrimaryClip(clipData); - } else { - android.text.ClipboardManager cm = (android.text.ClipboardManager) getActivity() - .getSystemService(Context.CLIPBOARD_SERVICE); - cm.setText(selectedURL); - } - Toast t = Toast.makeText(getActivity(), - R.string.copied_url_msg, Toast.LENGTH_SHORT); - t.show(); - break; - default: - handled = false; - break; - - } - selectedURL = null; - } - return handled; - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, - ContextMenu.ContextMenuInfo menuInfo) { - if (selectedURL != null) { - super.onCreateContextMenu(menu, v, menuInfo); - Uri uri = Uri.parse(selectedURL); - final Intent intent = new Intent(Intent.ACTION_VIEW, uri); - if(IntentUtils.isCallable(getActivity(), intent)) { - menu.add(Menu.NONE, R.id.open_in_browser_item, Menu.NONE, - R.string.open_in_browser_label); - } - menu.add(Menu.NONE, R.id.copy_url_item, Menu.NONE, - R.string.copy_url_label); - menu.add(Menu.NONE, R.id.share_url_item, Menu.NONE, - R.string.share_url_label); - menu.setHeaderTitle(selectedURL); - } - } - - private void openPodcast() { - Fragment fragment = ItemlistFragment.newInstance(item.getFeedId()); - ((MainActivity)getActivity()).loadChildFragment(fragment); - } - - public void onEventMainThread(FeedItemEvent event) { - Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); - for(FeedItem item : event.items) { - if(feedItems[feedItemPos] == item.getId()) { - load(); - return; - } - } - } - - public void onEventMainThread(DownloadEvent event) { - Log.d(TAG, "onEventMainThread() called with: " + "event = [" + event + "]"); - DownloaderUpdate update = event.update; - downloaderList = update.downloaders; - if(item == null || item.getMedia() == null) { - return; - } - long mediaId = item.getMedia().getId(); - if(ArrayUtils.contains(update.mediaIds, mediaId)) { - if (itemsLoaded && getActivity() != null) { - updateAppearance(); - } - } - } - - - private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() { - @Override - public void update(EventDistributor eventDistributor, Integer arg) { - if ((arg & EVENTS) != 0) { - load(); - } - } - }; - - private void load() { - if(subscription != null) { - subscription.unsubscribe(); - } - progbarLoading.setVisibility(View.VISIBLE); - subscription = Observable.fromCallable(this::loadInBackground) - .subscribeOn(Schedulers.newThread()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> { - progbarLoading.setVisibility(View.GONE); - item = result; - itemsLoaded = true; - onFragmentLoaded(); - }, error -> { - Log.e(TAG, Log.getStackTraceString(error)); - }); - } - - private FeedItem loadInBackground() { - FeedItem feedItem = DBReader.getFeedItem(feedItems[feedItemPos]); - if (feedItem != null) { - Timeline t = new Timeline(getActivity(), feedItem); - webviewData = t.processShownotes(false); - } - return feedItem; - } - -} diff --git a/app/src/free/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java b/app/src/free/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java new file mode 100644 index 000000000..de604c7c4 --- /dev/null +++ b/app/src/free/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java @@ -0,0 +1,13 @@ +package de.danoeh.antennapod.preferences; + +import de.danoeh.antennapod.core.preferences.UserPreferences; + +/** + * Implements functions from PreferenceController that are flavor dependent. + */ +public class PreferenceControllerFlavorHelper { + + static void setupFlavoredUI(PreferenceController.PreferenceUI ui) { + ui.findPreference(UserPreferences.PREF_CAST_ENABLED).setEnabled(false); + } +} diff --git a/app/src/play/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java similarity index 97% rename from app/src/play/java/de/danoeh/antennapod/activity/MainActivity.java rename to app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java index 17965ca8e..447edc567 100644 --- a/app/src/play/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -48,6 +48,7 @@ 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.Flavors; import de.danoeh.antennapod.core.util.StorageUtils; import de.danoeh.antennapod.dialog.RatingDialog; import de.danoeh.antennapod.fragment.AddFeedFragment; @@ -507,21 +508,24 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi @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; + if (Flavors.FLAVOR == Flavors.PLAY) { + 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; + } + } else { + return retVal; } - } @Override diff --git a/app/src/free/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java similarity index 99% rename from app/src/free/java/de/danoeh/antennapod/activity/MediaplayerActivity.java rename to app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java index 0d6e5504a..ce8672b59 100644 --- a/app/src/free/java/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -39,6 +39,7 @@ 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.Flavors; import de.danoeh.antennapod.core.util.ShareUtils; import de.danoeh.antennapod.core.util.StorageUtils; import de.danoeh.antennapod.core.util.playback.MediaPlayerError; @@ -281,7 +282,9 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); -// requestCastButton(MenuItem.SHOW_AS_ACTION_ALWAYS); + if (Flavors.FLAVOR == Flavors.PLAY) { + requestCastButton(MenuItem.SHOW_AS_ACTION_ALWAYS); + } MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.mediaplayer, menu); return true; diff --git a/app/src/play/java/de/danoeh/antennapod/fragment/ItemFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java similarity index 98% rename from app/src/play/java/de/danoeh/antennapod/fragment/ItemFragment.java rename to app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java index 524b78ed2..6c2e608dc 100644 --- a/app/src/play/java/de/danoeh/antennapod/fragment/ItemFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -58,6 +58,7 @@ import de.danoeh.antennapod.core.storage.DownloadRequestException; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.DateUtils; +import de.danoeh.antennapod.core.util.Flavors; import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.ShareUtils; @@ -312,7 +313,10 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { if(!isAdded() || item == null) { return; } - ((CastEnabledActivity) getActivity()).requestCastButton(MenuItem.SHOW_AS_ACTION_ALWAYS); + super.onCreateOptionsMenu(menu, inflater); + if (Flavors.FLAVOR == Flavors.PLAY) { + ((CastEnabledActivity) getActivity()).requestCastButton(MenuItem.SHOW_AS_ACTION_ALWAYS); + } inflater.inflate(R.menu.feeditem_options, menu); popupMenu = menu; if (item.hasMedia()) { diff --git a/app/src/free/java/de/danoeh/antennapod/preferences/PreferenceController.java b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java similarity index 98% rename from app/src/free/java/de/danoeh/antennapod/preferences/PreferenceController.java rename to app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java index 5cd44c0a7..e1ef24afd 100644 --- a/app/src/free/java/de/danoeh/antennapod/preferences/PreferenceController.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java @@ -410,22 +410,7 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc ui.getActivity().startActivity(Intent.createChooser(emailIntent, intentTitle)); return true; }); - //checks whether Google Play Services is installed on the device (condition necessary for Cast support) -// ui.findPreference(UserPreferences.PREF_CAST_ENABLED).setOnPreferenceChangeListener((preference, o) -> { -// if (o instanceof Boolean && ((Boolean) o)) { -// final int googlePlayServicesCheck = GoogleApiAvailability.getInstance() -// .isGooglePlayServicesAvailable(ui.getActivity()); -// if (googlePlayServicesCheck == ConnectionResult.SUCCESS) { -// return true; -// } else { -// GoogleApiAvailability.getInstance() -// .getErrorDialog(ui.getActivity(), googlePlayServicesCheck, 0) -// .show(); -// return false; -// } -// } -// return true; -// }); + PreferenceControllerFlavorHelper.setupFlavoredUI(ui); buildEpisodeCleanupPreference(); buildSmartMarkAsPlayedPreference(); buildAutodownloadSelectedNetworsPreference(); diff --git a/app/src/play/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/play/java/de/danoeh/antennapod/activity/MediaplayerActivity.java deleted file mode 100644 index 71d288725..000000000 --- a/app/src/play/java/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ /dev/null @@ -1,876 +0,0 @@ -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)); - } - ); - } - } - } - -} diff --git a/app/src/play/java/de/danoeh/antennapod/preferences/PreferenceController.java b/app/src/play/java/de/danoeh/antennapod/preferences/PreferenceController.java deleted file mode 100644 index 183ef338a..000000000 --- a/app/src/play/java/de/danoeh/antennapod/preferences/PreferenceController.java +++ /dev/null @@ -1,953 +0,0 @@ -package de.danoeh.antennapod.preferences; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.TimePickerDialog; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.net.Uri; -import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiManager; -import android.os.Build; -import android.preference.CheckBoxPreference; -import android.preference.EditTextPreference; -import android.preference.ListPreference; -import android.preference.Preference; -import android.preference.PreferenceManager; -import android.preference.PreferenceScreen; -import android.support.design.widget.Snackbar; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AlertDialog; -import android.text.Editable; -import android.text.Html; -import android.text.TextWatcher; -import android.text.format.DateFormat; -import android.util.Log; -import android.widget.EditText; -import android.widget.Toast; -import android.widget.ListView; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.GoogleApiAvailability; - -import org.apache.commons.lang3.ArrayUtils; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import de.danoeh.antennapod.CrashReportWriter; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.AboutActivity; -import de.danoeh.antennapod.activity.DirectoryChooserActivity; -import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.activity.PreferenceActivity; -import de.danoeh.antennapod.activity.PreferenceActivityGingerbread; -import de.danoeh.antennapod.activity.StatisticsActivity; -import de.danoeh.antennapod.asynctask.OpmlExportWorker; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.service.GpodnetSyncService; -import de.danoeh.antennapod.core.util.Converter; -import de.danoeh.antennapod.core.util.StorageUtils; -import de.danoeh.antennapod.core.util.flattr.FlattrUtils; -import de.danoeh.antennapod.dialog.AuthenticationDialog; -import de.danoeh.antennapod.dialog.AutoFlattrPreferenceDialog; -import de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog; -import de.danoeh.antennapod.dialog.ProxyDialog; -import de.danoeh.antennapod.dialog.VariableSpeedDialog; - -/** - * Sets up a preference UI that lets the user change user preferences. - */ - -public class PreferenceController implements SharedPreferences.OnSharedPreferenceChangeListener { - - private static final String TAG = "PreferenceController"; - - public static final String PREF_FLATTR_SETTINGS = "prefFlattrSettings"; - public static final String PREF_FLATTR_AUTH = "pref_flattr_authenticate"; - public static final String PREF_FLATTR_REVOKE = "prefRevokeAccess"; - public static final String PREF_AUTO_FLATTR_PREFS = "prefAutoFlattrPrefs"; - public static final String PREF_OPML_EXPORT = "prefOpmlExport"; - public static final String STATISTICS = "statistics"; - public static final String PREF_ABOUT = "prefAbout"; - public static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir"; - public static final String AUTO_DL_PREF_SCREEN = "prefAutoDownloadSettings"; - public static final String PREF_PLAYBACK_SPEED_LAUNCHER = "prefPlaybackSpeedLauncher"; - public static final String PREF_GPODNET_LOGIN = "pref_gpodnet_authenticate"; - public static final String PREF_GPODNET_SETLOGIN_INFORMATION = "pref_gpodnet_setlogin_information"; - public static final String PREF_GPODNET_SYNC = "pref_gpodnet_sync"; - public static final String PREF_GPODNET_LOGOUT = "pref_gpodnet_logout"; - public static final String PREF_GPODNET_HOSTNAME = "pref_gpodnet_hostname"; - public static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify"; - public static final String PREF_PROXY = "prefProxy"; - public static final String PREF_KNOWN_ISSUES = "prefKnownIssues"; - public static final String PREF_FAQ = "prefFaq"; - public static final String PREF_SEND_CRASH_REPORT = "prefSendCrashReport"; - - private final PreferenceUI ui; - - private CheckBoxPreference[] selectedNetworks; - - private static final String[] EXTERNAL_STORAGE_PERMISSIONS = { - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE }; - private static final int PERMISSION_REQUEST_EXTERNAL_STORAGE = 41; - - public PreferenceController(PreferenceUI ui) { - this.ui = ui; - PreferenceManager.getDefaultSharedPreferences(ui.getActivity().getApplicationContext()) - .registerOnSharedPreferenceChangeListener(this); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if(key.equals(UserPreferences.PREF_SONIC)) { - CheckBoxPreference prefSonic = (CheckBoxPreference) ui.findPreference(UserPreferences.PREF_SONIC); - if(prefSonic != null) { - prefSonic.setChecked(sharedPreferences.getBoolean(UserPreferences.PREF_SONIC, false)); - } - } - } - - /** - * Returns the preference activity that should be used on this device. - * - * @return PreferenceActivity if the API level is greater than 10, PreferenceActivityGingerbread otherwise. - */ - public static Class getPreferenceActivity() { - if (Build.VERSION.SDK_INT > 10) { - return PreferenceActivity.class; - } else { - return PreferenceActivityGingerbread.class; - } - } - - public void onCreate() { - final Activity activity = ui.getActivity(); - - if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { - // disable expanded notification option on unsupported android versions - ui.findPreference(PreferenceController.PREF_EXPANDED_NOTIFICATION).setEnabled(false); - ui.findPreference(PreferenceController.PREF_EXPANDED_NOTIFICATION).setOnPreferenceClickListener( - preference -> { - Toast toast = Toast.makeText(activity, - R.string.pref_expand_notify_unsupport_toast, Toast.LENGTH_SHORT); - toast.show(); - return true; - } - ); - } - ui.findPreference(PreferenceController.PREF_FLATTR_REVOKE).setOnPreferenceClickListener( - preference -> { - FlattrUtils.revokeAccessToken(activity); - checkItemVisibility(); - return true; - } - ); - ui.findPreference(PreferenceController.PREF_ABOUT).setOnPreferenceClickListener( - preference -> { - activity.startActivity(new Intent(activity, AboutActivity.class)); - return true; - } - ); - ui.findPreference(PreferenceController.STATISTICS).setOnPreferenceClickListener( - preference -> { - activity.startActivity(new Intent(activity, StatisticsActivity.class)); - return true; - } - ); - ui.findPreference(PreferenceController.PREF_OPML_EXPORT).setOnPreferenceClickListener( - preference -> { - new OpmlExportWorker(activity).executeAsync(); - return true; - } - ); - ui.findPreference(PreferenceController.PREF_CHOOSE_DATA_DIR).setOnPreferenceClickListener( - preference -> { - if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT && - Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { - showChooseDataFolderDialog(); - } else { - int readPermission = ActivityCompat.checkSelfPermission( - activity, Manifest.permission.READ_EXTERNAL_STORAGE); - int writePermission = ActivityCompat.checkSelfPermission( - activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); - if (readPermission == PackageManager.PERMISSION_GRANTED && - writePermission == PackageManager.PERMISSION_GRANTED) { - openDirectoryChooser(); - } else { - requestPermission(); - } - } - return true; - } - ); - ui.findPreference(PreferenceController.PREF_CHOOSE_DATA_DIR) - .setOnPreferenceClickListener( - preference -> { - if (Build.VERSION.SDK_INT >= 19) { - showChooseDataFolderDialog(); - } else { - Intent intent = new Intent(activity, DirectoryChooserActivity.class); - activity.startActivityForResult(intent, - DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED); - } - return true; - } - ); - ui.findPreference(UserPreferences.PREF_THEME) - .setOnPreferenceChangeListener( - (preference, newValue) -> { - Intent i = new Intent(activity, MainActivity.class); - i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK - | Intent.FLAG_ACTIVITY_NEW_TASK); - activity.finish(); - activity.startActivity(i); - return true; - } - ); - ui.findPreference(UserPreferences.PREF_HIDDEN_DRAWER_ITEMS) - .setOnPreferenceClickListener(preference -> { - showDrawerPreferencesDialog(); - return true; - }); - - ui.findPreference(UserPreferences.PREF_COMPACT_NOTIFICATION_BUTTONS) - .setOnPreferenceClickListener(preference -> { - showNotificationButtonsDialog(); - return true; - }); - - ui.findPreference(UserPreferences.PREF_UPDATE_INTERVAL) - .setOnPreferenceClickListener(preference -> { - showUpdateIntervalTimePreferencesDialog(); - return true; - }); - - ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL).setOnPreferenceChangeListener( - (preference, newValue) -> { - if (newValue instanceof Boolean) { - boolean enabled = (Boolean) newValue; - ui.findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE).setEnabled(enabled); - ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_ON_BATTERY).setEnabled(enabled); - ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER).setEnabled(enabled); - setSelectedNetworksEnabled(enabled && UserPreferences.isEnableAutodownloadWifiFilter()); - } - return true; - }); - ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER) - .setOnPreferenceChangeListener( - (preference, newValue) -> { - if (newValue instanceof Boolean) { - setSelectedNetworksEnabled((Boolean) newValue); - return true; - } else { - return false; - } - } - ); - ui.findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS) - .setOnPreferenceChangeListener( - (preference, o) -> { - if (o instanceof String) { - try { - int value = Integer.parseInt((String) o); - if (1 <= value && value <= 50) { - setParallelDownloadsText(value); - return true; - } - } catch (NumberFormatException e) { - return false; - } - } - return false; - } - ); - // validate and set correct value: number of downloads between 1 and 50 (inclusive) - final EditText ev = ((EditTextPreference) ui.findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS)).getEditText(); - ev.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable s) { - if (s.length() > 0) { - try { - int value = Integer.parseInt(s.toString()); - if (value <= 0) { - ev.setText("1"); - } else if (value > 50) { - ev.setText("50"); - } - } catch (NumberFormatException e) { - ev.setText("6"); - } - ev.setSelection(ev.getText().length()); - } - } - }); - ui.findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE) - .setOnPreferenceChangeListener( - (preference, o) -> { - if (o instanceof String) { - setEpisodeCacheSizeText(UserPreferences.readEpisodeCacheSize((String) o)); - } - return true; - } - ); - ui.findPreference(PreferenceController.PREF_PLAYBACK_SPEED_LAUNCHER) - .setOnPreferenceClickListener(preference -> { - VariableSpeedDialog.showDialog(activity); - return true; - }); - ui.findPreference(PreferenceController.PREF_GPODNET_SETLOGIN_INFORMATION) - .setOnPreferenceClickListener(preference -> { - AuthenticationDialog dialog = new AuthenticationDialog(activity, - R.string.pref_gpodnet_setlogin_information_title, false, false, GpodnetPreferences.getUsername(), - null) { - - @Override - protected void onConfirmed(String username, String password, boolean saveUsernamePassword) { - GpodnetPreferences.setPassword(password); - } - }; - dialog.show(); - return true; - }); - ui.findPreference(PreferenceController.PREF_GPODNET_SYNC). - setOnPreferenceClickListener(preference -> { - GpodnetSyncService.sendSyncIntent(ui.getActivity().getApplicationContext()); - Toast toast = Toast.makeText(ui.getActivity(), R.string.pref_gpodnet_sync_started, - Toast.LENGTH_SHORT); - toast.show(); - return true; - }); - ui.findPreference(PreferenceController.PREF_GPODNET_LOGOUT).setOnPreferenceClickListener( - preference -> { - GpodnetPreferences.logout(); - Toast toast = Toast.makeText(activity, R.string.pref_gpodnet_logout_toast, Toast.LENGTH_SHORT); - toast.show(); - updateGpodnetPreferenceScreen(); - return true; - }); - ui.findPreference(PreferenceController.PREF_GPODNET_HOSTNAME).setOnPreferenceClickListener( - preference -> { - GpodnetSetHostnameDialog.createDialog(activity).setOnDismissListener(dialog -> updateGpodnetPreferenceScreen()); - return true; - }); - - ui.findPreference(PreferenceController.PREF_AUTO_FLATTR_PREFS) - .setOnPreferenceClickListener(preference -> { - AutoFlattrPreferenceDialog.newAutoFlattrPreferenceDialog(activity, - new AutoFlattrPreferenceDialog.AutoFlattrPreferenceDialogInterface() { - @Override - public void onCancelled() { - - } - - @Override - public void onConfirmed(boolean autoFlattrEnabled, float autoFlattrValue) { - UserPreferences.setAutoFlattrSettings(autoFlattrEnabled, autoFlattrValue); - checkItemVisibility(); - } - }); - return true; - }); - ui.findPreference(UserPreferences.PREF_IMAGE_CACHE_SIZE).setOnPreferenceChangeListener( - (preference, o) -> { - if (o instanceof String) { - int newValue = Integer.parseInt((String) o) * 1024 * 1024; - if (newValue != UserPreferences.getImageCacheSize()) { - AlertDialog.Builder dialog = new AlertDialog.Builder(ui.getActivity()); - dialog.setTitle(android.R.string.dialog_alert_title); - dialog.setMessage(R.string.pref_restart_required); - dialog.setPositiveButton(android.R.string.ok, null); - dialog.show(); - } - return true; - } - return false; - } - ); - ui.findPreference(PREF_PROXY).setOnPreferenceClickListener(preference -> { - ProxyDialog dialog = new ProxyDialog(ui.getActivity()); - dialog.createDialog().show(); - return true; - }); - ui.findPreference(PREF_KNOWN_ISSUES).setOnPreferenceClickListener(preference -> { - openInBrowser("https://github.com/AntennaPod/AntennaPod/labels/bug"); - return true; - }); - ui.findPreference(PREF_FAQ).setOnPreferenceClickListener(preference -> { - openInBrowser("http://antennapod.org/faq.html"); - return true; - }); - ui.findPreference(PREF_SEND_CRASH_REPORT).setOnPreferenceClickListener(preference -> { - Intent emailIntent = new Intent(Intent.ACTION_SEND); - emailIntent.setType("text/plain"); - emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{"Martin.Fietz@gmail.com"}); - emailIntent.putExtra(Intent.EXTRA_SUBJECT, "AntennaPod Crash Report"); - emailIntent.putExtra(Intent.EXTRA_TEXT, "Please describe what you were doing when the app crashed"); - // the attachment - emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(CrashReportWriter.getFile())); - String intentTitle = ui.getActivity().getString(R.string.send_email); - ui.getActivity().startActivity(Intent.createChooser(emailIntent, intentTitle)); - return true; - }); - //checks whether Google Play Services is installed on the device (condition necessary for Cast support) - ui.findPreference(UserPreferences.PREF_CAST_ENABLED).setOnPreferenceChangeListener((preference, o) -> { - if (o instanceof Boolean && ((Boolean) o)) { - final int googlePlayServicesCheck = GoogleApiAvailability.getInstance() - .isGooglePlayServicesAvailable(ui.getActivity()); - if (googlePlayServicesCheck == ConnectionResult.SUCCESS) { - return true; - } else { - GoogleApiAvailability.getInstance() - .getErrorDialog(ui.getActivity(), googlePlayServicesCheck, 0) - .show(); - return false; - } - } - return true; - }); - buildEpisodeCleanupPreference(); - buildSmartMarkAsPlayedPreference(); - buildAutodownloadSelectedNetworsPreference(); - setSelectedNetworksEnabled(UserPreferences.isEnableAutodownloadWifiFilter()); - } - - private void openInBrowser(String url) { - try { - Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - ui.getActivity().startActivity(myIntent); - } catch (ActivityNotFoundException e) { - Toast.makeText(ui.getActivity(), R.string.pref_no_browser_found, Toast.LENGTH_LONG).show(); - Log.e(TAG, Log.getStackTraceString(e)); - } - } - - public void onResume() { - checkItemVisibility(); - setUpdateIntervalText(); - setParallelDownloadsText(UserPreferences.getParallelDownloads()); - setEpisodeCacheSizeText(UserPreferences.getEpisodeCacheSize()); - setDataFolderText(); - updateGpodnetPreferenceScreen(); - } - - @SuppressLint("NewApi") - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode == Activity.RESULT_OK && - requestCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) { - String dir = data.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR); - - File path; - if(dir != null) { - path = new File(dir); - } else { - path = ui.getActivity().getExternalFilesDir(null); - } - String message = null; - final Context context= ui.getActivity().getApplicationContext(); - if(!path.exists()) { - message = String.format(context.getString(R.string.folder_does_not_exist_error), dir); - } else if(!path.canRead()) { - message = String.format(context.getString(R.string.folder_not_readable_error), dir); - } else if(!path.canWrite()) { - message = String.format(context.getString(R.string.folder_not_writable_error), dir); - } - - if(message == null) { - Log.d(TAG, "Setting data folder: " + dir); - UserPreferences.setDataFolder(dir); - setDataFolderText(); - } else { - AlertDialog.Builder ab = new AlertDialog.Builder(ui.getActivity()); - ab.setMessage(message); - ab.setPositiveButton(android.R.string.ok, null); - ab.show(); - } - } - } - - - private void updateGpodnetPreferenceScreen() { - final boolean loggedIn = GpodnetPreferences.loggedIn(); - ui.findPreference(PreferenceController.PREF_GPODNET_LOGIN).setEnabled(!loggedIn); - ui.findPreference(PreferenceController.PREF_GPODNET_SETLOGIN_INFORMATION).setEnabled(loggedIn); - ui.findPreference(PreferenceController.PREF_GPODNET_SYNC).setEnabled(loggedIn); - ui.findPreference(PreferenceController.PREF_GPODNET_LOGOUT).setEnabled(loggedIn); - if(loggedIn) { - String format = ui.getActivity().getString(R.string.pref_gpodnet_login_status); - String summary = String.format(format, GpodnetPreferences.getUsername(), - GpodnetPreferences.getDeviceID()); - ui.findPreference(PreferenceController.PREF_GPODNET_LOGOUT).setSummary(Html.fromHtml(summary)); - } else { - ui.findPreference(PreferenceController.PREF_GPODNET_LOGOUT).setSummary(null); - } - ui.findPreference(PreferenceController.PREF_GPODNET_HOSTNAME).setSummary(GpodnetPreferences.getHostname()); - } - - private String[] getUpdateIntervalEntries(final String[] values) { - final Resources res = ui.getActivity().getResources(); - String[] entries = new String[values.length]; - for (int x = 0; x < values.length; x++) { - Integer v = Integer.parseInt(values[x]); - switch (v) { - case 0: - entries[x] = res.getString(R.string.pref_update_interval_hours_manual); - break; - case 1: - entries[x] = v + " " + res.getString(R.string.pref_update_interval_hours_singular); - break; - default: - entries[x] = v + " " + res.getString(R.string.pref_update_interval_hours_plural); - break; - - } - } - return entries; - } - - private void buildEpisodeCleanupPreference() { - final Resources res = ui.getActivity().getResources(); - - ListPreference pref = (ListPreference) ui.findPreference(UserPreferences.PREF_EPISODE_CLEANUP); - String[] values = res.getStringArray( - R.array.episode_cleanup_values); - String[] entries = new String[values.length]; - for (int x = 0; x < values.length; x++) { - int v = Integer.parseInt(values[x]); - if (v == UserPreferences.EPISODE_CLEANUP_QUEUE) { - entries[x] = res.getString(R.string.episode_cleanup_queue_removal); - } else if (v == UserPreferences.EPISODE_CLEANUP_NULL){ - entries[x] = res.getString(R.string.episode_cleanup_never); - } else if (v == 0) { - entries[x] = res.getString(R.string.episode_cleanup_after_listening); - } else { - entries[x] = res.getQuantityString(R.plurals.episode_cleanup_days_after_listening, v, v); - } - } - pref.setEntries(entries); - } - - private void buildSmartMarkAsPlayedPreference() { - final Resources res = ui.getActivity().getResources(); - - ListPreference pref = (ListPreference) ui.findPreference(UserPreferences.PREF_SMART_MARK_AS_PLAYED_SECS); - String[] values = res.getStringArray(R.array.smart_mark_as_played_values); - String[] entries = new String[values.length]; - for (int x = 0; x < values.length; x++) { - if(x == 0) { - entries[x] = res.getString(R.string.pref_smart_mark_as_played_disabled); - } else { - Integer v = Integer.parseInt(values[x]); - if(v < 60) { - entries[x] = res.getQuantityString(R.plurals.time_seconds_quantified, v, v); - } else { - v /= 60; - entries[x] = res.getQuantityString(R.plurals.time_minutes_quantified, v, v); - } - } - } - pref.setEntries(entries); - } - - private void setSelectedNetworksEnabled(boolean b) { - if (selectedNetworks != null) { - for (Preference p : selectedNetworks) { - p.setEnabled(b); - } - } - } - - @SuppressWarnings("deprecation") - private void checkItemVisibility() { - boolean hasFlattrToken = FlattrUtils.hasToken(); - ui.findPreference(PreferenceController.PREF_FLATTR_SETTINGS).setEnabled(FlattrUtils.hasAPICredentials()); - ui.findPreference(PreferenceController.PREF_FLATTR_AUTH).setEnabled(!hasFlattrToken); - ui.findPreference(PreferenceController.PREF_FLATTR_REVOKE).setEnabled(hasFlattrToken); - ui.findPreference(PreferenceController.PREF_AUTO_FLATTR_PREFS).setEnabled(hasFlattrToken); - - boolean autoDownload = UserPreferences.isEnableAutodownload(); - ui.findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE).setEnabled(autoDownload); - ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_ON_BATTERY).setEnabled(autoDownload); - ui.findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER).setEnabled(autoDownload); - setSelectedNetworksEnabled(autoDownload && UserPreferences.isEnableAutodownloadWifiFilter()); - - ui.findPreference(PREF_SEND_CRASH_REPORT).setEnabled(CrashReportWriter.getFile().exists()); - - if (Build.VERSION.SDK_INT >= 16) { - ui.findPreference(UserPreferences.PREF_SONIC).setEnabled(true); - } else { - Preference prefSonic = ui.findPreference(UserPreferences.PREF_SONIC); - prefSonic.setSummary("[Android 4.1+]\n" + prefSonic.getSummary()); - } - } - - private void setUpdateIntervalText() { - Context context = ui.getActivity().getApplicationContext(); - String val; - long interval = UserPreferences.getUpdateInterval(); - if(interval > 0) { - int hours = (int) TimeUnit.MILLISECONDS.toHours(interval); - String hoursStr = context.getResources().getQuantityString(R.plurals.time_hours_quantified, hours, hours); - val = String.format(context.getString(R.string.pref_autoUpdateIntervallOrTime_every), hoursStr); - } else { - int[] timeOfDay = UserPreferences.getUpdateTimeOfDay(); - if(timeOfDay.length == 2) { - Calendar cal = new GregorianCalendar(); - cal.set(Calendar.HOUR_OF_DAY, timeOfDay[0]); - cal.set(Calendar.MINUTE, timeOfDay[1]); - String timeOfDayStr = DateFormat.getTimeFormat(context).format(cal.getTime()); - val = String.format(context.getString(R.string.pref_autoUpdateIntervallOrTime_at), - timeOfDayStr); - } else { - val = context.getString(R.string.pref_smart_mark_as_played_disabled); - } - } - String summary = context.getString(R.string.pref_autoUpdateIntervallOrTime_sum) + "\n" - + String.format(context.getString(R.string.pref_current_value), val); - ui.findPreference(UserPreferences.PREF_UPDATE_INTERVAL).setSummary(summary); - } - - private void setParallelDownloadsText(int downloads) { - final Resources res = ui.getActivity().getResources(); - - String s = Integer.toString(downloads) - + res.getString(R.string.parallel_downloads_suffix); - ui.findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS).setSummary(s); - } - - private void setEpisodeCacheSizeText(int cacheSize) { - final Resources res = ui.getActivity().getResources(); - - String s; - if (cacheSize == res.getInteger( - R.integer.episode_cache_size_unlimited)) { - s = res.getString(R.string.pref_episode_cache_unlimited); - } else { - s = Integer.toString(cacheSize) - + res.getString(R.string.episodes_suffix); - } - ui.findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE).setSummary(s); - } - - private void setDataFolderText() { - File f = UserPreferences.getDataFolder(null); - if (f != null) { - ui.findPreference(PreferenceController.PREF_CHOOSE_DATA_DIR) - .setSummary(f.getAbsolutePath()); - } - } - - private void buildAutodownloadSelectedNetworsPreference() { - final Activity activity = ui.getActivity(); - - if (selectedNetworks != null) { - clearAutodownloadSelectedNetworsPreference(); - } - // get configured networks - WifiManager wifiservice = (WifiManager) activity.getSystemService(Context.WIFI_SERVICE); - List networks = wifiservice.getConfiguredNetworks(); - - if (networks != null) { - selectedNetworks = new CheckBoxPreference[networks.size()]; - List prefValues = Arrays.asList(UserPreferences - .getAutodownloadSelectedNetworks()); - PreferenceScreen prefScreen = (PreferenceScreen) ui.findPreference(PreferenceController.AUTO_DL_PREF_SCREEN); - Preference.OnPreferenceClickListener clickListener = preference -> { - if (preference instanceof CheckBoxPreference) { - String key = preference.getKey(); - List prefValuesList = new ArrayList<>( - Arrays.asList(UserPreferences - .getAutodownloadSelectedNetworks()) - ); - boolean newValue = ((CheckBoxPreference) preference) - .isChecked(); - Log.d(TAG, "Selected network " + key + ". New state: " + newValue); - - int index = prefValuesList.indexOf(key); - if (index >= 0 && !newValue) { - // remove network - prefValuesList.remove(index); - } else if (index < 0 && newValue) { - prefValuesList.add(key); - } - - UserPreferences.setAutodownloadSelectedNetworks( - prefValuesList.toArray(new String[prefValuesList.size()]) - ); - return true; - } else { - return false; - } - }; - // create preference for each known network. attach listener and set - // value - for (int i = 0; i < networks.size(); i++) { - WifiConfiguration config = networks.get(i); - - CheckBoxPreference pref = new CheckBoxPreference(activity); - String key = Integer.toString(config.networkId); - pref.setTitle(config.SSID); - pref.setKey(key); - pref.setOnPreferenceClickListener(clickListener); - pref.setPersistent(false); - pref.setChecked(prefValues.contains(key)); - selectedNetworks[i] = pref; - prefScreen.addPreference(pref); - } - } else { - Log.e(TAG, "Couldn't get list of configure Wi-Fi networks"); - } - } - - private void clearAutodownloadSelectedNetworsPreference() { - if (selectedNetworks != null) { - PreferenceScreen prefScreen = (PreferenceScreen) ui.findPreference(PreferenceController.AUTO_DL_PREF_SCREEN); - - for (CheckBoxPreference network : selectedNetworks) { - if (network != null) { - prefScreen.removePreference(network); - } - } - } - } - - private void showDrawerPreferencesDialog() { - final Context context = ui.getActivity(); - final List hiddenDrawerItems = UserPreferences.getHiddenDrawerItems(); - final String[] navTitles = context.getResources().getStringArray(R.array.nav_drawer_titles); - final String[] NAV_DRAWER_TAGS = MainActivity.NAV_DRAWER_TAGS; - boolean[] checked = new boolean[MainActivity.NAV_DRAWER_TAGS.length]; - for(int i=0; i < NAV_DRAWER_TAGS.length; i++) { - String tag = NAV_DRAWER_TAGS[i]; - if(!hiddenDrawerItems.contains(tag)) { - checked[i] = true; - } - } - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(R.string.drawer_preferences); - builder.setMultiChoiceItems(navTitles, 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(); - } - - private void showNotificationButtonsDialog() { - final Context context = ui.getActivity(); - final List preferredButtons = UserPreferences.getCompactNotificationButtons(); - final String[] allButtonNames = context.getResources().getStringArray( - R.array.compact_notification_buttons_options); - boolean[] checked = new boolean[allButtonNames.length]; // booleans default to false in java - - for(int i=0; i < checked.length; i++) { - if(preferredButtons.contains(i)) { - checked[i] = true; - } - } - - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(String.format(context.getResources().getString( - R.string.pref_compact_notification_buttons_dialog_title), 2)); - builder.setMultiChoiceItems(allButtonNames, checked, (dialog, which, isChecked) -> { - checked[which] = isChecked; - - if (isChecked) { - if (preferredButtons.size() < 2) { - preferredButtons.add(which); - } else { - // Only allow a maximum of two selections. This is because the notification - // on the lock screen can only display 3 buttons, and the play/pause button - // is always included. - checked[which] = false; - ListView selectionView = ((AlertDialog) dialog).getListView(); - selectionView.setItemChecked(which, false); - Snackbar.make( - selectionView, - String.format(context.getResources().getString( - R.string.pref_compact_notification_buttons_dialog_error), 2), - Snackbar.LENGTH_SHORT).show(); - } - } else { - preferredButtons.remove((Integer) which); - } - }); - builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { - UserPreferences.setCompactNotificationButtons(preferredButtons); - }); - builder.setNegativeButton(R.string.cancel_label, null); - builder.create().show(); - } - - // CHOOSE DATA FOLDER - - private void requestPermission() { - ActivityCompat.requestPermissions(ui.getActivity(), EXTERNAL_STORAGE_PERMISSIONS, - PERMISSION_REQUEST_EXTERNAL_STORAGE); - } - - private void openDirectoryChooser() { - Activity activity = ui.getActivity(); - Intent intent = new Intent(activity, DirectoryChooserActivity.class); - activity.startActivityForResult(intent, DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED); - } - - private void showChooseDataFolderDialog() { - Context context = ui.getActivity(); - File dataFolder = UserPreferences.getDataFolder(null); - if(dataFolder == null) { - new MaterialDialog.Builder(ui.getActivity()) - .title(R.string.error_label) - .content(R.string.external_storage_error_msg) - .neutralText(android.R.string.ok) - .show(); - return; - } - String dataFolderPath = dataFolder.getAbsolutePath(); - int selectedIndex = -1; - File[] mediaDirs = ContextCompat.getExternalFilesDirs(context, null); - List folders = new ArrayList<>(mediaDirs.length); - List choices = new ArrayList<>(mediaDirs.length); - for(int i=0; i < mediaDirs.length; i++) { - File dir = mediaDirs[i]; - if(dir == null || !dir.exists() || !dir.canRead() || !dir.canWrite()) { - continue; - } - String path = mediaDirs[i].getAbsolutePath(); - folders.add(path); - if(dataFolderPath.equals(path)) { - selectedIndex = i; - } - int index = path.indexOf("Android"); - String choice; - if(index >= 0) { - choice = path.substring(0, index); - } else { - choice = path; - } - long bytes = StorageUtils.getFreeSpaceAvailable(path); - String freeSpace = String.format(context.getString(R.string.free_space_label), - Converter.byteToString(bytes)); - choices.add(Html.fromHtml("" + choice - + " [" + freeSpace + "]" + "")); - } - if(choices.size() == 0) { - new MaterialDialog.Builder(ui.getActivity()) - .title(R.string.error_label) - .content(R.string.external_storage_error_msg) - .neutralText(android.R.string.ok) - .show(); - return; - } - MaterialDialog dialog = new MaterialDialog.Builder(ui.getActivity()) - .title(R.string.choose_data_directory) - .content(R.string.choose_data_directory_message) - .items(choices.toArray(new CharSequence[choices.size()])) - .itemsCallbackSingleChoice(selectedIndex, (dialog1, itemView, which, text) -> { - String folder = folders.get(which); - Log.d(TAG, "data folder: " + folder); - UserPreferences.setDataFolder(folder); - setDataFolderText(); - return true; - }) - .negativeText(R.string.cancel_label) - .cancelable(true) - .build(); - dialog.show(); - } - - // UPDATE TIME/INTERVAL DIALOG - - private void showUpdateIntervalTimePreferencesDialog() { - final Context context = ui.getActivity(); - - MaterialDialog.Builder builder = new MaterialDialog.Builder(context); - builder.title(R.string.pref_autoUpdateIntervallOrTime_title); - builder.content(R.string.pref_autoUpdateIntervallOrTime_message); - builder.positiveText(R.string.pref_autoUpdateIntervallOrTime_Interval); - builder.negativeText(R.string.pref_autoUpdateIntervallOrTime_TimeOfDay); - builder.neutralText(R.string.pref_autoUpdateIntervallOrTime_Disable); - builder.onPositive((dialog, which) -> { - AlertDialog.Builder builder1 = new AlertDialog.Builder(context); - builder1.setTitle(context.getString(R.string.pref_autoUpdateIntervallOrTime_Interval)); - final String[] values = context.getResources().getStringArray(R.array.update_intervall_values); - final String[] entries = getUpdateIntervalEntries(values); - long currInterval = UserPreferences.getUpdateInterval(); - int checkedItem = -1; - if(currInterval > 0) { - String currIntervalStr = String.valueOf(TimeUnit.MILLISECONDS.toHours(currInterval)); - checkedItem = ArrayUtils.indexOf(values, currIntervalStr); - } - builder1.setSingleChoiceItems(entries, checkedItem, (dialog1, which1) -> { - int hours = Integer.parseInt(values[which1]); - UserPreferences.setUpdateInterval(hours); - dialog1.dismiss(); - setUpdateIntervalText(); - }); - builder1.setNegativeButton(context.getString(R.string.cancel_label), null); - builder1.show(); - }); - builder.onNegative((dialog, which) -> { - int hourOfDay = 7, minute = 0; - int[] updateTime = UserPreferences.getUpdateTimeOfDay(); - if (updateTime.length == 2) { - hourOfDay = updateTime[0]; - minute = updateTime[1]; - } - TimePickerDialog timePickerDialog = new TimePickerDialog(context, - (view, selectedHourOfDay, selectedMinute) -> { - if (view.getTag() == null) { // onTimeSet() may get called twice! - view.setTag("TAGGED"); - UserPreferences.setUpdateTimeOfDay(selectedHourOfDay, selectedMinute); - setUpdateIntervalText(); - } - }, hourOfDay, minute, DateFormat.is24HourFormat(context)); - timePickerDialog.setTitle(context.getString(R.string.pref_autoUpdateIntervallOrTime_TimeOfDay)); - timePickerDialog.show(); - }); - builder.onNeutral((dialog, which) -> { - UserPreferences.setUpdateInterval(0); - setUpdateIntervalText(); - }); - builder.show(); - } - - - public interface PreferenceUI { - - /** - * Finds a preference based on its key. - */ - Preference findPreference(CharSequence key); - - Activity getActivity(); - } -} diff --git a/app/src/play/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java b/app/src/play/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java new file mode 100644 index 000000000..b9b4e891f --- /dev/null +++ b/app/src/play/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java @@ -0,0 +1,31 @@ +package de.danoeh.antennapod.preferences; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; + +import de.danoeh.antennapod.core.preferences.UserPreferences; + +/** + * Implements functions from PreferenceController that are flavor dependent. + */ +public class PreferenceControllerFlavorHelper { + + static void setupFlavoredUI(PreferenceController.PreferenceUI ui) { + //checks whether Google Play Services is installed on the device (condition necessary for Cast support) + ui.findPreference(UserPreferences.PREF_CAST_ENABLED).setOnPreferenceChangeListener((preference, o) -> { + if (o instanceof Boolean && ((Boolean) o)) { + final int googlePlayServicesCheck = GoogleApiAvailability.getInstance() + .isGooglePlayServicesAvailable(ui.getActivity()); + if (googlePlayServicesCheck == ConnectionResult.SUCCESS) { + return true; + } else { + GoogleApiAvailability.getInstance() + .getErrorDialog(ui.getActivity(), googlePlayServicesCheck, 0) + .show(); + return false; + } + } + return true; + }); + } +} diff --git a/core/src/free/java/de/danoeh/antennapod/core/feed/FeedMedia.java b/core/src/free/java/de/danoeh/antennapod/core/feed/FeedMedia.java deleted file mode 100644 index cde66835a..000000000 --- a/core/src/free/java/de/danoeh/antennapod/core/feed/FeedMedia.java +++ /dev/null @@ -1,567 +0,0 @@ -package de.danoeh.antennapod.core.feed; - -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.database.Cursor; -import android.media.MediaMetadataRetriever; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.annotation.Nullable; - -import java.util.Date; -import java.util.List; -import java.util.concurrent.Callable; - -import de.danoeh.antennapod.core.preferences.PlaybackPreferences; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.storage.PodDBAdapter; -import de.danoeh.antennapod.core.util.ChapterUtils; -import de.danoeh.antennapod.core.util.playback.Playable; - -public class FeedMedia extends FeedFile implements Playable { - private static final String TAG = "FeedMedia"; - - public static final int FEEDFILETYPE_FEEDMEDIA = 2; - public static final int PLAYABLE_TYPE_FEEDMEDIA = 1; - - public static final String PREF_MEDIA_ID = "FeedMedia.PrefMediaId"; - public static final String PREF_FEED_ID = "FeedMedia.PrefFeedId"; - - /** - * Indicates we've checked on the size of the item via the network - * and got an invalid response. Using Integer.MIN_VALUE because - * 1) we'll still check on it in case it gets downloaded (it's <= 0) - * 2) By default all FeedMedia have a size of 0 if we don't know it, - * so this won't conflict with existing practice. - */ - private static final int CHECKED_ON_SIZE_BUT_UNKNOWN = Integer.MIN_VALUE; - - private int duration; - private int position; // Current position in file - private long lastPlayedTime; // Last time this media was played (in ms) - private int played_duration; // How many ms of this file have been played (for autoflattring) - private long size; // File size in Byte - private String mime_type; - @Nullable private volatile FeedItem item; - private Date playbackCompletionDate; - - // if null: unknown, will be checked - private Boolean hasEmbeddedPicture; - - /* Used for loading item when restoring from parcel. */ - private long itemID; - - public FeedMedia(FeedItem i, String download_url, long size, - String mime_type) { - super(null, download_url, false); - this.item = i; - this.size = size; - this.mime_type = mime_type; - } - - public FeedMedia(long id, FeedItem item, int duration, int position, - long size, String mime_type, String file_url, String download_url, - boolean downloaded, Date playbackCompletionDate, int played_duration, - long lastPlayedTime) { - super(file_url, download_url, downloaded); - this.id = id; - this.item = item; - this.duration = duration; - this.position = position; - this.played_duration = played_duration; - this.size = size; - this.mime_type = mime_type; - this.playbackCompletionDate = playbackCompletionDate == null - ? null : (Date) playbackCompletionDate.clone(); - this.lastPlayedTime = lastPlayedTime; - } - - public FeedMedia(long id, FeedItem item, int duration, int position, - long size, String mime_type, String file_url, String download_url, - boolean downloaded, Date playbackCompletionDate, int played_duration, - Boolean hasEmbeddedPicture, long lastPlayedTime) { - this(id, item, duration, position, size, mime_type, file_url, download_url, downloaded, - playbackCompletionDate, played_duration, lastPlayedTime); - this.hasEmbeddedPicture = hasEmbeddedPicture; - } - - public static FeedMedia fromCursor(Cursor cursor) { - int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID); - int indexPlaybackCompletionDate = cursor.getColumnIndex(PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE); - int indexDuration = cursor.getColumnIndex(PodDBAdapter.KEY_DURATION); - int indexPosition = cursor.getColumnIndex(PodDBAdapter.KEY_POSITION); - int indexSize = cursor.getColumnIndex(PodDBAdapter.KEY_SIZE); - int indexMimeType = cursor.getColumnIndex(PodDBAdapter.KEY_MIME_TYPE); - int indexFileUrl = cursor.getColumnIndex(PodDBAdapter.KEY_FILE_URL); - int indexDownloadUrl = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL); - int indexDownloaded = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOADED); - int indexPlayedDuration = cursor.getColumnIndex(PodDBAdapter.KEY_PLAYED_DURATION); - int indexLastPlayedTime = cursor.getColumnIndex(PodDBAdapter.KEY_LAST_PLAYED_TIME); - - long mediaId = cursor.getLong(indexId); - Date playbackCompletionDate = null; - long playbackCompletionTime = cursor.getLong(indexPlaybackCompletionDate); - if (playbackCompletionTime > 0) { - playbackCompletionDate = new Date(playbackCompletionTime); - } - - Boolean hasEmbeddedPicture; - switch(cursor.getInt(cursor.getColumnIndex(PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE))) { - case 1: - hasEmbeddedPicture = Boolean.TRUE; - break; - case 0: - hasEmbeddedPicture = Boolean.FALSE; - break; - default: - hasEmbeddedPicture = null; - break; - } - - return new FeedMedia( - mediaId, - null, - cursor.getInt(indexDuration), - cursor.getInt(indexPosition), - cursor.getLong(indexSize), - cursor.getString(indexMimeType), - cursor.getString(indexFileUrl), - cursor.getString(indexDownloadUrl), - cursor.getInt(indexDownloaded) > 0, - playbackCompletionDate, - cursor.getInt(indexPlayedDuration), - hasEmbeddedPicture, - cursor.getLong(indexLastPlayedTime) - ); - } - - - @Override - public String getHumanReadableIdentifier() { - if (item != null && item.getTitle() != null) { - return item.getTitle(); - } else { - return download_url; - } - } - - /** - * Uses mimetype to determine the type of media. - */ - public MediaType getMediaType() { - return MediaType.fromMimeType(mime_type); - } - - public void updateFromOther(FeedMedia other) { - super.updateFromOther(other); - if (other.size > 0) { - size = other.size; - } - if (other.mime_type != null) { - mime_type = other.mime_type; - } - } - - public boolean compareWithOther(FeedMedia other) { - if (super.compareWithOther(other)) { - return true; - } - if (other.mime_type != null) { - if (mime_type == null || !mime_type.equals(other.mime_type)) { - return true; - } - } - if (other.size > 0 && other.size != size) { - return true; - } - return false; - } - - /** - * Reads playback preferences to determine whether this FeedMedia object is - * currently being played. - */ - public boolean isPlaying() { - return PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA - && PlaybackPreferences.getCurrentlyPlayingFeedMediaId() == id; - } - - /** - * Reads playback preferences to determine whether this FeedMedia object is - * currently being played and the current player status is playing. - */ - public boolean isCurrentlyPlaying() { - return isPlaying() && - ((PlaybackPreferences.getCurrentPlayerStatus() == PlaybackPreferences.PLAYER_STATUS_PLAYING)); - } - - /** - * Reads playback preferences to determine whether this FeedMedia object is - * currently being played and the current player status is paused. - */ - public boolean isCurrentlyPaused() { - return isPlaying() && - ((PlaybackPreferences.getCurrentPlayerStatus() == PlaybackPreferences.PLAYER_STATUS_PAUSED)); - } - - - public boolean hasAlmostEnded() { - int smartMarkAsPlayedSecs = UserPreferences.getSmartMarkAsPlayedSecs(); - return this.position >= this.duration - smartMarkAsPlayedSecs * 1000; - } - - @Override - public int getTypeAsInt() { - return FEEDFILETYPE_FEEDMEDIA; - } - - public int getDuration() { - return duration; - } - - public void setDuration(int duration) { - this.duration = duration; - } - - @Override - public void setLastPlayedTime(long lastPlayedTime) { - this.lastPlayedTime = lastPlayedTime; - } - - public int getPlayedDuration() { - return played_duration; - } - - public void setPlayedDuration(int played_duration) { - this.played_duration = played_duration; - } - - public int getPosition() { - return position; - } - - @Override - public long getLastPlayedTime() { - return lastPlayedTime; - } - - public void setPosition(int position) { - this.position = position; - if(position > 0 && item != null && item.isNew()) { - this.item.setPlayed(false); - } - } - - public long getSize() { - return size; - } - - public void setSize(long size) { - this.size = size; - } - - /** - * Indicates we asked the service what the size was, but didn't - * get a valid answer and we shoudln't check using the network again. - */ - public void setCheckedOnSizeButUnknown() { - this.size = CHECKED_ON_SIZE_BUT_UNKNOWN; - } - - public boolean checkedOnSizeButUnknown() { - return (CHECKED_ON_SIZE_BUT_UNKNOWN == this.size); - } - - public String getMime_type() { - return mime_type; - } - - public void setMime_type(String mime_type) { - this.mime_type = mime_type; - } - - @Nullable - public FeedItem getItem() { - return item; - } - - /** - * Sets the item object of this FeedMedia. If the given - * FeedItem object is not null, it's 'media'-attribute value - * will also be set to this media object. - */ - public void setItem(FeedItem item) { - this.item = item; - if (item != null && item.getMedia() != this) { - item.setMedia(this); - } - } - - public Date getPlaybackCompletionDate() { - return playbackCompletionDate == null - ? null : (Date) playbackCompletionDate.clone(); - } - - public void setPlaybackCompletionDate(Date playbackCompletionDate) { - this.playbackCompletionDate = playbackCompletionDate == null - ? null : (Date) playbackCompletionDate.clone(); - } - - public boolean isInProgress() { - return (this.position > 0); - } - - @Override - public int describeContents() { - return 0; - } - - public boolean hasEmbeddedPicture() { - if(hasEmbeddedPicture == null) { - checkEmbeddedPicture(); - } - return hasEmbeddedPicture; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeLong(id); - dest.writeLong(item != null ? item.getId() : 0L); - - dest.writeInt(duration); - dest.writeInt(position); - dest.writeLong(size); - dest.writeString(mime_type); - dest.writeString(file_url); - dest.writeString(download_url); - dest.writeByte((byte) ((downloaded) ? 1 : 0)); - dest.writeLong((playbackCompletionDate != null) ? playbackCompletionDate.getTime() : 0); - dest.writeInt(played_duration); - dest.writeLong(lastPlayedTime); - } - - @Override - public void writeToPreferences(Editor prefEditor) { - if(item != null && item.getFeed() != null) { - prefEditor.putLong(PREF_FEED_ID, item.getFeed().getId()); - } else { - prefEditor.putLong(PREF_FEED_ID, 0L); - } - prefEditor.putLong(PREF_MEDIA_ID, id); - } - - @Override - public void loadMetadata() throws PlayableException { - if (item == null && itemID != 0) { - item = DBReader.getFeedItem(itemID); - } - } - - @Override - public void loadChapterMarks() { - if (item == null && itemID != 0) { - item = DBReader.getFeedItem(itemID); - } - // check if chapters are stored in db and not loaded yet. - if (item != null && item.hasChapters() && item.getChapters() == null) { - DBReader.loadChaptersOfFeedItem(item); - } else if (item != null && item.getChapters() == null) { - if(localFileAvailable()) { - ChapterUtils.loadChaptersFromFileUrl(this); - } else { - ChapterUtils.loadChaptersFromStreamUrl(this); - } - if (getChapters() != null && item != null) { - DBWriter.setFeedItem(item); - } - } - } - - @Override - public String getEpisodeTitle() { - if (item == null) { - return null; - } - if (item.getTitle() != null) { - return item.getTitle(); - } else { - return item.getIdentifyingValue(); - } - } - - @Override - public List getChapters() { - if (item == null) { - return null; - } - return item.getChapters(); - } - - @Override - public String getWebsiteLink() { - if (item == null) { - return null; - } - return item.getLink(); - } - - @Override - public String getFeedTitle() { - if (item == null || item.getFeed() == null) { - return null; - } - return item.getFeed().getTitle(); - } - - @Override - public Object getIdentifier() { - return id; - } - - @Override - public String getLocalMediaUrl() { - return file_url; - } - - @Override - public String getStreamUrl() { - return download_url; - } - - @Override - public String getPaymentLink() { - if (item == null) { - return null; - } - return item.getPaymentLink(); - } - - @Override - public boolean localFileAvailable() { - return isDownloaded() && file_url != null; - } - - @Override - public boolean streamAvailable() { - return download_url != null; - } - - @Override - public void saveCurrentPosition(SharedPreferences pref, int newPosition, long timeStamp) { - if(item != null && item.isNew()) { - DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.getId()); - } - setPosition(newPosition); - setLastPlayedTime(timeStamp); - DBWriter.setFeedMediaPlaybackInformation(this); - } - - @Override - public void onPlaybackStart() { - } - @Override - public void onPlaybackCompleted() { - - } - - @Override - public int getPlayableType() { - return PLAYABLE_TYPE_FEEDMEDIA; - } - - @Override - public void setChapters(List chapters) { - if(item != null) { - item.setChapters(chapters); - } - } - - @Override - public Callable loadShownotes() { - return () -> { - if (item == null) { - item = DBReader.getFeedItem( - itemID); - } - if (item.getContentEncoded() == null || item.getDescription() == null) { - DBReader.loadExtraInformationOfFeedItem( - item); - - } - return (item.getContentEncoded() != null) ? item.getContentEncoded() : item.getDescription(); - }; - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - public FeedMedia createFromParcel(Parcel in) { - final long id = in.readLong(); - final long itemID = in.readLong(); - FeedMedia result = new FeedMedia(id, null, in.readInt(), in.readInt(), in.readLong(), in.readString(), in.readString(), - in.readString(), in.readByte() != 0, new Date(in.readLong()), in.readInt(), in.readLong()); - result.itemID = itemID; - return result; - } - - public FeedMedia[] newArray(int size) { - return new FeedMedia[size]; - } - }; - - @Override - public String getImageLocation() { - if (hasEmbeddedPicture()) { - return getLocalMediaUrl(); - } else if(item != null) { - return item.getImageLocation(); - } else { - return null; - } - } - - public void setHasEmbeddedPicture(Boolean hasEmbeddedPicture) { - this.hasEmbeddedPicture = hasEmbeddedPicture; - } - - @Override - public void setDownloaded(boolean downloaded) { - super.setDownloaded(downloaded); - if(item != null && downloaded) { - item.setPlayed(false); - } - } - - @Override - public void setFile_url(String file_url) { - super.setFile_url(file_url); - } - - public void checkEmbeddedPicture() { - if (!localFileAvailable()) { - hasEmbeddedPicture = Boolean.FALSE; - return; - } - MediaMetadataRetriever mmr = new MediaMetadataRetriever(); - try { - mmr.setDataSource(getLocalMediaUrl()); - byte[] image = mmr.getEmbeddedPicture(); - if(image != null) { - hasEmbeddedPicture = Boolean.TRUE; - } else { - hasEmbeddedPicture = Boolean.FALSE; - } - } catch (Exception e) { - e.printStackTrace(); - hasEmbeddedPicture = Boolean.FALSE; - } - } - -// @Override -// public boolean equals(Object o) { -// if (o instanceof RemoteMedia) { -// return o.equals(this); -// } -// return super.equals(o); -// } -} diff --git a/core/src/free/java/de/danoeh/antennapod/core/feed/FeedMediaFlavorHelper.java b/core/src/free/java/de/danoeh/antennapod/core/feed/FeedMediaFlavorHelper.java new file mode 100644 index 000000000..7595d9d0b --- /dev/null +++ b/core/src/free/java/de/danoeh/antennapod/core/feed/FeedMediaFlavorHelper.java @@ -0,0 +1,10 @@ +package de.danoeh.antennapod.core.feed; + +/** + * Implements methods for FeedMedia that are flavor dependent. + */ +public class FeedMediaFlavorHelper { + static boolean instanceOfRemoteMedia(Object o) { + return false; + } +} diff --git a/core/src/free/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java b/core/src/free/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java new file mode 100644 index 000000000..6bc3ed7c5 --- /dev/null +++ b/core/src/free/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java @@ -0,0 +1,44 @@ +package de.danoeh.antennapod.core.service.playback; + +import android.content.Context; +import android.support.annotation.StringRes; + +/** + * Class intended to work along PlaybackService and provide support for different flavors. + */ +public class PlaybackServiceFlavorHelper { + + private PlaybackService.FlavorHelperCallback callback; + + PlaybackServiceFlavorHelper(Context context, PlaybackService.FlavorHelperCallback callback) { + this.callback = callback; + } + + void initializeMediaPlayer(Context context) { + callback.setMediaPlayer(new LocalPSMP(context, callback.getMediaPlayerCallback())); + } + + void removeCastConsumer() { + // no-op + } + + boolean castDisconnect(boolean castDisconnect) { + return false; + } + + boolean onMediaPlayerInfo(Context context, int code, @StringRes int resourceId) { + return false; + } + + void registerWifiBroadcastReceiver() { + // no-op + } + + void unregisterWifiBroadcastReceiver() { + // no-op + } + + boolean onSharedPreference(String key) { + return false; + } +} diff --git a/core/src/free/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java b/core/src/free/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java deleted file mode 100644 index abf787ce8..000000000 --- a/core/src/free/java/de/danoeh/antennapod/core/service/playback/RemotePSMP.java +++ /dev/null @@ -1,592 +0,0 @@ -//package de.danoeh.antennapod.core.service.playback; -// -//import android.content.Context; -//import android.media.MediaPlayer; -//import android.support.annotation.NonNull; -//import android.util.Log; -//import android.util.Pair; -//import android.view.SurfaceHolder; -// -//import com.google.android.gms.cast.Cast; -//import com.google.android.gms.cast.CastStatusCodes; -//import com.google.android.gms.cast.MediaInfo; -//import com.google.android.gms.cast.MediaStatus; -//import com.google.android.libraries.cast.companionlibrary.cast.exceptions.CastException; -//import com.google.android.libraries.cast.companionlibrary.cast.exceptions.NoConnectionException; -//import com.google.android.libraries.cast.companionlibrary.cast.exceptions.TransientNetworkDisconnectionException; -// -//import java.util.concurrent.atomic.AtomicBoolean; -// -//import de.danoeh.antennapod.core.R; -//import de.danoeh.antennapod.core.cast.CastConsumer; -//import de.danoeh.antennapod.core.cast.CastManager; -//import de.danoeh.antennapod.core.cast.CastUtils; -//import de.danoeh.antennapod.core.cast.DefaultCastConsumer; -//import de.danoeh.antennapod.core.cast.RemoteMedia; -//import de.danoeh.antennapod.core.feed.FeedMedia; -//import de.danoeh.antennapod.core.feed.MediaType; -//import de.danoeh.antennapod.core.util.RewindAfterPauseUtils; -//import de.danoeh.antennapod.core.util.playback.Playable; -// -///** -// * Implementation of PlaybackServiceMediaPlayer suitable for remote playback on Cast Devices. -// */ -//public class RemotePSMP extends PlaybackServiceMediaPlayer { -// -// public static final String TAG = "RemotePSMP"; -// -// public static final int CAST_ERROR = 3001; -// -// public static final int CAST_ERROR_PRIORITY_HIGH = 3005; -// -// private final CastManager castMgr; -// -// private volatile Playable media; -// private volatile MediaInfo remoteMedia; -// private volatile MediaType mediaType; -// -// private final AtomicBoolean isBuffering; -// -// private final AtomicBoolean startWhenPrepared; -// -// public RemotePSMP(@NonNull Context context, @NonNull PSMPCallback callback) { -// super(context, callback); -// -// castMgr = CastManager.getInstance(); -// media = null; -// mediaType = null; -// startWhenPrepared = new AtomicBoolean(false); -// isBuffering = new AtomicBoolean(false); -// -// try { -// if (castMgr.isConnected() && castMgr.isRemoteMediaLoaded()) { -// // updates the state, but does not start playing new media if it was going to -// onRemoteMediaPlayerStatusUpdated( -// ((p, playNextEpisode, wasSkipped, switchingPlayers) -> -// this.callback.endPlayback(p, false, wasSkipped, switchingPlayers))); -// } -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to do initial check for loaded media", e); -// } -// -// castMgr.addCastConsumer(castConsumer); -// //TODO -// } -// -// private CastConsumer castConsumer = new DefaultCastConsumer() { -// @Override -// public void onRemoteMediaPlayerMetadataUpdated() { -// RemotePSMP.this.onRemoteMediaPlayerStatusUpdated(callback::endPlayback); -// } -// -// @Override -// public void onRemoteMediaPlayerStatusUpdated() { -// RemotePSMP.this.onRemoteMediaPlayerStatusUpdated(callback::endPlayback); -// } -// -// @Override -// public void onMediaLoadResult(int statusCode) { -// if (playerStatus == PlayerStatus.PREPARING) { -// if (statusCode == CastStatusCodes.SUCCESS) { -// setPlayerStatus(PlayerStatus.PREPARED, media); -// if (media.getDuration() == 0) { -// Log.d(TAG, "Setting duration of media"); -// try { -// media.setDuration((int) castMgr.getMediaDuration()); -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to get remote media's duration"); -// } -// } -// } else if (statusCode != CastStatusCodes.REPLACED){ -// Log.d(TAG, "Remote media failed to load"); -// setPlayerStatus(PlayerStatus.INITIALIZED, media); -// } -// } else { -// Log.d(TAG, "onMediaLoadResult called, but Player Status wasn't in preparing state, so we ignore the result"); -// } -// } -// -// @Override -// public void onApplicationStatusChanged(String appStatus) { -// if (playerStatus != PlayerStatus.PLAYING) { -// Log.d(TAG, "onApplicationStatusChanged, but no media was playing"); -// return; -// } -// boolean playbackEnded = false; -// try { -// int standbyState = castMgr.getApplicationStandbyState(); -// Log.d(TAG, "standbyState: " + standbyState); -// playbackEnded = standbyState == Cast.STANDBY_STATE_YES; -// } catch (IllegalStateException e) { -// Log.d(TAG, "unable to get standbyState on onApplicationStatusChanged()"); -// } -// if (playbackEnded) { -// setPlayerStatus(PlayerStatus.INDETERMINATE, media); -// callback.endPlayback(media, true, false, false); -// } -// } -// -// @Override -// public void onFailed(int resourceId, int statusCode) { -// callback.onMediaPlayerInfo(CAST_ERROR, resourceId); -// } -// }; -// -// private void setBuffering(boolean buffering) { -// if (buffering && isBuffering.compareAndSet(false, true)) { -// callback.onMediaPlayerInfo(MediaPlayer.MEDIA_INFO_BUFFERING_START, 0); -// } else if (!buffering && isBuffering.compareAndSet(true, false)) { -// callback.onMediaPlayerInfo(MediaPlayer.MEDIA_INFO_BUFFERING_END, 0); -// } -// } -// -// private Playable localVersion(MediaInfo info){ -// if (info == null) { -// return null; -// } -// if (CastUtils.matches(info, media)) { -// return media; -// } -// return CastUtils.getPlayable(info, true); -// } -// -// private MediaInfo remoteVersion(Playable playable) { -// if (playable == null) { -// return null; -// } -// if (CastUtils.matches(remoteMedia, playable)) { -// return remoteMedia; -// } -// if (playable instanceof FeedMedia) { -// return CastUtils.convertFromFeedMedia((FeedMedia) playable); -// } -// if (playable instanceof RemoteMedia) { -// return ((RemoteMedia) playable).extractMediaInfo(); -// } -// return null; -// } -// -// private void onRemoteMediaPlayerStatusUpdated(@NonNull EndPlaybackCall endPlaybackCall) { -// MediaStatus status = castMgr.getMediaStatus(); -// if (status == null) { -// Log.d(TAG, "Received null MediaStatus"); -// //setBuffering(false); -// //setPlayerStatus(PlayerStatus.INDETERMINATE, null); -// return; -// } else { -// Log.d(TAG, "Received remote status/media update. New state=" + status.getPlayerState()); -// } -// Playable currentMedia = localVersion(status.getMediaInfo()); -// boolean updateUI = currentMedia != media; -// if (currentMedia != null) { -// long position = status.getStreamPosition(); -// if (position > 0 && currentMedia.getPosition() == 0) { -// currentMedia.setPosition((int) position); -// } -// } -// int state = status.getPlayerState(); -// setBuffering(state == MediaStatus.PLAYER_STATE_BUFFERING); -// switch (state) { -// case MediaStatus.PLAYER_STATE_PLAYING: -// setPlayerStatus(PlayerStatus.PLAYING, currentMedia); -// break; -// case MediaStatus.PLAYER_STATE_PAUSED: -// setPlayerStatus(PlayerStatus.PAUSED, currentMedia); -// break; -// case MediaStatus.PLAYER_STATE_BUFFERING: -// setPlayerStatus(playerStatus, currentMedia); -// break; -// case MediaStatus.PLAYER_STATE_IDLE: -// int reason = status.getIdleReason(); -// switch (reason) { -// case MediaStatus.IDLE_REASON_CANCELED: -// // check if we're already loading something else -// if (!updateUI || media == null) { -// setPlayerStatus(PlayerStatus.STOPPED, currentMedia); -// } else { -// updateUI = false; -// } -// break; -// case MediaStatus.IDLE_REASON_INTERRUPTED: -// // check if we're already loading something else -// if (!updateUI || media == null) { -// setPlayerStatus(PlayerStatus.PREPARING, currentMedia); -// } else { -// updateUI = false; -// } -// break; -// case MediaStatus.IDLE_REASON_NONE: -// setPlayerStatus(PlayerStatus.INITIALIZED, currentMedia); -// break; -// case MediaStatus.IDLE_REASON_FINISHED: -// boolean playing = playerStatus == PlayerStatus.PLAYING; -// setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia); -// endPlaybackCall.endPlayback(currentMedia,playing, false, false); -// // endPlayback already updates the UI, so no need to trigger it ourselves -// updateUI = false; -// break; -// case MediaStatus.IDLE_REASON_ERROR: -// Log.w(TAG, "Got an error status from the Chromecast. Skipping, if possible, to the next episode..."); -// setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia); -// callback.onMediaPlayerInfo(CAST_ERROR_PRIORITY_HIGH, -// R.string.cast_failed_media_error_skipping); -// endPlaybackCall.endPlayback(currentMedia, startWhenPrepared.get(), true, false); -// // endPlayback already updates the UI, so no need to trigger it ourselves -// updateUI = false; -// } -// break; -// case MediaStatus.PLAYER_STATE_UNKNOWN: -// //is this right? -// setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia); -// break; -// default: -// Log.e(TAG, "Remote media state undetermined!"); -// setPlayerStatus(PlayerStatus.INDETERMINATE, currentMedia); -// } -// if (updateUI) { -// callback.onMediaChanged(true); -// } -// } -// -// @Override -// public void playMediaObject(@NonNull final Playable playable, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) { -// Log.d(TAG, "playMediaObject() called"); -// playMediaObject(playable, false, stream, startWhenPrepared, prepareImmediately); -// } -// -// /** -// * Internal implementation of playMediaObject. This method has an additional parameter that allows the caller to force a media player reset even if -// * the given playable parameter is the same object as the currently playing media. -// * -// * @see #playMediaObject(de.danoeh.antennapod.core.util.playback.Playable, boolean, boolean, boolean) -// */ -// private void playMediaObject(@NonNull final Playable playable, final boolean forceReset, final boolean stream, final boolean startWhenPrepared, final boolean prepareImmediately) { -// if (!CastUtils.isCastable(playable)) { -// Log.d(TAG, "media provided is not compatible with cast device"); -// callback.onMediaPlayerInfo(CAST_ERROR_PRIORITY_HIGH, R.string.cast_not_castable); -// try { -// playable.loadMetadata(); -// } catch (Playable.PlayableException e) { -// Log.e(TAG, "Unable to load metadata of playable", e); -// } -// callback.endPlayback(playable, startWhenPrepared, true, false); -// return; -// } -// -// if (media != null) { -// if (!forceReset && media.getIdentifier().equals(playable.getIdentifier()) -// && playerStatus == PlayerStatus.PLAYING) { -// // episode is already playing -> ignore method call -// Log.d(TAG, "Method call to playMediaObject was ignored: media file already playing."); -// return; -// } else { -// // set temporarily to pause in order to update list with current position -// try { -// if (castMgr.isRemoteMediaPlaying()) { -// setPlayerStatus(PlayerStatus.PAUSED, media); -// } -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to determine whether media was playing, falling back to stored player status", e); -// // this might end up just being pointless if we need to query the remote device for the position -// if (playerStatus == PlayerStatus.PLAYING) { -// setPlayerStatus(PlayerStatus.PAUSED, media); -// } -// } -// smartMarkAsPlayed(media); -// -// -// setPlayerStatus(PlayerStatus.INDETERMINATE, null); -// } -// } -// -// this.media = playable; -// remoteMedia = remoteVersion(playable); -// //this.stream = stream; -// this.mediaType = media.getMediaType(); -// this.startWhenPrepared.set(startWhenPrepared); -// setPlayerStatus(PlayerStatus.INITIALIZING, media); -// try { -// media.loadMetadata(); -// callback.onMediaChanged(true); -// setPlayerStatus(PlayerStatus.INITIALIZED, media); -// if (prepareImmediately) { -// prepare(); -// } -// } catch (Playable.PlayableException e) { -// Log.e(TAG, "Error while loading media metadata", e); -// setPlayerStatus(PlayerStatus.STOPPED, null); -// } -// } -// -// @Override -// public void resume() { -// try { -// // TODO see comment on prepare() -// // setVolume(UserPreferences.getLeftVolume(), UserPreferences.getRightVolume()); -// if (playerStatus == PlayerStatus.PREPARED && media.getPosition() > 0) { -// int newPosition = RewindAfterPauseUtils.calculatePositionWithRewind( -// media.getPosition(), -// media.getLastPlayedTime()); -// castMgr.play(newPosition); -// } -// castMgr.play(); -// } catch (CastException | TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to resume remote playback", e); -// } -// } -// -// @Override -// public void pause(boolean abandonFocus, boolean reinit) { -// try { -// if (castMgr.isRemoteMediaPlaying()) { -// castMgr.pause(); -// } -// } catch (CastException | TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to pause", e); -// } -// } -// -// @Override -// public void prepare() { -// if (playerStatus == PlayerStatus.INITIALIZED) { -// Log.d(TAG, "Preparing media player"); -// setPlayerStatus(PlayerStatus.PREPARING, media); -// try { -// int position = media.getPosition(); -// if (position > 0) { -// position = RewindAfterPauseUtils.calculatePositionWithRewind( -// position, -// media.getLastPlayedTime()); -// } -// // TODO We're not supporting user set stream volume yet, as we need to make a UI -// // that doesn't allow changing playback speed or have different values for left/right -// //setVolume(UserPreferences.getLeftVolume(), UserPreferences.getRightVolume()); -// castMgr.loadMedia(remoteMedia, startWhenPrepared.get(), position); -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Error loading media", e); -// setPlayerStatus(PlayerStatus.INITIALIZED, media); -// } -// } -// } -// -// @Override -// public void reinit() { -// Log.d(TAG, "reinit() called"); -// if (media != null) { -// playMediaObject(media, true, false, startWhenPrepared.get(), false); -// } else { -// Log.d(TAG, "Call to reinit was ignored: media was null"); -// } -// } -// -// @Override -// public void seekTo(int t) { -// //TODO check other seek implementations and see if there's no issue with sending too many seek commands to the remote media player -// try { -// if (castMgr.isRemoteMediaLoaded()) { -// setPlayerStatus(PlayerStatus.SEEKING, media); -// castMgr.seek(t); -// } else if (media != null && playerStatus == PlayerStatus.INITIALIZED){ -// media.setPosition(t); -// startWhenPrepared.set(false); -// prepare(); -// } -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to seek", e); -// } -// } -// -// @Override -// public void seekDelta(int d) { -// int position = getPosition(); -// if (position != INVALID_TIME) { -// seekTo(position + d); -// } else { -// Log.e(TAG, "getPosition() returned INVALID_TIME in seekDelta"); -// } -// } -// -// @Override -// public int getDuration() { -// int retVal = INVALID_TIME; -// boolean prepared; -// try { -// prepared = castMgr.isRemoteMediaLoaded(); -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to check if remote media is loaded", e); -// prepared = playerStatus.isAtLeast(PlayerStatus.PREPARED); -// } -// if (prepared) { -// try { -// retVal = (int) castMgr.getMediaDuration(); -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to determine remote media's duration", e); -// } -// } -// if(retVal == INVALID_TIME && media != null && media.getDuration() > 0) { -// retVal = media.getDuration(); -// } -// Log.d(TAG, "getDuration() -> " + retVal); -// return retVal; -// } -// -// @Override -// public int getPosition() { -// int retVal = INVALID_TIME; -// boolean prepared; -// try { -// prepared = castMgr.isRemoteMediaLoaded(); -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to check if remote media is loaded", e); -// prepared = playerStatus.isAtLeast(PlayerStatus.PREPARED); -// } -// if (prepared) { -// try { -// retVal = (int) castMgr.getCurrentMediaPosition(); -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Unable to determine remote media's position", e); -// } -// } -// if(retVal <= 0 && media != null && media.getPosition() >= 0) { -// retVal = media.getPosition(); -// } -// Log.d(TAG, "getPosition() -> " + retVal); -// return retVal; -// } -// -// @Override -// public boolean isStartWhenPrepared() { -// return startWhenPrepared.get(); -// } -// -// @Override -// public void setStartWhenPrepared(boolean startWhenPrepared) { -// this.startWhenPrepared.set(startWhenPrepared); -// } -// -// //TODO I believe some parts of the code make the same decision skipping this check, so that -// //should be changed as well -// @Override -// public boolean canSetSpeed() { -// return false; -// } -// -// @Override -// public void setSpeed(float speed) { -// throw new UnsupportedOperationException("Setting playback speed unsupported for Remote Playback"); -// } -// -// @Override -// public float getPlaybackSpeed() { -// return 1; -// } -// -// @Override -// public void setVolume(float volumeLeft, float volumeRight) { -// Log.d(TAG, "Setting the Stream volume on Remote Media Player"); -// double volume = (volumeLeft+volumeRight)/2; -// if (volume > 1.0) { -// volume = 1.0; -// } -// if (volume < 0.0) { -// volume = 0.0; -// } -// try { -// castMgr.setStreamVolume(volume); -// } catch (TransientNetworkDisconnectionException | NoConnectionException | CastException e) { -// Log.e(TAG, "Unable to set the volume", e); -// } -// } -// -// @Override -// public boolean canDownmix() { -// return false; -// } -// -// @Override -// public void setDownmix(boolean enable) { -// throw new UnsupportedOperationException("Setting downmix unsupported in Remote Media Player"); -// } -// -// @Override -// public MediaType getCurrentMediaType() { -// return mediaType; -// } -// -// @Override -// public boolean isStreaming() { -// return true; -// } -// -// @Override -// public void shutdown() { -// castMgr.removeCastConsumer(castConsumer); -// } -// -// @Override -// public void shutdownQuietly() { -// shutdown(); -// } -// -// @Override -// public void setVideoSurface(SurfaceHolder surface) { -// throw new UnsupportedOperationException("Setting Video Surface unsupported in Remote Media Player"); -// } -// -// @Override -// public void resetVideoSurface() { -// Log.e(TAG, "Resetting Video Surface unsupported in Remote Media Player"); -// } -// -// @Override -// public Pair getVideoSize() { -// return null; -// } -// -// @Override -// public Playable getPlayable() { -// return media; -// } -// -// @Override -// protected void setPlayable(Playable playable) { -// if (playable != media) { -// media = playable; -// remoteMedia = remoteVersion(playable); -// } -// } -// -// @Override -// public void endPlayback(boolean wasSkipped, boolean switchingPlayers) { -// Log.d(TAG, "endPlayback() called"); -// boolean isPlaying = playerStatus == PlayerStatus.PLAYING; -// try { -// isPlaying = castMgr.isRemoteMediaPlaying(); -// } catch (TransientNetworkDisconnectionException | NoConnectionException e) { -// Log.e(TAG, "Could not determine if media is playing", e); -// } -// // TODO make sure we stop playback whenever there's no next episode. -// if (playerStatus != PlayerStatus.INDETERMINATE) { -// setPlayerStatus(PlayerStatus.INDETERMINATE, media); -// } -// callback.endPlayback(media, isPlaying, wasSkipped, switchingPlayers); -// } -// -// @Override -// public void stop() { -// if (playerStatus == PlayerStatus.INDETERMINATE) { -// setPlayerStatus(PlayerStatus.STOPPED, null); -// } else { -// Log.d(TAG, "Ignored call to stop: Current player state is: " + playerStatus); -// } -// } -// -// @Override -// protected boolean shouldLockWifi() { -// return false; -// } -// -// private interface EndPlaybackCall { -// boolean endPlayback(Playable media, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers); -// } -//} diff --git a/core/src/free/res/values/strings.xml b/core/src/free/res/values/strings.xml new file mode 100644 index 000000000..579b12e12 --- /dev/null +++ b/core/src/free/res/values/strings.xml @@ -0,0 +1,4 @@ + + + @string/pref_cast_message_free_flavor + diff --git a/core/src/play/java/de/danoeh/antennapod/core/feed/FeedMedia.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java similarity index 99% rename from core/src/play/java/de/danoeh/antennapod/core/feed/FeedMedia.java rename to core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java index 068669af9..b8b154ff0 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/feed/FeedMedia.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java @@ -12,7 +12,6 @@ import java.util.Date; import java.util.List; import java.util.concurrent.Callable; -import de.danoeh.antennapod.core.cast.RemoteMedia; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBReader; @@ -560,7 +559,7 @@ public class FeedMedia extends FeedFile implements Playable { @Override public boolean equals(Object o) { - if (o instanceof RemoteMedia) { + if (FeedMediaFlavorHelper.instanceOfRemoteMedia(o)) { return o.equals(this); } return super.equals(o); diff --git a/core/src/free/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java similarity index 87% rename from core/src/free/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java rename to core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java index 01b803d80..76c960607 100644 --- a/core/src/free/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java @@ -15,14 +15,11 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.AudioManager; import android.media.MediaPlayer; -import android.net.NetworkInfo; -import android.net.wifi.WifiManager; import android.os.Binder; import android.os.Build; import android.os.IBinder; import android.os.Vibrator; import android.preference.PreferenceManager; -import android.support.annotation.NonNull; import android.support.annotation.StringRes; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; @@ -61,6 +58,7 @@ import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.IntList; import de.danoeh.antennapod.core.util.QueueAccess; import de.danoeh.antennapod.core.util.flattr.FlattrUtils; +import de.danoeh.antennapod.core.util.playback.ExternalMedia; import de.danoeh.antennapod.core.util.playback.Playable; /** @@ -174,12 +172,6 @@ public class PlaybackService extends Service { */ public static final int INVALID_TIME = -1; - /** - * Time in seconds during which the CastManager will try to reconnect to the Cast Device after - * the Wifi Connection is regained. - */ - private static final int RECONNECTION_ATTEMPT_PERIOD_S = 15; - /** * Is true if service is running. */ @@ -196,21 +188,13 @@ public class PlaybackService extends Service { * Is true if a Cast Device is connected to the service. */ private static volatile boolean isCasting = false; - /** - * Stores the state of the cast playback just before it disconnects. - */ - private volatile PlaybackServiceMediaPlayer.PSMPInfo infoBeforeCastDisconnection; - - private boolean wifiConnectivity = true; - private BroadcastReceiver wifiBroadcastReceiver; private static final int NOTIFICATION_ID = 1; private PlaybackServiceMediaPlayer mediaPlayer; private PlaybackServiceTaskManager taskManager; + private PlaybackServiceFlavorHelper flavorHelper; -// private CastManager castManager; -// private MediaRouter mediaRouter; /** * Only used for Lollipop notifications. */ @@ -284,7 +268,7 @@ public class PlaybackService extends Service { ACTION_RESUME_PLAY_CURRENT_EPISODE)); taskManager = new PlaybackServiceTaskManager(this, taskManagerCallback); -// mediaRouter = MediaRouter.getInstance(getApplicationContext()); + flavorHelper = new PlaybackServiceFlavorHelper(PlaybackService.this, flavorHelperCallback); PreferenceManager.getDefaultSharedPreferences(this) .registerOnSharedPreferenceChangeListener(prefListener); @@ -308,18 +292,7 @@ public class PlaybackService extends Service { npe.printStackTrace(); } -// castManager = CastManager.getInstance(); -// castManager.addCastConsumer(castConsumer); -// isCasting = castManager.isConnected(); -// if (isCasting) { -// if (UserPreferences.isCastEnabled()) { -// onCastAppConnected(false); -// } else { -// castManager.disconnect(); -// } -// } else { - mediaPlayer = new LocalPSMP(this, mediaPlayerCallback); -// } + flavorHelper.initializeMediaPlayer(PlaybackService.this); mediaSession.setActive(true); } @@ -346,8 +319,8 @@ public class PlaybackService extends Service { unregisterReceiver(skipCurrentEpisodeReceiver); unregisterReceiver(pausePlayCurrentEpisodeReceiver); unregisterReceiver(pauseResumeCurrentEpisodeReceiver); -// castManager.removeCastConsumer(castConsumer); - unregisterWifiBroadcastReceiver(); + flavorHelper.removeCastConsumer(); + flavorHelper.unregisterWifiBroadcastReceiver(); mediaPlayer.shutdown(); taskManager.shutdown(); } @@ -381,9 +354,7 @@ public class PlaybackService extends Service { Log.d(TAG, "Received media button event"); handleKeycode(keycode, intent.getIntExtra(MediaButtonReceiver.EXTRA_SOURCE, InputDevice.SOURCE_CLASS_NONE)); -// } else if (castDisconnect) { -// castManager.disconnect(); - } else { + } else if (!flavorHelper.castDisconnect(castDisconnect)) { started = true; boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM, true); @@ -391,9 +362,7 @@ public class PlaybackService extends Service { boolean prepareImmediately = intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false); sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); //If the user asks to play External Media, the casting session, if on, should end. -// if (playable instanceof ExternalMedia) { -// castManager.disconnect(); -// } + flavorHelper.castDisconnect(playable instanceof ExternalMedia); mediaPlayer.playMediaObject(playable, stream, startWhenPrepared, prepareImmediately); } } @@ -649,14 +618,8 @@ public class PlaybackService extends Service { case MediaPlayer.MEDIA_INFO_BUFFERING_END: sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_END, 0); return true; -// case RemotePSMP.CAST_ERROR: -// sendNotificationBroadcast(NOTIFICATION_TYPE_SHOW_TOAST, resourceId); -// return true; -// case RemotePSMP.CAST_ERROR_PRIORITY_HIGH: -// Toast.makeText(PlaybackService.this, resourceId, Toast.LENGTH_SHORT).show(); -// return true; default: - return false; + return flavorHelper.onMediaPlayerInfo(PlaybackService.this, code, resourceId); } } @@ -1527,67 +1490,6 @@ public class PlaybackService extends Service { } } -// private CastConsumer castConsumer = new DefaultCastConsumer() { -// @Override -// public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) { -// PlaybackService.this.onCastAppConnected(wasLaunched); -// } -// -// @Override -// public void onDisconnectionReason(int reason) { -// Log.d(TAG, "onDisconnectionReason() with code " + reason); -// // This is our final chance to update the underlying stream position -// // In onDisconnected(), the underlying CastPlayback#mVideoCastConsumer -// // is disconnected and hence we update our local value of stream position -// // to the latest position. -// if (mediaPlayer != null) { -// saveCurrentPosition(false, 0); -// infoBeforeCastDisconnection = mediaPlayer.getPSMPInfo(); -// if (reason != BaseCastManager.DISCONNECT_REASON_EXPLICIT && -// infoBeforeCastDisconnection.playerStatus == PlayerStatus.PLAYING) { -// // If it's NOT based on user action, we shouldn't automatically resume local playback -// infoBeforeCastDisconnection.playerStatus = PlayerStatus.PAUSED; -// } -// } -// } -// -// @Override -// public void onDisconnected() { -// Log.d(TAG, "onDisconnected()"); -// isCasting = false; -// PlaybackServiceMediaPlayer.PSMPInfo info = infoBeforeCastDisconnection; -// infoBeforeCastDisconnection = null; -// if (info == null && mediaPlayer != null) { -// info = mediaPlayer.getPSMPInfo(); -// } -// if (info == null) { -// info = new PlaybackServiceMediaPlayer.PSMPInfo(PlayerStatus.STOPPED, null); -// } -// switchMediaPlayer(new LocalPSMP(PlaybackService.this, mediaPlayerCallback), -// info, true); -// if (info.playable != null) { -// sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, -// info.playable.getMediaType() == MediaType.AUDIO ? EXTRA_CODE_AUDIO : EXTRA_CODE_VIDEO); -// } else { -// Log.d(TAG, "Cast session disconnected, but no current media"); -// sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0); -// } -// // hardware volume buttons control the local device volume -// mediaRouter.setMediaSessionCompat(null); -// unregisterWifiBroadcastReceiver(); -// PlayerStatus status = info.playerStatus; -// if ((status == PlayerStatus.PLAYING || -// status == PlayerStatus.SEEKING || -// status == PlayerStatus.PREPARING || -// UserPreferences.isPersistNotify()) && -// android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { -// setupNotification(info); -// } else if (!UserPreferences.isPersistNotify()){ -// stopForeground(true); -// } -// } -// }; - private final MediaSessionCompat.Callback sessionCallback = new MediaSessionCompat.Callback() { private static final String TAG = "MediaSessionCompat"; @@ -1673,101 +1575,90 @@ public class PlaybackService extends Service { } }; -// private void onCastAppConnected(boolean wasLaunched) { -// Log.d(TAG, "A cast device application was " + (wasLaunched ? "launched" : "joined")); -// isCasting = true; -// PlaybackServiceMediaPlayer.PSMPInfo info = null; -// if (mediaPlayer != null) { -// info = mediaPlayer.getPSMPInfo(); -// if (info.playerStatus == PlayerStatus.PLAYING) { -// // could be pause, but this way we make sure the new player will get the correct position, -// // since pause runs asynchronously and we could be directing the new player to play even before -// // the old player gives us back the position. -// saveCurrentPosition(false, 0); -// } -// } -// if (info == null) { -// info = new PlaybackServiceMediaPlayer.PSMPInfo(PlayerStatus.STOPPED, null); -// } -// sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, EXTRA_CODE_CAST); -// switchMediaPlayer(new RemotePSMP(PlaybackService.this, mediaPlayerCallback), -// info, -// wasLaunched); -// // hardware volume buttons control the remote device volume -// mediaRouter.setMediaSessionCompat(mediaSession); -// registerWifiBroadcastReceiver(); -// setupNotification(info); -// } - - private void switchMediaPlayer(@NonNull PlaybackServiceMediaPlayer newPlayer, - @NonNull PlaybackServiceMediaPlayer.PSMPInfo info, - boolean wasLaunched) { - if (mediaPlayer != null) { - mediaPlayer.endPlayback(true, true); - mediaPlayer.shutdownQuietly(); - } - mediaPlayer = newPlayer; - Log.d(TAG, "switched to " + mediaPlayer.getClass().getSimpleName()); - if (!wasLaunched) { - PlaybackServiceMediaPlayer.PSMPInfo candidate = mediaPlayer.getPSMPInfo(); - if (candidate.playable != null && - candidate.playerStatus.isAtLeast(PlayerStatus.PREPARING)) { - // do not automatically send new media to cast device - info.playable = null; - } - } - if (info.playable != null) { - mediaPlayer.playMediaObject(info.playable, - !info.playable.localFileAvailable(), - info.playerStatus == PlayerStatus.PLAYING, - info.playerStatus.isAtLeast(PlayerStatus.PREPARING)); - } - } - - private void registerWifiBroadcastReceiver() { - if (wifiBroadcastReceiver != null) { - return; - } - wifiBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { - NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); - boolean isConnected = info.isConnected(); - //apparently this method gets called twice when a change happens, but one run is enough. - if (isConnected && !wifiConnectivity) { - wifiConnectivity = true; -// castManager.startCastDiscovery(); -// castManager.reconnectSessionIfPossible(RECONNECTION_ATTEMPT_PERIOD_S, NetworkUtils.getWifiSsid()); - } else { - wifiConnectivity = isConnected; - } - } - } - }; - registerReceiver(wifiBroadcastReceiver, - new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION)); - } - - private void unregisterWifiBroadcastReceiver() { - if (wifiBroadcastReceiver != null) { - unregisterReceiver(wifiBroadcastReceiver); - wifiBroadcastReceiver = null; - } - } - private SharedPreferences.OnSharedPreferenceChangeListener prefListener = (sharedPreferences, key) -> { -// if (UserPreferences.PREF_CAST_ENABLED.equals(key)) { -// if (!UserPreferences.isCastEnabled()) { -// if (castManager.isConnecting() || castManager.isConnected()) { -// Log.d(TAG, "Disconnecting cast device due to a change in user preferences"); -// castManager.disconnect(); -// } -// } -// } else if (UserPreferences.PREF_LOCKSCREEN_BACKGROUND.equals(key)) { updateMediaSessionMetadata(getPlayable()); + } else { + flavorHelper.onSharedPreference(key); } }; + + interface FlavorHelperCallback { + PlaybackServiceMediaPlayer.PSMPCallback getMediaPlayerCallback(); + void setMediaPlayer(PlaybackServiceMediaPlayer mediaPlayer); + PlaybackServiceMediaPlayer getMediaPlayer(); + void setIsCasting(boolean isCasting); + void sendNotificationBroadcast(int type, int code); + void saveCurrentPosition(boolean updatePlayedDuration, int deltaPlayedDuration); + void setupNotification(boolean connected, PlaybackServiceMediaPlayer.PSMPInfo info); + MediaSessionCompat getMediaSession(); + Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter); + void unregisterReceiver(BroadcastReceiver receiver); + } + + private FlavorHelperCallback flavorHelperCallback = new FlavorHelperCallback() { + @Override + public PlaybackServiceMediaPlayer.PSMPCallback getMediaPlayerCallback() { + return PlaybackService.this.mediaPlayerCallback; + } + + @Override + public void setMediaPlayer(PlaybackServiceMediaPlayer mediaPlayer) { + PlaybackService.this.mediaPlayer = mediaPlayer; + } + + @Override + public PlaybackServiceMediaPlayer getMediaPlayer() { + return PlaybackService.this.mediaPlayer; + } + + @Override + public void setIsCasting(boolean isCasting) { + PlaybackService.isCasting = isCasting; + } + + @Override + public void sendNotificationBroadcast(int type, int code) { + PlaybackService.this.sendNotificationBroadcast(type, code); + } + + @Override + public void saveCurrentPosition(boolean updatePlayedDuration, int deltaPlayedDuration) { + PlaybackService.this.saveCurrentPosition(updatePlayedDuration, deltaPlayedDuration); + } + + @Override + public void setupNotification(boolean connected, PlaybackServiceMediaPlayer.PSMPInfo info) { + if (connected) { + PlaybackService.this.setupNotification(info); + } else { + PlayerStatus status = info.playerStatus; + if ((status == PlayerStatus.PLAYING || + status == PlayerStatus.SEEKING || + status == PlayerStatus.PREPARING || + UserPreferences.isPersistNotify()) && + android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + PlaybackService.this.setupNotification(info); + } else if (!UserPreferences.isPersistNotify()){ + PlaybackService.this.stopForeground(true); + } + } + } + + @Override + public MediaSessionCompat getMediaSession() { + return PlaybackService.this.mediaSession; + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + return PlaybackService.this.registerReceiver(receiver, filter); + } + + @Override + public void unregisterReceiver(BroadcastReceiver receiver) { + PlaybackService.this.unregisterReceiver(receiver); + } + }; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/Flavors.java b/core/src/main/java/de/danoeh/antennapod/core/util/Flavors.java new file mode 100644 index 000000000..5feb232e7 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/util/Flavors.java @@ -0,0 +1,24 @@ +package de.danoeh.antennapod.core.util; + +import de.danoeh.antennapod.core.BuildConfig; + +/** + * Helper class to handle the different build flavors. + */ +public enum Flavors { + FREE, + PLAY, + UNKNOWN; + + public static final Flavors FLAVOR; + + static { + if (BuildConfig.FLAVOR.equals("free")) { + FLAVOR = FREE; + } else if (BuildConfig.FLAVOR.equals("play")) { + FLAVOR = PLAY; + } else { + FLAVOR = UNKNOWN; + } + } +} diff --git a/core/src/free/java/de/danoeh/antennapod/core/util/playback/Playable.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java similarity index 94% rename from core/src/free/java/de/danoeh/antennapod/core/util/playback/Playable.java rename to core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java index bc22e063c..6459d86ed 100644 --- a/core/src/free/java/de/danoeh/antennapod/core/util/playback/Playable.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/Playable.java @@ -183,9 +183,6 @@ public interface Playable extends Parcelable, case ExternalMedia.PLAYABLE_TYPE_EXTERNAL_MEDIA: result = createExternalMediaInstance(pref); break; -// case RemoteMedia.PLAYABLE_TYPE_REMOTE_MEDIA: -// result = createRemoteMediaInstance(pref); -// break; } if (result == null) { Log.e(TAG, "Could not restore Playable object from preferences"); @@ -214,12 +211,6 @@ public interface Playable extends Parcelable, } return result; } - - private static Playable createRemoteMediaInstance(SharedPreferences pref) { - //TODO there's probably no point in restoring RemoteMedia from preferences, because we - //only care about it while it's playing on the cast device. - return null; - } } class PlayableException extends Exception { diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index f4ed79937..d775c6fe5 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -403,7 +403,8 @@ Known issues No web browser found. Chromecast support - Enable support for remote media playback on Cast devices (such as Chromecast, Audio Speakers or Android TV) + Enable support for remote media playback on Cast devices (such as Chromecast, Audio Speakers or Android TV) + Chromecast requires third party proprietary libraries that are disabled in this version of AntennaPod Enable automatic flattring diff --git a/core/src/play/java/de/danoeh/antennapod/core/feed/FeedMediaFlavorHelper.java b/core/src/play/java/de/danoeh/antennapod/core/feed/FeedMediaFlavorHelper.java new file mode 100644 index 000000000..b89904af9 --- /dev/null +++ b/core/src/play/java/de/danoeh/antennapod/core/feed/FeedMediaFlavorHelper.java @@ -0,0 +1,12 @@ +package de.danoeh.antennapod.core.feed; + +import de.danoeh.antennapod.core.cast.RemoteMedia; + +/** + * Implements methods for FeedMedia that are flavor dependent. + */ +public class FeedMediaFlavorHelper { + static boolean instanceOfRemoteMedia(Object o) { + return o instanceof RemoteMedia; + } +} diff --git a/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java deleted file mode 100644 index e2d63a385..000000000 --- a/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ /dev/null @@ -1,1780 +0,0 @@ -package de.danoeh.antennapod.core.service.playback; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.bluetooth.BluetoothA2dp; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.net.NetworkInfo; -import android.net.wifi.WifiManager; -import android.os.Binder; -import android.os.Build; -import android.os.IBinder; -import android.os.Vibrator; -import android.preference.PreferenceManager; -import android.support.annotation.NonNull; -import android.support.annotation.StringRes; -import android.support.v4.media.MediaMetadataCompat; -import android.support.v4.media.session.MediaSessionCompat; -import android.support.v4.media.session.PlaybackStateCompat; -import android.support.v7.app.NotificationCompat; -import android.support.v7.media.MediaRouter; -import android.text.TextUtils; -import android.util.Log; -import android.util.Pair; -import android.view.Display; -import android.view.InputDevice; -import android.view.KeyEvent; -import android.view.SurfaceHolder; -import android.view.WindowManager; -import android.widget.Toast; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.target.Target; -import com.google.android.gms.cast.ApplicationMetadata; -import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager; - -import java.util.List; - -import de.danoeh.antennapod.core.ClientConfig; -import de.danoeh.antennapod.core.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.feed.Chapter; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.feed.MediaType; -import de.danoeh.antennapod.core.glide.ApGlideSettings; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction; -import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction.Action; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; -import de.danoeh.antennapod.core.preferences.PlaybackPreferences; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; -import de.danoeh.antennapod.core.storage.DBTasks; -import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.util.IntList; -import de.danoeh.antennapod.core.util.NetworkUtils; -import de.danoeh.antennapod.core.util.QueueAccess; -import de.danoeh.antennapod.core.util.flattr.FlattrUtils; -import de.danoeh.antennapod.core.util.playback.ExternalMedia; -import de.danoeh.antennapod.core.util.playback.Playable; - -/** - * Controls the MediaPlayer that plays a FeedMedia-file - */ -public class PlaybackService extends Service { - public static final String FORCE_WIDGET_UPDATE = "de.danoeh.antennapod.FORCE_WIDGET_UPDATE"; - public static final String STOP_WIDGET_UPDATE = "de.danoeh.antennapod.STOP_WIDGET_UPDATE"; - /** - * Logging tag - */ - private static final String TAG = "PlaybackService"; - - /** - * Parcelable of type Playable. - */ - public static final String EXTRA_PLAYABLE = "PlaybackService.PlayableExtra"; - /** - * True if cast session should disconnect. - */ - public static final String EXTRA_CAST_DISCONNECT = "extra.de.danoeh.antennapod.core.service.castDisconnect"; - /** - * True if media should be streamed. - */ - public static final String EXTRA_SHOULD_STREAM = "extra.de.danoeh.antennapod.core.service.shouldStream"; - /** - * True if playback should be started immediately after media has been - * prepared. - */ - public static final String EXTRA_START_WHEN_PREPARED = "extra.de.danoeh.antennapod.core.service.startWhenPrepared"; - - public static final String EXTRA_PREPARE_IMMEDIATELY = "extra.de.danoeh.antennapod.core.service.prepareImmediately"; - - public static final String ACTION_PLAYER_STATUS_CHANGED = "action.de.danoeh.antennapod.core.service.playerStatusChanged"; - public static final String EXTRA_NEW_PLAYER_STATUS = "extra.de.danoeh.antennapod.service.playerStatusChanged.newStatus"; - private static final String AVRCP_ACTION_PLAYER_STATUS_CHANGED = "com.android.music.playstatechanged"; - private static final String AVRCP_ACTION_META_CHANGED = "com.android.music.metachanged"; - - public static final String ACTION_PLAYER_NOTIFICATION = "action.de.danoeh.antennapod.core.service.playerNotification"; - public static final String EXTRA_NOTIFICATION_CODE = "extra.de.danoeh.antennapod.core.service.notificationCode"; - public static final String EXTRA_NOTIFICATION_TYPE = "extra.de.danoeh.antennapod.core.service.notificationType"; - - /** - * If the PlaybackService receives this action, it will stop playback and - * try to shutdown. - */ - public static final String ACTION_SHUTDOWN_PLAYBACK_SERVICE = "action.de.danoeh.antennapod.core.service.actionShutdownPlaybackService"; - - /** - * If the PlaybackService receives this action, it will end playback of the - * current episode and load the next episode if there is one available. - */ - public static final String ACTION_SKIP_CURRENT_EPISODE = "action.de.danoeh.antennapod.core.service.skipCurrentEpisode"; - - /** - * If the PlaybackService receives this action, it will pause playback. - */ - public static final String ACTION_PAUSE_PLAY_CURRENT_EPISODE = "action.de.danoeh.antennapod.core.service.pausePlayCurrentEpisode"; - - - /** - * If the PlaybackService receives this action, it will resume playback. - */ - public static final String ACTION_RESUME_PLAY_CURRENT_EPISODE = "action.de.danoeh.antennapod.core.service.resumePlayCurrentEpisode"; - - - /** - * Used in NOTIFICATION_TYPE_RELOAD. - */ - public static final int EXTRA_CODE_AUDIO = 1; - public static final int EXTRA_CODE_VIDEO = 2; - public static final int EXTRA_CODE_CAST = 3; - - public static final int NOTIFICATION_TYPE_ERROR = 0; - public static final int NOTIFICATION_TYPE_INFO = 1; - public static final int NOTIFICATION_TYPE_BUFFER_UPDATE = 2; - - /** - * Receivers of this intent should update their information about the curently playing media - */ - public static final int NOTIFICATION_TYPE_RELOAD = 3; - /** - * The state of the sleeptimer changed. - */ - public static final int NOTIFICATION_TYPE_SLEEPTIMER_UPDATE = 4; - public static final int NOTIFICATION_TYPE_BUFFER_START = 5; - public static final int NOTIFICATION_TYPE_BUFFER_END = 6; - /** - * No more episodes are going to be played. - */ - public static final int NOTIFICATION_TYPE_PLAYBACK_END = 7; - - /** - * Playback speed has changed - */ - public static final int NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE = 8; - - /** - * Ability to set the playback speed has changed - */ - public static final int NOTIFICATION_TYPE_SET_SPEED_ABILITY_CHANGED = 9; - - /** - * Send a message to the user (with provided String resource id) - */ - public static final int NOTIFICATION_TYPE_SHOW_TOAST = 10; - - /** - * Returned by getPositionSafe() or getDurationSafe() if the playbackService - * is in an invalid state. - */ - public static final int INVALID_TIME = -1; - - /** - * Time in seconds during which the CastManager will try to reconnect to the Cast Device after - * the Wifi Connection is regained. - */ - private static final int RECONNECTION_ATTEMPT_PERIOD_S = 15; - - /** - * Is true if service is running. - */ - public static boolean isRunning = false; - /** - * Is true if service has received a valid start command. - */ - public static boolean started = false; - /** - * Is true if the service was running, but paused due to headphone disconnect - */ - public static boolean transientPause = false; - /** - * Is true if a Cast Device is connected to the service. - */ - private static volatile boolean isCasting = false; - /** - * Stores the state of the cast playback just before it disconnects. - */ - private volatile PlaybackServiceMediaPlayer.PSMPInfo infoBeforeCastDisconnection; - - private boolean wifiConnectivity = true; - private BroadcastReceiver wifiBroadcastReceiver; - - private static final int NOTIFICATION_ID = 1; - - private PlaybackServiceMediaPlayer mediaPlayer; - private PlaybackServiceTaskManager taskManager; - - private CastManager castManager; - private MediaRouter mediaRouter; - /** - * Only used for Lollipop notifications. - */ - private MediaSessionCompat mediaSession; - - private int startPosition; - - private static volatile MediaType currentMediaType = MediaType.UNKNOWN; - - private final IBinder mBinder = new LocalBinder(); - - public class LocalBinder extends Binder { - public PlaybackService getService() { - return PlaybackService.this; - } - } - - @Override - public boolean onUnbind(Intent intent) { - Log.d(TAG, "Received onUnbind event"); - return super.onUnbind(intent); - } - - /** - * Returns an intent which starts an audio- or videoplayer, depending on the - * type of media that is being played. If the playbackservice is not - * running, the type of the last played media will be looked up. - */ - public static Intent getPlayerActivityIntent(Context context) { - if (isRunning) { - return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, currentMediaType, isCasting); - } else { - if (PlaybackPreferences.getCurrentEpisodeIsVideo()) { - return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, MediaType.VIDEO, isCasting); - } else { - return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, MediaType.AUDIO, isCasting); - } - } - } - - /** - * Same as getPlayerActivityIntent(context), but here the type of activity - * depends on the FeedMedia that is provided as an argument. - */ - public static Intent getPlayerActivityIntent(Context context, Playable media) { - MediaType mt = media.getMediaType(); - return ClientConfig.playbackServiceCallbacks.getPlayerActivityIntent(context, mt, isCasting); - } - - @Override - public void onCreate() { - super.onCreate(); - Log.d(TAG, "Service created."); - isRunning = true; - - registerReceiver(headsetDisconnected, new IntentFilter( - Intent.ACTION_HEADSET_PLUG)); - registerReceiver(shutdownReceiver, new IntentFilter( - ACTION_SHUTDOWN_PLAYBACK_SERVICE)); - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - registerReceiver(bluetoothStateUpdated, new IntentFilter( - BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)); - } - registerReceiver(audioBecomingNoisy, new IntentFilter( - AudioManager.ACTION_AUDIO_BECOMING_NOISY)); - registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter( - ACTION_SKIP_CURRENT_EPISODE)); - registerReceiver(pausePlayCurrentEpisodeReceiver, new IntentFilter( - ACTION_PAUSE_PLAY_CURRENT_EPISODE)); - registerReceiver(pauseResumeCurrentEpisodeReceiver, new IntentFilter( - ACTION_RESUME_PLAY_CURRENT_EPISODE)); - taskManager = new PlaybackServiceTaskManager(this, taskManagerCallback); - - mediaRouter = MediaRouter.getInstance(getApplicationContext()); - PreferenceManager.getDefaultSharedPreferences(this) - .registerOnSharedPreferenceChangeListener(prefListener); - - ComponentName eventReceiver = new ComponentName(getApplicationContext(), - MediaButtonReceiver.class); - Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - mediaButtonIntent.setComponent(eventReceiver); - PendingIntent buttonReceiverIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT); - - mediaSession = new MediaSessionCompat(getApplicationContext(), TAG, eventReceiver, buttonReceiverIntent); - - try { - mediaSession.setCallback(sessionCallback); - mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); - } catch (NullPointerException npe) { - // on some devices (Huawei) setting active can cause a NullPointerException - // even with correct use of the api. - // See http://stackoverflow.com/questions/31556679/android-huawei-mediassessioncompat - // and https://plus.google.com/+IanLake/posts/YgdTkKFxz7d - Log.e(TAG, "NullPointerException while setting up MediaSession"); - npe.printStackTrace(); - } - - castManager = CastManager.getInstance(); - castManager.addCastConsumer(castConsumer); - isCasting = castManager.isConnected(); - if (isCasting) { - if (UserPreferences.isCastEnabled()) { - onCastAppConnected(false); - } else { - castManager.disconnect(); - } - } else { - mediaPlayer = new LocalPSMP(this, mediaPlayerCallback); - } - - mediaSession.setActive(true); - } - - @Override - public void onDestroy() { - super.onDestroy(); - Log.d(TAG, "Service is about to be destroyed"); - isRunning = false; - started = false; - currentMediaType = MediaType.UNKNOWN; - - PreferenceManager.getDefaultSharedPreferences(this) - .unregisterOnSharedPreferenceChangeListener(prefListener); - if (mediaSession != null) { - mediaSession.release(); - } - unregisterReceiver(headsetDisconnected); - unregisterReceiver(shutdownReceiver); - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - unregisterReceiver(bluetoothStateUpdated); - } - unregisterReceiver(audioBecomingNoisy); - unregisterReceiver(skipCurrentEpisodeReceiver); - unregisterReceiver(pausePlayCurrentEpisodeReceiver); - unregisterReceiver(pauseResumeCurrentEpisodeReceiver); - castManager.removeCastConsumer(castConsumer); - unregisterWifiBroadcastReceiver(); - mediaPlayer.shutdown(); - taskManager.shutdown(); - } - - @Override - public IBinder onBind(Intent intent) { - Log.d(TAG, "Received onBind event"); - return mBinder; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - super.onStartCommand(intent, flags, startId); - - Log.d(TAG, "OnStartCommand called"); - final int keycode = intent.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1); - final boolean castDisconnect = intent.getBooleanExtra(EXTRA_CAST_DISCONNECT, false); - final Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE); - if (keycode == -1 && playable == null && !castDisconnect) { - Log.e(TAG, "PlaybackService was started with no arguments"); - stopSelf(); - return Service.START_REDELIVER_INTENT; - } - - if ((flags & Service.START_FLAG_REDELIVERY) != 0) { - Log.d(TAG, "onStartCommand is a redelivered intent, calling stopForeground now."); - stopForeground(true); - } else { - - if (keycode != -1) { - Log.d(TAG, "Received media button event"); - handleKeycode(keycode, intent.getIntExtra(MediaButtonReceiver.EXTRA_SOURCE, - InputDevice.SOURCE_CLASS_NONE)); - } else if (castDisconnect) { - castManager.disconnect(); - } else { - started = true; - boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM, - true); - boolean startWhenPrepared = intent.getBooleanExtra(EXTRA_START_WHEN_PREPARED, false); - boolean prepareImmediately = intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false); - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); - //If the user asks to play External Media, the casting session, if on, should end. - if (playable instanceof ExternalMedia) { - castManager.disconnect(); - } - mediaPlayer.playMediaObject(playable, stream, startWhenPrepared, prepareImmediately); - } - } - - return Service.START_REDELIVER_INTENT; - } - - /** - * Handles media button events - */ - private void handleKeycode(int keycode, int source) { - Log.d(TAG, "Handling keycode: " + keycode); - final PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo(); - final PlayerStatus status = info.playerStatus; - switch (keycode) { - case KeyEvent.KEYCODE_HEADSETHOOK: - case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - if (status == PlayerStatus.PLAYING) { - mediaPlayer.pause(!UserPreferences.isPersistNotify(), true); - } else if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) { - mediaPlayer.resume(); - } else if (status == PlayerStatus.PREPARING) { - mediaPlayer.setStartWhenPrepared(!mediaPlayer.isStartWhenPrepared()); - } else if (status == PlayerStatus.INITIALIZED) { - mediaPlayer.setStartWhenPrepared(true); - mediaPlayer.prepare(); - } - break; - case KeyEvent.KEYCODE_MEDIA_PLAY: - if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) { - mediaPlayer.resume(); - } else if (status == PlayerStatus.INITIALIZED) { - mediaPlayer.setStartWhenPrepared(true); - mediaPlayer.prepare(); - } - break; - case KeyEvent.KEYCODE_MEDIA_PAUSE: - if (status == PlayerStatus.PLAYING) { - mediaPlayer.pause(!UserPreferences.isPersistNotify(), true); - } - - break; - case KeyEvent.KEYCODE_MEDIA_NEXT: - if(source == InputDevice.SOURCE_CLASS_NONE || - UserPreferences.shouldHardwareButtonSkip()) { - // assume the skip command comes from a notification or the lockscreen - // a >| skip button should actually skip - mediaPlayer.endPlayback(true, false); - } else { - // assume skip command comes from a (bluetooth) media button - // user actually wants to fast-forward - seekDelta(UserPreferences.getFastFowardSecs() * 1000); - } - break; - case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: - mediaPlayer.seekDelta(UserPreferences.getFastFowardSecs() * 1000); - break; - case KeyEvent.KEYCODE_MEDIA_PREVIOUS: - case KeyEvent.KEYCODE_MEDIA_REWIND: - mediaPlayer.seekDelta(-UserPreferences.getRewindSecs() * 1000); - break; - case KeyEvent.KEYCODE_MEDIA_STOP: - if (status == PlayerStatus.PLAYING) { - mediaPlayer.pause(true, true); - started = false; - } - - stopForeground(true); // gets rid of persistent notification - break; - default: - Log.d(TAG, "Unhandled key code: " + keycode); - if (info.playable != null && info.playerStatus == PlayerStatus.PLAYING) { // only notify the user about an unknown key event if it is actually doing something - String message = String.format(getResources().getString(R.string.unknown_media_key), keycode); - Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); - } - break; - } - } - - /** - * Called by a mediaplayer Activity as soon as it has prepared its - * mediaplayer. - */ - public void setVideoSurface(SurfaceHolder sh) { - Log.d(TAG, "Setting display"); - mediaPlayer.setVideoSurface(sh); - } - - /** - * Called when the surface holder of the mediaplayer has to be changed. - */ - private void resetVideoSurface() { - taskManager.cancelPositionSaver(); - mediaPlayer.resetVideoSurface(); - } - - public void notifyVideoSurfaceAbandoned() { - stopForeground(!UserPreferences.isPersistNotify()); - mediaPlayer.resetVideoSurface(); - } - - private final PlaybackServiceTaskManager.PSTMCallback taskManagerCallback = new PlaybackServiceTaskManager.PSTMCallback() { - @Override - public void positionSaverTick() { - saveCurrentPosition(true, PlaybackServiceTaskManager.POSITION_SAVER_WAITING_INTERVAL); - } - - @Override - public void onSleepTimerAlmostExpired() { - float leftVolume = 0.1f * UserPreferences.getLeftVolume(); - float rightVolume = 0.1f * UserPreferences.getRightVolume(); - mediaPlayer.setVolume(leftVolume, rightVolume); - } - - @Override - public void onSleepTimerExpired() { - mediaPlayer.pause(true, true); - float leftVolume = UserPreferences.getLeftVolume(); - float rightVolume = UserPreferences.getRightVolume(); - mediaPlayer.setVolume(leftVolume, rightVolume); - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); - } - - @Override - public void onSleepTimerReset() { - float leftVolume = UserPreferences.getLeftVolume(); - float rightVolume = UserPreferences.getRightVolume(); - mediaPlayer.setVolume(leftVolume, rightVolume); - } - - @Override - public void onWidgetUpdaterTick() { - updateWidget(); - } - - @Override - public void onChapterLoaded(Playable media) { - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); - } - }; - - private final PlaybackServiceMediaPlayer.PSMPCallback mediaPlayerCallback = new PlaybackServiceMediaPlayer.PSMPCallback() { - @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { - currentMediaType = mediaPlayer.getCurrentMediaType(); - updateMediaSession(newInfo.playerStatus); - switch (newInfo.playerStatus) { - case INITIALIZED: - writePlaybackPreferences(); - break; - - case PREPARED: - taskManager.startChapterLoader(newInfo.playable); - break; - - case PAUSED: - taskManager.cancelPositionSaver(); - saveCurrentPosition(false, 0); - taskManager.cancelWidgetUpdater(); - if ((UserPreferences.isPersistNotify() || isCasting) && - android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - // do not remove notification on pause based on user pref and whether android version supports expanded notifications - // Change [Play] button to [Pause] - setupNotification(newInfo); - } else if (!UserPreferences.isPersistNotify() && !isCasting) { - // remove notification on pause - stopForeground(true); - } - writePlayerStatusPlaybackPreferences(); - - final Playable playable = newInfo.playable; - - // Gpodder: send play action - if(GpodnetPreferences.loggedIn() && playable instanceof FeedMedia) { - FeedMedia media = (FeedMedia) playable; - FeedItem item = media.getItem(); - GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.PLAY) - .currentDeviceId() - .currentTimestamp() - .started(startPosition / 1000) - .position(getCurrentPosition() / 1000) - .total(getDuration() / 1000) - .build(); - GpodnetPreferences.enqueueEpisodeAction(action); - } - break; - - case STOPPED: - //setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING); - //stopSelf(); - break; - - case PLAYING: - Log.d(TAG, "Audiofocus successfully requested"); - Log.d(TAG, "Resuming/Starting playback"); - - taskManager.startPositionSaver(); - taskManager.startWidgetUpdater(); - writePlayerStatusPlaybackPreferences(); - setupNotification(newInfo); - started = true; - startPosition = mediaPlayer.getPosition(); - break; - - case ERROR: - writePlaybackPreferencesNoMediaPlaying(); - break; - - } - - Intent statusUpdate = new Intent(ACTION_PLAYER_STATUS_CHANGED); - // statusUpdate.putExtra(EXTRA_NEW_PLAYER_STATUS, newInfo.playerStatus.ordinal()); - sendBroadcast(statusUpdate); - updateWidget(); - bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED); - bluetoothNotifyChange(newInfo, AVRCP_ACTION_META_CHANGED); - } - - @Override - public void shouldStop() { - stopSelf(); - } - - @Override - public void playbackSpeedChanged(float s) { - sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE, 0); - } - - public void setSpeedAbilityChanged() { - sendNotificationBroadcast(NOTIFICATION_TYPE_SET_SPEED_ABILITY_CHANGED, 0); - } - - @Override - public void onBufferingUpdate(int percent) { - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent); - } - - @Override - public void onMediaChanged(boolean reloadUI) { - Log.d(TAG, "reloadUI callback reached"); - if (reloadUI) { - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0); - } - PlaybackService.this.updateMediaSessionMetadata(getPlayable()); - } - - @Override - public boolean onMediaPlayerInfo(int code, @StringRes int resourceId) { - switch (code) { - case MediaPlayer.MEDIA_INFO_BUFFERING_START: - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0); - return true; - case MediaPlayer.MEDIA_INFO_BUFFERING_END: - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_END, 0); - return true; - case RemotePSMP.CAST_ERROR: - sendNotificationBroadcast(NOTIFICATION_TYPE_SHOW_TOAST, resourceId); - return true; - case RemotePSMP.CAST_ERROR_PRIORITY_HIGH: - Toast.makeText(PlaybackService.this, resourceId, Toast.LENGTH_SHORT).show(); - return true; - default: - return false; - } - } - - @Override - public boolean onMediaPlayerError(Object inObj, int what, int extra) { - final String TAG = "PlaybackSvc.onErrorLtsn"; - Log.w(TAG, "An error has occured: " + what + " " + extra); - if (mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING) { - mediaPlayer.pause(true, false); - } - sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what); - writePlaybackPreferencesNoMediaPlaying(); - stopSelf(); - return true; - } - - @Override - public boolean endPlayback(Playable media, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { - PlaybackService.this.endPlayback(media, playNextEpisode, wasSkipped, switchingPlayers); - return true; - } - }; - - private void endPlayback(final Playable playable, boolean playNextEpisode, boolean wasSkipped, boolean switchingPlayers) { - Log.d(TAG, "Playback ended" + (switchingPlayers ? " from switching players": "")); - - if (playable == null) { - Log.e(TAG, "Cannot end playback: media was null"); - return; - } - - taskManager.cancelPositionSaver(); - - boolean isInQueue = false; - FeedItem nextItem = null; - - if (playable instanceof FeedMedia && ((FeedMedia) playable).getItem() != null) { - FeedMedia media = (FeedMedia) playable; - FeedItem item = media.getItem(); - - if (!switchingPlayers) { - try { - final List queue = taskManager.getQueue(); - isInQueue = QueueAccess.ItemListAccess(queue).contains(item.getId()); - nextItem = DBTasks.getQueueSuccessorOfItem(item.getId(), queue); - } catch (InterruptedException e) { - e.printStackTrace(); - // isInQueue remains false - } - - boolean shouldKeep = wasSkipped && UserPreferences.shouldSkipKeepEpisode(); - - if (!shouldKeep) { - // only mark the item as played if we're not keeping it anyways - DBWriter.markItemPlayed(item, FeedItem.PLAYED, true); - - if (isInQueue) { - DBWriter.removeQueueItem(PlaybackService.this, item, true); - } - - // Delete episode if enabled - if (item.getFeed().getPreferences().getCurrentAutoDelete()) { - DBWriter.deleteFeedMediaOfItem(PlaybackService.this, media.getId()); - Log.d(TAG, "Episode Deleted"); - } - } - } - - - DBWriter.addItemToPlaybackHistory(media); - - // auto-flattr if enabled - if (isAutoFlattrable(media) && UserPreferences.getAutoFlattrPlayedDurationThreshold() == 1.0f) { - DBTasks.flattrItemIfLoggedIn(PlaybackService.this, item); - } - - // gpodder play action - if(GpodnetPreferences.loggedIn()) { - GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.PLAY) - .currentDeviceId() - .currentTimestamp() - .started(startPosition / 1000) - .position(getDuration() / 1000) - .total(getDuration() / 1000) - .build(); - GpodnetPreferences.enqueueEpisodeAction(action); - } - } - - if (!switchingPlayers) { - // Load next episode if previous episode was in the queue and if there - // is an episode in the queue left. - // Start playback immediately if continuous playback is enabled - Playable nextMedia = null; - boolean loadNextItem = ClientConfig.playbackServiceCallbacks.useQueue() && - isInQueue && - nextItem != null; - - playNextEpisode = playNextEpisode && - loadNextItem && - UserPreferences.isFollowQueue(); - - if (loadNextItem) { - Log.d(TAG, "Loading next item in queue"); - nextMedia = nextItem.getMedia(); - } - final boolean prepareImmediately; - final boolean startWhenPrepared; - final boolean stream; - - if (playNextEpisode) { - Log.d(TAG, "Playback of next episode will start immediately."); - prepareImmediately = startWhenPrepared = true; - } else { - Log.d(TAG, "No more episodes available to play"); - prepareImmediately = startWhenPrepared = false; - stopForeground(true); - stopWidgetUpdater(); - } - - writePlaybackPreferencesNoMediaPlaying(); - if (nextMedia != null) { - stream = !nextMedia.localFileAvailable(); - mediaPlayer.playMediaObject(nextMedia, stream, startWhenPrepared, prepareImmediately); - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, - isCasting ? EXTRA_CODE_CAST : - (nextMedia.getMediaType() == MediaType.VIDEO) ? EXTRA_CODE_VIDEO : EXTRA_CODE_AUDIO); - } else { - sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0); - mediaPlayer.stop(); - //stopSelf(); - } - } - } - - public void setSleepTimer(long waitingTime, boolean shakeToReset, boolean vibrate) { - Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) + " milliseconds"); - taskManager.setSleepTimer(waitingTime, shakeToReset, vibrate); - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); - } - - public void disableSleepTimer() { - taskManager.disableSleepTimer(); - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); - } - - private void writePlaybackPreferencesNoMediaPlaying() { - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putInt( - PlaybackPreferences.PREF_CURRENT_PLAYER_STATUS, - PlaybackPreferences.PLAYER_STATUS_OTHER); - editor.commit(); - } - - private int getCurrentPlayerStatusAsInt(PlayerStatus playerStatus) { - int playerStatusAsInt; - switch (playerStatus) { - case PLAYING: - playerStatusAsInt = PlaybackPreferences.PLAYER_STATUS_PLAYING; - break; - case PAUSED: - playerStatusAsInt = PlaybackPreferences.PLAYER_STATUS_PAUSED; - break; - default: - playerStatusAsInt = PlaybackPreferences.PLAYER_STATUS_OTHER; - } - return playerStatusAsInt; - } - - private void writePlaybackPreferences() { - Log.d(TAG, "Writing playback preferences"); - - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo(); - MediaType mediaType = mediaPlayer.getCurrentMediaType(); - boolean stream = mediaPlayer.isStreaming(); - int playerStatus = getCurrentPlayerStatusAsInt(info.playerStatus); - - if (info.playable != null) { - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, - info.playable.getPlayableType()); - editor.putBoolean( - PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM, - stream); - editor.putBoolean( - PlaybackPreferences.PREF_CURRENT_EPISODE_IS_VIDEO, - mediaType == MediaType.VIDEO); - if (info.playable instanceof FeedMedia) { - FeedMedia fMedia = (FeedMedia) info.playable; - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - fMedia.getItem().getFeed().getId()); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - fMedia.getId()); - } else { - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - } - info.playable.writeToPreferences(editor); - } else { - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - } - editor.putInt( - PlaybackPreferences.PREF_CURRENT_PLAYER_STATUS, playerStatus); - - editor.commit(); - } - - private void writePlayerStatusPlaybackPreferences() { - Log.d(TAG, "Writing player status playback preferences"); - - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - int playerStatus = getCurrentPlayerStatusAsInt(mediaPlayer.getPlayerStatus()); - - editor.putInt( - PlaybackPreferences.PREF_CURRENT_PLAYER_STATUS, playerStatus); - - editor.commit(); - } - - /** - * Send ACTION_PLAYER_STATUS_CHANGED without changing the status attribute. - */ - private void postStatusUpdateIntent() { - sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED)); - } - - private void sendNotificationBroadcast(int type, int code) { - Intent intent = new Intent(ACTION_PLAYER_NOTIFICATION); - intent.putExtra(EXTRA_NOTIFICATION_TYPE, type); - intent.putExtra(EXTRA_NOTIFICATION_CODE, code); - sendBroadcast(intent); - } - - /** - * Updates the Media Session for the corresponding status. - * @param playerStatus the current {@link PlayerStatus} - */ - private void updateMediaSession(final PlayerStatus playerStatus) { - PlaybackStateCompat.Builder sessionState = new PlaybackStateCompat.Builder(); - - int state; - if (playerStatus != null) { - switch (playerStatus) { - case PLAYING: - state = PlaybackStateCompat.STATE_PLAYING; - break; - case PREPARED: - case PAUSED: - state = PlaybackStateCompat.STATE_PAUSED; - break; - case STOPPED: - state = PlaybackStateCompat.STATE_STOPPED; - break; - case SEEKING: - state = PlaybackStateCompat.STATE_FAST_FORWARDING; - break; - case PREPARING: - case INITIALIZING: - state = PlaybackStateCompat.STATE_CONNECTING; - break; - case INITIALIZED: - case INDETERMINATE: - state = PlaybackStateCompat.STATE_NONE; - break; - case ERROR: - state = PlaybackStateCompat.STATE_ERROR; - break; - default: - state = PlaybackStateCompat.STATE_NONE; - break; - } - } else { - state = PlaybackStateCompat.STATE_NONE; - } - sessionState.setState(state, mediaPlayer.getPosition(), mediaPlayer.getPlaybackSpeed()); - sessionState.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE - | PlaybackStateCompat.ACTION_REWIND - | PlaybackStateCompat.ACTION_FAST_FORWARD - | PlaybackStateCompat.ACTION_SKIP_TO_NEXT); - mediaSession.setPlaybackState(sessionState.build()); - } - - /** - * Used by updateMediaSessionMetadata to load notification data in another thread. - */ - private Thread mediaSessionSetupThread; - - private void updateMediaSessionMetadata(final Playable p) { - if (p == null || mediaSession == null) { - return; - } - if (mediaSessionSetupThread != null) { - mediaSessionSetupThread.interrupt(); - } - - Runnable mediaSessionSetupTask = () -> { - MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(); - builder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, p.getFeedTitle()); - builder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, p.getEpisodeTitle()); - builder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, p.getDuration()); - builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, p.getEpisodeTitle()); - builder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, p.getFeedTitle()); - - if (p.getImageLocation() != null && UserPreferences.setLockscreenBackground()) { - builder.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, p.getImageLocation().toString()); - try { - if (isCasting) { - Bitmap art = Glide.with(this) - .load(p.getImageLocation()) - .asBitmap() - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .into(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) - .get(); - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, art); - } else { - WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); - Display display = wm.getDefaultDisplay(); - Bitmap art = Glide.with(this) - .load(p.getImageLocation()) - .asBitmap() - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .centerCrop() - .into(display.getWidth(), display.getHeight()) - .get(); - builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, art); - } - } catch (Throwable tr) { - Log.e(TAG, Log.getStackTraceString(tr)); - } - } - if (!Thread.currentThread().isInterrupted() && started) { - mediaSession.setMetadata(builder.build()); - } - }; - - mediaSessionSetupThread = new Thread(mediaSessionSetupTask); - mediaSessionSetupThread.start(); - } - - /** - * Used by setupNotification to load notification data in another thread. - */ - private Thread notificationSetupThread; - - /** - * Prepares notification and starts the service in the foreground. - */ - private void setupNotification(final PlaybackServiceMediaPlayer.PSMPInfo info) { - final PendingIntent pIntent = PendingIntent.getActivity(this, 0, - PlaybackService.getPlayerActivityIntent(this), - PendingIntent.FLAG_UPDATE_CURRENT); - - if (notificationSetupThread != null) { - notificationSetupThread.interrupt(); - } - Runnable notificationSetupTask = new Runnable() { - Bitmap icon = null; - - @Override - public void run() { - Log.d(TAG, "Starting background work"); - if (android.os.Build.VERSION.SDK_INT >= 11) { - if (info.playable != null) { - int iconSize = getResources().getDimensionPixelSize( - android.R.dimen.notification_large_icon_width); - try { - icon = Glide.with(PlaybackService.this) - .load(info.playable.getImageLocation()) - .asBitmap() - .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) - .centerCrop() - .into(iconSize, iconSize) - .get(); - } catch (Throwable tr) { - Log.e(TAG, "Error loading the media icon for the notification", tr); - } - } - } - if (icon == null) { - icon = BitmapFactory.decodeResource(getApplicationContext().getResources(), - ClientConfig.playbackServiceCallbacks.getNotificationIconResource(getApplicationContext())); - } - - if (mediaPlayer == null) { - return; - } - PlayerStatus playerStatus = mediaPlayer.getPlayerStatus(); - final int smallIcon = ClientConfig.playbackServiceCallbacks.getNotificationIconResource(getApplicationContext()); - - if (!Thread.currentThread().isInterrupted() && started && info.playable != null) { - String contentText = info.playable.getEpisodeTitle(); - String contentTitle = info.playable.getFeedTitle(); - Notification notification; - - // Builder is v7, even if some not overwritten methods return its parent's v4 interface - NotificationCompat.Builder notificationBuilder = (NotificationCompat.Builder) new NotificationCompat.Builder( - PlaybackService.this) - .setContentTitle(contentTitle) - .setContentText(contentText) - .setOngoing(false) - .setContentIntent(pIntent) - .setLargeIcon(icon) - .setSmallIcon(smallIcon) - .setWhen(0) // we don't need the time - .setPriority(UserPreferences.getNotifyPriority()); // set notification priority - IntList compactActionList = new IntList(); - - int numActions = 0; // we start and 0 and then increment by 1 for each call to addAction - - if (isCasting) { - Intent stopCastingIntent = new Intent(PlaybackService.this, PlaybackService.class); - stopCastingIntent.putExtra(EXTRA_CAST_DISCONNECT, true); - PendingIntent stopCastingPendingIntent = PendingIntent.getService(PlaybackService.this, - numActions, stopCastingIntent, PendingIntent.FLAG_UPDATE_CURRENT); - notificationBuilder.addAction(R.drawable.ic_media_cast_disconnect, - getString(R.string.cast_disconnect_label), - stopCastingPendingIntent); - numActions++; - } - - // always let them rewind - PendingIntent rewindButtonPendingIntent = getPendingIntentForMediaAction( - KeyEvent.KEYCODE_MEDIA_REWIND, numActions); - notificationBuilder.addAction(android.R.drawable.ic_media_rew, - getString(R.string.rewind_label), - rewindButtonPendingIntent); - if(UserPreferences.showRewindOnCompactNotification()) { - compactActionList.add(numActions); - } - numActions++; - - if (playerStatus == PlayerStatus.PLAYING) { - PendingIntent pauseButtonPendingIntent = getPendingIntentForMediaAction( - KeyEvent.KEYCODE_MEDIA_PAUSE, numActions); - notificationBuilder.addAction(android.R.drawable.ic_media_pause, //pause action - getString(R.string.pause_label), - pauseButtonPendingIntent); - compactActionList.add(numActions++); - } else { - PendingIntent playButtonPendingIntent = getPendingIntentForMediaAction( - KeyEvent.KEYCODE_MEDIA_PLAY, numActions); - notificationBuilder.addAction(android.R.drawable.ic_media_play, //play action - getString(R.string.play_label), - playButtonPendingIntent); - compactActionList.add(numActions++); - } - - // ff follows play, then we have skip (if it's present) - PendingIntent ffButtonPendingIntent = getPendingIntentForMediaAction( - KeyEvent.KEYCODE_MEDIA_FAST_FORWARD, numActions); - notificationBuilder.addAction(android.R.drawable.ic_media_ff, - getString(R.string.fast_forward_label), - ffButtonPendingIntent); - if(UserPreferences.showFastForwardOnCompactNotification()) { - compactActionList.add(numActions); - } - numActions++; - - if (UserPreferences.isFollowQueue()) { - PendingIntent skipButtonPendingIntent = getPendingIntentForMediaAction( - KeyEvent.KEYCODE_MEDIA_NEXT, numActions); - notificationBuilder.addAction(android.R.drawable.ic_media_next, - getString(R.string.skip_episode_label), - skipButtonPendingIntent); - if(UserPreferences.showSkipOnCompactNotification()) { - compactActionList.add(numActions); - } - numActions++; - } - - PendingIntent stopButtonPendingIntent = getPendingIntentForMediaAction( - KeyEvent.KEYCODE_MEDIA_STOP, numActions); - notificationBuilder.setStyle(new android.support.v7.app.NotificationCompat.MediaStyle() - .setMediaSession(mediaSession.getSessionToken()) - .setShowActionsInCompactView(compactActionList.toArray()) - .setShowCancelButton(true) - .setCancelButtonIntent(stopButtonPendingIntent)) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .setColor(Notification.COLOR_DEFAULT); - - notification = notificationBuilder.build(); - - if (playerStatus == PlayerStatus.PLAYING || - playerStatus == PlayerStatus.PREPARING || - playerStatus == PlayerStatus.SEEKING || - isCasting) { - startForeground(NOTIFICATION_ID, notification); - } else { - stopForeground(false); - NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - mNotificationManager.notify(NOTIFICATION_ID, notification); - } - Log.d(TAG, "Notification set up"); - } - } - }; - notificationSetupThread = new Thread(notificationSetupTask); - notificationSetupThread.start(); - } - - private PendingIntent getPendingIntentForMediaAction(int keycodeValue, int requestCode) { - Intent intent = new Intent( - PlaybackService.this, PlaybackService.class); - intent.putExtra( - MediaButtonReceiver.EXTRA_KEYCODE, - keycodeValue); - return PendingIntent - .getService(PlaybackService.this, requestCode, - intent, - PendingIntent.FLAG_UPDATE_CURRENT); - } - - /** - * Persists the current position and last played time of the media file. - * - * @param updatePlayedDuration true if played_duration should be updated. This applies only to FeedMedia objects - * @param deltaPlayedDuration value by which played_duration should be increased. - */ - private synchronized void saveCurrentPosition(boolean updatePlayedDuration, int deltaPlayedDuration) { - int position = getCurrentPosition(); - int duration = getDuration(); - float playbackSpeed = getCurrentPlaybackSpeed(); - final Playable playable = mediaPlayer.getPlayable(); - if (position != INVALID_TIME && duration != INVALID_TIME && playable != null) { - Log.d(TAG, "Saving current position to " + position); - if (updatePlayedDuration && playable instanceof FeedMedia) { - FeedMedia media = (FeedMedia) playable; - FeedItem item = media.getItem(); - media.setPlayedDuration(media.getPlayedDuration() + ((int) (deltaPlayedDuration * playbackSpeed))); - // Auto flattr - if (isAutoFlattrable(media) && - (media.getPlayedDuration() > UserPreferences.getAutoFlattrPlayedDurationThreshold() * duration)) { - Log.d(TAG, "saveCurrentPosition: performing auto flattr since played duration " + Integer.toString(media.getPlayedDuration()) - + " is " + UserPreferences.getAutoFlattrPlayedDurationThreshold() * 100 + "% of file duration " + Integer.toString(duration)); - DBTasks.flattrItemIfLoggedIn(this, item); - } - } - playable.saveCurrentPosition( - PreferenceManager.getDefaultSharedPreferences(getApplicationContext()), - position, - System.currentTimeMillis()); - } - } - - private void stopWidgetUpdater() { - taskManager.cancelWidgetUpdater(); - sendBroadcast(new Intent(STOP_WIDGET_UPDATE)); - } - - private void updateWidget() { - PlaybackService.this.sendBroadcast(new Intent( - FORCE_WIDGET_UPDATE)); - } - - public boolean sleepTimerActive() { - return taskManager.isSleepTimerActive(); - } - - public long getSleepTimerTimeLeft() { - return taskManager.getSleepTimerTimeLeft(); - } - - private void bluetoothNotifyChange(PlaybackServiceMediaPlayer.PSMPInfo info, String whatChanged) { - boolean isPlaying = false; - - if (info.playerStatus == PlayerStatus.PLAYING) { - isPlaying = true; - } - - if (info.playable != null) { - Intent i = new Intent(whatChanged); - i.putExtra("id", 1); - i.putExtra("artist", ""); - i.putExtra("album", info.playable.getFeedTitle()); - i.putExtra("track", info.playable.getEpisodeTitle()); - i.putExtra("playing", isPlaying); - final List queue = taskManager.getQueueIfLoaded(); - if (queue != null) { - i.putExtra("ListSize", queue.size()); - } - i.putExtra("duration", info.playable.getDuration()); - i.putExtra("position", info.playable.getPosition()); - sendBroadcast(i); - } - } - - /** - * Pauses playback when the headset is disconnected and the preference is - * set - */ - private final BroadcastReceiver headsetDisconnected = new BroadcastReceiver() { - private static final String TAG = "headsetDisconnected"; - private static final int UNPLUGGED = 0; - private static final int PLUGGED = 1; - - @Override - public void onReceive(Context context, Intent intent) { - if (TextUtils.equals(intent.getAction(), Intent.ACTION_HEADSET_PLUG)) { - int state = intent.getIntExtra("state", -1); - if (state != -1) { - Log.d(TAG, "Headset plug event. State is " + state); - if (state == UNPLUGGED) { - Log.d(TAG, "Headset was unplugged during playback."); - pauseIfPauseOnDisconnect(); - } else if (state == PLUGGED) { - Log.d(TAG, "Headset was plugged in during playback."); - unpauseIfPauseOnDisconnect(false); - } - } else { - Log.e(TAG, "Received invalid ACTION_HEADSET_PLUG intent"); - } - } - } - }; - - private final BroadcastReceiver bluetoothStateUpdated = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - if (TextUtils.equals(intent.getAction(), BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) { - int state = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, -1); - if (state == BluetoothA2dp.STATE_CONNECTED) { - Log.d(TAG, "Received bluetooth connection intent"); - unpauseIfPauseOnDisconnect(true); - } - } - } - } - }; - - private final BroadcastReceiver audioBecomingNoisy = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - // sound is about to change, eg. bluetooth -> speaker - Log.d(TAG, "Pausing playback because audio is becoming noisy"); - pauseIfPauseOnDisconnect(); - } - // android.media.AUDIO_BECOMING_NOISY - }; - - /** - * Pauses playback if PREF_PAUSE_ON_HEADSET_DISCONNECT was set to true. - */ - private void pauseIfPauseOnDisconnect() { - if (UserPreferences.isPauseOnHeadsetDisconnect()) { - if (mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING) { - transientPause = true; - } - mediaPlayer.pause(!UserPreferences.isPersistNotify(), true); - } - } - - /** - * @param bluetooth true if the event for unpausing came from bluetooth - */ - private void unpauseIfPauseOnDisconnect(boolean bluetooth) { - if (transientPause) { - transientPause = false; - if (!bluetooth && UserPreferences.isUnpauseOnHeadsetReconnect()) { - mediaPlayer.resume(); - } else if (bluetooth && UserPreferences.isUnpauseOnBluetoothReconnect()){ - // let the user know we've started playback again... - Vibrator v = (Vibrator) getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE); - if(v != null) { - v.vibrate(500); - } - mediaPlayer.resume(); - } - } - } - - private final BroadcastReceiver shutdownReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - if (TextUtils.equals(intent.getAction(), ACTION_SHUTDOWN_PLAYBACK_SERVICE)) { - stopSelf(); - } - } - - }; - - private final BroadcastReceiver skipCurrentEpisodeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (TextUtils.equals(intent.getAction(), ACTION_SKIP_CURRENT_EPISODE)) { - Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent"); - mediaPlayer.endPlayback(true, false); - } - } - }; - - private final BroadcastReceiver pauseResumeCurrentEpisodeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (TextUtils.equals(intent.getAction(), ACTION_RESUME_PLAY_CURRENT_EPISODE)) { - Log.d(TAG, "Received RESUME_PLAY_CURRENT_EPISODE intent"); - mediaPlayer.resume(); - } - } - }; - - private final BroadcastReceiver pausePlayCurrentEpisodeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (TextUtils.equals(intent.getAction(), ACTION_PAUSE_PLAY_CURRENT_EPISODE)) { - Log.d(TAG, "Received PAUSE_PLAY_CURRENT_EPISODE intent"); - mediaPlayer.pause(false, false); - } - } - }; - - public static MediaType getCurrentMediaType() { - return currentMediaType; - } - - public static boolean isCasting() { - return isCasting; - } - - public void resume() { - mediaPlayer.resume(); - } - - public void prepare() { - mediaPlayer.prepare(); - } - - public void pause(boolean abandonAudioFocus, boolean reinit) { - mediaPlayer.pause(abandonAudioFocus, reinit); - } - - public void reinit() { - mediaPlayer.reinit(); - } - - public PlaybackServiceMediaPlayer.PSMPInfo getPSMPInfo() { - return mediaPlayer.getPSMPInfo(); - } - - public PlayerStatus getStatus() { - return mediaPlayer.getPlayerStatus(); - } - - public Playable getPlayable() { return mediaPlayer.getPlayable(); } - - public boolean canSetSpeed() { - return mediaPlayer.canSetSpeed(); - } - - public void setSpeed(float speed) { - mediaPlayer.setSpeed(speed); - } - - public void setVolume(float leftVolume, float rightVolume) { - mediaPlayer.setVolume(leftVolume, rightVolume); - } - - public float getCurrentPlaybackSpeed() { - return mediaPlayer.getPlaybackSpeed(); - } - - public boolean canDownmix() { - return mediaPlayer.canDownmix(); - } - - public void setDownmix(boolean enable) { - mediaPlayer.setDownmix(enable); - } - - public boolean isStartWhenPrepared() { - return mediaPlayer.isStartWhenPrepared(); - } - - public void setStartWhenPrepared(boolean s) { - mediaPlayer.setStartWhenPrepared(s); - } - - - public void seekTo(final int t) { - if(mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING - && GpodnetPreferences.loggedIn()) { - final Playable playable = mediaPlayer.getPlayable(); - if (playable instanceof FeedMedia) { - FeedMedia media = (FeedMedia) playable; - FeedItem item = media.getItem(); - GpodnetEpisodeAction action = new GpodnetEpisodeAction.Builder(item, Action.PLAY) - .currentDeviceId() - .currentTimestamp() - .started(startPosition / 1000) - .position(getCurrentPosition() / 1000) - .total(getDuration() / 1000) - .build(); - GpodnetPreferences.enqueueEpisodeAction(action); - } - } - mediaPlayer.seekTo(t); - if(mediaPlayer.getPlayerStatus() == PlayerStatus.PLAYING ) { - startPosition = t; - } - } - - - public void seekDelta(final int d) { - mediaPlayer.seekDelta(d); - } - - /** - * @see LocalPSMP#seekToChapter(de.danoeh.antennapod.core.feed.Chapter) - */ - public void seekToChapter(Chapter c) { - mediaPlayer.seekToChapter(c); - } - - /** - * call getDuration() on mediaplayer or return INVALID_TIME if player is in - * an invalid state. - */ - public int getDuration() { - return mediaPlayer.getDuration(); - } - - /** - * call getCurrentPosition() on mediaplayer or return INVALID_TIME if player - * is in an invalid state. - */ - public int getCurrentPosition() { - return mediaPlayer.getPosition(); - } - - public boolean isStreaming() { - return mediaPlayer.isStreaming(); - } - - public Pair getVideoSize() { - return mediaPlayer.getVideoSize(); - } - - private boolean isAutoFlattrable(FeedMedia media) { - if (media != null) { - FeedItem item = media.getItem(); - return item != null && FlattrUtils.hasToken() && UserPreferences.isAutoFlattr() && item.getPaymentLink() != null && item.getFlattrStatus().getUnflattred(); - } else { - return false; - } - } - - private CastConsumer castConsumer = new DefaultCastConsumer() { - @Override - public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) { - PlaybackService.this.onCastAppConnected(wasLaunched); - } - - @Override - public void onDisconnectionReason(int reason) { - Log.d(TAG, "onDisconnectionReason() with code " + reason); - // This is our final chance to update the underlying stream position - // In onDisconnected(), the underlying CastPlayback#mVideoCastConsumer - // is disconnected and hence we update our local value of stream position - // to the latest position. - if (mediaPlayer != null) { - saveCurrentPosition(false, 0); - infoBeforeCastDisconnection = mediaPlayer.getPSMPInfo(); - if (reason != BaseCastManager.DISCONNECT_REASON_EXPLICIT && - infoBeforeCastDisconnection.playerStatus == PlayerStatus.PLAYING) { - // If it's NOT based on user action, we shouldn't automatically resume local playback - infoBeforeCastDisconnection.playerStatus = PlayerStatus.PAUSED; - } - } - } - - @Override - public void onDisconnected() { - Log.d(TAG, "onDisconnected()"); - isCasting = false; - PlaybackServiceMediaPlayer.PSMPInfo info = infoBeforeCastDisconnection; - infoBeforeCastDisconnection = null; - if (info == null && mediaPlayer != null) { - info = mediaPlayer.getPSMPInfo(); - } - if (info == null) { - info = new PlaybackServiceMediaPlayer.PSMPInfo(PlayerStatus.STOPPED, null); - } - switchMediaPlayer(new LocalPSMP(PlaybackService.this, mediaPlayerCallback), - info, true); - if (info.playable != null) { - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, - info.playable.getMediaType() == MediaType.AUDIO ? EXTRA_CODE_AUDIO : EXTRA_CODE_VIDEO); - } else { - Log.d(TAG, "Cast session disconnected, but no current media"); - sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0); - } - // hardware volume buttons control the local device volume - mediaRouter.setMediaSessionCompat(null); - unregisterWifiBroadcastReceiver(); - PlayerStatus status = info.playerStatus; - if ((status == PlayerStatus.PLAYING || - status == PlayerStatus.SEEKING || - status == PlayerStatus.PREPARING || - UserPreferences.isPersistNotify()) && - android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - setupNotification(info); - } else if (!UserPreferences.isPersistNotify()){ - stopForeground(true); - } - } - }; - - private final MediaSessionCompat.Callback sessionCallback = new MediaSessionCompat.Callback() { - - private static final String TAG = "MediaSessionCompat"; - - @Override - public void onPlay() { - Log.d(TAG, "onPlay()"); - PlayerStatus status = getStatus(); - if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) { - resume(); - } else if (status == PlayerStatus.INITIALIZED) { - setStartWhenPrepared(true); - prepare(); - } - } - - @Override - public void onPause() { - Log.d(TAG, "onPause()"); - if (getStatus() == PlayerStatus.PLAYING) { - pause(false, true); - } - if (UserPreferences.isPersistNotify()) { - pause(false, true); - } else { - pause(true, true); - } - } - - @Override - public void onStop() { - Log.d(TAG, "onStop()"); - mediaPlayer.stop(); - } - - @Override - public void onSkipToPrevious() { - Log.d(TAG, "onSkipToPrevious()"); - seekDelta(-UserPreferences.getRewindSecs() * 1000); - } - - @Override - public void onRewind() { - Log.d(TAG, "onRewind()"); - seekDelta(-UserPreferences.getRewindSecs() * 1000); - } - - @Override - public void onFastForward() { - Log.d(TAG, "onFastForward()"); - seekDelta(UserPreferences.getFastFowardSecs() * 1000); - } - - @Override - public void onSkipToNext() { - Log.d(TAG, "onSkipToNext()"); - if(UserPreferences.shouldHardwareButtonSkip()) { - mediaPlayer.endPlayback(true, false); - } else { - seekDelta(UserPreferences.getFastFowardSecs() * 1000); - } - } - - - @Override - public void onSeekTo(long pos) { - Log.d(TAG, "onSeekTo()"); - seekTo((int) pos); - } - - @Override - public boolean onMediaButtonEvent(final Intent mediaButton) { - Log.d(TAG, "onMediaButtonEvent(" + mediaButton + ")"); - if (mediaButton != null) { - KeyEvent keyEvent = (KeyEvent) mediaButton.getExtras().get(Intent.EXTRA_KEY_EVENT); - if (keyEvent != null && - keyEvent.getAction() == KeyEvent.ACTION_DOWN && - keyEvent.getRepeatCount() == 0){ - handleKeycode(keyEvent.getKeyCode(), keyEvent.getSource()); - } - } - return false; - } - }; - - private void onCastAppConnected(boolean wasLaunched) { - Log.d(TAG, "A cast device application was " + (wasLaunched ? "launched" : "joined")); - isCasting = true; - PlaybackServiceMediaPlayer.PSMPInfo info = null; - if (mediaPlayer != null) { - info = mediaPlayer.getPSMPInfo(); - if (info.playerStatus == PlayerStatus.PLAYING) { - // could be pause, but this way we make sure the new player will get the correct position, - // since pause runs asynchronously and we could be directing the new player to play even before - // the old player gives us back the position. - saveCurrentPosition(false, 0); - } - } - if (info == null) { - info = new PlaybackServiceMediaPlayer.PSMPInfo(PlayerStatus.STOPPED, null); - } - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, EXTRA_CODE_CAST); - switchMediaPlayer(new RemotePSMP(PlaybackService.this, mediaPlayerCallback), - info, - wasLaunched); - // hardware volume buttons control the remote device volume - mediaRouter.setMediaSessionCompat(mediaSession); - registerWifiBroadcastReceiver(); - setupNotification(info); - } - - private void switchMediaPlayer(@NonNull PlaybackServiceMediaPlayer newPlayer, - @NonNull PlaybackServiceMediaPlayer.PSMPInfo info, - boolean wasLaunched) { - if (mediaPlayer != null) { - mediaPlayer.endPlayback(true, true); - mediaPlayer.shutdownQuietly(); - } - mediaPlayer = newPlayer; - Log.d(TAG, "switched to " + mediaPlayer.getClass().getSimpleName()); - if (!wasLaunched) { - PlaybackServiceMediaPlayer.PSMPInfo candidate = mediaPlayer.getPSMPInfo(); - if (candidate.playable != null && - candidate.playerStatus.isAtLeast(PlayerStatus.PREPARING)) { - // do not automatically send new media to cast device - info.playable = null; - } - } - if (info.playable != null) { - mediaPlayer.playMediaObject(info.playable, - !info.playable.localFileAvailable(), - info.playerStatus == PlayerStatus.PLAYING, - info.playerStatus.isAtLeast(PlayerStatus.PREPARING)); - } - } - - private void registerWifiBroadcastReceiver() { - if (wifiBroadcastReceiver != null) { - return; - } - wifiBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { - NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); - boolean isConnected = info.isConnected(); - //apparently this method gets called twice when a change happens, but one run is enough. - if (isConnected && !wifiConnectivity) { - wifiConnectivity = true; - castManager.startCastDiscovery(); - castManager.reconnectSessionIfPossible(RECONNECTION_ATTEMPT_PERIOD_S, NetworkUtils.getWifiSsid()); - } else { - wifiConnectivity = isConnected; - } - } - } - }; - registerReceiver(wifiBroadcastReceiver, - new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION)); - } - - private void unregisterWifiBroadcastReceiver() { - if (wifiBroadcastReceiver != null) { - unregisterReceiver(wifiBroadcastReceiver); - wifiBroadcastReceiver = null; - } - } - - private SharedPreferences.OnSharedPreferenceChangeListener prefListener = - (sharedPreferences, key) -> { - if (UserPreferences.PREF_CAST_ENABLED.equals(key)) { - if (!UserPreferences.isCastEnabled()) { - if (castManager.isConnecting() || castManager.isConnected()) { - Log.d(TAG, "Disconnecting cast device due to a change in user preferences"); - castManager.disconnect(); - } - } - } else if (UserPreferences.PREF_LOCKSCREEN_BACKGROUND.equals(key)) { - updateMediaSessionMetadata(getPlayable()); - } - }; -} diff --git a/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java b/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java new file mode 100644 index 000000000..aef3e3c2b --- /dev/null +++ b/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java @@ -0,0 +1,252 @@ +package de.danoeh.antennapod.core.service.playback; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.NetworkInfo; +import android.net.wifi.WifiManager; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.support.v7.media.MediaRouter; +import android.util.Log; +import android.widget.Toast; + +import com.google.android.gms.cast.ApplicationMetadata; +import com.google.android.libraries.cast.companionlibrary.cast.BaseCastManager; + +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.feed.MediaType; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.util.NetworkUtils; + +/** + * Class intended to work along PlaybackService and provide support for different flavors. + */ +public class PlaybackServiceFlavorHelper { + public static final String TAG = "PlaybackSrvFlavorHelper"; + + /** + * Time in seconds during which the CastManager will try to reconnect to the Cast Device after + * the Wifi Connection is regained. + */ + private static final int RECONNECTION_ATTEMPT_PERIOD_S = 15; + /** + * Stores the state of the cast playback just before it disconnects. + */ + private volatile PlaybackServiceMediaPlayer.PSMPInfo infoBeforeCastDisconnection; + + private boolean wifiConnectivity = true; + private BroadcastReceiver wifiBroadcastReceiver; + + private CastManager castManager; + private MediaRouter mediaRouter; + private PlaybackService.FlavorHelperCallback callback; + private CastConsumer castConsumer; + + PlaybackServiceFlavorHelper(Context context, PlaybackService.FlavorHelperCallback callback) { + this.callback = callback; + mediaRouter = MediaRouter.getInstance(context.getApplicationContext()); + setCastConsumer(context); + } + + void initializeMediaPlayer(Context context) { + castManager = CastManager.getInstance(); + castManager.addCastConsumer(castConsumer); + boolean isCasting = castManager.isConnected(); + callback.setIsCasting(isCasting); + if (isCasting) { + if (UserPreferences.isCastEnabled()) { + onCastAppConnected(context, false); + } else { + castManager.disconnect(); + } + } else { + callback.setMediaPlayer(new LocalPSMP(context, callback.getMediaPlayerCallback())); + } + } + + void removeCastConsumer() { + castManager.removeCastConsumer(castConsumer); + } + + boolean castDisconnect(boolean castDisconnect) { + if (castDisconnect) { + castManager.disconnect(); + return true; + } + return false; + } + + boolean onMediaPlayerInfo(Context context, int code, @StringRes int resourceId) { + switch (code) { + case RemotePSMP.CAST_ERROR: + callback.sendNotificationBroadcast(PlaybackService.NOTIFICATION_TYPE_SHOW_TOAST, resourceId); + return true; + case RemotePSMP.CAST_ERROR_PRIORITY_HIGH: + Toast.makeText(context, resourceId, Toast.LENGTH_SHORT).show(); + return true; + default: + return false; + } + } + + private void setCastConsumer(Context context) { + castConsumer = new DefaultCastConsumer() { + @Override + public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) { + onCastAppConnected(context, wasLaunched); + } + + @Override + public void onDisconnectionReason(int reason) { + Log.d(TAG, "onDisconnectionReason() with code " + reason); + // This is our final chance to update the underlying stream position + // In onDisconnected(), the underlying CastPlayback#mVideoCastConsumer + // is disconnected and hence we update our local value of stream position + // to the latest position. + PlaybackServiceMediaPlayer mediaPlayer = callback.getMediaPlayer(); + if (mediaPlayer != null) { + callback.saveCurrentPosition(false, 0); + infoBeforeCastDisconnection = mediaPlayer.getPSMPInfo(); + if (reason != BaseCastManager.DISCONNECT_REASON_EXPLICIT && + infoBeforeCastDisconnection.playerStatus == PlayerStatus.PLAYING) { + // If it's NOT based on user action, we shouldn't automatically resume local playback + infoBeforeCastDisconnection.playerStatus = PlayerStatus.PAUSED; + } + } + } + + @Override + public void onDisconnected() { + Log.d(TAG, "onDisconnected()"); + callback.setIsCasting(false); + PlaybackServiceMediaPlayer.PSMPInfo info = infoBeforeCastDisconnection; + infoBeforeCastDisconnection = null; + PlaybackServiceMediaPlayer mediaPlayer = callback.getMediaPlayer(); + if (info == null && mediaPlayer != null) { + info = mediaPlayer.getPSMPInfo(); + } + if (info == null) { + info = new PlaybackServiceMediaPlayer.PSMPInfo(PlayerStatus.STOPPED, null); + } + switchMediaPlayer(new LocalPSMP(context, callback.getMediaPlayerCallback()), + info, true); + if (info.playable != null) { + callback.sendNotificationBroadcast(PlaybackService.NOTIFICATION_TYPE_RELOAD, + info.playable.getMediaType() == MediaType.AUDIO ? + PlaybackService.EXTRA_CODE_AUDIO : PlaybackService.EXTRA_CODE_VIDEO); + } else { + Log.d(TAG, "Cast session disconnected, but no current media"); + callback.sendNotificationBroadcast(PlaybackService.NOTIFICATION_TYPE_PLAYBACK_END, 0); + } + // hardware volume buttons control the local device volume + mediaRouter.setMediaSessionCompat(null); + unregisterWifiBroadcastReceiver(); + callback.setupNotification(false, info); + } + }; + } + + private void onCastAppConnected(Context context, boolean wasLaunched) { + Log.d(TAG, "A cast device application was " + (wasLaunched ? "launched" : "joined")); + callback.setIsCasting(true); + PlaybackServiceMediaPlayer.PSMPInfo info = null; + PlaybackServiceMediaPlayer mediaPlayer = callback.getMediaPlayer(); + if (mediaPlayer != null) { + info = mediaPlayer.getPSMPInfo(); + if (info.playerStatus == PlayerStatus.PLAYING) { + // could be pause, but this way we make sure the new player will get the correct position, + // since pause runs asynchronously and we could be directing the new player to play even before + // the old player gives us back the position. + callback.saveCurrentPosition(false, 0); + } + } + if (info == null) { + info = new PlaybackServiceMediaPlayer.PSMPInfo(PlayerStatus.STOPPED, null); + } + callback.sendNotificationBroadcast(PlaybackService.NOTIFICATION_TYPE_RELOAD, + PlaybackService.EXTRA_CODE_CAST); + switchMediaPlayer(new RemotePSMP(context, callback.getMediaPlayerCallback()), + info, + wasLaunched); + // hardware volume buttons control the remote device volume + mediaRouter.setMediaSessionCompat(callback.getMediaSession()); + registerWifiBroadcastReceiver(); + callback.setupNotification(true, info); + } + + private void switchMediaPlayer(@NonNull PlaybackServiceMediaPlayer newPlayer, + @NonNull PlaybackServiceMediaPlayer.PSMPInfo info, + boolean wasLaunched) { + PlaybackServiceMediaPlayer mediaPlayer = callback.getMediaPlayer(); + if (mediaPlayer != null) { + mediaPlayer.endPlayback(true, true); + mediaPlayer.shutdownQuietly(); + } + mediaPlayer = newPlayer; + callback.setMediaPlayer(mediaPlayer); + Log.d(TAG, "switched to " + mediaPlayer.getClass().getSimpleName()); + if (!wasLaunched) { + PlaybackServiceMediaPlayer.PSMPInfo candidate = mediaPlayer.getPSMPInfo(); + if (candidate.playable != null && + candidate.playerStatus.isAtLeast(PlayerStatus.PREPARING)) { + // do not automatically send new media to cast device + info.playable = null; + } + } + if (info.playable != null) { + mediaPlayer.playMediaObject(info.playable, + !info.playable.localFileAvailable(), + info.playerStatus == PlayerStatus.PLAYING, + info.playerStatus.isAtLeast(PlayerStatus.PREPARING)); + } + } + + void registerWifiBroadcastReceiver() { + if (wifiBroadcastReceiver != null) { + return; + } + wifiBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + boolean isConnected = info.isConnected(); + //apparently this method gets called twice when a change happens, but one run is enough. + if (isConnected && !wifiConnectivity) { + wifiConnectivity = true; + castManager.startCastDiscovery(); + castManager.reconnectSessionIfPossible(RECONNECTION_ATTEMPT_PERIOD_S, NetworkUtils.getWifiSsid()); + } else { + wifiConnectivity = isConnected; + } + } + } + }; + callback.registerReceiver(wifiBroadcastReceiver, + new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION)); + } + + void unregisterWifiBroadcastReceiver() { + if (wifiBroadcastReceiver != null) { + callback.unregisterReceiver(wifiBroadcastReceiver); + wifiBroadcastReceiver = null; + } + } + + boolean onSharedPreference(String key) { + if (UserPreferences.PREF_CAST_ENABLED.equals(key)) { + if (!UserPreferences.isCastEnabled()) { + if (castManager.isConnecting() || castManager.isConnected()) { + Log.d(TAG, "Disconnecting cast device due to a change in user preferences"); + castManager.disconnect(); + } + } + return true; + } + return false; + } +} diff --git a/core/src/play/java/de/danoeh/antennapod/core/util/playback/Playable.java b/core/src/play/java/de/danoeh/antennapod/core/util/playback/Playable.java deleted file mode 100644 index 201efbc81..000000000 --- a/core/src/play/java/de/danoeh/antennapod/core/util/playback/Playable.java +++ /dev/null @@ -1,246 +0,0 @@ -package de.danoeh.antennapod.core.util.playback; - -import android.content.Context; -import android.content.SharedPreferences; -import android.os.Parcelable; -import android.util.Log; - -import java.util.List; - -import de.danoeh.antennapod.core.asynctask.ImageResource; -import de.danoeh.antennapod.core.cast.RemoteMedia; -import de.danoeh.antennapod.core.feed.Chapter; -import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.feed.MediaType; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.util.ShownotesProvider; - -/** - * Interface for objects that can be played by the PlaybackService. - */ -public interface Playable extends Parcelable, - ShownotesProvider, ImageResource { - - /** - * Save information about the playable in a preference so that it can be - * restored later via PlayableUtils.createInstanceFromPreferences. - * Implementations must NOT call commit() after they have written the values - * to the preferences file. - */ - void writeToPreferences(SharedPreferences.Editor prefEditor); - - /** - * This method is called from a separate thread by the PlaybackService. - * Playable objects should load their metadata in this method. This method - * should execute as quickly as possible and NOT load chapter marks if no - * local file is available. - */ - void loadMetadata() throws PlayableException; - - /** - * This method is called from a separate thread by the PlaybackService. - * Playable objects should load their chapter marks in this method if no - * local file was available when loadMetadata() was called. - */ - void loadChapterMarks(); - - /** - * Returns the title of the episode that this playable represents - */ - String getEpisodeTitle(); - - /** - * Returns a list of chapter marks or null if this Playable has no chapters. - */ - List getChapters(); - - /** - * Returns a link to a website that is meant to be shown in a browser - */ - String getWebsiteLink(); - - String getPaymentLink(); - - /** - * Returns the title of the feed this Playable belongs to. - */ - String getFeedTitle(); - - /** - * Returns a unique identifier, for example a file url or an ID from a - * database. - */ - Object getIdentifier(); - - /** - * Return duration of object or 0 if duration is unknown. - */ - int getDuration(); - - /** - * Return position of object or 0 if position is unknown. - */ - int getPosition(); - - /** - * Returns last time (in ms) when this playable was played or 0 - * if last played time is unknown. - */ - long getLastPlayedTime(); - - /** - * Returns the type of media. This method should return the correct value - * BEFORE loadMetadata() is called. - */ - MediaType getMediaType(); - - /** - * Returns an url to a local file that can be played or null if this file - * does not exist. - */ - String getLocalMediaUrl(); - - /** - * Returns an url to a file that can be streamed by the player or null if - * this url is not known. - */ - String getStreamUrl(); - - /** - * Returns true if a local file that can be played is available. getFileUrl - * MUST return a non-null string if this method returns true. - */ - boolean localFileAvailable(); - - /** - * Returns true if a streamable file is available. getStreamUrl MUST return - * a non-null string if this method returns true. - */ - boolean streamAvailable(); - - /** - * Saves the current position of this object. Implementations can use the - * provided SharedPreference to save this information and retrieve it later - * via PlayableUtils.createInstanceFromPreferences. - * - * @param pref shared prefs that might be used to store this object - * @param newPosition new playback position in ms - * @param timestamp current time in ms - */ - void saveCurrentPosition(SharedPreferences pref, int newPosition, long timestamp); - - void setPosition(int newPosition); - - void setDuration(int newDuration); - - /** - * @param lastPlayedTimestamp timestamp in ms - */ - void setLastPlayedTime(long lastPlayedTimestamp); - - /** - * Is called by the PlaybackService when playback starts. - */ - void onPlaybackStart(); - - /** - * Is called by the PlaybackService when playback is completed. - */ - void onPlaybackCompleted(); - - /** - * Returns an integer that must be unique among all Playable classes. The - * return value is later used by PlayableUtils to determine the type of the - * Playable object that is restored. - */ - int getPlayableType(); - - void setChapters(List chapters); - - /** - * Provides utility methods for Playable objects. - */ - class PlayableUtils { - private static final String TAG = "PlayableUtils"; - - /** - * Restores a playable object from a sharedPreferences file. This method might load data from the database, - * depending on the type of playable that was restored. - * - * @param type An integer that represents the type of the Playable object - * that is restored. - * @param pref The SharedPreferences file from which the Playable object - * is restored - * @return The restored Playable object - */ - public static Playable createInstanceFromPreferences(Context context, int type, - SharedPreferences pref) { - Playable result = null; - // ADD new Playable types here: - switch (type) { - case FeedMedia.PLAYABLE_TYPE_FEEDMEDIA: - result = createFeedMediaInstance(pref); - break; - case ExternalMedia.PLAYABLE_TYPE_EXTERNAL_MEDIA: - result = createExternalMediaInstance(pref); - break; - case RemoteMedia.PLAYABLE_TYPE_REMOTE_MEDIA: - result = createRemoteMediaInstance(pref); - break; - } - if (result == null) { - Log.e(TAG, "Could not restore Playable object from preferences"); - } - return result; - } - - private static Playable createFeedMediaInstance(SharedPreferences pref) { - Playable result = null; - long mediaId = pref.getLong(FeedMedia.PREF_MEDIA_ID, -1); - if (mediaId != -1) { - result = DBReader.getFeedMedia(mediaId); - } - return result; - } - - private static Playable createExternalMediaInstance(SharedPreferences pref) { - Playable result = null; - String source = pref.getString(ExternalMedia.PREF_SOURCE_URL, null); - String mediaType = pref.getString(ExternalMedia.PREF_MEDIA_TYPE, null); - if (source != null && mediaType != null) { - int position = pref.getInt(ExternalMedia.PREF_POSITION, 0); - long lastPlayedTime = pref.getLong(ExternalMedia.PREF_LAST_PLAYED_TIME, 0); - result = new ExternalMedia(source, MediaType.valueOf(mediaType), - position, lastPlayedTime); - } - return result; - } - - private static Playable createRemoteMediaInstance(SharedPreferences pref) { - //TODO there's probably no point in restoring RemoteMedia from preferences, because we - //only care about it while it's playing on the cast device. - return null; - } - } - - class PlayableException extends Exception { - private static final long serialVersionUID = 1L; - - public PlayableException() { - super(); - } - - public PlayableException(String detailMessage, Throwable throwable) { - super(detailMessage, throwable); - } - - public PlayableException(String detailMessage) { - super(detailMessage); - } - - public PlayableException(Throwable throwable) { - super(throwable); - } - - } -} diff --git a/core/src/play/res/values/strings.xml b/core/src/play/res/values/strings.xml new file mode 100644 index 000000000..4fd13d2de --- /dev/null +++ b/core/src/play/res/values/strings.xml @@ -0,0 +1,4 @@ + + + @string/pref_cast_message_play_flavor +