Merge pull request #2001 from domingos86/flavors-optimization

Flavors code optimization
This commit is contained in:
Martin Fietz 2016-06-09 00:00:47 +02:00
commit 405630ee50
26 changed files with 518 additions and 6625 deletions

View File

@ -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;

View File

@ -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<String> hiddenDrawerItems = UserPreferences.getHiddenDrawerItems();
String[] navLabels = new String[NAV_DRAWER_TAGS.length];
final boolean[] checked = new boolean[NAV_DRAWER_TAGS.length];
for (int i = 0; i < NAV_DRAWER_TAGS.length; i++) {
String tag = NAV_DRAWER_TAGS[i];
navLabels[i] = navAdapter.getLabel(tag);
if (!hiddenDrawerItems.contains(tag)) {
checked[i] = true;
}
}
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle(R.string.drawer_preferences);
builder.setMultiChoiceItems(navLabels, checked, (dialog, which, isChecked) -> {
if (isChecked) {
hiddenDrawerItems.remove(NAV_DRAWER_TAGS[which]);
} else {
hiddenDrawerItems.add(NAV_DRAWER_TAGS[which]);
}
});
builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
UserPreferences.setHiddenDrawerItems(hiddenDrawerItems);
});
builder.setNegativeButton(R.string.cancel_label, null);
builder.create().show();
}
public boolean isDrawerOpen() {
return drawerLayout != null && navDrawer != null && drawerLayout.isDrawerOpen(navDrawer);
}
public List<Feed> getFeeds() {
return (navDrawerData != null) ? navDrawerData.feeds : null;
}
public void loadFragment(int index, Bundle args) {
Log.d(TAG, "loadFragment(index: " + index + ", args: " + args + ")");
if (index < navAdapter.getSubscriptionOffset()) {
String tag = navAdapter.getTags().get(index);
loadFragment(tag, args);
} else {
int pos = index - navAdapter.getSubscriptionOffset();
loadFeedFragmentByPosition(pos, args);
}
}
public void loadFragment(String tag, Bundle args) {
Log.d(TAG, "loadFragment(tag: " + tag + ", args: " + args + ")");
Fragment fragment = null;
switch (tag) {
case QueueFragment.TAG:
fragment = new QueueFragment();
break;
case EpisodesFragment.TAG:
fragment = new EpisodesFragment();
break;
case DownloadsFragment.TAG:
fragment = new DownloadsFragment();
break;
case PlaybackHistoryFragment.TAG:
fragment = new PlaybackHistoryFragment();
break;
case AddFeedFragment.TAG:
fragment = new AddFeedFragment();
break;
case SubscriptionFragment.TAG:
SubscriptionFragment subscriptionFragment = new SubscriptionFragment();
fragment = subscriptionFragment;
break;
default:
// default to the queue
tag = QueueFragment.TAG;
fragment = new QueueFragment();
args = null;
break;
}
currentTitle = navAdapter.getLabel(tag);
getSupportActionBar().setTitle(currentTitle);
saveLastNavFragment(tag);
if (args != null) {
fragment.setArguments(args);
}
loadFragment(fragment);
}
private void loadFeedFragmentByPosition(int relPos, Bundle args) {
if(relPos < 0) {
return;
}
Feed feed = itemAccess.getItem(relPos);
loadFeedFragmentById(feed.getId(), args);
}
public void loadFeedFragmentById(long feedId, Bundle args) {
Fragment fragment = ItemlistFragment.newInstance(feedId);
if(args != null) {
fragment.setArguments(args);
}
saveLastNavFragment(String.valueOf(feedId));
currentTitle = "";
getSupportActionBar().setTitle(currentTitle);
loadFragment(fragment);
}
private void loadFragment(Fragment fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();
// clear back stack
for (int i = 0; i < fragmentManager.getBackStackEntryCount(); i++) {
fragmentManager.popBackStack();
}
FragmentTransaction t = fragmentManager.beginTransaction();
t.replace(R.id.main_view, fragment, "main");
fragmentManager.popBackStack();
// TODO: we have to allow state loss here
// since this function can get called from an AsyncTask which
// could be finishing after our app has already committed state
// and is about to get shutdown. What we *should* do is
// not commit anything in an AsyncTask, but that's a bigger
// change than we want now.
t.commitAllowingStateLoss();
if (navAdapter != null) {
navAdapter.notifyDataSetChanged();
}
}
public void loadChildFragment(Fragment fragment) {
Validate.notNull(fragment);
FragmentManager fm = getSupportFragmentManager();
fm.beginTransaction()
.replace(R.id.main_view, fragment, "main")
.addToBackStack(null)
.commit();
}
public void dismissChildFragment() {
getSupportFragmentManager().popBackStack();
}
private int getSelectedNavListIndex() {
String currentFragment = getLastNavFragment();
if(currentFragment == null) {
// should not happen, but better safe than sorry
return -1;
}
int tagIndex = navAdapter.getTags().indexOf(currentFragment);
if(tagIndex >= 0) {
return tagIndex;
} else if(ArrayUtils.contains(NAV_DRAWER_TAGS, currentFragment)) {
// the fragment was just hidden
return -1;
} else { // last fragment was not a list, but a feed
long feedId = Long.parseLong(currentFragment);
if (navDrawerData != null) {
List<Feed> feeds = navDrawerData.feeds;
for (int i = 0; i < feeds.size(); i++) {
if (feeds.get(i).getId() == feedId) {
return i + navAdapter.getSubscriptionOffset();
}
}
}
return -1;
}
}
private AdapterView.OnItemClickListener navListClickListener = new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
int viewType = parent.getAdapter().getItemViewType(position);
if (viewType != NavListAdapter.VIEW_TYPE_SECTION_DIVIDER && position != selectedNavListIndex) {
loadFragment(position, null);
}
drawerLayout.closeDrawer(navDrawer);
}
};
private AdapterView.OnItemLongClickListener newListLongClickListener = new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
if(position < navAdapter.getTags().size()) {
showDrawerPreferencesDialog();
return true;
} else {
mPosition = position;
return false;
}
}
};
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
drawerToggle.syncState();
if (savedInstanceState != null) {
currentTitle = savedInstanceState.getString(SAVE_TITLE);
if (!drawerLayout.isDrawerOpen(navDrawer)) {
getSupportActionBar().setTitle(currentTitle);
}
selectedNavListIndex = getSelectedNavListIndex();
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
drawerToggle.onConfigurationChanged(newConfig);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(SAVE_TITLE, getSupportActionBar().getTitle().toString());
outState.putInt(SAVE_BACKSTACK_COUNT, getSupportFragmentManager().getBackStackEntryCount());
}
@Override
public void onStart() {
super.onStart();
EventDistributor.getInstance().register(contentUpdate);
EventBus.getDefault().register(this);
RatingDialog.init(this);
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
StorageUtils.checkStorageAvailability(this);
Intent intent = getIntent();
if (intent.hasExtra(EXTRA_FEED_ID) ||
(navDrawerData != null && intent.hasExtra(EXTRA_NAV_TYPE) &&
(intent.hasExtra(EXTRA_NAV_INDEX) || intent.hasExtra(EXTRA_FRAGMENT_TAG)))) {
handleNavIntent();
}
loadData();
RatingDialog.check();
}
@Override
protected void onStop() {
super.onStop();
EventDistributor.getInstance().unregister(contentUpdate);
EventBus.getDefault().unregister(this);
if(subscription != null) {
subscription.unsubscribe();
}
if(pd != null) {
pd.dismiss();
}
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
Glide.get(this).trimMemory(level);
}
@Override
public void onLowMemory() {
super.onLowMemory();
Glide.get(this).clearMemory();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
boolean retVal = super.onCreateOptionsMenu(menu);
switch (getLastNavFragment()) {
case QueueFragment.TAG:
case EpisodesFragment.TAG:
// requestCastButton(MenuItem.SHOW_AS_ACTION_IF_ROOM);
return retVal;
case DownloadsFragment.TAG:
case PlaybackHistoryFragment.TAG:
case AddFeedFragment.TAG:
case SubscriptionFragment.TAG:
return retVal;
default:
// requestCastButton(MenuItem.SHOW_AS_ACTION_NEVER);
return retVal;
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (drawerToggle.onOptionsItemSelected(item)) {
return true;
} else if (item.getItemId() == android.R.id.home) {
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
dismissChildFragment();
}
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
if(v.getId() != R.id.nav_list) {
return;
}
AdapterView.AdapterContextMenuInfo adapterInfo = (AdapterView.AdapterContextMenuInfo) menuInfo;
int position = adapterInfo.position;
if(position < navAdapter.getSubscriptionOffset()) {
return;
}
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.nav_feed_context, menu);
Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset());
menu.setHeaderTitle(feed.getTitle());
// episodes are not loaded, so we cannot check if the podcast has new or unplayed ones!
}
@Override
public boolean onContextItemSelected(MenuItem item) {
final int position = mPosition;
mPosition = -1; // reset
if(position < 0) {
return false;
}
Feed feed = navDrawerData.feeds.get(position - navAdapter.getSubscriptionOffset());
switch(item.getItemId()) {
case R.id.mark_all_seen_item:
DBWriter.markFeedSeen(feed.getId());
return true;
case R.id.mark_all_read_item:
DBWriter.markFeedRead(feed.getId());
return true;
case R.id.remove_item:
final FeedRemover remover = new FeedRemover(this, feed) {
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
if(getSelectedNavListIndex() == position) {
loadFragment(EpisodesFragment.TAG, null);
}
}
};
ConfirmationDialog conDialog = new ConfirmationDialog(this,
R.string.remove_feed_label,
R.string.feed_delete_confirmation_msg) {
@Override
public void onConfirmButtonPressed(
DialogInterface dialog) {
dialog.dismiss();
long mediaId = PlaybackPreferences.getCurrentlyPlayingFeedMediaId();
if (mediaId > 0 &&
FeedItemUtil.indexOfItemWithMediaId(feed.getItems(), mediaId) >= 0) {
Log.d(TAG, "Currently playing episode is about to be deleted, skipping");
remover.skipOnCompletion = true;
int playerStatus = PlaybackPreferences.getCurrentPlayerStatus();
if(playerStatus == PlaybackPreferences.PLAYER_STATUS_PLAYING) {
sendBroadcast(new Intent(
PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE));
}
}
remover.executeAsync();
}
};
conDialog.createNewDialog().show();
return true;
default:
return super.onContextItemSelected(item);
}
}
@Override
public void onBackPressed() {
if(isDrawerOpen()) {
drawerLayout.closeDrawer(navDrawer);
} else {
super.onBackPressed();
}
}
private DBReader.NavDrawerData navDrawerData;
private int selectedNavListIndex = 0;
private NavListAdapter.ItemAccess itemAccess = new NavListAdapter.ItemAccess() {
@Override
public int getCount() {
if (navDrawerData != null) {
return navDrawerData.feeds.size();
} else {
return 0;
}
}
@Override
public Feed getItem(int position) {
if (navDrawerData != null && 0 <= position && position < navDrawerData.feeds.size()) {
return navDrawerData.feeds.get(position);
} else {
return null;
}
}
@Override
public int getSelectedItemIndex() {
return selectedNavListIndex;
}
@Override
public int getQueueSize() {
return (navDrawerData != null) ? navDrawerData.queueSize : 0;
}
@Override
public int getNumberOfNewItems() {
return (navDrawerData != null) ? navDrawerData.numNewItems : 0;
}
@Override
public int getNumberOfDownloadedItems() {
return (navDrawerData != null) ? navDrawerData.numDownloadedItems : 0;
}
@Override
public int getReclaimableItems() {
return (navDrawerData != null) ? navDrawerData.reclaimableSpace : 0;
}
@Override
public int getFeedCounter(long feedId) {
return navDrawerData != null ? navDrawerData.feedCounters.get(feedId) : 0;
}
@Override
public int getFeedCounterSum() {
if(navDrawerData == null) {
return 0;
}
int sum = 0;
for(int counter : navDrawerData.feedCounters.values()) {
sum += counter;
}
return sum;
}
};
private void loadData() {
subscription = Observable.fromCallable(DBReader::getNavDrawerData)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
boolean handleIntent = (navDrawerData == null);
navDrawerData = result;
navAdapter.notifyDataSetChanged();
if (handleIntent) {
handleNavIntent();
}
}, error -> {
Log.e(TAG, Log.getStackTraceString(error));
});
}
public void onEvent(QueueEvent event) {
Log.d(TAG, "onEvent(" + event + ")");
// we are only interested in the number of queue items, not download status or position
if(event.action == QueueEvent.Action.DELETED_MEDIA ||
event.action == QueueEvent.Action.SORTED ||
event.action == QueueEvent.Action.MOVED) {
return;
}
loadData();
}
public void onEventMainThread(ProgressEvent event) {
Log.d(TAG, "onEvent(" + event + ")");
switch(event.action) {
case START:
pd = new ProgressDialog(this);
pd.setMessage(event.message);
pd.setIndeterminate(true);
pd.setCancelable(false);
pd.show();
break;
case END:
if(pd != null) {
pd.dismiss();
}
break;
}
}
private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if ((EVENTS & arg) != 0) {
Log.d(TAG, "Received contentUpdate Intent.");
loadData();
}
}
};
private void handleNavIntent() {
Log.d(TAG, "handleNavIntent()");
Intent intent = getIntent();
if (intent.hasExtra(EXTRA_FEED_ID) ||
(intent.hasExtra(EXTRA_NAV_TYPE) &&
(intent.hasExtra(EXTRA_NAV_INDEX) || intent.hasExtra(EXTRA_FRAGMENT_TAG)))) {
int index = intent.getIntExtra(EXTRA_NAV_INDEX, -1);
String tag = intent.getStringExtra(EXTRA_FRAGMENT_TAG);
Bundle args = intent.getBundleExtra(EXTRA_FRAGMENT_ARGS);
long feedId = intent.getLongExtra(EXTRA_FEED_ID, 0);
if (index >= 0) {
loadFragment(index, args);
} else if (tag != null) {
loadFragment(tag, args);
} else if(feedId > 0) {
loadFeedFragmentById(feedId, args);
}
}
setIntent(new Intent(MainActivity.this, MainActivity.class)); // to avoid handling the intent twice when the configuration changes
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
}

View File

@ -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<Downloader> 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;
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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;

View File

@ -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()) {

View File

@ -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();

View File

@ -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));
}
);
}
}
}
}

View File

@ -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<? extends Activity> 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<WifiConfiguration> networks = wifiservice.getConfiguredNetworks();
if (networks != null) {
selectedNetworks = new CheckBoxPreference[networks.size()];
List<String> 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<String> 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<String> 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<Integer> 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<String> folders = new ArrayList<>(mediaDirs.length);
List<CharSequence> 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("<html><small>" + choice
+ " [" + freeSpace + "]" + "</small></html>"));
}
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();
}
}

View File

@ -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;
});
}
}

View File

@ -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<Chapter> 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<Chapter> chapters) {
if(item != null) {
item.setChapters(chapters);
}
}
@Override
public Callable<String> 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<FeedMedia> CREATOR = new Parcelable.Creator<FeedMedia>() {
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);
// }
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<Integer, Integer> 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);
// }
//}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_cast_message">@string/pref_cast_message_free_flavor</string>
</resources>

View File

@ -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);

View File

@ -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);
}
};
}

View File

@ -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;
}
}
}

View File

@ -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 {

View File

@ -403,7 +403,8 @@
<string name="pref_known_issues">Known issues</string>
<string name="pref_no_browser_found">No web browser found.</string>
<string name="pref_cast_title">Chromecast support</string>
<string name="pref_cast_message">Enable support for remote media playback on Cast devices (such as Chromecast, Audio Speakers or Android TV)</string>
<string name="pref_cast_message_play_flavor">Enable support for remote media playback on Cast devices (such as Chromecast, Audio Speakers or Android TV)</string>
<string name="pref_cast_message_free_flavor">Chromecast requires third party proprietary libraries that are disabled in this version of AntennaPod</string>
<!-- Auto-Flattr dialog -->
<string name="auto_flattr_enable">Enable automatic flattring</string>

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<Chapter> 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<Chapter> 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);
}
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_cast_message">@string/pref_cast_message_play_flavor</string>
</resources>