Merge branch 'feedmanager_removal' into develop

Conflicts:
	src/de/danoeh/antennapod/activity/DownloadLogActivity.java
	src/de/danoeh/antennapod/activity/FeedItemlistActivity.java
	src/de/danoeh/antennapod/activity/ItemviewActivity.java
	src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java
	src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java
	src/de/danoeh/antennapod/activity/PreferenceActivity.java
	src/de/danoeh/antennapod/fragment/EpisodesFragment.java
	src/de/danoeh/antennapod/fragment/FeedlistFragment.java
	src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java
	src/de/danoeh/antennapod/fragment/ItemlistFragment.java
This commit is contained in:
daniel oeh 2013-08-10 16:10:32 +02:00
commit 8e16ad08c8
63 changed files with 9219 additions and 8112 deletions

View File

@ -19,7 +19,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:text="@string/no_feeds_label" />
android:gravity="center"/>
</LinearLayout>

View File

@ -16,7 +16,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:text="@string/no_items_label" />
android:gravity="center"/>
</LinearLayout>

View File

@ -15,7 +15,5 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:layout_gravity="center"
android:text="@string/no_feeds_label" />
android:layout_gravity="center"/>
</LinearLayout>

View File

@ -5,7 +5,6 @@ import android.content.res.Configuration;
import android.util.Log;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.PlaybackPreferences;
import de.danoeh.antennapod.preferences.UserPreferences;
@ -32,8 +31,6 @@ public class PodcastApp extends Application {
UserPreferences.createInstance(this);
PlaybackPreferences.createInstance(this);
EventDistributor.getInstance();
FeedManager manager = FeedManager.getInstance();
manager.loadDBData(getApplicationContext());
}
@Override

View File

@ -11,6 +11,7 @@ import android.content.res.TypedArray;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.view.ActionMode;
import android.util.Log;
@ -25,8 +26,8 @@ import android.widget.ListView;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.DownloadlistAdapter;
import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.download.DownloadRequest;
import de.danoeh.antennapod.service.download.DownloadService;
import de.danoeh.antennapod.storage.DownloadRequester;
@ -35,226 +36,227 @@ import de.danoeh.antennapod.storage.DownloadRequester;
* objects created by a DownloadObserver.
*/
public class DownloadActivity extends ActionBarActivity implements
ActionMode.Callback {
ActionMode.Callback {
private static final String TAG = "DownloadActivity";
private static final int MENU_SHOW_LOG = 0;
private static final int MENU_CANCEL_ALL_DOWNLOADS = 1;
private DownloadlistAdapter dla;
private DownloadRequester requester;
private static final String TAG = "DownloadActivity";
private static final int MENU_SHOW_LOG = 0;
private static final int MENU_CANCEL_ALL_DOWNLOADS = 1;
private DownloadlistAdapter dla;
private DownloadRequester requester;
private ActionMode mActionMode;
private DownloadStatus selectedDownload;
private ActionMode mActionMode;
private DownloadRequest selectedDownload;
private DownloadService downloadService = null;
boolean mIsBound;
private DownloadService downloadService = null;
boolean mIsBound;
private AsyncTask<Void, Void, Void> contentRefresher;
private AsyncTask<Void, Void, Void> contentRefresher;
private ListView listview;
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
setContentView(R.layout.listview_activity);
listview = (ListView) findViewById(R.id.listview);
if (AppConfig.DEBUG)
Log.d(TAG, "Creating Activity");
requester = DownloadRequester.getInstance();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
if (AppConfig.DEBUG)
Log.d(TAG, "Creating Activity");
requester = DownloadRequester.getInstance();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
protected void onPause() {
super.onPause();
unbindService(mConnection);
unregisterReceiver(contentChanged);
}
@Override
protected void onPause() {
super.onPause();
unbindService(mConnection);
unregisterReceiver(contentChanged);
}
@Override
protected void onResume() {
super.onResume();
registerReceiver(contentChanged, new IntentFilter(
DownloadService.ACTION_DOWNLOADS_CONTENT_CHANGED));
bindService(new Intent(this, DownloadService.class), mConnection, 0);
startContentRefresher();
if (dla != null) {
dla.notifyDataSetChanged();
}
}
@Override
protected void onResume() {
super.onResume();
registerReceiver(contentChanged, new IntentFilter(
DownloadService.ACTION_DOWNLOADS_CONTENT_CHANGED));
bindService(new Intent(this, DownloadService.class), mConnection, 0);
startContentRefresher();
if (dla != null) {
dla.notifyDataSetChanged();
}
}
@Override
protected void onStop() {
super.onStop();
if (AppConfig.DEBUG)
Log.d(TAG, "Stopping Activity");
stopContentRefresher();
}
@Override
protected void onStop() {
super.onStop();
if (AppConfig.DEBUG)
Log.d(TAG, "Stopping Activity");
stopContentRefresher();
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceDisconnected(ComponentName className) {
downloadService = null;
mIsBound = false;
Log.i(TAG, "Closed connection with DownloadService.");
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceDisconnected(ComponentName className) {
downloadService = null;
mIsBound = false;
Log.i(TAG, "Closed connection with DownloadService.");
}
public void onServiceConnected(ComponentName name, IBinder service) {
downloadService = ((DownloadService.LocalBinder) service)
.getService();
mIsBound = true;
if (AppConfig.DEBUG)
Log.d(TAG, "Connection to service established");
dla = new DownloadlistAdapter(DownloadActivity.this, 0,
downloadService.getDownloads());
listview.setAdapter(dla);
dla.notifyDataSetChanged();
}
};
public void onServiceConnected(ComponentName name, IBinder service) {
downloadService = ((DownloadService.LocalBinder) service)
.getService();
mIsBound = true;
if (AppConfig.DEBUG)
Log.d(TAG, "Connection to service established");
dla = new DownloadlistAdapter(DownloadActivity.this, 0,
downloadService.getDownloads());
listview.setAdapter(dla);
dla.notifyDataSetChanged();
}
};
@SuppressLint("NewApi")
private void startContentRefresher() {
if (contentRefresher != null) {
contentRefresher.cancel(true);
}
contentRefresher = new AsyncTask<Void, Void, Void>() {
private final int WAITING_INTERVALL = 1000;
@SuppressLint("NewApi")
private void startContentRefresher() {
if (contentRefresher != null) {
contentRefresher.cancel(true);
}
contentRefresher = new AsyncTask<Void, Void, Void>() {
private final int WAITING_INTERVALL = 1000;
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
if (dla != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Refreshing content automatically");
dla.notifyDataSetChanged();
}
}
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
if (dla != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Refreshing content automatically");
dla.notifyDataSetChanged();
}
}
@Override
protected Void doInBackground(Void... params) {
while (!isCancelled()) {
try {
Thread.sleep(WAITING_INTERVALL);
publishProgress();
} catch (InterruptedException e) {
return null;
}
}
return null;
}
};
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
contentRefresher.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
contentRefresher.execute();
}
}
@Override
protected Void doInBackground(Void... params) {
while (!isCancelled()) {
try {
Thread.sleep(WAITING_INTERVALL);
publishProgress();
} catch (InterruptedException e) {
return null;
}
}
return null;
}
};
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
contentRefresher.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
contentRefresher.execute();
}
}
private void stopContentRefresher() {
if (contentRefresher != null) {
contentRefresher.cancel(true);
}
}
private void stopContentRefresher() {
if (contentRefresher != null) {
contentRefresher.cancel(true);
}
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
listview.setOnItemLongClickListener(new OnItemLongClickListener() {
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
listview.setOnItemLongClickListener(new OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> arg0, View view,
int position, long id) {
DownloadStatus selection = dla.getItem(position).getStatus();
if (selection != null && mActionMode != null) {
mActionMode.finish();
}
dla.setSelectedItemIndex(position);
selectedDownload = selection;
mActionMode = startSupportActionMode(DownloadActivity.this);
return true;
}
@Override
public boolean onItemLongClick(AdapterView<?> arg0, View view,
int position, long id) {
DownloadRequest selection = dla.getItem(position)
.getDownloadRequest();
if (selection != null && mActionMode != null) {
mActionMode.finish();
}
dla.setSelectedItemIndex(position);
selectedDownload = selection;
mActionMode = startSupportActionMode(DownloadActivity.this);
return true;
}
});
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(Menu.NONE, MENU_SHOW_LOG, Menu.NONE,
R.string.show_download_log).setShowAsAction(
MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
menu.add(Menu.NONE, MENU_CANCEL_ALL_DOWNLOADS, Menu.NONE,
R.string.cancel_all_downloads_label).setShowAsAction(
MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, MENU_SHOW_LOG, Menu.NONE,
R.string.show_download_log),
MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, MENU_CANCEL_ALL_DOWNLOADS, Menu.NONE,
R.string.cancel_all_downloads_label),
MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
break;
case MENU_SHOW_LOG:
startActivity(new Intent(this, DownloadLogActivity.class));
break;
case MENU_CANCEL_ALL_DOWNLOADS:
requester.cancelAllDownloads(this);
break;
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
break;
case MENU_SHOW_LOG:
startActivity(new Intent(this, DownloadLogActivity.class));
break;
case MENU_CANCEL_ALL_DOWNLOADS:
requester.cancelAllDownloads(this);
break;
}
return true;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
if (!selectedDownload.isDone()) {
TypedArray drawables = obtainStyledAttributes(new int[] { R.attr.navigation_cancel });
menu.add(Menu.NONE, R.id.cancel_download_item, Menu.NONE,
R.string.cancel_download_label).setIcon(
drawables.getDrawable(0));
}
return true;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
if (selectedDownload != null) {
TypedArray drawables = obtainStyledAttributes(new int[]{R.attr.navigation_cancel});
menu.add(Menu.NONE, R.id.cancel_download_item, Menu.NONE,
R.string.cancel_download_label).setIcon(
drawables.getDrawable(0));
}
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
boolean handled = false;
switch (item.getItemId()) {
case R.id.cancel_download_item:
requester.cancelDownload(this, selectedDownload.getFeedFile());
handled = true;
break;
}
mActionMode.finish();
return handled;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
boolean handled = false;
switch (item.getItemId()) {
case R.id.cancel_download_item:
requester.cancelDownload(this, selectedDownload.getSource());
handled = true;
break;
}
mActionMode.finish();
return handled;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
selectedDownload = null;
dla.setSelectedItemIndex(DownloadlistAdapter.SELECTION_NONE);
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
selectedDownload = null;
dla.setSelectedItemIndex(DownloadlistAdapter.SELECTION_NONE);
}
private BroadcastReceiver contentChanged = new BroadcastReceiver() {
private BroadcastReceiver contentChanged = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (dla != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Refreshing content");
dla.notifyDataSetChanged();
}
}
};
@Override
public void onReceive(Context context, Intent intent) {
if (dla != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Refreshing content");
dla.notifyDataSetChanged();
}
}
};
}

View File

@ -1,27 +1,31 @@
package de.danoeh.antennapod.activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ListView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.DownloadLogAdapter;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.storage.DBReader;
import java.util.List;
/**
* Displays completed and failed downloads in a list. The data comes from the
* FeedManager.
* Displays completed and failed downloads in a list.
*/
public class DownloadLogActivity extends ActionBarActivity {
private static final String TAG = "DownloadLogActivity";
DownloadLogAdapter dla;
FeedManager manager;
private List<DownloadStatus> downloadLog;
private DownloadLogAdapter dla;
private ListView listview;
@ -30,13 +34,14 @@ public class DownloadLogActivity extends ActionBarActivity {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
setContentView(R.layout.listview_activity);
manager = FeedManager.getInstance();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
listview = (ListView) findViewById(R.layout.listview_activity);
dla = new DownloadLogAdapter(this);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
dla = new DownloadLogAdapter(this, itemAccess);
listview.setAdapter(dla);
loadData();
}
@Override
@ -69,12 +74,48 @@ public class DownloadLogActivity extends ActionBarActivity {
return true;
}
private void loadData() {
AsyncTask<Void, Void, List<DownloadStatus>> loadTask = new AsyncTask<Void, Void, List<DownloadStatus>>() {
@Override
protected List<DownloadStatus> doInBackground(Void... voids) {
return DBReader.getDownloadLog(DownloadLogActivity.this);
}
@Override
protected void onPostExecute(List<DownloadStatus> downloadStatuses) {
super.onPostExecute(downloadStatuses);
if (downloadStatuses != null) {
downloadLog = downloadStatuses;
if (dla != null) {
dla.notifyDataSetChanged();
}
} else {
Log.e(TAG, "Could not load download log");
}
}
};
loadTask.execute();
}
private DownloadLogAdapter.ItemAccess itemAccess = new DownloadLogAdapter.ItemAccess() {
@Override
public int getCount() {
return (downloadLog != null) ? downloadLog.size() : 0;
}
@Override
public DownloadStatus getItem(int position) {
return (downloadLog != null) ? downloadLog.get(position) : null;
}
};
private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if ((arg & EventDistributor.DOWNLOADLOG_UPDATE) != 0) {
dla.notifyDataSetChanged();
loadData();
}
}
};

View File

@ -1,5 +1,6 @@
package de.danoeh.antennapod.activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
@ -14,8 +15,8 @@ import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.util.LangUtils;
import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler;
@ -41,47 +42,66 @@ public class FeedInfoActivity extends ActionBarActivity {
setContentView(R.layout.feedinfo);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
long feedId = getIntent().getLongExtra(EXTRA_FEED_ID, -1);
FeedManager manager = FeedManager.getInstance();
feed = manager.getFeed(feedId);
if (feed != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Language is " + feed.getLanguage());
if (AppConfig.DEBUG)
Log.d(TAG, "Author is " + feed.getAuthor());
imgvCover = (ImageView) findViewById(R.id.imgvCover);
txtvTitle = (TextView) findViewById(R.id.txtvTitle);
txtvDescription = (TextView) findViewById(R.id.txtvDescription);
txtvLanguage = (TextView) findViewById(R.id.txtvLanguage);
txtvAuthor = (TextView) findViewById(R.id.txtvAuthor);
imgvCover.post(new Runnable() {
AsyncTask<Long, Void, Feed> loadTask = new AsyncTask<Long, Void, Feed>() {
@Override
public void run() {
ImageLoader.getInstance().loadThumbnailBitmap(
feed.getImage(), imgvCover);
@Override
protected Feed doInBackground(Long... params) {
return DBReader.getFeed(FeedInfoActivity.this, params[0]);
}
@Override
protected void onPostExecute(Feed result) {
super.onPostExecute(result);
if (result != null) {
feed = result;
if (feed != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Language is " + feed.getLanguage());
if (AppConfig.DEBUG)
Log.d(TAG, "Author is " + feed.getAuthor());
imgvCover = (ImageView) findViewById(R.id.imgvCover);
txtvTitle = (TextView) findViewById(R.id.txtvTitle);
txtvDescription = (TextView) findViewById(R.id.txtvDescription);
txtvLanguage = (TextView) findViewById(R.id.txtvLanguage);
txtvAuthor = (TextView) findViewById(R.id.txtvAuthor);
imgvCover.post(new Runnable() {
@Override
public void run() {
ImageLoader.getInstance().loadThumbnailBitmap(
feed.getImage(), imgvCover);
}
});
txtvTitle.setText(feed.getTitle());
txtvDescription.setText(feed.getDescription());
if (feed.getAuthor() != null) {
txtvAuthor.setText(feed.getAuthor());
}
if (feed.getLanguage() != null) {
txtvLanguage.setText(LangUtils
.getLanguageString(feed.getLanguage()));
}
supportInvalidateOptionsMenu();
}
} else {
Log.e(TAG, "Activity was started with invalid arguments");
}
});
txtvTitle.setText(feed.getTitle());
txtvDescription.setText(feed.getDescription());
if (feed.getAuthor() != null) {
txtvAuthor.setText(feed.getAuthor());
}
if (feed.getLanguage() != null) {
txtvLanguage.setText(LangUtils.getLanguageString(feed
.getLanguage()));
}
} else {
Log.e(TAG, "Activity was started with invalid arguments");
}
};
loadTask.execute(feedId);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = new MenuInflater(this);
inflater.inflate(R.menu.feedinfo, menu);
return true;
if (feed != null) {
MenuInflater inflater = new MenuInflater(this);
inflater.inflate(R.menu.feedinfo, menu);
return true;
} else {
return false;
}
}
@Override
@ -104,7 +124,8 @@ public class FeedInfoActivity extends ActionBarActivity {
return FeedMenuHandler.onOptionsItemClicked(this, item, feed);
} catch (DownloadRequestException e) {
e.printStackTrace();
DownloadRequestErrorDialogCreator.newRequestErrorDialog(this, e.getMessage());
DownloadRequestErrorDialogCreator.newRequestErrorDialog(this,
e.getMessage());
}
return super.onOptionsItemSelected(item);
}

View File

@ -4,9 +4,11 @@ import android.annotation.SuppressLint;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.TypedArray;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
@ -14,137 +16,173 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.Window;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.FeedRemover;
import de.danoeh.antennapod.dialog.ConfirmationDialog;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.fragment.ExternalPlayerFragment;
import de.danoeh.antennapod.fragment.FeedlistFragment;
import de.danoeh.antennapod.fragment.ItemlistFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.util.StorageUtils;
import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler;
/** Displays a List of FeedItems */
/**
* Displays a List of FeedItems
*/
public class FeedItemlistActivity extends ActionBarActivity {
private static final String TAG = "FeedItemlistActivity";
private static final String TAG = "FeedItemlistActivity";
private FeedManager manager;
/**
* The feed which the activity displays
*/
private Feed feed;
private ItemlistFragment filf;
private ExternalPlayerFragment externalPlayerFragment;
/** The feed which the activity displays */
private Feed feed;
private ItemlistFragment filf;
private ExternalPlayerFragment externalPlayerFragment;
@Override
public void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
StorageUtils.checkStorageAvailability(this);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
@Override
public void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
StorageUtils.checkStorageAvailability(this);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.feeditemlist_activity);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.feeditemlist_activity);
long feedId = getIntent().getLongExtra(
FeedlistFragment.EXTRA_SELECTED_FEED, -1);
if (feedId == -1) {
Log.e(TAG, "Received invalid feed selection.");
} else {
loadData(feedId);
}
manager = FeedManager.getInstance();
long feedId = getIntent().getLongExtra(
FeedlistFragment.EXTRA_SELECTED_FEED, -1);
if (feedId == -1)
Log.e(TAG, "Received invalid feed selection.");
}
feed = manager.getFeed(feedId);
setTitle(feed.getTitle());
private void loadData(long id) {
AsyncTask<Long, Void, Feed> loadTask = new AsyncTask<Long, Void, Feed>() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fT = fragmentManager.beginTransaction();
@Override
protected Feed doInBackground(Long... longs) {
if (AppConfig.DEBUG)
Log.d(TAG, "Loading feed data in background");
return DBReader.getFeed(FeedItemlistActivity.this, longs[0]);
}
filf = ItemlistFragment.newInstance(feed.getId());
fT.replace(R.id.feeditemlistFragment, filf);
@Override
protected void onPostExecute(Feed result) {
super.onPostExecute(result);
if (result != null) {
if (AppConfig.DEBUG) Log.d(TAG, "Finished loading feed data");
feed = result;
setTitle(feed.getTitle());
externalPlayerFragment = new ExternalPlayerFragment();
fT.replace(R.id.playerFragment, externalPlayerFragment);
fT.commit();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fT = fragmentManager.beginTransaction();
}
filf = ItemlistFragment.newInstance(feed.getId());
fT.replace(R.id.feeditemlistFragment, filf);
@Override
protected void onResume() {
super.onResume();
StorageUtils.checkStorageAvailability(this);
}
externalPlayerFragment = new ExternalPlayerFragment();
fT.replace(R.id.playerFragment, externalPlayerFragment);
fT.commit();
supportInvalidateOptionsMenu();
} else {
Log.e(TAG, "Error: Feed was null");
}
}
};
loadTask.execute(id);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
TypedArray drawables = obtainStyledAttributes(new int[] { R.attr.action_search });
menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
.setIcon(drawables.getDrawable(0))
.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
return FeedMenuHandler
.onCreateOptionsMenu(new MenuInflater(this), menu);
}
@Override
protected void onResume() {
super.onResume();
StorageUtils.checkStorageAvailability(this);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
return FeedMenuHandler.onPrepareOptionsMenu(menu, feed);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (feed != null) {
TypedArray drawables = obtainStyledAttributes(new int[]{R.attr.action_search});
MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
.setIcon(drawables.getDrawable(0)),
MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
return FeedMenuHandler
.onCreateOptionsMenu(new MenuInflater(this), menu);
} else {
return false;
}
}
@SuppressLint("NewApi")
@Override
public boolean onOptionsItemSelected(MenuItem item) {
try {
if (FeedMenuHandler.onOptionsItemClicked(this, item, feed)) {
filf.getListAdapter().notifyDataSetChanged();
} else {
switch (item.getItemId()) {
case R.id.remove_item:
final FeedRemover remover = new FeedRemover(
FeedItemlistActivity.this, feed) {
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
finish();
}
};
ConfirmationDialog conDialog = new ConfirmationDialog(this,
R.string.remove_feed_label,
R.string.feed_delete_confirmation_msg) {
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
return FeedMenuHandler.onPrepareOptionsMenu(menu, feed);
}
@Override
public void onConfirmButtonPressed(
DialogInterface dialog) {
dialog.dismiss();
remover.executeAsync();
}
};
conDialog.createNewDialog().show();
break;
case R.id.search_item:
onSearchRequested();
break;
case android.R.id.home:
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
break;
}
}
} catch (DownloadRequestException e) {
e.printStackTrace();
DownloadRequestErrorDialogCreator.newRequestErrorDialog(this,
e.getMessage());
}
return true;
}
@SuppressLint("NewApi")
@Override
public boolean onOptionsItemSelected(MenuItem item) {
try {
if (FeedMenuHandler.onOptionsItemClicked(this, item, feed)) {
filf.getListAdapter().notifyDataSetChanged();
} else {
switch (item.getItemId()) {
case R.id.remove_item:
final FeedRemover remover = new FeedRemover(
FeedItemlistActivity.this, feed) {
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
finish();
}
};
ConfirmationDialog conDialog = new ConfirmationDialog(this,
R.string.remove_feed_label,
R.string.feed_delete_confirmation_msg) {
@Override
public boolean onSearchRequested() {
Bundle bundle = new Bundle();
bundle.putLong(SearchActivity.EXTRA_FEED_ID, feed.getId());
startSearch(null, false, bundle, false);
return true;
}
@Override
public void onConfirmButtonPressed(
DialogInterface dialog) {
dialog.dismiss();
remover.executeAsync();
}
};
conDialog.createNewDialog().show();
break;
case R.id.search_item:
onSearchRequested();
break;
case android.R.id.home:
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
break;
}
}
} catch (DownloadRequestException e) {
e.printStackTrace();
DownloadRequestErrorDialogCreator.newRequestErrorDialog(this,
e.getMessage());
}
return true;
}
@Override
public boolean onSearchRequested() {
if (feed != null) {
Bundle bundle = new Bundle();
bundle.putLong(SearchActivity.EXTRA_FEED_ID, feed.getId());
startSearch(null, false, bundle, false);
return true;
} else {
return false;
}
}
}

View File

@ -1,7 +1,6 @@
package de.danoeh.antennapod.activity;
import java.text.DateFormat;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
@ -17,38 +16,43 @@ import android.widget.TextView;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.fragment.FeedlistFragment;
import de.danoeh.antennapod.fragment.ItemDescriptionFragment;
import de.danoeh.antennapod.fragment.ItemlistFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.StorageUtils;
import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler;
import java.text.DateFormat;
/** Displays a single FeedItem and provides various actions */
public class ItemviewActivity extends ActionBarActivity {
private static final String TAG = "ItemviewActivity";
private FeedManager manager;
private FeedItem item;
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | EventDistributor.DOWNLOAD_QUEUED;
// Widgets
private TextView txtvTitle;
private TextView txtvPublished;
private FeedItem item;
@Override
public void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
StorageUtils.checkStorageAvailability(this);
manager = FeedManager.getInstance();
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
getSupportActionBar().setDisplayShowTitleEnabled(false);
extractFeeditem();
populateUI();
EventDistributor.getInstance().register(contentUpdate);
long itemId = getIntent().getLongExtra(
ItemlistFragment.EXTRA_SELECTED_FEEDITEM, -1);
if (itemId == -1) {
Log.e(TAG, "Received invalid selection of either feeditem or feed.");
} else {
loadData(itemId);
}
}
@Override
@ -65,28 +69,38 @@ public class ItemviewActivity extends ActionBarActivity {
Log.d(TAG, "Stopping Activity");
}
/** Extracts FeedItem object the activity is supposed to display */
private void extractFeeditem() {
long itemId = getIntent().getLongExtra(
ItemlistFragment.EXTRA_SELECTED_FEEDITEM, -1);
long feedId = getIntent().getLongExtra(
FeedlistFragment.EXTRA_SELECTED_FEED, -1);
if (itemId == -1 || feedId == -1) {
Log.e(TAG, "Received invalid selection of either feeditem or feed.");
}
Feed feed = manager.getFeed(feedId);
item = manager.getFeedItem(itemId, feed);
if (AppConfig.DEBUG)
Log.d(TAG, "Title of item is " + item.getTitle());
if (AppConfig.DEBUG)
Log.d(TAG, "Title of feed is " + item.getFeed().getTitle());
}
private void loadData(long itemId) {
AsyncTask<Long, Void, FeedItem> loadTask = new AsyncTask<Long, Void, FeedItem>() {
@Override
protected FeedItem doInBackground(Long... longs) {
return DBReader.getFeedItem(ItemviewActivity.this, longs[0]);
}
@Override
protected void onPostExecute(FeedItem feedItem) {
super.onPostExecute(feedItem);
if (feedItem != null && feedItem.getFeed() != null) {
item = feedItem;
populateUI();
supportInvalidateOptionsMenu();
} else {
if (feedItem == null) {
Log.e(TAG, "Error: FeedItem was null");
} else if (feedItem.getFeed() == null) {
Log.e(TAG, "Error: Feed was null");
}
}
}
};
loadTask.execute(itemId);
}
private void populateUI() {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.feeditemview);
txtvTitle = (TextView) findViewById(R.id.txtvItemname);
txtvPublished = (TextView) findViewById(R.id.txtvPublished);
TextView txtvTitle = (TextView) findViewById(R.id.txtvItemname);
TextView txtvPublished = (TextView) findViewById(R.id.txtvPublished);
setTitle(item.getFeed().getTitle());
txtvPublished.setText(DateUtils.formatSameDayTime(item.getPubDate()
@ -105,9 +119,13 @@ public class ItemviewActivity extends ActionBarActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.feeditem, menu);
return true;
if (item != null) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.feeditem, menu);
return true;
} else {
return false;
}
}
@Override
@ -133,13 +151,28 @@ public class ItemviewActivity extends ActionBarActivity {
@Override
public boolean onPrepareOptionsMenu(final Menu menu) {
return FeedItemMenuHandler.onPrepareMenu(
new FeedItemMenuHandler.MenuInterface() {
new FeedItemMenuHandler.MenuInterface() {
@Override
public void setItemVisibility(int id, boolean visible) {
menu.findItem(id).setVisible(visible);
}
}, item, true);
@Override
public void setItemVisibility(int id, boolean visible) {
menu.findItem(id).setVisible(visible);
}
}, item, true, QueueAccess.NotInQueueAccess());
}
private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if ((EVENTS & arg) != 0) {
if (AppConfig.DEBUG)
Log.d(TAG, "Received contentUpdate Intent.");
if (item != null) {
loadData(item.getId());
}
}
}
};
}

View File

@ -20,13 +20,14 @@ import android.view.Window;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.fragment.EpisodesFragment;
import de.danoeh.antennapod.fragment.ExternalPlayerFragment;
import de.danoeh.antennapod.fragment.FeedlistFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.download.DownloadService;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DBTasks;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.StorageUtils;
@ -37,7 +38,6 @@ public class MainActivity extends ActionBarActivity {
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED
| EventDistributor.DOWNLOAD_QUEUED;
private FeedManager manager;
private ViewPager viewpager;
private TabsAdapter pagerAdapter;
private ExternalPlayerFragment externalPlayerFragment;
@ -49,7 +49,6 @@ public class MainActivity extends ActionBarActivity {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
StorageUtils.checkStorageAvailability(this);
manager = FeedManager.getInstance();
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.main);
@ -78,7 +77,7 @@ public class MainActivity extends ActionBarActivity {
if (!appLaunched && getIntent().getAction() != null
&& getIntent().getAction().equals(Intent.ACTION_MAIN)) {
appLaunched = true;
if (manager.getUnreadItemsSize(true) > 0) {
if (DBReader.getNumberOfUnreadItems(this) > 0) {
// select 'episodes' tab
getSupportActionBar().setSelectedNavigationItem(1);
}
@ -140,7 +139,7 @@ public class MainActivity extends ActionBarActivity {
startActivity(new Intent(this, AddFeedActivity.class));
return true;
case R.id.all_feed_refresh:
manager.refreshAllFeeds(this);
DBTasks.refreshAllFeeds(this, null);
return true;
case R.id.show_downloads:
startActivity(new Intent(this, DownloadActivity.class));
@ -171,9 +170,6 @@ public class MainActivity extends ActionBarActivity {
} else {
refreshAll.setVisible(true);
}
boolean hasFeeds = manager.getFeedsSize() > 0;
menu.findItem(R.id.all_feed_refresh).setVisible(hasFeeds);
return true;
}

View File

@ -20,7 +20,6 @@ import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.dialog.TimeDialog;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.util.Converter;
@ -38,8 +37,6 @@ public abstract class MediaplayerActivity extends ActionBarActivity
implements OnSeekBarChangeListener {
private static final String TAG = "MediaplayerActivity";
protected FeedManager manager;
protected PlaybackController controller;
protected TextView txtvPosition;
@ -148,7 +145,6 @@ public abstract class MediaplayerActivity extends ActionBarActivity
StorageUtils.checkStorageAvailability(this);
orientation = getResources().getConfiguration().orientation;
manager = FeedManager.getInstance();
getWindow().setFormat(PixelFormat.TRANSPARENT);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}

View File

@ -1,6 +1,7 @@
package de.danoeh.antennapod.activity;
import java.util.Date;
import java.util.List;
import android.annotation.SuppressLint;
import android.content.Intent;
@ -24,11 +25,11 @@ import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.MiroGuideItemlistAdapter;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.miroguide.conn.MiroGuideException;
import de.danoeh.antennapod.miroguide.conn.MiroGuideService;
import de.danoeh.antennapod.miroguide.model.MiroGuideChannel;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.storage.DownloadRequester;
@ -39,144 +40,161 @@ import de.danoeh.antennapod.storage.DownloadRequester;
public class MiroGuideChannelViewActivity extends ActionBarActivity {
private static final String TAG = "MiroGuideChannelViewActivity";
public static final String EXTRA_CHANNEL_ID = "id";
public static final String EXTRA_CHANNEL_URL = "url";
public static final String EXTRA_CHANNEL_ID = "id";
public static final String EXTRA_CHANNEL_URL = "url";
private RelativeLayout layoutContent;
private ProgressBar progLoading;
private TextView txtvTitle;
private TextView txtVDescription;
private ListView listEntries;
private RelativeLayout layoutContent;
private ProgressBar progLoading;
private TextView txtvTitle;
private TextView txtVDescription;
private ListView listEntries;
private long channelId;
private String channelUrl;
private MiroGuideChannel channel;
private long channelId;
private String channelUrl;
private MiroGuideChannel channel;
private volatile List<Feed> feeds;
@Override
protected void onPause() {
super.onPause();
channelLoader.cancel(true);
}
@Override
protected void onPause() {
super.onPause();
channelLoader.cancel(true);
}
@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.miroguide_channelview);
@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.miroguide_channelview);
layoutContent = (RelativeLayout) findViewById(R.id.layout_content);
progLoading = (ProgressBar) findViewById(R.id.progLoading);
txtvTitle = (TextView) findViewById(R.id.txtvTitle);
txtVDescription = (TextView) findViewById(R.id.txtvDescription);
listEntries = (ListView) findViewById(R.id.itemlist);
layoutContent = (RelativeLayout) findViewById(R.id.layout_content);
progLoading = (ProgressBar) findViewById(R.id.progLoading);
txtvTitle = (TextView) findViewById(R.id.txtvTitle);
txtVDescription = (TextView) findViewById(R.id.txtvDescription);
listEntries = (ListView) findViewById(R.id.itemlist);
channelId = getIntent().getLongExtra(EXTRA_CHANNEL_ID, -1);
channelUrl = getIntent().getStringExtra(EXTRA_CHANNEL_URL);
channelId = getIntent().getLongExtra(EXTRA_CHANNEL_ID, -1);
channelUrl = getIntent().getStringExtra(EXTRA_CHANNEL_URL);
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
channelLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
channelLoader.execute();
}
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
channelLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
channelLoader.execute();
}
}
}
/** Is used to load channel information asynchronously. */
private AsyncTask<Void, Void, Void> channelLoader = new AsyncTask<Void, Void, Void>() {
private static final String TAG = "ChannelLoader";
private Exception exception;
/**
* Is used to load channel information asynchronously.
*/
private AsyncTask<Void, Void, Void> channelLoader = new AsyncTask<Void, Void, Void>() {
private static final String TAG = "ChannelLoader";
private Exception exception;
@Override
protected Void doInBackground(Void... params) {
if (AppConfig.DEBUG)
Log.d(TAG, "Starting background task");
MiroGuideService service = new MiroGuideService();
try {
channel = service.getChannel(channelId);
} catch (MiroGuideException e) {
e.printStackTrace();
exception = e;
}
return null;
}
@Override
protected Void doInBackground(Void... params) {
if (AppConfig.DEBUG)
Log.d(TAG, "Starting background task");
feeds = DBReader.getFeedList(MiroGuideChannelViewActivity.this);
MiroGuideService service = new MiroGuideService();
try {
channel = service.getChannel(channelId);
} catch (MiroGuideException e) {
e.printStackTrace();
exception = e;
}
return null;
}
@SuppressLint("NewApi")
@Override
protected void onPostExecute(Void result) {
if (AppConfig.DEBUG)
Log.d(TAG, "Loading finished");
if (exception == null) {
txtvTitle.setText(channel.getName());
txtVDescription.setText(channel.getDescription());
@SuppressLint("NewApi")
@Override
protected void onPostExecute(Void result) {
if (AppConfig.DEBUG)
Log.d(TAG, "Loading finished");
if (exception == null) {
txtvTitle.setText(channel.getName());
txtVDescription.setText(channel.getDescription());
MiroGuideItemlistAdapter listAdapter = new MiroGuideItemlistAdapter(
MiroGuideChannelViewActivity.this, 0,
channel.getItems());
listEntries.setAdapter(listAdapter);
progLoading.setVisibility(View.GONE);
layoutContent.setVisibility(View.VISIBLE);
supportInvalidateOptionsMenu();
} else {
finish();
}
}
MiroGuideItemlistAdapter listAdapter = new MiroGuideItemlistAdapter(
MiroGuideChannelViewActivity.this, 0,
channel.getItems());
listEntries.setAdapter(listAdapter);
progLoading.setVisibility(View.GONE);
layoutContent.setVisibility(View.VISIBLE);
supportInvalidateOptionsMenu();
} else {
finish();
}
}
};
};
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = new MenuInflater(this);
inflater.inflate(R.menu.channelview, menu);
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = new MenuInflater(this);
inflater.inflate(R.menu.channelview, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
boolean channelLoaded = channel != null;
boolean beingDownloaded = channelLoaded
&& DownloadRequester.getInstance().isDownloadingFile(
channel.getDownloadUrl());
boolean notAdded = channelLoaded
&& !((FeedManager.getInstance().feedExists(
channel.getDownloadUrl()) || beingDownloaded));
menu.findItem(R.id.add_feed).setVisible(notAdded);
menu.findItem(R.id.visit_website_item).setVisible(
channelLoaded && channel.getWebsiteUrl() != null);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
boolean channelLoaded = channel != null;
boolean beingDownloaded = channelLoaded
&& DownloadRequester.getInstance().isDownloadingFile(
channel.getDownloadUrl());
boolean notAdded = channelLoaded
&& !((feedExists(
channel.getDownloadUrl()) || beingDownloaded));
menu.findItem(R.id.add_feed).setVisible(notAdded);
menu.findItem(R.id.visit_website_item).setVisible(
channelLoaded && channel.getWebsiteUrl() != null);
return true;
}
@SuppressLint("NewApi")
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
case R.id.visit_website_item:
Uri uri = Uri.parse(channel.getWebsiteUrl());
startActivity(new Intent(Intent.ACTION_VIEW, uri));
return true;
case R.id.add_feed:
try {
DownloadRequester.getInstance().downloadFeed(
this,
new Feed(channel.getDownloadUrl(), new Date(), channel
.getName()));
} catch (DownloadRequestException e) {
e.printStackTrace();
DownloadRequestErrorDialogCreator.newRequestErrorDialog(this,
e.getMessage());
}
Toast toast = Toast.makeText(this, R.string.miro_feed_added,
Toast.LENGTH_LONG);
toast.show();
supportInvalidateOptionsMenu();
return true;
default:
return false;
}
}
@SuppressLint("NewApi")
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
case R.id.visit_website_item:
Uri uri = Uri.parse(channel.getWebsiteUrl());
startActivity(new Intent(Intent.ACTION_VIEW, uri));
return true;
case R.id.add_feed:
try {
DownloadRequester.getInstance().downloadFeed(
this,
new Feed(channel.getDownloadUrl(), new Date(), channel
.getName()));
} catch (DownloadRequestException e) {
e.printStackTrace();
DownloadRequestErrorDialogCreator.newRequestErrorDialog(this,
e.getMessage());
}
Toast toast = Toast.makeText(this, R.string.miro_feed_added,
Toast.LENGTH_LONG);
toast.show();
supportInvalidateOptionsMenu();
return true;
default:
return false;
}
}
private boolean feedExists(String downloadUrl) {
if (feeds == null) {
return false;
}
for (Feed feed : feeds) {
if (feed.getDownload_url().equals(downloadUrl)) {
return true;
}
}
return false;
}
}

View File

@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu;
@ -24,126 +25,126 @@ import de.danoeh.antennapod.preferences.UserPreferences;
* Shows a list of available categories and offers a search button. If the user
* selects a category, the MiroGuideCategoryActivity is started.
*/
public class MiroGuideMainActivity extends ActionBarActivity implements AdapterView.OnItemClickListener{
private static final String TAG = "MiroGuideMainActivity";
public class MiroGuideMainActivity extends ActionBarActivity implements AdapterView.OnItemClickListener {
private static final String TAG = "MiroGuideMainActivity";
private static String[] categories;
private ArrayAdapter<String> listAdapter;
private static String[] categories;
private ArrayAdapter<String> listAdapter;
private TextView txtvStatus;
private TextView txtvStatus;
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.miroguide_categorylist);
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.miroguide_categorylist);
txtvStatus = (TextView) findViewById(android.R.id.empty);
txtvStatus = (TextView) findViewById(android.R.id.empty);
listView = (ListView) findViewById(android.R.id.list);
listView.setOnItemClickListener(this);
}
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
if (categories != null) {
createAdapter();
} else {
loadCategories();
}
}
@Override
protected void onResume() {
super.onResume();
if (categories != null) {
createAdapter();
} else {
loadCategories();
}
}
private void createAdapter() {
if (categories != null) {
listAdapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, categories);
txtvStatus.setText(R.string.no_items_label);
listView.setAdapter(listAdapter);
}
}
private void createAdapter() {
if (categories != null) {
listAdapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, categories);
txtvStatus.setText(R.string.no_items_label);
listView.setAdapter(listAdapter);
}
}
/**
* Launches an AsyncTask to load the available categories in the background.
*/
@SuppressLint("NewApi")
private void loadCategories() {
AsyncTask<Void, Void, Void> listLoader = new AsyncTask<Void, Void, Void>() {
/**
* Launches an AsyncTask to load the available categories in the background.
*/
@SuppressLint("NewApi")
private void loadCategories() {
AsyncTask<Void, Void, Void> listLoader = new AsyncTask<Void, Void, Void>() {
private String[] c;
private MiroGuideException exception;
private String[] c;
private MiroGuideException exception;
@Override
protected void onPostExecute(Void result) {
if (exception == null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Successfully loaded categories");
categories = c;
createAdapter();
} else {
Log.e(TAG, "Error happened while trying to load categories");
txtvStatus.setText(exception.getMessage());
}
}
@Override
protected void onPostExecute(Void result) {
if (exception == null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Successfully loaded categories");
categories = c;
createAdapter();
} else {
Log.e(TAG, "Error happened while trying to load categories");
txtvStatus.setText(exception.getMessage());
}
}
@Override
protected void onPreExecute() {
txtvStatus.setText(R.string.loading_categories_label);
}
@Override
protected void onPreExecute() {
txtvStatus.setText(R.string.loading_categories_label);
}
@Override
protected Void doInBackground(Void... params) {
MiroGuideService service = new MiroGuideService();
try {
c = service.getCategories();
} catch (MiroGuideException e) {
e.printStackTrace();
exception = e;
} finally {
service.close();
}
return null;
}
@Override
protected Void doInBackground(Void... params) {
MiroGuideService service = new MiroGuideService();
try {
c = service.getCategories();
} catch (MiroGuideException e) {
e.printStackTrace();
exception = e;
} finally {
service.close();
}
return null;
}
};
};
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
listLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
listLoader.execute();
}
}
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
listLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
listLoader.execute();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
.setIcon(
obtainStyledAttributes(
new int[] { R.attr.action_search })
.getDrawable(0))
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
.setIcon(
obtainStyledAttributes(
new int[]{R.attr.action_search})
.getDrawable(0)),
MenuItem.SHOW_AS_ACTION_IF_ROOM);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
case R.id.search_item:
onSearchRequested();
return true;
default:
return false;
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
case R.id.search_item:
onSearchRequested();
return true;
default:
return false;
}
}
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {

View File

@ -4,6 +4,7 @@ import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
@ -19,71 +20,71 @@ import de.danoeh.antennapod.preferences.UserPreferences;
* uses a MiroGuideChannelListFragment to display the results.
*/
public class MiroGuideSearchActivity extends ActionBarActivity {
private static final String TAG = "MiroGuideSearchActivity";
private static final String TAG = "MiroGuideSearchActivity";
private MiroGuideChannellistFragment listFragment;
private MiroGuideChannellistFragment listFragment;
@Override
protected void onCreate(Bundle arg0) {
setTheme(UserPreferences.getTheme());
super.onCreate(arg0);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.miroguidesearch);
}
@Override
protected void onCreate(Bundle arg0) {
setTheme(UserPreferences.getTheme());
super.onCreate(arg0);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.miroguidesearch);
}
@Override
protected void onResume() {
super.onResume();
Intent intent = getIntent();
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
getSupportActionBar()
.setSubtitle(
getString(R.string.search_term_label) + "\""
+ query + "\"");
handleSearchRequest(query);
}
}
@Override
protected void onResume() {
super.onResume();
Intent intent = getIntent();
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
getSupportActionBar()
.setSubtitle(
getString(R.string.search_term_label) + "\""
+ query + "\"");
handleSearchRequest(query);
}
}
private void handleSearchRequest(String query) {
if (AppConfig.DEBUG)
Log.d(TAG, "Performing search");
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
listFragment = MiroGuideChannellistFragment.newInstance("name", query,
"name");
ft.replace(R.id.channellistFragment, listFragment);
ft.commit();
}
private void handleSearchRequest(String query) {
if (AppConfig.DEBUG)
Log.d(TAG, "Performing search");
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
listFragment = MiroGuideChannellistFragment.newInstance("name", query,
"name");
ft.replace(R.id.channellistFragment, listFragment);
ft.commit();
}
@Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
}
@Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
.setIcon(
obtainStyledAttributes(
new int[] { R.attr.action_search })
.getDrawable(0))
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
.setIcon(
obtainStyledAttributes(
new int[]{R.attr.action_search})
.getDrawable(0)),
MenuItem.SHOW_AS_ACTION_IF_ROOM);
return true;
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
case R.id.search_item:
onSearchRequested();
return true;
default:
return false;
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
case R.id.search_item:
onSearchRequested();
return true;
default:
return false;
}
}
}

View File

@ -20,9 +20,10 @@ import android.widget.ProgressBar;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.download.DownloadRequest;
import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.service.download.Downloader;
import de.danoeh.antennapod.service.download.DownloaderCallback;
import de.danoeh.antennapod.service.download.HttpDownloader;
@ -44,7 +45,7 @@ import de.danoeh.antennapod.util.URLChecker;
public abstract class OnlineFeedViewActivity extends ActionBarActivity {
private static final String TAG = "OnlineFeedViewActivity";
private static final String ARG_FEEDURL = "arg.feedurl";
public static final int RESULT_ERROR = 2;
private Feed feed;
@ -69,7 +70,7 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
@Override
protected void onStop() {
super.onStop();
if (downloader != null && downloader.getStatus().isDone() == false) {
if (downloader != null && !downloader.isFinished()) {
downloader.cancel();
}
}
@ -81,7 +82,7 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
@Override
public void run() {
DownloadStatus status = downloader.getStatus();
DownloadStatus status = downloader.getResult();
if (status != null) {
if (!status.isCancelled()) {
if (status.isSuccessful()) {
@ -117,10 +118,13 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
FileNameGenerator.generateFileName(feed.getDownload_url()))
.toString();
feed.setFile_url(fileUrl);
DownloadStatus status = new DownloadStatus(feed, "OnlineFeed");
DownloadRequest request = new DownloadRequest(feed.getFile_url(),
feed.getDownload_url(), "OnlineFeed", 0, Feed.FEEDFILETYPE_FEED);
/* TODO update
HttpDownloader httpDownloader = new HttpDownloader(downloaderCallback,
status);
request);
httpDownloader.start();
*/
}
/** Displays a progress indicator. */
@ -215,13 +219,14 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
} else {
builder.setMessage(R.string.error_msg_prefix);
}
builder.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.setNeutralButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {

View File

@ -1,31 +1,41 @@
package de.danoeh.antennapod.activity;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v7.app.ActionBarActivity;
import android.view.*;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.mobeta.android.dslv.DragSortListView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DBTasks;
import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.util.UndoBarController;
import java.util.List;
public class OrganizeQueueActivity extends ActionBarActivity implements
UndoBarController.UndoListener {
private static final String TAG = "OrganizeQueueActivity";
private static final int MENU_ID_ACCEPT = 2;
private List<FeedItem> queue;
private OrganizeAdapter adapter;
private UndoBarController undoBarController;
@ -42,13 +52,37 @@ public class OrganizeQueueActivity extends ActionBarActivity implements
listView.setDropListener(dropListener);
listView.setRemoveListener(removeListener);
adapter = new OrganizeAdapter(this);
listView.setAdapter(adapter);
loadData();
undoBarController = new UndoBarController(findViewById(R.id.undobar),
this);
}
private void loadData() {
AsyncTask<Void, Void, List<FeedItem>> loadTask = new AsyncTask<Void, Void, List<FeedItem>>() {
@Override
protected List<FeedItem> doInBackground(Void... voids) {
return DBReader.getQueue(OrganizeQueueActivity.this);
}
@Override
protected void onPostExecute(List<FeedItem> feedItems) {
super.onPostExecute(feedItems);
if (feedItems != null) {
queue = feedItems;
if (adapter == null) {
adapter = new OrganizeAdapter(OrganizeQueueActivity.this);
listView.setAdapter(adapter);
}
adapter.notifyDataSetChanged();
} else {
Log.e(TAG, "Queue was null");
}
}
};
loadTask.execute();
}
@Override
protected void onPause() {
super.onPause();
@ -58,8 +92,7 @@ public class OrganizeQueueActivity extends ActionBarActivity implements
@Override
protected void onStop() {
super.onStop();
FeedManager.getInstance().autodownloadUndownloadedItems(
getApplicationContext());
DBTasks.autodownloadUndownloadedItems(getApplicationContext());
}
@Override
@ -73,9 +106,7 @@ public class OrganizeQueueActivity extends ActionBarActivity implements
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if (((EventDistributor.QUEUE_UPDATE | EventDistributor.FEED_LIST_UPDATE) & arg) != 0) {
if (adapter != null) {
adapter.notifyDataSetChanged();
}
loadData();
}
}
};
@ -84,9 +115,10 @@ public class OrganizeQueueActivity extends ActionBarActivity implements
@Override
public void drop(int from, int to) {
FeedManager manager = FeedManager.getInstance();
manager.moveQueueItem(OrganizeQueueActivity.this, from, to, false);
adapter.notifyDataSetChanged();
final FeedItem item = queue.remove(from);
queue.add(to, item);
adapter.notifyDataSetChanged();
DBWriter.moveQueueItem(OrganizeQueueActivity.this, from, to, true);
}
};
@ -94,9 +126,8 @@ public class OrganizeQueueActivity extends ActionBarActivity implements
@Override
public void remove(int which) {
FeedManager manager = FeedManager.getInstance();
FeedItem item = (FeedItem) listView.getAdapter().getItem(which);
manager.removeQueueItem(OrganizeQueueActivity.this, item, false);
DBWriter.removeQueueItem(OrganizeQueueActivity.this, item.getId(), true);
undoBarController.showUndoBar(false,
getString(R.string.removed_from_queue), new UndoToken(item,
which));
@ -124,22 +155,18 @@ public class OrganizeQueueActivity extends ActionBarActivity implements
public void onUndo(Parcelable token) {
// Perform the undo
UndoToken undoToken = (UndoToken) token;
FeedItem feedItem = undoToken.getFeedItem();
long itemId = undoToken.getFeedItemId();
int position = undoToken.getPosition();
FeedManager manager = FeedManager.getInstance();
manager.addQueueItemAt(OrganizeQueueActivity.this, feedItem, position,
false);
DBWriter.addQueueItemAt(OrganizeQueueActivity.this, itemId, position, false);
}
private static class OrganizeAdapter extends BaseAdapter {
private Context context;
private FeedManager manager = FeedManager.getInstance();
private OrganizeQueueActivity organizeQueueActivity;
public OrganizeAdapter(Context context) {
public OrganizeAdapter(OrganizeQueueActivity organizeQueueActivity) {
super();
this.context = context;
this.organizeQueueActivity = organizeQueueActivity;
}
@Override
@ -149,7 +176,7 @@ public class OrganizeQueueActivity extends ActionBarActivity implements
if (convertView == null) {
holder = new Holder();
LayoutInflater inflater = (LayoutInflater) context
LayoutInflater inflater = (LayoutInflater) organizeQueueActivity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(
R.layout.organize_queue_listitem, null);
@ -186,13 +213,20 @@ public class OrganizeQueueActivity extends ActionBarActivity implements
@Override
public int getCount() {
int queueSize = manager.getQueueSize(true);
return queueSize;
if (organizeQueueActivity.queue != null) {
return organizeQueueActivity.queue.size();
} else {
return 0;
}
}
@Override
public FeedItem getItem(int position) {
return manager.getQueueItemAtIndex(position, true);
if (organizeQueueActivity.queue != null) {
return organizeQueueActivity.queue.get(position);
} else {
return null;
}
}
@Override
@ -208,7 +242,6 @@ public class OrganizeQueueActivity extends ActionBarActivity implements
private int position;
public UndoToken(FeedItem item, int position) {
FeedManager manager = FeedManager.getInstance();
this.itemId = item.getId();
this.feedId = item.getFeed().getId();
this.position = position;
@ -240,9 +273,8 @@ public class OrganizeQueueActivity extends ActionBarActivity implements
out.writeInt(position);
}
public FeedItem getFeedItem() {
FeedManager manager = FeedManager.getInstance();
return manager.getFeedItem(itemId, feedId);
public long getFeedItemId() {
return itemId;
}
public int getPosition() {

View File

@ -11,9 +11,9 @@ import android.view.Menu;
import android.view.MenuItem;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.storage.DBWriter;
public class PlaybackHistoryActivity extends ActionBarActivity {
private static final String TAG = "PlaybackHistoryActivity";
@ -36,7 +36,7 @@ public class PlaybackHistoryActivity extends ActionBarActivity {
startActivity(intent);
return true;
case R.id.clear_history_item:
FeedManager.getInstance().clearPlaybackHistory(this);
DBWriter.clearPlaybackHistory(this);
return true;
}
return super.onOptionsItemSelected(item);

View File

@ -25,29 +25,30 @@ import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.asynctask.OpmlExportWorker;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.util.flattr.FlattrUtils;
/** The main preference activity */
/**
* The main preference activity
*/
public class PreferenceActivity extends android.preference.PreferenceActivity {
private static final String TAG = "PreferenceActivity";
private static final String TAG = "PreferenceActivity";
private static final String PREF_FLATTR_THIS_APP = "prefFlattrThisApp";
private static final String PREF_FLATTR_AUTH = "pref_flattr_authenticate";
private static final String PREF_FLATTR_REVOKE = "prefRevokeAccess";
private static final String PREF_OPML_EXPORT = "prefOpmlExport";
private static final String PREF_ABOUT = "prefAbout";
private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir";
private static final String AUTO_DL_PREF_SCREEN = "prefAutoDownloadSettings";
private static final String PREF_FLATTR_THIS_APP = "prefFlattrThisApp";
private static final String PREF_FLATTR_AUTH = "pref_flattr_authenticate";
private static final String PREF_FLATTR_REVOKE = "prefRevokeAccess";
private static final String PREF_OPML_EXPORT = "prefOpmlExport";
private static final String PREF_ABOUT = "prefAbout";
private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir";
private static final String AUTO_DL_PREF_SCREEN = "prefAutoDownloadSettings";
private CheckBoxPreference[] selectedNetworks;
private CheckBoxPreference[] selectedNetworks;
@SuppressWarnings("deprecation")
@Override
public void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
@SuppressWarnings("deprecation")
@Override
public void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
if (android.os.Build.VERSION.SDK_INT >= 11) {
getActionBar().setDisplayHomeAsUpEnabled(true);
@ -57,332 +58,319 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
findPreference(PREF_FLATTR_THIS_APP).setOnPreferenceClickListener(
new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
new FlattrClickWorker(PreferenceActivity.this,
FlattrUtils.APP_URL).executeAsync();
@Override
public boolean onPreferenceClick(Preference preference) {
new FlattrClickWorker(PreferenceActivity.this,
FlattrUtils.APP_URL).executeAsync();
return true;
}
});
return true;
}
});
findPreference(PREF_FLATTR_REVOKE).setOnPreferenceClickListener(
new OnPreferenceClickListener() {
findPreference(PREF_FLATTR_REVOKE).setOnPreferenceClickListener(
new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
FlattrUtils.revokeAccessToken(PreferenceActivity.this);
checkItemVisibility();
return true;
}
@Override
public boolean onPreferenceClick(Preference preference) {
FlattrUtils.revokeAccessToken(PreferenceActivity.this);
checkItemVisibility();
return true;
}
});
});
findPreference(PREF_ABOUT).setOnPreferenceClickListener(
new OnPreferenceClickListener() {
findPreference(PREF_ABOUT).setOnPreferenceClickListener(
new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
PreferenceActivity.this.startActivity(new Intent(
PreferenceActivity.this, AboutActivity.class));
return true;
}
@Override
public boolean onPreferenceClick(Preference preference) {
PreferenceActivity.this.startActivity(new Intent(
PreferenceActivity.this, AboutActivity.class));
return true;
}
});
});
findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener(
new OnPreferenceClickListener() {
findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener(
new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
if (FeedManager.getInstance().getFeedsSize() > 0) {
new OpmlExportWorker(PreferenceActivity.this)
.executeAsync();
}
return true;
}
});
@Override
public boolean onPreferenceClick(Preference preference) {
new OpmlExportWorker(PreferenceActivity.this)
.executeAsync();
findPreference(PREF_CHOOSE_DATA_DIR).setOnPreferenceClickListener(
new OnPreferenceClickListener() {
return true;
}
});
@Override
public boolean onPreferenceClick(Preference preference) {
startActivityForResult(
new Intent(PreferenceActivity.this,
DirectoryChooserActivity.class),
DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED);
return true;
}
});
findPreference(UserPreferences.PREF_THEME)
.setOnPreferenceChangeListener(
new OnPreferenceChangeListener() {
findPreference(PREF_CHOOSE_DATA_DIR).setOnPreferenceClickListener(
new OnPreferenceClickListener() {
@Override
public boolean onPreferenceChange(
Preference preference, Object newValue) {
Intent i = getIntent();
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK
| Intent.FLAG_ACTIVITY_NEW_TASK);
finish();
startActivity(i);
return true;
}
});
findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER)
.setOnPreferenceChangeListener(
new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
startActivityForResult(
new Intent(PreferenceActivity.this,
DirectoryChooserActivity.class),
DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED);
return true;
}
});
findPreference(UserPreferences.PREF_THEME)
.setOnPreferenceChangeListener(
new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(
Preference preference, Object newValue) {
if (newValue instanceof Boolean) {
setSelectedNetworksEnabled((Boolean) newValue);
return true;
} else {
return false;
}
}
});
findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE)
.setOnPreferenceChangeListener(
new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(
Preference preference, Object newValue) {
Intent i = getIntent();
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK
| Intent.FLAG_ACTIVITY_NEW_TASK);
finish();
startActivity(i);
return true;
}
});
findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER)
.setOnPreferenceChangeListener(
new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(
Preference preference, Object newValue) {
if (newValue instanceof String) {
setEpisodeCacheSizeText(Integer
.valueOf((String) newValue));
}
return true;
}
});
findPreference(UserPreferences.PREF_ENABLE_AUTODL)
.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceChange(
Preference preference, Object newValue) {
if (newValue instanceof Boolean) {
setSelectedNetworksEnabled((Boolean) newValue);
return true;
} else {
return false;
}
}
});
findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE)
.setOnPreferenceChangeListener(
new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
checkItemVisibility();
return true;
}
});
buildUpdateIntervalPreference();
buildAutodownloadSelectedNetworsPreference();
setSelectedNetworksEnabled(UserPreferences
.isEnableAutodownloadWifiFilter());
}
@Override
public boolean onPreferenceChange(Preference preference, Object o) {
checkItemVisibility();
return true;
}
});
buildUpdateIntervalPreference();
buildAutodownloadSelectedNetworsPreference();
setSelectedNetworksEnabled(UserPreferences
.isEnableAutodownloadWifiFilter());
private void buildUpdateIntervalPreference() {
ListPreference pref = (ListPreference) findPreference(UserPreferences.PREF_UPDATE_INTERVAL);
String[] values = getResources().getStringArray(
R.array.update_intervall_values);
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] = getString(R.string.pref_update_interval_hours_manual);
break;
case 1:
entries[x] = v
+ " "
+ getString(R.string.pref_update_interval_hours_singular);
break;
default:
entries[x] = v + " "
+ getString(R.string.pref_update_interval_hours_plural);
break;
}
}
}
pref.setEntries(entries);
private void buildUpdateIntervalPreference() {
ListPreference pref = (ListPreference) findPreference(UserPreferences.PREF_UPDATE_INTERVAL);
String[] values = getResources().getStringArray(
R.array.update_intervall_values);
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] = getString(R.string.pref_update_interval_hours_manual);
break;
case 1:
entries[x] = v
+ " "
+ getString(R.string.pref_update_interval_hours_singular);
break;
default:
entries[x] = v + " "
+ getString(R.string.pref_update_interval_hours_plural);
break;
}
}
}
pref.setEntries(entries);
private void setSelectedNetworksEnabled(boolean b) {
if (selectedNetworks != null) {
for (Preference p : selectedNetworks) {
p.setEnabled(b);
}
}
}
}
@Override
protected void onResume() {
super.onResume();
checkItemVisibility();
setEpisodeCacheSizeText(UserPreferences.getEpisodeCacheSize());
setDataFolderText();
}
private void setSelectedNetworksEnabled(boolean b) {
if (selectedNetworks != null) {
for (Preference p : selectedNetworks) {
p.setEnabled(b);
}
}
}
@SuppressWarnings("deprecation")
private void checkItemVisibility() {
@Override
protected void onResume() {
super.onResume();
checkItemVisibility();
setEpisodeCacheSizeText(UserPreferences.getEpisodeCacheSize());
setDataFolderText();
}
boolean hasFlattrToken = FlattrUtils.hasToken();
@SuppressWarnings("deprecation")
private void checkItemVisibility() {
findPreference(PREF_FLATTR_AUTH).setEnabled(!hasFlattrToken);
findPreference(PREF_FLATTR_REVOKE).setEnabled(hasFlattrToken);
boolean hasFlattrToken = FlattrUtils.hasToken();
findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER)
.setEnabled(UserPreferences.isEnableAutodownload());
setSelectedNetworksEnabled(UserPreferences.isEnableAutodownload()
&& UserPreferences.isEnableAutodownloadWifiFilter());
findPreference(PREF_FLATTR_AUTH).setEnabled(!hasFlattrToken);
findPreference(PREF_FLATTR_REVOKE).setEnabled(hasFlattrToken);
}
findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER)
.setEnabled(UserPreferences.isEnableAutodownload());
setSelectedNetworksEnabled(UserPreferences.isEnableAutodownload()
&& UserPreferences.isEnableAutodownloadWifiFilter());
private void setEpisodeCacheSizeText(int cacheSize) {
String s;
if (cacheSize == getResources().getInteger(
R.integer.episode_cache_size_unlimited)) {
s = getString(R.string.pref_episode_cache_unlimited);
} else {
s = Integer.toString(cacheSize)
+ getString(R.string.episodes_suffix);
}
findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE).setSummary(s);
}
}
private void setDataFolderText() {
File f = UserPreferences.getDataFolder(this, null);
if (f != null) {
findPreference(PREF_CHOOSE_DATA_DIR)
.setSummary(f.getAbsolutePath());
}
}
private void setEpisodeCacheSizeText(int cacheSize) {
String s;
if (cacheSize == getResources().getInteger(
R.integer.episode_cache_size_unlimited)) {
s = getString(R.string.pref_episode_cache_unlimited);
} else {
s = Integer.toString(cacheSize)
+ getString(R.string.episodes_suffix);
}
findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE).setSummary(s);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
return true;
}
private void setDataFolderText() {
File f = UserPreferences.getDataFolder(this, null);
if (f != null) {
findPreference(PREF_CHOOSE_DATA_DIR)
.setSummary(f.getAbsolutePath());
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
break;
default:
return false;
}
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
return true;
}
@Override
protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
theme.applyStyle(UserPreferences.getTheme(), true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
break;
default:
return false;
}
return true;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) {
String dir = data
.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR);
if (AppConfig.DEBUG)
Log.d(TAG, "Setting data folder");
UserPreferences.setDataFolder(dir);
}
}
@Override
protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
theme.applyStyle(UserPreferences.getTheme(), true);
}
private void buildAutodownloadSelectedNetworsPreference() {
if (selectedNetworks != null) {
clearAutodownloadSelectedNetworsPreference();
}
// get configured networks
WifiManager wifiservice = (WifiManager) getSystemService(Context.WIFI_SERVICE);
List<WifiConfiguration> networks = wifiservice.getConfiguredNetworks();
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) {
String dir = data
.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR);
if (AppConfig.DEBUG)
Log.d(TAG, "Setting data folder");
UserPreferences.setDataFolder(dir);
}
}
if (networks != null) {
selectedNetworks = new CheckBoxPreference[networks.size()];
List<String> prefValues = Arrays.asList(UserPreferences
.getAutodownloadSelectedNetworks());
PreferenceScreen prefScreen = (PreferenceScreen) findPreference(AUTO_DL_PREF_SCREEN);
OnPreferenceClickListener clickListener = new OnPreferenceClickListener() {
private void buildAutodownloadSelectedNetworsPreference() {
if (selectedNetworks != null) {
clearAutodownloadSelectedNetworsPreference();
}
// get configured networks
WifiManager wifiservice = (WifiManager) getSystemService(Context.WIFI_SERVICE);
List<WifiConfiguration> networks = wifiservice.getConfiguredNetworks();
@Override
public boolean onPreferenceClick(Preference preference) {
if (preference instanceof CheckBoxPreference) {
String key = preference.getKey();
ArrayList<String> prefValuesList = new ArrayList<String>(
Arrays.asList(UserPreferences
.getAutodownloadSelectedNetworks()));
boolean newValue = ((CheckBoxPreference) preference)
.isChecked();
if (AppConfig.DEBUG)
Log.d(TAG, "Selected network " + key
+ ". New state: " + newValue);
if (networks != null) {
selectedNetworks = new CheckBoxPreference[networks.size()];
List<String> prefValues = Arrays.asList(UserPreferences
.getAutodownloadSelectedNetworks());
PreferenceScreen prefScreen = (PreferenceScreen) findPreference(AUTO_DL_PREF_SCREEN);
OnPreferenceClickListener clickListener = new OnPreferenceClickListener() {
int index = prefValuesList.indexOf(key);
if (index >= 0 && newValue == false) {
// remove network
prefValuesList.remove(index);
} else if (index < 0 && newValue == true) {
prefValuesList.add(key);
}
@Override
public boolean onPreferenceClick(Preference preference) {
if (preference instanceof CheckBoxPreference) {
String key = preference.getKey();
ArrayList<String> prefValuesList = new ArrayList<String>(
Arrays.asList(UserPreferences
.getAutodownloadSelectedNetworks()));
boolean newValue = ((CheckBoxPreference) preference)
.isChecked();
if (AppConfig.DEBUG)
Log.d(TAG, "Selected network " + key
+ ". New state: " + newValue);
UserPreferences.setAutodownloadSelectedNetworks(
PreferenceActivity.this, 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);
int index = prefValuesList.indexOf(key);
if (index >= 0 && newValue == false) {
// remove network
prefValuesList.remove(index);
} else if (index < 0 && newValue == true) {
prefValuesList.add(key);
}
CheckBoxPreference pref = new CheckBoxPreference(this);
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");
}
}
UserPreferences.setAutodownloadSelectedNetworks(
PreferenceActivity.this, 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);
private void clearAutodownloadSelectedNetworsPreference() {
if (selectedNetworks != null) {
PreferenceScreen prefScreen = (PreferenceScreen) findPreference(AUTO_DL_PREF_SCREEN);
CheckBoxPreference pref = new CheckBoxPreference(this);
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");
}
}
for (int i = 0; i < selectedNetworks.length; i++) {
if (selectedNetworks[i] != null) {
prefScreen.removePreference(selectedNetworks[i]);
}
}
}
}
private void clearAutodownloadSelectedNetworsPreference() {
if (selectedNetworks != null) {
PreferenceScreen prefScreen = (PreferenceScreen) findPreference(AUTO_DL_PREF_SCREEN);
@SuppressWarnings("deprecation")
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
Preference preference) {
super.onPreferenceTreeClick(preferenceScreen, preference);
if (preference != null)
if (preference instanceof PreferenceScreen)
if (((PreferenceScreen) preference).getDialog() != null)
((PreferenceScreen) preference)
.getDialog()
.getWindow()
.getDecorView()
.setBackgroundDrawable(
this.getWindow().getDecorView()
.getBackground().getConstantState()
.newDrawable());
return false;
}
for (int i = 0; i < selectedNetworks.length; i++) {
if (selectedNetworks[i] != null) {
prefScreen.removePreference(selectedNetworks[i]);
}
}
}
}
@SuppressWarnings("deprecation")
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
Preference preference) {
super.onPreferenceTreeClick(preferenceScreen, preference);
if (preference != null)
if (preference instanceof PreferenceScreen)
if (((PreferenceScreen) preference).getDialog() != null)
((PreferenceScreen) preference)
.getDialog()
.getWindow()
.getDecorView()
.setBackgroundDrawable(
this.getWindow().getDecorView()
.getBackground().getConstantState()
.newDrawable());
return false;
}
}

View File

@ -1,11 +1,12 @@
package de.danoeh.antennapod.activity;
import java.util.ArrayList;
import java.util.List;
import android.annotation.SuppressLint;
import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Menu;
@ -20,150 +21,152 @@ import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.SearchlistAdapter;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.feed.FeedSearcher;
import de.danoeh.antennapod.storage.FeedSearcher;
import de.danoeh.antennapod.feed.SearchResult;
import de.danoeh.antennapod.fragment.FeedlistFragment;
import de.danoeh.antennapod.fragment.ItemlistFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
/** Displays the results when the user searches for FeedItems or Feeds. */
public class SearchActivity extends ActionBarActivity implements AdapterView.OnItemClickListener{
private static final String TAG = "SearchActivity";
/**
* Displays the results when the user searches for FeedItems or Feeds.
*/
public class SearchActivity extends ActionBarActivity implements AdapterView.OnItemClickListener {
private static final String TAG = "SearchActivity";
public static final String EXTRA_FEED_ID = "de.danoeh.antennapod.searchactivity.extra.feedId";
public static final String EXTRA_FEED_ID = "de.danoeh.antennapod.searchactivity.extra.feedId";
private SearchlistAdapter searchAdapter;
private ArrayList<SearchResult> content;
private SearchlistAdapter searchAdapter;
private List<SearchResult> content;
/** Feed that is being searched or null if the search is global. */
private Feed selectedFeed;
/**
* ID of the feed that is being searched or null if the search is global.
*/
private long feedID;
private ListView listView;
private TextView txtvStatus;
private TextView txtvStatus;
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.searchlist);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.searchlist);
listView = (ListView) findViewById(android.R.id.list);
txtvStatus = (TextView) findViewById(android.R.id.empty);
txtvStatus = (TextView) findViewById(android.R.id.empty);
listView.setOnItemClickListener(this);
}
@Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
}
@Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
}
@Override
protected void onResume() {
super.onResume();
Intent intent = getIntent();
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
Bundle extra = intent.getBundleExtra(SearchManager.APP_DATA);
if (extra != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Found bundle extra");
long feedId = extra.getLong(EXTRA_FEED_ID);
selectedFeed = FeedManager.getInstance().getFeed(feedId);
}
if (AppConfig.DEBUG)
Log.d(TAG, "Starting search");
String query = intent.getStringExtra(SearchManager.QUERY);
getSupportActionBar()
.setSubtitle(
getString(R.string.search_term_label) + "\""
+ query + "\"");
handleSearchRequest(query);
}
}
@Override
protected void onResume() {
super.onResume();
Intent intent = getIntent();
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
Bundle extra = intent.getBundleExtra(SearchManager.APP_DATA);
if (extra != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Found bundle extra");
feedID = extra.getLong(EXTRA_FEED_ID);
}
if (AppConfig.DEBUG)
Log.d(TAG, "Starting search");
String query = intent.getStringExtra(SearchManager.QUERY);
getSupportActionBar()
.setSubtitle(
getString(R.string.search_term_label) + "\""
+ query + "\"");
handleSearchRequest(query);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
.setIcon(
obtainStyledAttributes(
new int[] { R.attr.action_search })
.getDrawable(0))
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
.setIcon(
obtainStyledAttributes(
new int[]{R.attr.action_search})
.getDrawable(0)),
(MenuItem.SHOW_AS_ACTION_IF_ROOM));
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
return true;
case R.id.search_item:
onSearchRequested();
return true;
default:
return false;
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
return true;
case R.id.search_item:
onSearchRequested();
return true;
default:
return false;
}
}
@Override
public boolean onSearchRequested() {
Bundle extra = null;
if (selectedFeed != null) {
extra = new Bundle();
extra.putLong(EXTRA_FEED_ID, selectedFeed.getId());
}
startSearch(null, false, extra, false);
return true;
}
@Override
public boolean onSearchRequested() {
Bundle extra = null;
if (feedID != 0) {
extra = new Bundle();
extra.putLong(EXTRA_FEED_ID, feedID);
}
startSearch(null, false, extra, false);
return true;
}
@SuppressLint({ "NewApi", "NewApi" })
private void handleSearchRequest(final String query) {
if (searchAdapter != null) {
searchAdapter.clear();
searchAdapter.notifyDataSetChanged();
}
txtvStatus.setText(R.string.search_status_searching);
@SuppressLint({"NewApi", "NewApi"})
private void handleSearchRequest(final String query) {
if (searchAdapter != null) {
searchAdapter.clear();
searchAdapter.notifyDataSetChanged();
}
txtvStatus.setText(R.string.search_status_searching);
Thread thread = new Thread() {
Thread thread = new Thread() {
@Override
public void run() {
Log.d(TAG, "Starting background work");
final ArrayList<SearchResult> result = FeedSearcher
.performSearch(SearchActivity.this, query, selectedFeed);
if (SearchActivity.this != null) {
SearchActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "Starting background work");
final List<SearchResult> result = FeedSearcher
.performSearch(SearchActivity.this, query, feedID);
if (SearchActivity.this != null) {
SearchActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
if (AppConfig.DEBUG)
Log.d(TAG, "Background work finished");
if (AppConfig.DEBUG)
Log.d(TAG, "Found " + result.size()
+ " results");
content = result;
@Override
public void run() {
if (AppConfig.DEBUG)
Log.d(TAG, "Background work finished");
if (AppConfig.DEBUG)
Log.d(TAG, "Found " + result.size()
+ " results");
content = result;
searchAdapter = new SearchlistAdapter(
SearchActivity.this, 0, content);
listView.setAdapter(searchAdapter);
searchAdapter.notifyDataSetChanged();
if (content.isEmpty()) {
txtvStatus
.setText(R.string.search_status_no_results);
}
}
});
}
}
};
thread.start();
searchAdapter = new SearchlistAdapter(
SearchActivity.this, 0, content);
listView.setAdapter(searchAdapter);
searchAdapter.notifyDataSetChanged();
if (content.isEmpty()) {
txtvStatus
.setText(R.string.search_status_no_results);
}
}
});
}
}
};
thread.start();
}
}
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {

View File

@ -8,21 +8,22 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedImage;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.util.DownloadError;
/** Displays a list of DownloadStatus entries. */
public class DownloadLogAdapter extends BaseAdapter {
private Context context;
private FeedManager manager = FeedManager.getInstance();
public DownloadLogAdapter(Context context) {
private ItemAccess itemAccess;
public DownloadLogAdapter(Context context, ItemAccess itemAccess) {
super();
this.itemAccess = itemAccess;
this.context = context;
}
@ -91,12 +92,12 @@ public class DownloadLogAdapter extends BaseAdapter {
@Override
public int getCount() {
return manager.getDownloadLogSize();
return itemAccess.getCount();
}
@Override
public DownloadStatus getItem(int position) {
return manager.getDownloadStatusFromLogAtIndex(position);
return itemAccess.getItem(position);
}
@Override
@ -104,4 +105,9 @@ public class DownloadLogAdapter extends BaseAdapter {
return position;
}
public static interface ItemAccess {
public int getCount();
public DownloadStatus getItem(int position);
}
}

View File

@ -10,11 +10,12 @@ import android.widget.ArrayAdapter;
import android.widget.ProgressBar;
import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedFile;
import de.danoeh.antennapod.feed.FeedImage;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.service.download.DownloadRequest;
import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.service.download.Downloader;
import de.danoeh.antennapod.util.Converter;
import de.danoeh.antennapod.util.ThemeUtils;
@ -33,8 +34,7 @@ public class DownloadlistAdapter extends ArrayAdapter<Downloader> {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Holder holder;
DownloadStatus status = getItem(position).getStatus();
FeedFile feedFile = status.getFeedFile();
DownloadRequest request = getItem(position).getDownloadRequest();
// Inflate layout
if (convertView == null) {
holder = new Holder();
@ -62,31 +62,16 @@ public class DownloadlistAdapter extends ArrayAdapter<Downloader> {
} else {
convertView.setBackgroundResource(0);
}
String titleText = null;
if (feedFile.getClass() == FeedMedia.class) {
titleText = ((FeedMedia) feedFile).getItem().getTitle();
} else if (feedFile.getClass() == Feed.class) {
titleText = ((Feed) feedFile).getTitle();
} else if (feedFile.getClass() == FeedImage.class) {
FeedImage image = (FeedImage) feedFile;
if (image.getFeed() != null) {
titleText = convertView.getResources().getString(
R.string.image_of_prefix)
+ image.getFeed().getTitle();
} else {
titleText = ((FeedImage) feedFile).getTitle();
}
holder.title.setText(request.getTitle());
if (request.getStatusMsg() != 0) {
holder.message.setText(request.getStatusMsg());
}
holder.title.setText(titleText);
if (status.getStatusMsg() != 0) {
holder.message.setText(status.getStatusMsg());
}
String strDownloaded = Converter.byteToString(status.getSoFar());
if (status.getSize() != DownloadStatus.SIZE_UNKNOWN) {
strDownloaded += " / " + Converter.byteToString(status.getSize());
holder.percent.setText(status.getProgressPercent() + "%");
holder.progbar.setProgress(status.getProgressPercent());
String strDownloaded = Converter.byteToString(request.getSoFar());
if (request.getSize() != DownloadStatus.SIZE_UNKNOWN) {
strDownloaded += " / " + Converter.byteToString(request.getSize());
holder.percent.setText(request.getProgressPercent() + "%");
holder.progbar.setProgress(request.getProgressPercent());
holder.percent.setVisibility(View.VISIBLE);
} else {
holder.progbar.setProgress(0);

View File

@ -14,7 +14,6 @@ import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.Converter;
@ -30,17 +29,18 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
public static final int GROUP_POS_UNREAD = 1;
private Context context;
private FeedManager manager = FeedManager.getInstance();
private ItemAccess itemAccess;
private ActionButtonCallback feedItemActionCallback;
private OnGroupActionClicked groupActionCallback;
public ExternalEpisodesListAdapter(Context context,
ActionButtonCallback callback,
OnGroupActionClicked groupActionCallback) {
OnGroupActionClicked groupActionCallback,
ItemAccess itemAccess) {
super();
this.context = context;
this.itemAccess = itemAccess;
this.feedItemActionCallback = callback;
this.groupActionCallback = groupActionCallback;
}
@ -53,10 +53,10 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
@Override
public FeedItem getChild(int groupPosition, int childPosition) {
if (groupPosition == GROUP_POS_QUEUE) {
return manager.getQueueItemAtIndex(childPosition, true);
return itemAccess.getQueueItemAt(childPosition);
} else if (groupPosition == GROUP_POS_UNREAD) {
return manager.getUnreadItemAtIndex(childPosition, true);
}
return itemAccess.getUnreadItemAt(childPosition);
}
return null;
}
@ -200,9 +200,9 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
@Override
public int getChildrenCount(int groupPosition) {
if (groupPosition == GROUP_POS_QUEUE) {
return manager.getQueueSize(true);
return itemAccess.getQueueSize();
} else if (groupPosition == GROUP_POS_UNREAD) {
return manager.getUnreadItemsSize(true);
return itemAccess.getUnreadItemsSize();
}
return 0;
}
@ -210,7 +210,7 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
@Override
public int getGroupCount() {
// Hide 'unread items' group if empty
if (manager.getUnreadItemsSize(true) > 0) {
if (itemAccess.getUnreadItemsSize() > 0) {
return 2;
} else {
return 1;
@ -264,8 +264,8 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
@Override
public boolean isEmpty() {
return manager.getUnreadItemsSize(true) == 0
&& manager.getQueueSize(true) == 0;
return itemAccess.getUnreadItemsSize() == 0
&& itemAccess.getQueueSize() == 0;
}
@Override
@ -287,4 +287,11 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
public void onClick(long groupId);
}
public static interface ItemAccess {
public int getQueueSize();
public int getUnreadItemsSize();
public FeedItem getQueueItemAt(int position);
public FeedItem getUnreadItemAt(int position);
}
}

View File

@ -11,23 +11,31 @@ import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.storage.FeedItemStatistics;
import de.danoeh.antennapod.util.ThemeUtils;
public class FeedlistAdapter extends BaseAdapter {
private static final String TAG = "FeedlistAdapter";
private Context context;
private FeedManager manager = FeedManager.getInstance();
protected ItemAccess itemAccess;
private int selectedItemIndex;
private ImageLoader imageLoader;
public static final int SELECTION_NONE = -1;
public FeedlistAdapter(Context context) {
public FeedlistAdapter(Context context, ItemAccess itemAccess) {
super();
if (context == null) {
throw new IllegalArgumentException("context must not be null");
}
if (itemAccess == null) {
throw new IllegalArgumentException("itemAccess must not be null");
}
this.context = context;
this.itemAccess = itemAccess;
selectedItemIndex = SELECTION_NONE;
imageLoader = ImageLoader.getInstance();
}
@ -36,6 +44,7 @@ public class FeedlistAdapter extends BaseAdapter {
public View getView(int position, View convertView, ViewGroup parent) {
final Holder holder;
final Feed feed = getItem(position);
final FeedItemStatistics feedItemStatistics = itemAccess.getFeedItemStatistics(position);
// Inflate Layout
if (convertView == null) {
@ -75,42 +84,40 @@ public class FeedlistAdapter extends BaseAdapter {
}
holder.title.setText(feed.getTitle());
int numOfItems = feed.getNumOfItems(true);
if (DownloadRequester.getInstance().isDownloadingFile(feed)) {
holder.lastUpdate.setText(R.string.refreshing_label);
} else {
if (numOfItems > 0) {
holder.lastUpdate.setText(convertView.getResources().getString(
R.string.most_recent_prefix)
+ DateUtils.getRelativeTimeSpanString(
feed.getItemAtIndex(true, 0).getPubDate().getTime(),
System.currentTimeMillis(), 0, 0));
} else {
holder.lastUpdate.setText("");
}
}
holder.numberOfEpisodes.setText(numOfItems
+ convertView.getResources()
.getString(R.string.episodes_suffix));
int newItems = feed.getNumOfNewItems();
int inProgressItems = feed.getNumOfStartedItems();
if (feedItemStatistics != null) {
if (DownloadRequester.getInstance().isDownloadingFile(feed)) {
holder.lastUpdate.setText(R.string.refreshing_label);
} else {
if (feedItemStatistics.getNumberOfItems() > 0) {
holder.lastUpdate.setText(convertView.getResources().getString(
R.string.most_recent_prefix)
+ DateUtils.getRelativeTimeSpanString(
feedItemStatistics.getLastUpdate().getTime(),
System.currentTimeMillis(), 0, 0));
} else {
holder.lastUpdate.setText("");
}
}
holder.numberOfEpisodes.setText(feedItemStatistics.getNumberOfItems()
+ convertView.getResources()
.getString(R.string.episodes_suffix));
if (newItems > 0) {
holder.newEpisodes.setText(Integer.toString(newItems));
holder.newEpisodesLabel.setVisibility(View.VISIBLE);
} else {
holder.newEpisodesLabel.setVisibility(View.INVISIBLE);
}
if (inProgressItems > 0) {
holder.inProgressEpisodes
.setText(Integer.toString(inProgressItems));
holder.inProgressEpisodesLabel.setVisibility(View.VISIBLE);
} else {
holder.inProgressEpisodesLabel.setVisibility(View.INVISIBLE);
}
if (feedItemStatistics.getNumberOfNewItems() > 0) {
holder.newEpisodes.setText(Integer.toString(feedItemStatistics.getNumberOfNewItems()));
holder.newEpisodesLabel.setVisibility(View.VISIBLE);
} else {
holder.newEpisodesLabel.setVisibility(View.INVISIBLE);
}
if (feedItemStatistics.getNumberOfInProgressItems() > 0) {
holder.inProgressEpisodes
.setText(Integer.toString(feedItemStatistics.getNumberOfInProgressItems()));
holder.inProgressEpisodesLabel.setVisibility(View.VISIBLE);
} else {
holder.inProgressEpisodesLabel.setVisibility(View.INVISIBLE);
}
}
final String imageUrl = (feed.getImage() != null) ? feed.getImage()
.getFile_url() : null;
holder.image.setTag(imageUrl);
@ -145,12 +152,12 @@ public class FeedlistAdapter extends BaseAdapter {
@Override
public int getCount() {
return manager.getFeedsSize();
return itemAccess.getCount();
}
@Override
public Feed getItem(int position) {
return manager.getFeedAtIndex(position);
return itemAccess.getItem(position);
}
@Override
@ -158,4 +165,11 @@ public class FeedlistAdapter extends BaseAdapter {
return position;
}
public interface ItemAccess {
int getCount();
Feed getItem(int position);
FeedItemStatistics getFeedItemStatistics(int position);
}
}

View File

@ -14,13 +14,14 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.feed.MediaType;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.Converter;
import de.danoeh.antennapod.util.ThemeUtils;
import java.util.Iterator;
/** List adapter for items of feeds that the user has already subscribed to. */
public class InternalFeedItemlistAdapter extends DefaultFeedItemlistAdapter {
@ -31,7 +32,7 @@ public class InternalFeedItemlistAdapter extends DefaultFeedItemlistAdapter {
public static final int SELECTION_NONE = -1;
public InternalFeedItemlistAdapter(Context context,
DefaultFeedItemlistAdapter.ItemAccess itemAccess,
ItemAccess itemAccess,
ActionButtonCallback callback, boolean showFeedtitle) {
super(context, itemAccess);
this.callback = callback;
@ -155,7 +156,7 @@ public class InternalFeedItemlistAdapter extends DefaultFeedItemlistAdapter {
}
holder.lenSize.setVisibility(View.VISIBLE);
if (FeedManager.getInstance().isInQueue(item)) {
if (((ItemAccess) itemAccess).isInQueue(item)) {
holder.inPlaylist.setVisibility(View.VISIBLE);
} else {
holder.inPlaylist.setVisibility(View.GONE);
@ -224,4 +225,8 @@ public class InternalFeedItemlistAdapter extends DefaultFeedItemlistAdapter {
notifyDataSetChanged();
}
public static interface ItemAccess extends DefaultFeedItemlistAdapter.ItemAccess {
public boolean isInQueue(FeedItem item);
}
}

View File

@ -7,7 +7,9 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.os.AsyncTask;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.storage.DBWriter;
import java.util.concurrent.ExecutionException;
/** Removes a feed in the background. */
public class FeedRemover extends AsyncTask<Void, Void, Void> {
@ -23,9 +25,14 @@ public class FeedRemover extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
FeedManager manager = FeedManager.getInstance();
manager.deleteFeed(context, feed);
return null;
try {
DBWriter.deleteFeed(context, feed.getId()).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return null;
}
@Override

View File

@ -14,9 +14,9 @@ import android.os.AsyncTask;
import android.util.Log;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.opml.OpmlWriter;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.storage.DBReader;
/** Writes an OPML file into the export directory in the background. */
public class OpmlExportWorker extends AsyncTask<Void, Void, Void> {
@ -51,8 +51,7 @@ public class OpmlExportWorker extends AsyncTask<Void, Void, Void> {
}
try {
FileWriter writer = new FileWriter(output);
opmlWriter.writeDocument(Arrays.asList(FeedManager.getInstance().getFeedsArray()),
writer);
opmlWriter.writeDocument(DBReader.getFeedList(context), writer);
writer.close();
} catch (IOException e) {
e.printStackTrace();

View File

@ -92,7 +92,7 @@ public class EventDistributor extends Observable {
super.addObserver(observer);
if (!(observer instanceof EventListener)) {
throw new IllegalArgumentException(
"Observer must be instance of FeedManager.EventListener");
"Observer must be instance of EventListener");
}
}

View File

@ -170,16 +170,7 @@ public class Feed extends FeedFile {
} else {
return download_url;
}
}
/** Calls cacheDescriptions on all items. */
protected void cacheDescriptionsOfItems() {
if (items != null) {
for (FeedItem item : items) {
item.cacheDescriptions();
}
}
}
}
public void updateFromOther(Feed other) {
super.updateFromOther(other);
@ -284,12 +275,12 @@ public class Feed extends FeedFile {
this.image = image;
}
List<FeedItem> getItems() {
public List<FeedItem> getItems() {
return items;
}
public void setItems(ArrayList<FeedItem> items) {
this.items = Collections.synchronizedList(items);
public void setItems(List<FeedItem> list) {
this.items = Collections.synchronizedList(list);
}
/** Returns an array that contains all the feeditems of this feed. */

View File

@ -9,7 +9,7 @@ import org.apache.commons.io.IOUtils;
import de.danoeh.antennapod.asynctask.ImageLoader;
;
public class FeedImage extends FeedFile implements
ImageLoader.ImageWorkerTaskResource {

View File

@ -4,279 +4,276 @@ import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.util.ShownotesProvider;
/**
* Data Object for a XML message
*
*
* @author daniel
*
*/
public class FeedItem extends FeedComponent implements
ImageLoader.ImageWorkerTaskResource {
ImageLoader.ImageWorkerTaskResource, ShownotesProvider {
/** The id/guid that can be found in the rss/atom feed. Might not be set. */
private String itemIdentifier;
private String title;
/**
* The description of a feeditem. This field should only be set by the
* parser.
*/
private String description;
/**
* The content of the content-encoded tag of a feeditem. This field should
* only be set by the parser.
*/
private String contentEncoded;
/**
* The id/guid that can be found in the rss/atom feed. Might not be set.
*/
private String itemIdentifier;
private String title;
/**
* The description of a feeditem.
*/
private String description;
/**
* The content of the content-encoded tag of a feeditem.
*/
private String contentEncoded;
private SoftReference<String> cachedDescription;
private SoftReference<String> cachedContentEncoded;
private String link;
private Date pubDate;
private FeedMedia media;
private String link;
private Date pubDate;
private FeedMedia media;
private Feed feed;
private boolean read;
private String paymentLink;
private List<Chapter> chapters;
private Feed feed;
private long feedId;
public FeedItem() {
this.read = true;
}
private boolean read;
private String paymentLink;
private List<Chapter> chapters;
public void updateFromOther(FeedItem other) {
super.updateFromOther(other);
if (other.title != null) {
title = other.title;
}
if (other.getDescription() != null) {
description = other.getDescription();
}
if (other.getContentEncoded() != null) {
contentEncoded = other.contentEncoded;
}
if (other.link != null) {
link = other.link;
}
if (other.pubDate != null && other.pubDate != pubDate) {
pubDate = other.pubDate;
}
if (other.media != null) {
if (media == null) {
media = other.media;
} else if (media.compareWithOther(other)) {
media.updateFromOther(other);
}
}
if (other.paymentLink != null) {
paymentLink = other.paymentLink;
}
if (other.chapters != null) {
if (chapters == null) {
chapters = other.chapters;
}
}
}
public FeedItem() {
this.read = true;
}
/**
* Moves the 'description' and 'contentEncoded' field of feeditem to their
* SoftReference fields.
*/
protected void cacheDescriptions() {
if (description != null) {
cachedDescription = new SoftReference<String>(description);
}
if (contentEncoded != null) {
cachedContentEncoded = new SoftReference<String>(contentEncoded);
}
description = null;
contentEncoded = null;
}
public void updateFromOther(FeedItem other) {
super.updateFromOther(other);
if (other.title != null) {
title = other.title;
}
if (other.getDescription() != null) {
description = other.getDescription();
}
if (other.getContentEncoded() != null) {
contentEncoded = other.contentEncoded;
}
if (other.link != null) {
link = other.link;
}
if (other.pubDate != null && other.pubDate != pubDate) {
pubDate = other.pubDate;
}
if (other.media != null) {
if (media == null) {
media = other.media;
} else if (media.compareWithOther(other)) {
media.updateFromOther(other);
}
}
if (other.paymentLink != null) {
paymentLink = other.paymentLink;
}
if (other.chapters != null) {
if (chapters == null) {
chapters = other.chapters;
}
}
}
/**
* Returns the value that uniquely identifies this FeedItem. If the
* itemIdentifier attribute is not null, it will be returned. Else it will
* try to return the title. If the title is not given, it will use the link
* of the entry.
* */
public String getIdentifyingValue() {
if (itemIdentifier != null) {
return itemIdentifier;
} else if (title != null) {
return title;
} else {
return link;
}
}
/**
* Returns the value that uniquely identifies this FeedItem. If the
* itemIdentifier attribute is not null, it will be returned. Else it will
* try to return the title. If the title is not given, it will use the link
* of the entry.
*/
public String getIdentifyingValue() {
if (itemIdentifier != null) {
return itemIdentifier;
} else if (title != null) {
return title;
} else {
return link;
}
}
public String getTitle() {
return title;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
if (description == null && cachedDescription != null) {
return cachedDescription.get();
}
return description;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public void setDescription(String description) {
this.description = description;
}
public String getLink() {
return link;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
public void setLink(String link) {
this.link = link;
}
public Date getPubDate() {
return pubDate;
}
public Date getPubDate() {
return pubDate;
}
public void setPubDate(Date pubDate) {
this.pubDate = pubDate;
}
public void setPubDate(Date pubDate) {
this.pubDate = pubDate;
}
public FeedMedia getMedia() {
return media;
}
public FeedMedia getMedia() {
return media;
}
public void setMedia(FeedMedia media) {
this.media = media;
}
public void setMedia(FeedMedia media) {
this.media = media;
}
public Feed getFeed() {
return feed;
}
public Feed getFeed() {
return feed;
}
public void setFeed(Feed feed) {
this.feed = feed;
}
public void setFeed(Feed feed) {
this.feed = feed;
}
public boolean isRead() {
return read || isInProgress();
}
public boolean isRead() {
return read || isInProgress();
}
public void setRead(boolean read) {
this.read = read;
}
public void setRead(boolean read) {
this.read = read;
}
private boolean isInProgress() {
return (media != null && media.isInProgress());
}
private boolean isInProgress() {
return (media != null && media.isInProgress());
}
public String getContentEncoded() {
if (contentEncoded == null && cachedContentEncoded != null) {
return cachedContentEncoded.get();
public String getContentEncoded() {
return contentEncoded;
}
}
return contentEncoded;
}
public void setContentEncoded(String contentEncoded) {
this.contentEncoded = contentEncoded;
}
public void setContentEncoded(String contentEncoded) {
this.contentEncoded = contentEncoded;
}
public String getPaymentLink() {
return paymentLink;
}
public String getPaymentLink() {
return paymentLink;
}
public void setPaymentLink(String paymentLink) {
this.paymentLink = paymentLink;
}
public void setPaymentLink(String paymentLink) {
this.paymentLink = paymentLink;
}
public List<Chapter> getChapters() {
return chapters;
}
public List<Chapter> getChapters() {
return chapters;
}
public void setChapters(List<Chapter> chapters) {
this.chapters = chapters;
}
public void setChapters(List<Chapter> chapters) {
this.chapters = chapters;
}
public String getItemIdentifier() {
return itemIdentifier;
}
public String getItemIdentifier() {
return itemIdentifier;
}
public void setItemIdentifier(String itemIdentifier) {
this.itemIdentifier = itemIdentifier;
}
public void setItemIdentifier(String itemIdentifier) {
this.itemIdentifier = itemIdentifier;
}
public boolean hasMedia() {
return media != null;
}
public boolean hasMedia() {
return media != null;
}
private boolean isPlaying() {
if (media != null) {
return media.isPlaying();
}
return false;
}
private boolean isPlaying() {
if (media != null) {
return media.isPlaying();
}
return false;
}
@Override
public Callable<String> loadShownotes() {
return new Callable<String>() {
@Override
public String call() throws Exception {
public void setCachedDescription(String d) {
cachedDescription = new SoftReference<String>(d);
}
if (contentEncoded == null || description == null) {
DBReader.loadExtraInformationOfFeedItem(PodcastApp.getInstance(), FeedItem.this);
public void setCachedContentEncoded(String c) {
cachedContentEncoded = new SoftReference<String>(c);
}
}
return (contentEncoded != null) ? contentEncoded : description;
}
};
}
public enum State {
NEW, IN_PROGRESS, READ, PLAYING
}
public enum State {
NEW, IN_PROGRESS, READ, PLAYING
}
public State getState() {
if (hasMedia()) {
if (isPlaying()) {
return State.PLAYING;
}
if (isInProgress()) {
return State.IN_PROGRESS;
}
}
return (isRead() ? State.READ : State.NEW);
}
public State getState() {
if (hasMedia()) {
if (isPlaying()) {
return State.PLAYING;
}
if (isInProgress()) {
return State.IN_PROGRESS;
}
}
return (isRead() ? State.READ : State.NEW);
}
@Override
public InputStream openImageInputStream() {
InputStream out = null;
if (hasMedia()) {
out = media.openImageInputStream();
}
if (out == null && feed.getImage() != null) {
out = feed.getImage().openImageInputStream();
}
return out;
}
@Override
public InputStream openImageInputStream() {
InputStream out = null;
if (hasMedia()) {
out = media.openImageInputStream();
}
if (out == null && feed.getImage() != null) {
out = feed.getImage().openImageInputStream();
}
return out;
}
@Override
public InputStream reopenImageInputStream(InputStream input) {
InputStream out = null;
if (hasMedia()) {
out = media.reopenImageInputStream(input);
}
if (out == null && feed.getImage() != null) {
out = feed.getImage().reopenImageInputStream(input);
}
return out;
}
@Override
public InputStream reopenImageInputStream(InputStream input) {
InputStream out = null;
if (hasMedia()) {
out = media.reopenImageInputStream(input);
}
if (out == null && feed.getImage() != null) {
out = feed.getImage().reopenImageInputStream(input);
}
return out;
}
@Override
public String getImageLoaderCacheKey() {
String out = null;
if (hasMedia()) {
out = media.getImageLoaderCacheKey();
}
if (out == null && feed.getImage() != null) {
out = feed.getImage().getImageLoaderCacheKey();
}
return out;
}
public long getFeedId() {
return feedId;
}
public void setFeedId(long feedId) {
this.feedId = feedId;
}
@Override
public String getImageLoaderCacheKey() {
String out = null;
if (hasMedia()) {
out = media.getImageLoaderCacheKey();
}
if (out == null && feed.getImage() != null) {
out = feed.getImage().getImageLoaderCacheKey();
}
return out;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@ import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
@ -11,358 +12,383 @@ import android.os.Parcel;
import android.os.Parcelable;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.preferences.PlaybackPreferences;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.util.ChapterUtils;
import de.danoeh.antennapod.util.playback.Playable;
public class FeedMedia extends FeedFile implements Playable {
public static final int FEEDFILETYPE_FEEDMEDIA = 2;
public static final int PLAYABLE_TYPE_FEEDMEDIA = 1;
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";
public static final String PREF_MEDIA_ID = "FeedMedia.PrefMediaId";
public static final String PREF_FEED_ID = "FeedMedia.PrefFeedId";
private int duration;
private int position; // Current position in file
private long size; // File size in Byte
private String mime_type;
private FeedItem item;
private Date playbackCompletionDate;
private int duration;
private int position; // Current position in file
private long size; // File size in Byte
private String mime_type;
private volatile FeedItem item;
private Date playbackCompletionDate;
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;
}
/* Used for loading item when restoring from parcel. */
private long itemID;
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) {
super(file_url, download_url, downloaded);
this.id = id;
this.item = item;
this.duration = duration;
this.position = position;
this.size = size;
this.mime_type = mime_type;
this.playbackCompletionDate = playbackCompletionDate;
}
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) {
super();
this.id = id;
this.item = item;
}
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) {
super(file_url, download_url, downloaded);
this.id = id;
this.item = item;
this.duration = duration;
this.position = position;
this.size = size;
this.mime_type = mime_type;
this.playbackCompletionDate = playbackCompletionDate;
}
@Override
public String getHumanReadableIdentifier() {
if (item != null && item.getTitle() != null) {
return item.getTitle();
} else {
return download_url;
}
}
public FeedMedia(long id, FeedItem item) {
super();
this.id = id;
this.item = item;
}
/** Uses mimetype to determine the type of media. */
public MediaType getMediaType() {
if (mime_type == null || mime_type.isEmpty()) {
return MediaType.UNKNOWN;
} else {
if (mime_type.startsWith("audio")) {
return MediaType.AUDIO;
} else if (mime_type.startsWith("video")) {
return MediaType.VIDEO;
} else if (mime_type.equals("application/ogg")) {
return MediaType.AUDIO;
}
}
return MediaType.UNKNOWN;
}
@Override
public String getHumanReadableIdentifier() {
if (item != null && item.getTitle() != null) {
return item.getTitle();
} else {
return download_url;
}
}
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;
}
}
/**
* Uses mimetype to determine the type of media.
*/
public MediaType getMediaType() {
if (mime_type == null || mime_type.isEmpty()) {
return MediaType.UNKNOWN;
} else {
if (mime_type.startsWith("audio")) {
return MediaType.AUDIO;
} else if (mime_type.startsWith("video")) {
return MediaType.VIDEO;
} else if (mime_type.equals("application/ogg")) {
return MediaType.AUDIO;
}
}
return MediaType.UNKNOWN;
}
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;
}
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;
}
}
/**
* 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;
}
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;
}
@Override
public int getTypeAsInt() {
return FEEDFILETYPE_FEEDMEDIA;
}
/**
* 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;
}
public int getDuration() {
return duration;
}
@Override
public int getTypeAsInt() {
return FEEDFILETYPE_FEEDMEDIA;
}
public void setDuration(int duration) {
this.duration = duration;
}
public int getDuration() {
return duration;
}
public int getPosition() {
return position;
}
public void setDuration(int duration) {
this.duration = duration;
}
public void setPosition(int position) {
this.position = position;
}
public int getPosition() {
return position;
}
public long getSize() {
return size;
}
public void setPosition(int position) {
this.position = position;
}
public void setSize(long size) {
this.size = size;
}
public long getSize() {
return size;
}
public String getMime_type() {
return mime_type;
}
public void setSize(long size) {
this.size = size;
}
public void setMime_type(String mime_type) {
this.mime_type = mime_type;
}
public String getMime_type() {
return mime_type;
}
public FeedItem getItem() {
return item;
}
public void setMime_type(String mime_type) {
this.mime_type = mime_type;
}
public void setItem(FeedItem item) {
this.item = item;
}
public FeedItem getItem() {
return item;
}
public Date getPlaybackCompletionDate() {
return playbackCompletionDate;
}
public void setItem(FeedItem item) {
this.item = item;
}
public void setPlaybackCompletionDate(Date playbackCompletionDate) {
this.playbackCompletionDate = playbackCompletionDate;
}
public Date getPlaybackCompletionDate() {
return playbackCompletionDate;
}
public boolean isInProgress() {
return (this.position > 0);
}
public void setPlaybackCompletionDate(Date playbackCompletionDate) {
this.playbackCompletionDate = playbackCompletionDate;
}
public FeedImage getImage() {
if (item != null && item.getFeed() != null) {
return item.getFeed().getImage();
}
return null;
}
public boolean isInProgress() {
return (this.position > 0);
}
@Override
public int describeContents() {
return 0;
}
public FeedImage getImage() {
if (item != null && item.getFeed() != null) {
return item.getFeed().getImage();
}
return null;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(item.getFeed().getId());
dest.writeLong(item.getId());
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToPreferences(Editor prefEditor) {
prefEditor.putLong(PREF_FEED_ID, item.getFeed().getId());
prefEditor.putLong(PREF_MEDIA_ID, id);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeLong(item.getId());
@Override
public void loadMetadata() throws PlayableException {
}
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);
}
@Override
public void loadChapterMarks() {
if (getChapters() == null && !localFileAvailable()) {
ChapterUtils.loadChaptersFromStreamUrl(this);
if (getChapters() != null) {
FeedManager.getInstance().setFeedItem(PodcastApp.getInstance(),
item);
}
}
@Override
public void writeToPreferences(Editor prefEditor) {
prefEditor.putLong(PREF_FEED_ID, item.getFeed().getId());
prefEditor.putLong(PREF_MEDIA_ID, id);
}
}
@Override
public void loadMetadata() throws PlayableException {
if (item == null && itemID != 0) {
item = DBReader.getFeedItem(PodcastApp.getInstance(), itemID);
}
}
@Override
public String getEpisodeTitle() {
if (getItem().getTitle() != null) {
return getItem().getTitle();
} else {
return getItem().getIdentifyingValue();
}
}
@Override
public void loadChapterMarks() {
if (getChapters() == null && !localFileAvailable()) {
ChapterUtils.loadChaptersFromStreamUrl(this);
if (getChapters() != null && item != null) {
DBWriter.setFeedItem(PodcastApp.getInstance(),
item);
}
}
@Override
public List<Chapter> getChapters() {
return getItem().getChapters();
}
}
@Override
public String getWebsiteLink() {
return getItem().getLink();
}
@Override
public String getEpisodeTitle() {
if (item == null) {
return null;
}
if (getItem().getTitle() != null) {
return getItem().getTitle();
} else {
return getItem().getIdentifyingValue();
}
}
@Override
public String getFeedTitle() {
return getItem().getFeed().getTitle();
}
@Override
public List<Chapter> getChapters() {
if (item == null) {
return null;
}
return getItem().getChapters();
}
@Override
public Object getIdentifier() {
return id;
}
@Override
public String getWebsiteLink() {
if (item == null) {
return null;
}
return getItem().getLink();
}
@Override
public String getLocalMediaUrl() {
return file_url;
}
@Override
public String getFeedTitle() {
if (item == null) {
return null;
}
return getItem().getFeed().getTitle();
}
@Override
public String getStreamUrl() {
return download_url;
}
@Override
public Object getIdentifier() {
return id;
}
@Override
public boolean localFileAvailable() {
return isDownloaded() && file_url != null;
}
@Override
public String getLocalMediaUrl() {
return file_url;
}
@Override
public boolean streamAvailable() {
return download_url != null;
}
@Override
public String getStreamUrl() {
return download_url;
}
@Override
public void saveCurrentPosition(SharedPreferences pref, int newPosition) {
position = newPosition;
FeedManager.getInstance().setFeedMedia(PodcastApp.getInstance(), this);
}
@Override
public String getPaymentLink() {
if (item == null) {
return null;
}
return getItem().getPaymentLink();
}
@Override
public void onPlaybackStart() {
}
@Override
public boolean localFileAvailable() {
return isDownloaded() && file_url != null;
}
@Override
public void onPlaybackCompleted() {
@Override
public boolean streamAvailable() {
return download_url != null;
}
}
@Override
public void saveCurrentPosition(SharedPreferences pref, int newPosition) {
position = newPosition;
DBWriter.setFeedMediaPosition(PodcastApp.getInstance(), this);
}
@Override
public int getPlayableType() {
return PLAYABLE_TYPE_FEEDMEDIA;
}
@Override
public void onPlaybackStart() {
}
@Override
public void setChapters(List<Chapter> chapters) {
getItem().setChapters(chapters);
}
@Override
public void onPlaybackCompleted() {
@Override
public String getPaymentLink() {
return getItem().getPaymentLink();
}
}
@Override
public void loadShownotes(final ShownoteLoaderCallback callback) {
String contentEncoded = item.getContentEncoded();
if (item.getDescription() == null || contentEncoded == null) {
FeedManager.getInstance().loadExtraInformationOfItem(
PodcastApp.getInstance(), item,
new FeedManager.TaskCallback<String[]>() {
@Override
public void onCompletion(String[] result) {
if (result[1] != null) {
callback.onShownotesLoaded(result[1]);
} else {
callback.onShownotesLoaded(result[0]);
@Override
public int getPlayableType() {
return PLAYABLE_TYPE_FEEDMEDIA;
}
}
@Override
public void setChapters(List<Chapter> chapters) {
getItem().setChapters(chapters);
}
}
});
} else {
callback.onShownotesLoaded(contentEncoded);
}
}
@Override
public Callable<String> loadShownotes() {
return new Callable<String>() {
@Override
public String call() throws Exception {
if (item == null) {
item = DBReader.getFeedItem(PodcastApp.getInstance(), itemID);
}
if (item.getContentEncoded() == null || item.getDescription() == null) {
DBReader.loadExtraInformationOfFeedItem(PodcastApp.getInstance(), item);
public static final Parcelable.Creator<FeedMedia> CREATOR = new Parcelable.Creator<FeedMedia>() {
public FeedMedia createFromParcel(Parcel in) {
long feedId = in.readLong();
long itemId = in.readLong();
FeedItem item = FeedManager.getInstance().getFeedItem(itemId,
feedId);
if (item != null) {
return item.getMedia();
} else {
return null;
}
}
}
return (item.getContentEncoded() != null) ? item.getContentEncoded() : item.getDescription();
}
};
}
public FeedMedia[] newArray(int size) {
return new FeedMedia[size];
}
};
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()));
result.itemID = itemID;
return result;
}
@Override
public InputStream openImageInputStream() {
InputStream out = new Playable.DefaultPlayableImageLoader(this)
.openImageInputStream();
if (out == null) {
if (item.getFeed().getImage() != null) {
return item.getFeed().getImage().openImageInputStream();
}
}
return out;
}
public FeedMedia[] newArray(int size) {
return new FeedMedia[size];
}
};
@Override
public String getImageLoaderCacheKey() {
String out = new Playable.DefaultPlayableImageLoader(this)
.getImageLoaderCacheKey();
if (out == null) {
if (item.getFeed().getImage() != null) {
return item.getFeed().getImage().getImageLoaderCacheKey();
}
}
return out;
}
@Override
public InputStream openImageInputStream() {
InputStream out = new Playable.DefaultPlayableImageLoader(this)
.openImageInputStream();
if (out == null) {
if (item.getFeed().getImage() != null) {
return item.getFeed().getImage().openImageInputStream();
}
}
return out;
}
@Override
public InputStream reopenImageInputStream(InputStream input) {
if (input instanceof FileInputStream) {
return item.getFeed().getImage().reopenImageInputStream(input);
} else {
return new Playable.DefaultPlayableImageLoader(this)
.reopenImageInputStream(input);
}
}
@Override
public String getImageLoaderCacheKey() {
String out = new Playable.DefaultPlayableImageLoader(this)
.getImageLoaderCacheKey();
if (out == null) {
if (item.getFeed().getImage() != null) {
return item.getFeed().getImage().getImageLoaderCacheKey();
}
}
return out;
}
@Override
public InputStream reopenImageInputStream(InputStream input) {
if (input instanceof FileInputStream) {
return item.getFeed().getImage().reopenImageInputStream(input);
} else {
return new Playable.DefaultPlayableImageLoader(this)
.reopenImageInputStream(input);
}
}
}

View File

@ -1,253 +0,0 @@
package de.danoeh.antennapod.feed;
import java.util.ArrayList;
import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.content.Context;
import android.database.Cursor;
import android.os.Looper;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.storage.PodDBAdapter;
import de.danoeh.antennapod.util.comparator.SearchResultValueComparator;
/** Performs search on Feeds and FeedItems */
public class FeedSearcher {
private static final String TAG = "FeedSearcher";
// Search result values
private static final int VALUE_FEED_TITLE = 3;
private static final int VALUE_ITEM_TITLE = 2;
private static final int VALUE_ITEM_CHAPTER = 1;
private static final int VALUE_ITEM_DESCRIPTION = 0;
private static final int VALUE_WORD_MATCH = 4;
/** Performs a search in all feeds or one specific feed. */
public static ArrayList<SearchResult> performSearch(final Context context,
final String query, final Feed selectedFeed) {
final String lcQuery = query.toLowerCase();
final ArrayList<SearchResult> result = new ArrayList<SearchResult>();
if (selectedFeed == null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Performing global search");
if (AppConfig.DEBUG)
Log.d(TAG, "Searching Feed titles");
searchFeedtitles(lcQuery, result);
} else if (AppConfig.DEBUG) {
Log.d(TAG, "Performing search on specific feed");
}
if (AppConfig.DEBUG)
Log.d(TAG, "Searching Feeditem titles");
searchFeedItemTitles(lcQuery, result, selectedFeed);
if (AppConfig.DEBUG)
Log.d(TAG, "Searching item-chaptertitles");
searchFeedItemChapters(lcQuery, result, selectedFeed);
final FeedManager manager = FeedManager.getInstance();
Looper.prepare();
manager.searchFeedItemDescription(context, selectedFeed, lcQuery,
new FeedManager.QueryTaskCallback() {
@Override
public void handleResult(Cursor cResult) {
searchFeedItemContentEncodedCursor(lcQuery, result,
selectedFeed, cResult);
}
@Override
public void onCompletion() {
manager.searchFeedItemContentEncoded(context,
selectedFeed, lcQuery,
new FeedManager.QueryTaskCallback() {
@Override
public void handleResult(Cursor cResult) {
searchFeedItemDescriptionCursor(
lcQuery, result, selectedFeed,
cResult);
}
@Override
public void onCompletion() {
Looper.myLooper().quit();
}
});
}
});
Looper.loop();
if (AppConfig.DEBUG)
Log.d(TAG, "Sorting results");
Collections.sort(result, new SearchResultValueComparator());
return result;
}
private static void searchFeedtitles(String query,
ArrayList<SearchResult> destination) {
FeedManager manager = FeedManager.getInstance();
for (Feed feed : manager.getFeeds()) {
SearchResult result = createSearchResult(feed, query, feed
.getTitle().toLowerCase(), VALUE_FEED_TITLE);
if (result != null) {
destination.add(result);
}
}
}
private static void searchFeedItemTitles(String query,
ArrayList<SearchResult> destination, Feed selectedFeed) {
FeedManager manager = FeedManager.getInstance();
if (selectedFeed == null) {
for (Feed feed : manager.getFeeds()) {
searchFeedItemTitlesSingleFeed(query, destination, feed);
}
} else {
searchFeedItemTitlesSingleFeed(query, destination, selectedFeed);
}
}
private static void searchFeedItemTitlesSingleFeed(String query,
ArrayList<SearchResult> destination, Feed feed) {
for (FeedItem item : feed.getItems()) {
SearchResult result = createSearchResult(item, query, item
.getTitle().toLowerCase(), VALUE_ITEM_TITLE);
if (result != null) {
result.setSubtitle(PodcastApp.getInstance().getString(
R.string.found_in_title_label));
destination.add(result);
}
}
}
private static void searchFeedItemChapters(String query,
ArrayList<SearchResult> destination, Feed selectedFeed) {
FeedManager manager = FeedManager.getInstance();
if (selectedFeed == null) {
for (Feed feed : manager.getFeeds()) {
searchFeedItemChaptersSingleFeed(query, destination, feed);
}
} else {
searchFeedItemChaptersSingleFeed(query, destination, selectedFeed);
}
}
private static void searchFeedItemChaptersSingleFeed(String query,
ArrayList<SearchResult> destination, Feed feed) {
for (FeedItem item : feed.getItems()) {
if (item.getChapters() != null) {
for (Chapter sc : item.getChapters()) {
SearchResult result = createSearchResult(item, query, sc
.getTitle().toLowerCase(), VALUE_ITEM_CHAPTER);
if (result != null) {
result.setSubtitle(PodcastApp.getInstance().getString(
R.string.found_in_chapters_label));
destination.add(result);
}
}
}
}
}
private static void searchFeedItemDescriptionCursor(String query,
ArrayList<SearchResult> destination, Feed feed, Cursor cursor) {
FeedManager manager = FeedManager.getInstance();
if (cursor.moveToFirst()) {
do {
final long itemId = cursor
.getLong(PodDBAdapter.IDX_FI_EXTRA_ID);
String content = cursor
.getString(PodDBAdapter.IDX_FI_EXTRA_DESCRIPTION);
if (content != null) {
content = content.toLowerCase();
final long feedId = cursor
.getLong(PodDBAdapter.IDX_FI_EXTRA_FEED);
FeedItem item = null;
if (feed == null) {
item = manager.getFeedItem(itemId, feedId);
} else {
item = manager.getFeedItem(itemId, feed);
}
if (item != null) {
SearchResult searchResult = createSearchResult(item,
query, content, VALUE_ITEM_DESCRIPTION);
if (searchResult != null) {
searchResult.setSubtitle(PodcastApp.getInstance()
.getString(
R.string.found_in_shownotes_label));
destination.add(searchResult);
}
}
}
} while (cursor.moveToNext());
}
}
private static void searchFeedItemContentEncodedCursor(String query,
ArrayList<SearchResult> destination, Feed feed, Cursor cursor) {
FeedManager manager = FeedManager.getInstance();
if (cursor.moveToFirst()) {
do {
final long itemId = cursor
.getLong(PodDBAdapter.IDX_FI_EXTRA_ID);
String content = cursor
.getString(PodDBAdapter.IDX_FI_EXTRA_CONTENT_ENCODED);
if (content != null) {
content = content.toLowerCase();
final long feedId = cursor
.getLong(PodDBAdapter.IDX_FI_EXTRA_FEED);
FeedItem item = null;
if (feed == null) {
item = manager.getFeedItem(itemId, feedId);
} else {
item = manager.getFeedItem(itemId, feed);
}
if (item != null) {
SearchResult searchResult = createSearchResult(item,
query, content, VALUE_ITEM_DESCRIPTION);
if (searchResult != null) {
searchResult.setSubtitle(PodcastApp.getInstance()
.getString(
R.string.found_in_shownotes_label));
destination.add(searchResult);
}
}
}
} while (cursor.moveToNext());
}
}
private static SearchResult createSearchResult(FeedComponent component,
String query, String text, int baseValue) {
int bonus = 0;
boolean found = false;
// try word search
Pattern word = Pattern.compile("\b" + query + "\b");
Matcher matcher = word.matcher(text);
found = matcher.find();
if (found) {
bonus = VALUE_WORD_MATCH;
} else {
// search for other occurence
found = text.contains(query);
}
if (found) {
return new SearchResult(component, baseValue + bonus);
} else {
return null;
}
}
}

View File

@ -7,10 +7,11 @@ public class SearchResult {
/** Higher value means more importance */
private int value;
public SearchResult(FeedComponent component, int value) {
public SearchResult(FeedComponent component, int value, String subtitle) {
super();
this.component = component;
this.value = value;
this.subtitle = subtitle;
}
public FeedComponent getComponent() {

View File

@ -1,6 +1,8 @@
package de.danoeh.antennapod.fragment;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
@ -18,10 +20,15 @@ import de.danoeh.antennapod.adapter.ExternalEpisodesListAdapter;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DBTasks;
import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler;
import java.util.List;
public class EpisodesFragment extends Fragment {
private static final String TAG = "EpisodesFragment";
@ -34,6 +41,9 @@ public class EpisodesFragment extends Fragment {
private ExpandableListView listView;
private ExternalEpisodesListAdapter adapter;
private List<FeedItem> queue;
private List<FeedItem> unreadItems;
protected FeedItem selectedItem = null;
protected long selectedGroupId = -1;
protected boolean contextMenuClosed = true;
@ -86,7 +96,7 @@ public class EpisodesFragment extends Fragment {
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
adapter = new ExternalEpisodesListAdapter(getActivity(),
adapterCallback, groupActionCallback);
adapterCallback, groupActionCallback, itemAccess);
listView.setAdapter(adapter);
listView.expandGroup(ExternalEpisodesListAdapter.GROUP_POS_QUEUE);
listView.expandGroup(ExternalEpisodesListAdapter.GROUP_POS_UNREAD);
@ -111,9 +121,73 @@ public class EpisodesFragment extends Fragment {
return true;
}
});
loadData();
registerForContextMenu(listView);
}
ExternalEpisodesListAdapter.ItemAccess itemAccess = new ExternalEpisodesListAdapter.ItemAccess() {
@Override
public int getQueueSize() {
return (queue != null) ? queue.size() : 0;
}
@Override
public int getUnreadItemsSize() {
return (unreadItems != null) ? unreadItems.size() : 0;
}
@Override
public FeedItem getQueueItemAt(int position) {
return (queue != null) ? queue.get(position) : null;
}
@Override
public FeedItem getUnreadItemAt(int position) {
return (unreadItems != null) ? unreadItems.get(position) : null;
}
};
private void loadData() {
AsyncTask<Void, Void, Void> loadTask = new AsyncTask<Void, Void, Void>() {
private volatile List<FeedItem> queueRef;
private volatile List<FeedItem> unreadItemsRef;
@Override
protected Void doInBackground(Void... voids) {
if (AppConfig.DEBUG) Log.d(TAG, "Starting to load list data");
Context context = EpisodesFragment.this.getActivity();
if (context != null) {
queueRef = DBReader.getQueue(context);
unreadItemsRef = DBReader.getUnreadItemsList(context);
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
if (queueRef != null && unreadItemsRef != null) {
if (AppConfig.DEBUG) Log.d(TAG, "Done loading list data");
queue = queueRef;
unreadItems = unreadItemsRef;
if (adapter != null) {
adapter.notifyDataSetChanged();
}
} else {
if (queueRef == null) {
Log.e(TAG, "Could not load queue");
}
if (unreadItemsRef == null) {
Log.e(TAG, "Could not load unread items");
}
}
}
};
loadTask.execute();
}
private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
@Override
@ -121,7 +195,7 @@ public class EpisodesFragment extends Fragment {
if ((EVENTS & arg) != 0) {
if (AppConfig.DEBUG)
Log.d(TAG, "Received contentUpdate Intent.");
adapter.notifyDataSetChanged();
loadData();
}
}
};
@ -140,13 +214,13 @@ public class EpisodesFragment extends Fragment {
menu.setHeaderTitle(selectedItem.getTitle());
FeedItemMenuHandler.onPrepareMenu(
new FeedItemMenuHandler.MenuInterface() {
new FeedItemMenuHandler.MenuInterface() {
@Override
public void setItemVisibility(int id, boolean visible) {
menu.findItem(id).setVisible(visible);
}
}, selectedItem, false);
@Override
public void setItemVisibility(int id, boolean visible) {
menu.findItem(id).setVisible(visible);
}
}, selectedItem, false, QueueAccess.ItemListAccess(queue));
} else if (selectedGroupId == ExternalEpisodesListAdapter.GROUP_POS_QUEUE) {
menu.add(Menu.NONE, R.id.organize_queue_item, Menu.NONE,
@ -166,7 +240,6 @@ public class EpisodesFragment extends Fragment {
@Override
public boolean onContextItemSelected(android.view.MenuItem item) {
boolean handled = false;
FeedManager manager = FeedManager.getInstance();
if (selectedItem != null) {
try {
handled = FeedItemMenuHandler.onMenuItemClicked(
@ -185,10 +258,10 @@ public class EpisodesFragment extends Fragment {
OrganizeQueueActivity.class));
break;
case R.id.clear_queue_item:
manager.clearQueue(getActivity());
DBWriter.clearQueue(getActivity());
break;
case R.id.download_all_item:
manager.downloadAllItemsInQueue(getActivity());
DBTasks.downloadAllItemsInQueue(getActivity());
break;
default:
handled = false;
@ -197,10 +270,10 @@ public class EpisodesFragment extends Fragment {
handled = true;
switch (item.getItemId()) {
case R.id.mark_all_read_item:
manager.markAllItemsRead(getActivity());
DBWriter.markAllItemsRead(getActivity());
break;
case R.id.enqueue_all_item:
manager.enqueueAllNewItems(getActivity());
DBTasks.enqueueAllNewItems(getActivity());
break;
default:
handled = false;

View File

@ -1,19 +1,18 @@
package de.danoeh.antennapod.fragment;
import java.util.List;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.view.ActionMode;
import android.util.Log;
import android.view.*;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.*;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
@ -24,8 +23,9 @@ import de.danoeh.antennapod.dialog.ConfirmationDialog;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.storage.FeedItemStatistics;
import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler;
public class FeedlistFragment extends Fragment implements
@ -33,191 +33,251 @@ public class FeedlistFragment extends Fragment implements
AdapterView.OnItemLongClickListener {
private static final String TAG = "FeedlistFragment";
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED
| EventDistributor.DOWNLOAD_QUEUED
| EventDistributor.FEED_LIST_UPDATE
| EventDistributor.UNREAD_ITEMS_UPDATE;
public static final String EXTRA_SELECTED_FEED = "extra.de.danoeh.antennapod.activity.selected_feed";
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED
| EventDistributor.DOWNLOAD_QUEUED
| EventDistributor.FEED_LIST_UPDATE
| EventDistributor.UNREAD_ITEMS_UPDATE;
private FeedManager manager;
private FeedlistAdapter fla;
public static final String EXTRA_SELECTED_FEED = "extra.de.danoeh.antennapod.activity.selected_feed";
private Feed selectedFeed;
private ActionMode mActionMode;
private FeedlistAdapter fla;
private List<Feed> feeds;
private List<FeedItemStatistics> feedItemStatistics;
private GridView gridView;
private ListView listView;
private TextView txtvEmpty;
private Feed selectedFeed;
private ActionMode mActionMode;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
}
private GridView gridView;
private ListView listView;
private TextView emptyView;
@Override
public void onDetach() {
super.onDetach();
}
private FeedlistAdapter.ItemAccess itemAccess = new FeedlistAdapter.ItemAccess() {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (AppConfig.DEBUG)
Log.d(TAG, "Creating");
manager = FeedManager.getInstance();
fla = new FeedlistAdapter(getActivity());
@Override
public Feed getItem(int position) {
if (feeds != null) {
return feeds.get(position);
} else {
return null;
}
}
}
@Override
public FeedItemStatistics getFeedItemStatistics(int position) {
if (feedItemStatistics != null && position < feedItemStatistics.size()) {
return feedItemStatistics.get(position);
} else {
return null;
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View result = inflater.inflate(R.layout.feedlist, container, false);
listView = (ListView) result.findViewById(android.R.id.list);
gridView = (GridView) result.findViewById(R.id.grid);
txtvEmpty = (TextView) result.findViewById(android.R.id.empty);
@Override
public int getCount() {
if (feeds != null) {
return feeds.size();
} else {
return 0;
}
}
};
return result;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (AppConfig.DEBUG)
Log.d(TAG, "Creating");
fla = new FeedlistAdapter(getActivity(), itemAccess);
loadFeeds();
}
}
private void loadFeeds() {
AsyncTask<Void, Void, List[]> loadTask = new AsyncTask<Void, Void, List[]>() {
@Override
protected List[] doInBackground(Void... params) {
return new List[]{DBReader.getFeedList(getActivity()),
DBReader.getFeedStatisticsList(getActivity())};
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (listView != null) {
listView.setOnItemClickListener(this);
listView.setOnItemLongClickListener(this);
listView.setAdapter(fla);
listView.setEmptyView(txtvEmpty);
if (AppConfig.DEBUG)
Log.d(TAG, "Using ListView");
} else {
gridView.setOnItemClickListener(this);
gridView.setOnItemLongClickListener(this);
gridView.setAdapter(fla);
gridView.setEmptyView(txtvEmpty);
if (AppConfig.DEBUG)
Log.d(TAG, "Using GridView");
}
}
@Override
public void onResume() {
super.onResume();
if (AppConfig.DEBUG)
Log.d(TAG, "Resuming");
EventDistributor.getInstance().register(contentUpdate);
fla.notifyDataSetChanged();
}
@Override
public void onPause() {
super.onPause();
EventDistributor.getInstance().unregister(contentUpdate);
if (mActionMode != null) {
mActionMode.finish();
}
}
@Override
protected void onPostExecute(List[] result) {
super.onPostExecute(result);
if (result != null) {
feeds = result[0];
feedItemStatistics = result[1];
setEmptyViewIfListIsEmpty();
if (fla != null) {
fla.notifyDataSetChanged();
}
} else {
Log.e(TAG, "Failed to load feeds");
}
}
};
loadTask.execute();
}
private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if ((EVENTS & arg) != 0) {
if (AppConfig.DEBUG)
Log.d(TAG, "Received contentUpdate Intent.");
fla.notifyDataSetChanged();
}
}
};
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View result = inflater.inflate(R.layout.feedlist, container, false);
listView = (ListView) result.findViewById(android.R.id.list);
gridView = (GridView) result.findViewById(R.id.grid);
emptyView = (TextView) result.findViewById(android.R.id.empty);
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
FeedMenuHandler.onCreateOptionsMenu(mode.getMenuInflater(), menu);
mode.setTitle(selectedFeed.getTitle());
return true;
}
return result;
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return FeedMenuHandler.onPrepareOptionsMenu(menu, selectedFeed);
}
}
@SuppressLint("NewApi")
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
try {
if (FeedMenuHandler.onOptionsItemClicked(getActivity(),
item, selectedFeed)) {
fla.notifyDataSetChanged();
} else {
switch (item.getItemId()) {
case R.id.remove_item:
final FeedRemover remover = new FeedRemover(
getActivity(), selectedFeed) {
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
fla.notifyDataSetChanged();
}
};
ConfirmationDialog conDialog = new ConfirmationDialog(
getActivity(), R.string.remove_feed_label,
R.string.feed_delete_confirmation_msg) {
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (listView != null) {
listView.setOnItemClickListener(this);
listView.setOnItemLongClickListener(this);
listView.setAdapter(fla);
listView.setEmptyView(emptyView);
if (AppConfig.DEBUG)
Log.d(TAG, "Using ListView");
} else {
gridView.setOnItemClickListener(this);
gridView.setOnItemLongClickListener(this);
gridView.setAdapter(fla);
gridView.setEmptyView(emptyView);
if (AppConfig.DEBUG)
Log.d(TAG, "Using GridView");
}
setEmptyViewIfListIsEmpty();
}
@Override
public void onConfirmButtonPressed(
DialogInterface dialog) {
dialog.dismiss();
remover.executeAsync();
}
};
conDialog.createNewDialog().show();
break;
}
}
} catch (DownloadRequestException e) {
e.printStackTrace();
DownloadRequestErrorDialogCreator.newRequestErrorDialog(
getActivity(), e.getMessage());
}
mode.finish();
return true;
}
@Override
public void onResume() {
super.onResume();
if (AppConfig.DEBUG)
Log.d(TAG, "Resuming");
EventDistributor.getInstance().register(contentUpdate);
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
selectedFeed = null;
fla.setSelectedItemIndex(FeedlistAdapter.SELECTION_NONE);
}
@Override
public void onPause() {
super.onPause();
EventDistributor.getInstance().unregister(contentUpdate);
if (mActionMode != null) {
mActionMode.finish();
}
}
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int position,
long id) {
Feed selection = fla.getItem(position);
Intent showFeed = new Intent(getActivity(), FeedItemlistActivity.class);
showFeed.putExtra(EXTRA_SELECTED_FEED, selection.getId());
private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
getActivity().startActivity(showFeed);
}
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if ((EVENTS & arg) != 0) {
if (AppConfig.DEBUG)
Log.d(TAG, "Received contentUpdate Intent.");
loadFeeds();
}
}
};
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
Feed selection = fla.getItem(position);
if (AppConfig.DEBUG)
Log.d(TAG, "Selected Feed with title " + selection.getTitle());
if (selection != null) {
if (mActionMode != null) {
mActionMode.finish();
}
fla.setSelectedItemIndex(position);
selectedFeed = selection;
mActionMode = ((ActionBarActivity) getActivity()).startSupportActionMode(FeedlistFragment.this);
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
FeedMenuHandler.onCreateOptionsMenu(mode.getMenuInflater(), menu);
mode.setTitle(selectedFeed.getTitle());
return true;
}
}
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return FeedMenuHandler.onPrepareOptionsMenu(menu, selectedFeed);
}
@SuppressLint("NewApi")
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
try {
if (FeedMenuHandler.onOptionsItemClicked(getActivity(),
item, selectedFeed)) {
loadFeeds();
} else {
switch (item.getItemId()) {
case R.id.remove_item:
final FeedRemover remover = new FeedRemover(
getActivity(), selectedFeed) {
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
loadFeeds();
}
};
ConfirmationDialog conDialog = new ConfirmationDialog(
getActivity(), R.string.remove_feed_label,
R.string.feed_delete_confirmation_msg) {
@Override
public void onConfirmButtonPressed(
DialogInterface dialog) {
dialog.dismiss();
remover.executeAsync();
}
};
conDialog.createNewDialog().show();
break;
}
}
} catch (DownloadRequestException e) {
e.printStackTrace();
DownloadRequestErrorDialogCreator.newRequestErrorDialog(
getActivity(), e.getMessage());
}
mode.finish();
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
selectedFeed = null;
fla.setSelectedItemIndex(FeedlistAdapter.SELECTION_NONE);
}
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int position,
long id) {
Feed selection = fla.getItem(position);
Intent showFeed = new Intent(getActivity(), FeedItemlistActivity.class);
showFeed.putExtra(EXTRA_SELECTED_FEED, selection.getId());
getActivity().startActivity(showFeed);
}
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
Feed selection = fla.getItem(position);
if (AppConfig.DEBUG)
Log.d(TAG, "Selected Feed with title " + selection.getTitle());
if (selection != null) {
if (mActionMode != null) {
mActionMode.finish();
}
fla.setSelectedItemIndex(position);
selectedFeed = selection;
mActionMode = ((ActionBarActivity) getActivity()).startSupportActionMode(FeedlistFragment.this);
}
return true;
}
private AbsListView getMainView() {
return (listView != null) ? listView : gridView;
}
private void setEmptyViewIfListIsEmpty() {
if (getMainView() != null && emptyView != null && feeds != null) {
if (feeds.isEmpty()) {
emptyView.setText(R.string.no_feeds_label);
}
}
}
}

View File

@ -2,6 +2,9 @@ package de.danoeh.antennapod.fragment;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBarActivity;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.util.ShownotesProvider;
import org.apache.commons.lang3.StringEscapeUtils;
import android.annotation.SuppressLint;
@ -30,439 +33,423 @@ import android.webkit.WebViewClient;
import android.widget.Toast;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.util.ShareUtils;
import de.danoeh.antennapod.util.playback.Playable;
import java.util.concurrent.Callable;
/** Displays the description of a Playable object in a Webview. */
public class ItemDescriptionFragment extends Fragment {
private static final String TAG = "ItemDescriptionFragment";
private static final String TAG = "ItemDescriptionFragment";
private static final String PREF = "ItemDescriptionFragmentPrefs";
private static final String PREF_SCROLL_Y = "prefScrollY";
private static final String PREF_PLAYABLE_ID = "prefPlayableId";
private static final String PREF = "ItemDescriptionFragmentPrefs";
private static final String PREF_SCROLL_Y = "prefScrollY";
private static final String PREF_PLAYABLE_ID = "prefPlayableId";
private static final String ARG_PLAYABLE = "arg.playable";
private static final String ARG_PLAYABLE = "arg.playable";
private static final String ARG_FEEDITEM_ID = "arg.feeditem";
private static final String ARG_FEED_ID = "arg.feedId";
private static final String ARG_FEED_ITEM_ID = "arg.feeditemId";
private static final String ARG_SAVE_STATE = "arg.saveState";
private static final String ARG_SAVE_STATE = "arg.saveState";
private WebView webvDescription;
private Playable media;
private WebView webvDescription;
private FeedItem item;
private ShownotesProvider shownotesProvider;
private Playable media;
private AsyncTask<Void, Void, Void> webViewLoader;
private String shownotes;
private AsyncTask<Void, Void, Void> webViewLoader;
/** URL that was selected via long-press. */
private String selectedURL;
/**
* URL that was selected via long-press.
*/
private String selectedURL;
/**
* True if Fragment should save its state (e.g. scrolling position) in a
* shared preference.
*/
private boolean saveState;
/**
* True if Fragment should save its state (e.g. scrolling position) in a
* shared preference.
*/
private boolean saveState;
public static ItemDescriptionFragment newInstance(Playable media,
boolean saveState) {
ItemDescriptionFragment f = new ItemDescriptionFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_PLAYABLE, media);
args.putBoolean(ARG_SAVE_STATE, saveState);
f.setArguments(args);
return f;
}
public static ItemDescriptionFragment newInstance(Playable media,
boolean saveState) {
ItemDescriptionFragment f = new ItemDescriptionFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_PLAYABLE, media);
args.putBoolean(ARG_SAVE_STATE, saveState);
f.setArguments(args);
return f;
}
public static ItemDescriptionFragment newInstance(FeedItem item,
boolean saveState) {
ItemDescriptionFragment f = new ItemDescriptionFragment();
Bundle args = new Bundle();
args.putLong(ARG_FEED_ID, item.getFeed().getId());
args.putLong(ARG_FEED_ITEM_ID, item.getId());
args.putBoolean(ARG_SAVE_STATE, saveState);
f.setArguments(args);
return f;
}
public static ItemDescriptionFragment newInstance(FeedItem item, boolean saveState) {
ItemDescriptionFragment f = new ItemDescriptionFragment();
Bundle args = new Bundle();
args.putLong(ARG_FEEDITEM_ID, item.getId());
args.putBoolean(ARG_SAVE_STATE, saveState);
f.setArguments(args);
return f;
}
@SuppressLint("NewApi")
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (AppConfig.DEBUG)
Log.d(TAG, "Creating view");
webvDescription = new WebView(getActivity());
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(getResources().getColor(
R.color.black));
}
webvDescription.getSettings().setUseWideViewPort(false);
webvDescription.getSettings().setLayoutAlgorithm(
LayoutAlgorithm.NARROW_COLUMNS);
webvDescription.getSettings().setLoadWithOverviewMode(true);
webvDescription.setOnLongClickListener(webViewLongClickListener);
webvDescription.setWebViewClient(new WebViewClient() {
@SuppressLint("NewApi")
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (AppConfig.DEBUG)
Log.d(TAG, "Creating view");
webvDescription = new WebView(getActivity());
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(getResources().getColor(
R.color.black));
}
webvDescription.getSettings().setUseWideViewPort(false);
webvDescription.getSettings().setLayoutAlgorithm(
LayoutAlgorithm.NARROW_COLUMNS);
webvDescription.getSettings().setLoadWithOverviewMode(true);
webvDescription.setOnLongClickListener(webViewLongClickListener);
webvDescription.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
return true;
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
return true;
}
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (AppConfig.DEBUG)
Log.d(TAG, "Page finished");
// Restoring the scroll position might not always work
view.postDelayed(new Runnable() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (AppConfig.DEBUG)
Log.d(TAG, "Page finished");
// Restoring the scroll position might not always work
view.postDelayed(new Runnable() {
@Override
public void run() {
restoreFromPreference();
}
@Override
public void run() {
restoreFromPreference();
}
}, 50);
}
}, 50);
}
});
registerForContextMenu(webvDescription);
return webvDescription;
}
});
registerForContextMenu(webvDescription);
return webvDescription;
}
@Override
public void onDestroyView() {
super.onDestroyView();
}
@Override
public void onDestroyView() {
super.onDestroyView();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (AppConfig.DEBUG)
Log.d(TAG, "Fragment attached");
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (AppConfig.DEBUG)
Log.d(TAG, "Fragment attached");
}
@Override
public void onDetach() {
super.onDetach();
if (AppConfig.DEBUG)
Log.d(TAG, "Fragment detached");
if (webViewLoader != null) {
webViewLoader.cancel(true);
}
}
@Override
public void onDetach() {
super.onDetach();
if (AppConfig.DEBUG)
Log.d(TAG, "Fragment detached");
if (webViewLoader != null) {
webViewLoader.cancel(true);
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (AppConfig.DEBUG)
Log.d(TAG, "Fragment destroyed");
if (webViewLoader != null) {
webViewLoader.cancel(true);
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (AppConfig.DEBUG)
Log.d(TAG, "Fragment destroyed");
if (webViewLoader != null) {
webViewLoader.cancel(true);
}
}
@SuppressLint("NewApi")
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (AppConfig.DEBUG)
Log.d(TAG, "Creating fragment");
Bundle args = getArguments();
saveState = args.getBoolean(ARG_SAVE_STATE, false);
if (args.containsKey(ARG_PLAYABLE)) {
media = args.getParcelable(ARG_PLAYABLE);
} else if (args.containsKey(ARG_FEED_ID)
&& args.containsKey(ARG_FEED_ITEM_ID)) {
long feedId = args.getLong(ARG_FEED_ID);
long itemId = args.getLong(ARG_FEED_ITEM_ID);
FeedItem f = FeedManager.getInstance().getFeedItem(itemId, feedId);
if (f != null) {
item = f;
}
}
}
@SuppressLint("NewApi")
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (AppConfig.DEBUG)
Log.d(TAG, "Creating fragment");
Bundle args = getArguments();
saveState = args.getBoolean(ARG_SAVE_STATE, false);
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (media != null) {
media.loadShownotes(new Playable.ShownoteLoaderCallback() {
}
@Override
public void onShownotesLoaded(String shownotes) {
ItemDescriptionFragment.this.shownotes = shownotes;
if (ItemDescriptionFragment.this.shownotes != null) {
startLoader();
}
}
});
} else if (item != null) {
if (item.getDescription() == null
|| item.getContentEncoded() == null) {
FeedManager.getInstance().loadExtraInformationOfItem(
PodcastApp.getInstance(), item,
new FeedManager.TaskCallback<String[]>() {
@Override
public void onCompletion(String[] result) {
if (result[1] != null) {
shownotes = result[1];
} else {
shownotes = result[0];
}
if (shownotes != null) {
startLoader();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Bundle args = getArguments();
if (args.containsKey(ARG_PLAYABLE)) {
media = args.getParcelable(ARG_PLAYABLE);
shownotesProvider = media;
startLoader();
} else if (args.containsKey(ARG_FEEDITEM_ID)) {
AsyncTask<Void, Void, FeedItem> itemLoadTask = new AsyncTask<Void, Void, FeedItem>() {
}
});
} else {
shownotes = item.getContentEncoded();
startLoader();
}
} else {
Log.e(TAG, "Error in onViewCreated: Item and media were null");
}
}
@Override
protected FeedItem doInBackground(Void... voids) {
return DBReader.getFeedItem(getActivity(), getArguments().getLong(ARG_FEEDITEM_ID));
}
@Override
public void onResume() {
super.onResume();
}
@Override
protected void onPostExecute(FeedItem feedItem) {
super.onPostExecute(feedItem);
shownotesProvider = feedItem;
startLoader();
}
};
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
itemLoadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
itemLoadTask.execute();
}
}
@SuppressLint("NewApi")
private void startLoader() {
webViewLoader = createLoader();
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
webViewLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
webViewLoader.execute();
}
}
/**
* Return the CSS style of the Webview.
*
* @param textColor
* the default color to use for the text in the webview. This
* value is inserted directly into the CSS String.
* */
private String applyWebviewStyle(String textColor, String data) {
final String WEBVIEW_STYLE = "<html><head><style type=\"text/css\"> * { color: %s; font-family: Helvetica; line-height: 1.5em; font-size: 11pt; } a { font-style: normal; text-decoration: none; font-weight: normal; color: #00A8DF; } img { display: block; margin: 10 auto; max-width: %s; height: auto; } body { margin: %dpx %dpx %dpx %dpx; }</style></head><body>%s</body></html>";
final int pageMargin = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 8, getResources()
.getDisplayMetrics());
return String.format(WEBVIEW_STYLE, textColor, "100%", pageMargin,
pageMargin, pageMargin, pageMargin, data);
}
}
private View.OnLongClickListener webViewLongClickListener = new View.OnLongClickListener() {
@Override
public void onResume() {
super.onResume();
}
@Override
public boolean onLongClick(View v) {
WebView.HitTestResult r = webvDescription.getHitTestResult();
if (r != null
&& r.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
if (AppConfig.DEBUG)
Log.d(TAG,
"Link of webview was long-pressed. Extra: "
+ r.getExtra());
selectedURL = r.getExtra();
webvDescription.showContextMenu();
return true;
}
selectedURL = null;
return false;
}
};
@SuppressLint("NewApi")
private void startLoader() {
webViewLoader = createLoader();
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
webViewLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
webViewLoader.execute();
}
}
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
@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);
getActivity()
.startActivity(new Intent(Intent.ACTION_VIEW, uri));
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;
/**
* Return the CSS style of the Webview.
*
* @param textColor the default color to use for the text in the webview. This
* value is inserted directly into the CSS String.
*/
private String applyWebviewStyle(String textColor, String data) {
final String WEBVIEW_STYLE = "<html><head><style type=\"text/css\"> * { color: %s; font-family: Helvetica; line-height: 1.5em; font-size: 11pt; } a { font-style: normal; text-decoration: none; font-weight: normal; color: #00A8DF; } img { display: block; margin: 10 auto; max-width: %s; height: auto; } body { margin: %dpx %dpx %dpx %dpx; }</style></head><body>%s</body></html>";
final int pageMargin = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 8, getResources()
.getDisplayMetrics());
return String.format(WEBVIEW_STYLE, textColor, "100%", pageMargin,
pageMargin, pageMargin, pageMargin, data);
}
}
selectedURL = null;
}
return handled;
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) {
if (AppConfig.DEBUG)
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 void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
if (selectedURL != null) {
super.onCreateContextMenu(menu, v, menuInfo);
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);
}
}
@SuppressWarnings("deprecation")
@SuppressLint("NewApi")
@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);
getActivity()
.startActivity(new Intent(Intent.ACTION_VIEW, uri));
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;
private AsyncTask<Void, Void, Void> createLoader() {
return new AsyncTask<Void, Void, Void>() {
@Override
protected void onCancelled() {
super.onCancelled();
if (getActivity() != null) {
}
selectedURL = null;
}
return handled;
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
if (selectedURL != null) {
super.onCreateContextMenu(menu, v, menuInfo);
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 AsyncTask<Void, Void, Void> createLoader() {
return new AsyncTask<Void, Void, Void>() {
@Override
protected void onCancelled() {
super.onCancelled();
if (getActivity() != null) {
((ActionBarActivity) getActivity())
.setSupportProgressBarIndeterminateVisibility(false);
}
webViewLoader = null;
}
.setSupportProgressBarIndeterminateVisibility(false);
}
webViewLoader = null;
}
String data;
String data;
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
// /webvDescription.loadData(url, "text/html", "utf-8");
webvDescription.loadDataWithBaseURL(null, data, "text/html",
"utf-8", "about:blank");
if (getActivity() != null) {
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
// /webvDescription.loadData(url, "text/html", "utf-8");
webvDescription.loadDataWithBaseURL(null, data, "text/html",
"utf-8", "about:blank");
if (getActivity() != null) {
((ActionBarActivity) getActivity())
.setSupportProgressBarIndeterminateVisibility(false);
}
if (AppConfig.DEBUG)
Log.d(TAG, "Webview loaded");
webViewLoader = null;
}
.setSupportProgressBarIndeterminateVisibility(false);
}
if (AppConfig.DEBUG)
Log.d(TAG, "Webview loaded");
webViewLoader = null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
if (getActivity() != null) {
@Override
protected void onPreExecute() {
super.onPreExecute();
if (getActivity() != null) {
((ActionBarActivity) getActivity())
.setSupportProgressBarIndeterminateVisibility(true);
}
}
.setSupportProgressBarIndeterminateVisibility(false);
}
}
@Override
protected Void doInBackground(Void... params) {
if (AppConfig.DEBUG)
Log.d(TAG, "Loading Webview");
data = "";
data = StringEscapeUtils.unescapeHtml4(shownotes);
Activity activity = getActivity();
if (activity != null) {
TypedArray res = getActivity()
.getTheme()
.obtainStyledAttributes(
new int[] { android.R.attr.textColorPrimary });
int colorResource = res.getColor(0, 0);
String colorString = String.format("#%06X",
0xFFFFFF & colorResource);
Log.i(TAG, "text color: " + colorString);
res.recycle();
data = applyWebviewStyle(colorString, data);
} else {
cancel(true);
}
return null;
}
@Override
protected Void doInBackground(Void... params) {
if (AppConfig.DEBUG)
Log.d(TAG, "Loading Webview");
try {
Callable<String> shownotesLoadTask = shownotesProvider.loadShownotes();
final String shownotes = shownotesLoadTask.call();
};
}
data = "";
data = StringEscapeUtils.unescapeHtml4(shownotes);
Activity activity = getActivity();
if (activity != null) {
TypedArray res = getActivity()
.getTheme()
.obtainStyledAttributes(
new int[]{android.R.attr.textColorPrimary});
int colorResource = res.getColor(0, 0);
String colorString = String.format("#%06X",
0xFFFFFF & colorResource);
Log.i(TAG, "text color: " + colorString);
res.recycle();
data = applyWebviewStyle(colorString, data);
} else {
cancel(true);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public void onPause() {
super.onPause();
savePreference();
}
};
}
private void savePreference() {
if (saveState) {
if (AppConfig.DEBUG)
Log.d(TAG, "Saving preferences");
SharedPreferences prefs = getActivity().getSharedPreferences(PREF,
Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
if (media != null && webvDescription != null) {
if (AppConfig.DEBUG)
Log.d(TAG,
"Saving scroll position: "
+ webvDescription.getScrollY());
editor.putInt(PREF_SCROLL_Y, webvDescription.getScrollY());
editor.putString(PREF_PLAYABLE_ID, media.getIdentifier()
.toString());
} else {
if (AppConfig.DEBUG)
Log.d(TAG,
"savePreferences was called while media or webview was null");
editor.putInt(PREF_SCROLL_Y, -1);
editor.putString(PREF_PLAYABLE_ID, "");
}
editor.commit();
}
}
@Override
public void onPause() {
super.onPause();
savePreference();
}
private boolean restoreFromPreference() {
if (saveState) {
if (AppConfig.DEBUG)
Log.d(TAG, "Restoring from preferences");
Activity activity = getActivity();
if (activity != null) {
SharedPreferences prefs = activity.getSharedPreferences(
PREF, Activity.MODE_PRIVATE);
String id = prefs.getString(PREF_PLAYABLE_ID, "");
int scrollY = prefs.getInt(PREF_SCROLL_Y, -1);
if (scrollY != -1 && media != null
&& id.equals(media.getIdentifier().toString())
&& webvDescription != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Restored scroll Position: " + scrollY);
webvDescription.scrollTo(webvDescription.getScrollX(),
scrollY);
return true;
}
}
}
return false;
}
private void savePreference() {
if (saveState) {
if (AppConfig.DEBUG)
Log.d(TAG, "Saving preferences");
SharedPreferences prefs = getActivity().getSharedPreferences(PREF,
Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
if (media != null && webvDescription != null) {
if (AppConfig.DEBUG)
Log.d(TAG,
"Saving scroll position: "
+ webvDescription.getScrollY());
editor.putInt(PREF_SCROLL_Y, webvDescription.getScrollY());
editor.putString(PREF_PLAYABLE_ID, media.getIdentifier()
.toString());
} else {
if (AppConfig.DEBUG)
Log.d(TAG,
"savePreferences was called while media or webview was null");
editor.putInt(PREF_SCROLL_Y, -1);
editor.putString(PREF_PLAYABLE_ID, "");
}
editor.commit();
}
}
private boolean restoreFromPreference() {
if (saveState) {
if (AppConfig.DEBUG)
Log.d(TAG, "Restoring from preferences");
Activity activity = getActivity();
if (activity != null) {
SharedPreferences prefs = activity.getSharedPreferences(
PREF, Activity.MODE_PRIVATE);
String id = prefs.getString(PREF_PLAYABLE_ID, "");
int scrollY = prefs.getInt(PREF_SCROLL_Y, -1);
if (scrollY != -1 && media != null
&& id.equals(media.getIdentifier().toString())
&& webvDescription != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Restored scroll Position: " + scrollY);
webvDescription.scrollTo(webvDescription.getScrollX(),
scrollY);
return true;
}
}
}
return false;
}
}

View File

@ -1,7 +1,9 @@
package de.danoeh.antennapod.fragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v7.app.ActionBarActivity;
@ -14,22 +16,27 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import android.widget.TextView;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.ItemviewActivity;
import de.danoeh.antennapod.adapter.ActionButtonCallback;
import de.danoeh.antennapod.adapter.DefaultFeedItemlistAdapter;
import de.danoeh.antennapod.adapter.InternalFeedItemlistAdapter;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.service.download.DownloadService;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler;
import java.util.Iterator;
import java.util.List;
/** Displays a list of FeedItems. */
@SuppressLint("ValidFragment")
public class ItemlistFragment extends ListFragment {
@ -43,12 +50,10 @@ public class ItemlistFragment extends ListFragment {
public static final String EXTRA_SELECTED_FEEDITEM = "extra.de.danoeh.antennapod.activity.selected_feeditem";
public static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id";
protected InternalFeedItemlistAdapter fila;
protected FeedManager manager = FeedManager.getInstance();
protected DownloadRequester requester = DownloadRequester.getInstance();
private DefaultFeedItemlistAdapter.ItemAccess itemAccess;
private Feed feed;
protected List<Long> queue;
protected FeedItem selectedItem = null;
protected boolean contextMenuClosed = true;
@ -56,10 +61,8 @@ public class ItemlistFragment extends ListFragment {
/** Argument for FeeditemlistAdapter */
protected boolean showFeedtitle;
public ItemlistFragment(DefaultFeedItemlistAdapter.ItemAccess itemAccess,
boolean showFeedtitle) {
public ItemlistFragment(boolean showFeedtitle) {
super();
this.itemAccess = itemAccess;
this.showFeedtitle = showFeedtitle;
}
@ -83,6 +86,30 @@ public class ItemlistFragment extends ListFragment {
return i;
}
private InternalFeedItemlistAdapter.ItemAccess itemAccessRef;
protected InternalFeedItemlistAdapter.ItemAccess itemAccess() {
if (itemAccessRef == null) {
itemAccessRef = new InternalFeedItemlistAdapter.ItemAccess() {
@Override
public FeedItem getItem(int position) {
return (feed != null) ? feed.getItemAtIndex(true, position) : null;
}
@Override
public int getCount() {
return (feed != null) ? feed.getNumOfItems(true) : 0;
}
@Override
public boolean isInQueue(FeedItem item) {
return (queue != null) && queue.contains(item.getId());
}
};
}
return itemAccessRef;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@ -92,27 +119,71 @@ public class ItemlistFragment extends ListFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (itemAccess == null) {
long feedId = getArguments().getLong(ARGUMENT_FEED_ID);
final Feed feed = FeedManager.getInstance().getFeed(feedId);
this.feed = feed;
itemAccess = new DefaultFeedItemlistAdapter.ItemAccess() {
@Override
public FeedItem getItem(int position) {
return feed.getItemAtIndex(true, position);
}
@Override
public int getCount() {
return feed.getNumOfItems(true);
}
};
}
loadData();
}
protected void loadData() {
final long feedId;
if (feed == null) {
feedId = getArguments().getLong(ARGUMENT_FEED_ID);
} else {
feedId = feed.getId();
}
AsyncTask<Long, Void, Feed> loadTask = new AsyncTask<Long, Void, Feed>(){
private volatile List<Long> queueRef;
@Override
protected Feed doInBackground(Long... longs) {
Context context = ItemlistFragment.this.getActivity();
if (context != null) {
Feed result = DBReader.getFeed(context, longs[0]);
if (result != null) {
result.setItems(DBReader.getFeedItemList(context, result));
queueRef = DBReader.getQueueIDList(context);
return result;
}
}
return null;
}
@Override
protected void onPostExecute(Feed result) {
super.onPostExecute(result);
if (result != null && result.getItems() != null) {
feed = result;
if (queueRef != null) {
queue = queueRef;
} else {
Log.e(TAG, "Could not load queue");
}
if (result.getItems().isEmpty()) {
}
setEmptyViewIfListIsEmpty();
if (fila != null) {
fila.notifyDataSetChanged();
}
} else {
if (result == null) {
Log.e(TAG, "Could not load feed with id " + feedId);
} else if (result.getItems() == null) {
Log.e(TAG, "Could not load feed items");
}
}
}
};
loadTask.execute(feedId);
}
private void setEmptyViewIfListIsEmpty() {
if (getListView() != null && feed != null && feed.getItems() != null) {
if (feed.getItems().isEmpty()) {
((TextView) getActivity().findViewById(android.R.id.empty)).setText(R.string.no_items_label);
}
}
}
protected InternalFeedItemlistAdapter createListAdapter() {
return new InternalFeedItemlistAdapter(getActivity(), itemAccess,
return new InternalFeedItemlistAdapter(getActivity(), itemAccess(),
adapterCallback, showFeedtitle);
}
@ -162,7 +233,9 @@ public class ItemlistFragment extends ListFragment {
if ((EventDistributor.DOWNLOAD_QUEUED & arg) != 0) {
updateProgressBarVisibility();
} else {
fila.notifyDataSetChanged();
if (feed != null) {
loadData();
}
updateProgressBarVisibility();
}
}
@ -218,13 +291,13 @@ public class ItemlistFragment extends ListFragment {
menu.setHeaderTitle(selectedItem.getTitle());
FeedItemMenuHandler.onPrepareMenu(
new FeedItemMenuHandler.MenuInterface() {
new FeedItemMenuHandler.MenuInterface() {
@Override
public void setItemVisibility(int id, boolean visible) {
menu.findItem(id).setVisible(visible);
}
}, selectedItem, false);
@Override
public void setItemVisibility(int id, boolean visible) {
menu.findItem(id).setVisible(visible);
}
}, selectedItem, false, QueueAccess.IDListAccess(queue));
}
}

View File

@ -1,33 +1,53 @@
package de.danoeh.antennapod.fragment;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.adapter.DefaultFeedItemlistAdapter;
import de.danoeh.antennapod.adapter.InternalFeedItemlistAdapter;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.storage.DBReader;
import java.util.Iterator;
import java.util.List;
public class PlaybackHistoryFragment extends ItemlistFragment {
private static final String TAG = "PlaybackHistoryFragment";
private List<FeedItem> playbackHistory;
public PlaybackHistoryFragment() {
super(new DefaultFeedItemlistAdapter.ItemAccess() {
@Override
public FeedItem getItem(int position) {
return FeedManager.getInstance().getPlaybackHistoryItemIndex(
position);
}
@Override
public int getCount() {
return FeedManager.getInstance().getPlaybackHistorySize();
}
}, true);
super(true);
}
@Override
InternalFeedItemlistAdapter.ItemAccess itemAccessRef;
@Override
protected InternalFeedItemlistAdapter.ItemAccess itemAccess() {
if (itemAccessRef == null) {
itemAccessRef = new InternalFeedItemlistAdapter.ItemAccess() {
@Override
public FeedItem getItem(int position) {
return (playbackHistory != null) ? playbackHistory.get(position) : null;
}
@Override
public int getCount() {
return (playbackHistory != null) ? playbackHistory.size() : 0;
}
@Override
public boolean isInQueue(FeedItem item) {
return (queue != null) ? queue.contains(item.getId()) : false;
}
};
}
return itemAccessRef;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EventDistributor.getInstance().register(historyUpdate);
@ -46,10 +66,48 @@ public class PlaybackHistoryFragment extends ItemlistFragment {
if ((EventDistributor.PLAYBACK_HISTORY_UPDATE & arg) != 0) {
if (AppConfig.DEBUG)
Log.d(TAG, "Received content update");
fila.notifyDataSetChanged();
loadData();
}
}
};
@Override
protected void loadData() {
AsyncTask<Void, Void, Void> loadTask = new AsyncTask<Void, Void, Void>() {
private volatile List<FeedItem> phRef;
private volatile List<Long> queueRef;
@Override
protected Void doInBackground(Void... voids) {
Context context = PlaybackHistoryFragment.this.getActivity();
if (context != null) {
queueRef = DBReader.getQueueIDList(context);
phRef = DBReader.getPlaybackHistory(context);
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
if (queueRef != null && phRef != null) {
queue = queueRef;
playbackHistory = phRef;
Log.i(TAG, "Number of items in playback history: " + playbackHistory.size());
if (fila != null) {
fila.notifyDataSetChanged();
}
} else {
if (queueRef == null) {
Log.e(TAG, "Could not load queue");
}
if (phRef == null) {
Log.e(TAG, "Could not load playback history");
}
}
}
};
loadTask.execute();
}
}

View File

@ -7,7 +7,7 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.storage.DBTasks;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.NetworkUtils;
@ -27,7 +27,7 @@ public class ConnectivityActionReceiver extends BroadcastReceiver {
new Thread() {
@Override
public void run() {
FeedManager.getInstance()
DBTasks
.autodownloadUndownloadedItems(context);
}
}.start();

View File

@ -7,8 +7,8 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.storage.DBTasks;
/** Refreshes all feeds when it receives an intent */
public class FeedUpdateReceiver extends BroadcastReceiver {
@ -22,7 +22,7 @@ public class FeedUpdateReceiver extends BroadcastReceiver {
Log.d(TAG, "Received intent");
boolean mobileUpdate = UserPreferences.isAllowMobileUpdate();
if (mobileUpdate || connectedToWifi(context)) {
FeedManager.getInstance().refreshExpiredFeeds(context);
DBTasks.refreshExpiredFeeds(context);
} else {
if (AppConfig.DEBUG)
Log.d(TAG,

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,177 @@
package de.danoeh.antennapod.service.download;
import android.os.Parcel;
import android.os.Parcelable;
public class DownloadRequest implements Parcelable {
private final String destination;
private final String source;
private final String title;
private final long feedfileId;
private final int feedfileType;
protected int progressPercent;
protected long soFar;
protected long size;
protected int statusMsg;
public DownloadRequest(String destination, String source, String title,
long feedfileId, int feedfileType) {
if (destination == null) {
throw new IllegalArgumentException("Destination must not be null");
}
if (source == null) {
throw new IllegalArgumentException("Source must not be null");
}
if (title == null) {
throw new IllegalArgumentException("Title must not be null");
}
this.destination = destination;
this.source = source;
this.title = title;
this.feedfileId = feedfileId;
this.feedfileType = feedfileType;
}
private DownloadRequest(Parcel in) {
destination = in.readString();
source = in.readString();
title = in.readString();
feedfileId = in.readLong();
feedfileType = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(destination);
dest.writeString(source);
dest.writeString(title);
dest.writeLong(feedfileId);
dest.writeInt(feedfileType);
}
public static final Parcelable.Creator<DownloadRequest> CREATOR = new Parcelable.Creator<DownloadRequest>() {
public DownloadRequest createFromParcel(Parcel in) {
return new DownloadRequest(in);
}
public DownloadRequest[] newArray(int size) {
return new DownloadRequest[size];
}
};
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((destination == null) ? 0 : destination.hashCode());
result = prime * result + (int) (feedfileId ^ (feedfileId >>> 32));
result = prime * result + feedfileType;
result = prime * result + progressPercent;
result = prime * result + (int) (size ^ (size >>> 32));
result = prime * result + (int) (soFar ^ (soFar >>> 32));
result = prime * result + ((source == null) ? 0 : source.hashCode());
result = prime * result + statusMsg;
result = prime * result + ((title == null) ? 0 : title.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DownloadRequest other = (DownloadRequest) obj;
if (destination == null) {
if (other.destination != null)
return false;
} else if (!destination.equals(other.destination))
return false;
if (feedfileId != other.feedfileId)
return false;
if (feedfileType != other.feedfileType)
return false;
if (progressPercent != other.progressPercent)
return false;
if (size != other.size)
return false;
if (soFar != other.soFar)
return false;
if (source == null) {
if (other.source != null)
return false;
} else if (!source.equals(other.source))
return false;
if (statusMsg != other.statusMsg)
return false;
if (title == null) {
if (other.title != null)
return false;
} else if (!title.equals(other.title))
return false;
return true;
}
public String getDestination() {
return destination;
}
public String getSource() {
return source;
}
public String getTitle() {
return title;
}
public long getFeedfileId() {
return feedfileId;
}
public int getFeedfileType() {
return feedfileType;
}
public int getProgressPercent() {
return progressPercent;
}
public void setProgressPercent(int progressPercent) {
this.progressPercent = progressPercent;
}
public long getSoFar() {
return soFar;
}
public void setSoFar(long soFar) {
this.soFar = soFar;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
public int getStatusMsg() {
return statusMsg;
}
public void setStatusMsg(int statusMsg) {
this.statusMsg = statusMsg;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.asynctask;
package de.danoeh.antennapod.service.download;
import java.util.Date;
@ -13,10 +13,6 @@ public class DownloadStatus {
*/
public static final int SIZE_UNKNOWN = -1;
public Date getCompletionDate() {
return completionDate;
}
// ----------------------------------- ATTRIBUTES STORED IN DB
/** Unique id for storing the object in database. */
protected long id;
@ -34,7 +30,7 @@ public class DownloadStatus {
protected String reasonDetailed;
protected boolean successful;
protected Date completionDate;
protected FeedFile feedfile;
protected long feedfileId;
/**
* Is used to determine the type of the feedfile even if the feedfile does
* not exist anymore. The value should be FEEDFILETYPE_FEED,
@ -43,33 +39,17 @@ public class DownloadStatus {
protected int feedfileType;
// ------------------------------------ NOT STORED IN DB
protected int progressPercent;
protected long soFar;
protected long size;
protected int statusMsg;
protected boolean done;
protected boolean cancelled;
public DownloadStatus(FeedFile feedfile, String title) {
this.feedfile = feedfile;
if (feedfile != null) {
feedfileType = feedfile.getTypeAsInt();
}
this.title = title;
}
/** Constructor for restoring Download status entries from DB. */
public DownloadStatus(long id, String title, FeedFile feedfile,
public DownloadStatus(long id, String title, long feedfileId,
int feedfileType, boolean successful, DownloadError reason,
Date completionDate, String reasonDetailed) {
progressPercent = 100;
soFar = 0;
size = 0;
this.id = id;
this.title = title;
this.done = true;
this.feedfile = feedfile;
this.feedfileId = feedfileId;
this.reason = reason;
this.successful = successful;
this.completionDate = completionDate;
@ -77,11 +57,49 @@ public class DownloadStatus {
this.feedfileType = feedfileType;
}
public DownloadStatus(DownloadRequest request, DownloadError reason,
boolean successful, boolean cancelled, String reasonDetailed) {
if (request == null) {
throw new IllegalArgumentException("request must not be null");
}
this.title = request.getTitle();
this.feedfileId = request.getFeedfileId();
this.feedfileType = request.getFeedfileType();
this.reason = reason;
this.successful = successful;
this.cancelled = cancelled;
this.reasonDetailed = reasonDetailed;
this.completionDate = new Date();
}
/** Constructor for creating new completed downloads. */
public DownloadStatus(FeedFile feedfile, String title, DownloadError reason,
boolean successful, String reasonDetailed) {
this(0, title, feedfile, feedfile.getTypeAsInt(), successful, reason,
new Date(), reasonDetailed);
if (feedfile == null) {
throw new IllegalArgumentException("feedfile must not be null");
}
this.title = title;
this.done = true;
this.feedfileId = feedfile.getId();
this.feedfileType = feedfile.getTypeAsInt();
this.reason = reason;
this.successful = successful;
this.completionDate = new Date();
this.reasonDetailed = reasonDetailed;
}
/** Constructor for creating new completed downloads. */
public DownloadStatus(long feedfileId, int feedfileType, String title,
DownloadError reason, boolean successful, String reasonDetailed) {
this.title = title;
this.done = true;
this.feedfileId = feedfileId;
this.feedfileType = feedfileType;
this.reason = reason;
this.successful = successful;
this.completionDate = new Date();
this.reasonDetailed = reasonDetailed;
}
@Override
@ -89,68 +107,50 @@ public class DownloadStatus {
return "DownloadStatus [id=" + id + ", title=" + title + ", reason="
+ reason + ", reasonDetailed=" + reasonDetailed
+ ", successful=" + successful + ", completionDate="
+ completionDate + ", feedfile=" + feedfile + ", feedfileType="
+ feedfileType + ", progressPercent=" + progressPercent
+ ", soFar=" + soFar + ", size=" + size + ", statusMsg="
+ statusMsg + ", done=" + done + ", cancelled=" + cancelled
+ "]";
+ completionDate + ", feedfileId=" + feedfileId
+ ", feedfileType=" + feedfileType + ", done=" + done
+ ", cancelled=" + cancelled + "]";
}
public FeedFile getFeedFile() {
return feedfile;
}
public long getId() {
return id;
}
public int getProgressPercent() {
return progressPercent;
}
public String getTitle() {
return title;
}
public long getSoFar() {
return soFar;
}
public DownloadError getReason() {
return reason;
}
public long getSize() {
return size;
}
public String getReasonDetailed() {
return reasonDetailed;
}
public int getStatusMsg() {
return statusMsg;
}
public boolean isSuccessful() {
return successful;
}
public DownloadError getReason() {
return reason;
}
public Date getCompletionDate() {
return completionDate;
}
public boolean isSuccessful() {
return successful;
}
public long getFeedfileId() {
return feedfileId;
}
public long getId() {
return id;
}
public int getFeedfileType() {
return feedfileType;
}
public void setId(long id) {
this.id = id;
}
public boolean isDone() {
return done;
}
public boolean isDone() {
return done;
}
public void setProgressPercent(int progressPercent) {
this.progressPercent = progressPercent;
}
public void setSoFar(long soFar) {
this.soFar = soFar;
}
public void setSize(long size) {
this.size = size;
}
public void setStatusMsg(int statusMsg) {
this.statusMsg = statusMsg;
}
public boolean isCancelled() {
return cancelled;
}
public void setSuccessful() {
this.successful = true;
@ -171,36 +171,11 @@ public class DownloadStatus {
this.cancelled = true;
}
public void setCompletionDate(Date completionDate) {
this.completionDate = completionDate;
}
public void setCompletionDate(Date completionDate) {
this.completionDate = completionDate;
}
public String getReasonDetailed() {
return reasonDetailed;
}
public void setReasonDetailed(String reasonDetailed) {
this.reasonDetailed = reasonDetailed;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getFeedfileType() {
return feedfileType;
}
public boolean isCancelled() {
return cancelled;
}
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}
public void setId(long id) {
this.id = id;
}
}

View File

@ -1,49 +1,50 @@
package de.danoeh.antennapod.service.download;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.DownloadStatus;
import java.util.concurrent.Callable;
/** Downloads files */
public abstract class Downloader extends Thread {
public abstract class Downloader implements Callable<Downloader> {
private static final String TAG = "Downloader";
private DownloaderCallback downloaderCallback;
protected boolean finished;
protected volatile boolean finished;
protected volatile boolean cancelled;
protected volatile DownloadStatus status;
protected DownloadRequest request;
protected DownloadStatus result;
public Downloader(DownloaderCallback downloaderCallback,
DownloadStatus status) {
public Downloader(DownloadRequest request) {
super();
this.downloaderCallback = downloaderCallback;
this.status = status;
this.status.setStatusMsg(R.string.download_pending);
this.request = request;
this.request.setStatusMsg(R.string.download_pending);
this.cancelled = false;
}
/**
* This method must be called when the download was completed, failed, or
* was cancelled
*/
protected void finish() {
if (!finished) {
finished = true;
downloaderCallback.onDownloadCompleted(this);
}
this.result = new DownloadStatus(request, null, false, false, null);
}
protected abstract void download();
@Override
public final void run() {
public final Downloader call() {
download();
finish();
if (result == null) {
throw new IllegalStateException(
"Downloader hasn't created DownloadStatus object");
}
finished = true;
return this;
}
public DownloadStatus getStatus() {
return status;
public DownloadRequest getDownloadRequest() {
return request;
}
public DownloadStatus getResult() {
return result;
}
public boolean isFinished() {
return finished;
}
public void cancel() {

View File

@ -26,7 +26,6 @@ import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.util.DownloadError;
import de.danoeh.antennapod.util.StorageUtils;
@ -39,9 +38,8 @@ public class HttpDownloader extends Downloader {
private static final int CONNECTION_TIMEOUT = 30000;
private static final int SOCKET_TIMEOUT = 30000;
public HttpDownloader(DownloaderCallback downloaderCallback,
DownloadStatus status) {
super(downloaderCallback, status);
public HttpDownloader(DownloadRequest request) {
super(request);
}
private DefaultHttpClient createHttpClient() {
@ -63,11 +61,10 @@ public class HttpDownloader extends Downloader {
@Override
protected void download() {
DefaultHttpClient httpClient = null;
BufferedOutputStream out = null;
OutputStream out = null;
InputStream connection = null;
try {
HttpGet httpGet = new HttpGet(status.getFeedFile()
.getDownload_url());
HttpGet httpGet = new HttpGet(request.getSource());
httpClient = createHttpClient();
HttpResponse response = httpClient.execute(httpGet);
HttpEntity httpEntity = response.getEntity();
@ -76,8 +73,7 @@ public class HttpDownloader extends Downloader {
Log.d(TAG, "Response code is " + responseCode);
if (responseCode == HttpURLConnection.HTTP_OK && httpEntity != null) {
if (StorageUtils.storageAvailable(PodcastApp.getInstance())) {
File destination = new File(status.getFeedFile()
.getFile_url());
File destination = new File(request.getDestination());
if (!destination.exists()) {
connection = AndroidHttpClient
.getUngzippedContent(httpEntity);
@ -86,34 +82,34 @@ public class HttpDownloader extends Downloader {
destination));
byte[] buffer = new byte[BUFFER_SIZE];
int count = 0;
status.setStatusMsg(R.string.download_running);
request.setStatusMsg(R.string.download_running);
if (AppConfig.DEBUG)
Log.d(TAG, "Getting size of download");
status.setSize(httpEntity.getContentLength());
request.setSize(httpEntity.getContentLength());
if (AppConfig.DEBUG)
Log.d(TAG, "Size is " + status.getSize());
if (status.getSize() < 0) {
status.setSize(DownloadStatus.SIZE_UNKNOWN);
Log.d(TAG, "Size is " + request.getSize());
if (request.getSize() < 0) {
request.setSize(DownloadStatus.SIZE_UNKNOWN);
}
long freeSpace = StorageUtils.getFreeSpaceAvailable();
if (AppConfig.DEBUG)
Log.d(TAG, "Free space is " + freeSpace);
if (status.getSize() == DownloadStatus.SIZE_UNKNOWN
|| status.getSize() <= freeSpace) {
if (request.getSize() == DownloadStatus.SIZE_UNKNOWN
|| request.getSize() <= freeSpace) {
if (AppConfig.DEBUG)
Log.d(TAG, "Starting download");
while (!cancelled
&& (count = in.read(buffer)) != -1) {
out.write(buffer, 0, count);
status.setSoFar(status.getSoFar() + count);
status.setProgressPercent((int) (((double) status
.getSoFar() / (double) status.getSize()) * 100));
request.setSoFar(request.getSoFar() + count);
request.setProgressPercent((int) (((double) request
.getSoFar() / (double) request
.getSize()) * 100));
}
if (cancelled) {
onCancelled();
} else {
out.flush();
onSuccess();
}
} else {
@ -145,10 +141,8 @@ public class HttpDownloader extends Downloader {
} catch (NullPointerException e) {
// might be thrown by connection.getInputStream()
e.printStackTrace();
onFail(DownloadError.ERROR_CONNECTION_ERROR, status.getFeedFile()
.getDownload_url());
onFail(DownloadError.ERROR_CONNECTION_ERROR, request.getSource());
} finally {
IOUtils.closeQuietly(connection);
IOUtils.closeQuietly(out);
if (httpClient != null) {
httpClient.getConnectionManager().shutdown();
@ -159,29 +153,28 @@ public class HttpDownloader extends Downloader {
private void onSuccess() {
if (AppConfig.DEBUG)
Log.d(TAG, "Download was successful");
status.setSuccessful();
result.setSuccessful();
}
private void onFail(DownloadError reason, String reasonDetailed) {
if (AppConfig.DEBUG) {
Log.d(TAG, "Download failed");
}
status.setFailed(reason, reasonDetailed);
result.setFailed(reason, reasonDetailed);
cleanup();
}
private void onCancelled() {
if (AppConfig.DEBUG)
Log.d(TAG, "Download was cancelled");
status.setCancelled();
result.setCancelled();
cleanup();
}
/** Deletes unfinished downloads. */
private void cleanup() {
if (status != null && status.getFeedFile() != null
&& status.getFeedFile().getFile_url() != null) {
File dest = new File(status.getFeedFile().getFile_url());
if (request.getDestination() != null) {
File dest = new File(request.getDestination());
if (dest.exists()) {
boolean rc = dest.delete();
if (AppConfig.DEBUG)

View File

@ -0,0 +1,752 @@
package de.danoeh.antennapod.storage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.feed.Chapter;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedImage;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.feed.ID3Chapter;
import de.danoeh.antennapod.feed.SimpleChapter;
import de.danoeh.antennapod.feed.VorbisCommentChapter;
import de.danoeh.antennapod.service.download.*;
import de.danoeh.antennapod.util.DownloadError;
import de.danoeh.antennapod.util.comparator.DownloadStatusComparator;
import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator;
/**
* Provides methods for reading data from the AntennaPod database.
* In general, all database calls in DBReader-methods are executed on the caller's thread.
* This means that the caller should make sure that DBReader-methods are not executed on the GUI-thread.
* This class will use the {@link de.danoeh.antennapod.feed.EventDistributor} to notify listeners about changes in the database.
*/
public final class DBReader {
private static final String TAG = "DBReader";
/**
* Maximum size of the list returned by {@link #getPlaybackHistory(android.content.Context)}.
*/
public static final int PLAYBACK_HISTORY_SIZE = 50;
/**
* Maximum size of the list returned by {@link #getDownloadLog(android.content.Context)}.
*/
public static final int DOWNLOAD_LOG_SIZE = 200;
private DBReader() {
}
/**
* Returns a list of Feeds, sorted alphabetically by their title.
*
* @param context A context that is used for opening a database connection.
* @return A list of Feeds, sorted alphabetically by their title. A Feed-object
* of the returned list does NOT have its list of FeedItems yet. The FeedItem-list
* can be loaded separately with {@link #getFeedItemList(android.content.Context, de.danoeh.antennapod.feed.Feed)}.
*/
public static List<Feed> getFeedList(final Context context) {
if (AppConfig.DEBUG)
Log.d(TAG, "Extracting Feedlist");
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
Cursor feedlistCursor = adapter.getAllFeedsCursor();
List<Feed> feeds = new ArrayList<Feed>(feedlistCursor.getCount());
if (feedlistCursor.moveToFirst()) {
do {
Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor);
feeds.add(feed);
} while (feedlistCursor.moveToNext());
}
feedlistCursor.close();
return feeds;
}
/**
* Returns a list of 'expired Feeds', i.e. Feeds that have not been updated for a certain amount of time.
*
* @param context A context that is used for opening a database connection.
* @param expirationTime Time that is used for determining whether a feed is outdated or not.
* A Feed is considered expired if 'lastUpdate < (currentTime - expirationTime)' evaluates to true.
* @return A list of Feeds, sorted alphabetically by their title. A Feed-object
* of the returned list does NOT have its list of FeedItems yet. The FeedItem-list
* can be loaded separately with {@link #getFeedItemList(android.content.Context, de.danoeh.antennapod.feed.Feed)}.
*/
static List<Feed> getExpiredFeedsList(final Context context, final long expirationTime) {
if (AppConfig.DEBUG)
Log.d(TAG, String.format("getExpiredFeedsList(%d)", expirationTime));
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
Cursor feedlistCursor = adapter.getExpiredFeedsCursor(expirationTime);
List<Feed> feeds = new ArrayList<Feed>(feedlistCursor.getCount());
if (feedlistCursor.moveToFirst()) {
do {
Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor);
feeds.add(feed);
} while (feedlistCursor.moveToNext());
}
feedlistCursor.close();
return feeds;
}
/**
* Takes a list of FeedItems and loads their corresponding Feed-objects from the database.
*
* @param context A context that is used for opening a database connection.
* @param items The FeedItems whose Feed-objects should be loaded.
*/
public static void loadFeedDataOfFeedItemlist(Context context,
List<FeedItem> items) {
List<Feed> feeds = getFeedList(context);
for (FeedItem item : items) {
for (Feed feed : feeds) {
if (feed.getId() == item.getFeedId()) {
item.setFeed(feed);
break;
}
}
if (item.getFeed() == null) {
Log.w(TAG, "No match found for item with ID " + item.getId() + ". Feed ID was " + item.getFeedId());
}
}
}
/**
* Loads the list of FeedItems for a certain Feed-object. This method should NOT be used if the FeedItems are not
* used. In order to get information ABOUT the list of FeedItems, consider using {@link #getFeedStatisticsList(android.content.Context)} instead.
*
* @param context A context that is used for opening a database connection.
* @param feed The Feed whose items should be loaded
* @return A list with the FeedItems of the Feed. The Feed-attribute of the FeedItems will already be set correctly.
* The method does NOT change the items-attribute of the feed.
*/
public static List<FeedItem> getFeedItemList(Context context,
final Feed feed) {
if (AppConfig.DEBUG)
Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle());
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
Cursor itemlistCursor = adapter.getAllItemsOfFeedCursor(feed);
List<FeedItem> items = extractItemlistFromCursor(adapter,
itemlistCursor);
itemlistCursor.close();
Collections.sort(items, new FeedItemPubdateComparator());
adapter.close();
for (FeedItem item : items) {
item.setFeed(feed);
}
return items;
}
static List<FeedItem> extractItemlistFromCursor(Context context, Cursor itemlistCursor) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
List<FeedItem> result = extractItemlistFromCursor(adapter, itemlistCursor);
adapter.close();
return result;
}
private static List<FeedItem> extractItemlistFromCursor(
PodDBAdapter adapter, Cursor itemlistCursor) {
ArrayList<String> itemIds = new ArrayList<String>();
List<FeedItem> items = new ArrayList<FeedItem>(
itemlistCursor.getCount());
if (itemlistCursor.moveToFirst()) {
do {
FeedItem item = new FeedItem();
item.setId(itemlistCursor.getLong(PodDBAdapter.IDX_FI_SMALL_ID));
item.setTitle(itemlistCursor
.getString(PodDBAdapter.IDX_FI_SMALL_TITLE));
item.setLink(itemlistCursor
.getString(PodDBAdapter.IDX_FI_SMALL_LINK));
item.setPubDate(new Date(itemlistCursor
.getLong(PodDBAdapter.IDX_FI_SMALL_PUBDATE)));
item.setPaymentLink(itemlistCursor
.getString(PodDBAdapter.IDX_FI_SMALL_PAYMENT_LINK));
item.setFeedId(itemlistCursor
.getLong(PodDBAdapter.IDX_FI_SMALL_FEED));
itemIds.add(String.valueOf(item.getId()));
item.setRead((itemlistCursor
.getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0));
item.setItemIdentifier(itemlistCursor
.getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER));
// extract chapters
boolean hasSimpleChapters = itemlistCursor
.getInt(PodDBAdapter.IDX_FI_SMALL_HAS_CHAPTERS) > 0;
if (hasSimpleChapters) {
Cursor chapterCursor = adapter
.getSimpleChaptersOfFeedItemCursor(item);
if (chapterCursor.moveToFirst()) {
item.setChapters(new ArrayList<Chapter>());
do {
int chapterType = chapterCursor
.getInt(PodDBAdapter.KEY_CHAPTER_TYPE_INDEX);
Chapter chapter = null;
long start = chapterCursor
.getLong(PodDBAdapter.KEY_CHAPTER_START_INDEX);
String title = chapterCursor
.getString(PodDBAdapter.KEY_TITLE_INDEX);
String link = chapterCursor
.getString(PodDBAdapter.KEY_CHAPTER_LINK_INDEX);
switch (chapterType) {
case SimpleChapter.CHAPTERTYPE_SIMPLECHAPTER:
chapter = new SimpleChapter(start, title, item,
link);
break;
case ID3Chapter.CHAPTERTYPE_ID3CHAPTER:
chapter = new ID3Chapter(start, title, item,
link);
break;
case VorbisCommentChapter.CHAPTERTYPE_VORBISCOMMENT_CHAPTER:
chapter = new VorbisCommentChapter(start,
title, item, link);
break;
}
chapter.setId(chapterCursor
.getLong(PodDBAdapter.KEY_ID_INDEX));
item.getChapters().add(chapter);
} while (chapterCursor.moveToNext());
}
chapterCursor.close();
}
items.add(item);
} while (itemlistCursor.moveToNext());
}
extractMediafromItemlist(adapter, items, itemIds);
return items;
}
private static void extractMediafromItemlist(PodDBAdapter adapter,
List<FeedItem> items, ArrayList<String> itemIds) {
List<FeedItem> itemsCopy = new ArrayList<FeedItem>(items);
Cursor cursor = adapter.getFeedMediaCursorByItemID(itemIds
.toArray(new String[itemIds.size()]));
if (cursor.moveToFirst()) {
do {
long itemId = cursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX);
// find matching feed item
FeedItem item = getMatchingItemForMedia(itemId, itemsCopy);
if (item != null) {
item.setMedia(extractFeedMediaFromCursorRow(cursor));
item.getMedia().setItem(item);
}
} while (cursor.moveToNext());
cursor.close();
}
}
private static FeedMedia extractFeedMediaFromCursorRow(final Cursor cursor) {
long mediaId = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
Date playbackCompletionDate = null;
long playbackCompletionTime = cursor
.getLong(PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE_INDEX);
if (playbackCompletionTime > 0) {
playbackCompletionDate = new Date(
playbackCompletionTime);
}
return new FeedMedia(
mediaId,
null,
cursor.getInt(PodDBAdapter.KEY_DURATION_INDEX),
cursor.getInt(PodDBAdapter.KEY_POSITION_INDEX),
cursor.getLong(PodDBAdapter.KEY_SIZE_INDEX),
cursor.getString(PodDBAdapter.KEY_MIME_TYPE_INDEX),
cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX),
cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX),
cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0,
playbackCompletionDate);
}
private static Feed extractFeedFromCursorRow(PodDBAdapter adapter,
Cursor cursor) {
Date lastUpdate = new Date(
cursor.getLong(PodDBAdapter.KEY_LAST_UPDATE_INDEX));
Feed feed = new Feed(lastUpdate);
feed.setId(cursor.getLong(PodDBAdapter.KEY_ID_INDEX));
feed.setTitle(cursor.getString(PodDBAdapter.KEY_TITLE_INDEX));
feed.setLink(cursor.getString(PodDBAdapter.KEY_LINK_INDEX));
feed.setDescription(cursor
.getString(PodDBAdapter.KEY_DESCRIPTION_INDEX));
feed.setPaymentLink(cursor
.getString(PodDBAdapter.KEY_PAYMENT_LINK_INDEX));
feed.setAuthor(cursor.getString(PodDBAdapter.KEY_AUTHOR_INDEX));
feed.setLanguage(cursor.getString(PodDBAdapter.KEY_LANGUAGE_INDEX));
feed.setType(cursor.getString(PodDBAdapter.KEY_TYPE_INDEX));
feed.setFeedIdentifier(cursor
.getString(PodDBAdapter.KEY_FEED_IDENTIFIER_INDEX));
long imageIndex = cursor.getLong(PodDBAdapter.KEY_IMAGE_INDEX);
if (imageIndex != 0) {
feed.setImage(getFeedImage(adapter, imageIndex));
feed.getImage().setFeed(feed);
}
feed.setFile_url(cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX));
feed.setDownload_url(cursor
.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX));
feed.setDownloaded(cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0);
return feed;
}
private static FeedItem getMatchingItemForMedia(long itemId,
List<FeedItem> items) {
for (FeedItem item : items) {
if (item.getId() == itemId) {
return item;
}
}
return null;
}
static List<FeedItem> getQueue(Context context, PodDBAdapter adapter) {
if (AppConfig.DEBUG)
Log.d(TAG, "Extracting queue");
Cursor itemlistCursor = adapter.getQueueCursor();
List<FeedItem> items = extractItemlistFromCursor(adapter,
itemlistCursor);
itemlistCursor.close();
loadFeedDataOfFeedItemlist(context, items);
return items;
}
/**
* Loads the IDs of the FeedItems in the queue. This method should be preferred over
* {@link #getQueue(android.content.Context)} if the FeedItems of the queue are not needed.
*
* @param context A context that is used for opening a database connection.
* @return A list of IDs sorted by the same order as the queue. The caller can wrap the returned
* list in a {@link de.danoeh.antennapod.util.QueueAccess} object for easier access to the queue's properties.
*/
public static List<Long> getQueueIDList(Context context) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
List<Long> result = getQueueIDList(adapter);
adapter.close();
return result;
}
static List<Long> getQueueIDList(PodDBAdapter adapter) {
adapter.open();
Cursor queueCursor = adapter.getQueueIDCursor();
List<Long> queueIds = new ArrayList<Long>(queueCursor.getCount());
if (queueCursor.moveToFirst()) {
do {
queueIds.add(queueCursor.getLong(0));
} while (queueCursor.moveToNext());
}
return queueIds;
}
/**
* Loads a list of the FeedItems in the queue. If the FeedItems of the queue are not used directly, consider using
* {@link #getQueueIDList(android.content.Context)} instead.
*
* @param context A context that is used for opening a database connection.
* @return A list of FeedItems sorted by the same order as the queue. The caller can wrap the returned
* list in a {@link de.danoeh.antennapod.util.QueueAccess} object for easier access to the queue's properties.
*/
public static List<FeedItem> getQueue(Context context) {
if (AppConfig.DEBUG)
Log.d(TAG, "Extracting queue");
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
List<FeedItem> items = getQueue(context, adapter);
adapter.close();
return items;
}
/**
* Loads a list of FeedItems whose episode has been downloaded.
*
* @param context A context that is used for opening a database connection.
* @return A list of FeedItems whose episdoe has been downloaded.
*/
public static List<FeedItem> getDownloadedItems(Context context) {
if (AppConfig.DEBUG)
Log.d(TAG, "Extracting downloaded items");
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
Cursor itemlistCursor = adapter.getDownloadedItemsCursor();
List<FeedItem> items = extractItemlistFromCursor(adapter,
itemlistCursor);
itemlistCursor.close();
loadFeedDataOfFeedItemlist(context, items);
Collections.sort(items, new FeedItemPubdateComparator());
adapter.close();
return items;
}
/**
* Loads a list of FeedItems whose 'read'-attribute is set to false.
*
* @param context A context that is used for opening a database connection.
* @return A list of FeedItems whose 'read'-attribute it set to false. If the FeedItems in the list are not used,
* consider using {@link #getUnreadItemIds(android.content.Context)} instead.
*/
public static List<FeedItem> getUnreadItemsList(Context context) {
if (AppConfig.DEBUG)
Log.d(TAG, "Extracting unread items list");
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
Cursor itemlistCursor = adapter.getUnreadItemsCursor();
List<FeedItem> items = extractItemlistFromCursor(adapter,
itemlistCursor);
itemlistCursor.close();
loadFeedDataOfFeedItemlist(context, items);
adapter.close();
return items;
}
/**
* Loads the IDs of the FeedItems whose 'read'-attribute is set to false.
*
* @param context A context that is used for opening a database connection.
* @return A list of IDs of the FeedItems whose 'read'-attribute is set to false. This method should be preferred
* over {@link #getUnreadItemsList(android.content.Context)} if the FeedItems in the UnreadItems list are not used.
*/
public static long[] getUnreadItemIds(Context context) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
Cursor cursor = adapter.getUnreadItemIdsCursor();
long[] itemIds = new long[cursor.getCount()];
int i = 0;
if (cursor.moveToFirst()) {
do {
itemIds[i] = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
i++;
} while (cursor.moveToNext());
}
return itemIds;
}
/**
* Loads the playback history from the database. A FeedItem is in the playback history if playback of the correpsonding episode
* has been completed at least once.
*
* @param context A context that is used for opening a database connection.
* @return The playback history. The FeedItems are sorted by their media's playbackCompletionDate in descending order.
* The size of the returned list is limited by {@link #PLAYBACK_HISTORY_SIZE}.
*/
public static List<FeedItem> getPlaybackHistory(final Context context) {
if (AppConfig.DEBUG)
Log.d(TAG, "Loading playback history");
final int PLAYBACK_HISTORY_SIZE = 50;
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
Cursor mediaCursor = adapter.getCompletedMediaCursor(PLAYBACK_HISTORY_SIZE);
String[] itemIds = new String[mediaCursor.getCount()];
for (int i = 0; i < itemIds.length && mediaCursor.moveToPosition(i); i++) {
itemIds[i] = Long.toString(mediaCursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX));
}
mediaCursor.close();
Cursor itemCursor = adapter.getFeedItemCursor(itemIds);
List<FeedItem> items = extractItemlistFromCursor(adapter, itemCursor);
loadFeedDataOfFeedItemlist(context, items);
itemCursor.close();
adapter.close();
return items;
}
/**
* Loads the download log from the database.
*
* @param context A context that is used for opening a database connection.
* @return A list with DownloadStatus objects that represent the download log.
* The size of the returned list is limited by {@link #DOWNLOAD_LOG_SIZE}.
*/
public static List<DownloadStatus> getDownloadLog(Context context) {
if (AppConfig.DEBUG)
Log.d(TAG, "Extracting DownloadLog");
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
Cursor logCursor = adapter.getDownloadLogCursor(DOWNLOAD_LOG_SIZE);
List<DownloadStatus> downloadLog = new ArrayList<DownloadStatus>(
logCursor.getCount());
if (logCursor.moveToFirst()) {
do {
long id = logCursor.getLong(PodDBAdapter.KEY_ID_INDEX);
long feedfileId = logCursor
.getLong(PodDBAdapter.KEY_FEEDFILE_INDEX);
int feedfileType = logCursor
.getInt(PodDBAdapter.KEY_FEEDFILETYPE_INDEX);
boolean successful = logCursor
.getInt(PodDBAdapter.KEY_SUCCESSFUL_INDEX) > 0;
int reason = logCursor.getInt(PodDBAdapter.KEY_REASON_INDEX);
String reasonDetailed = logCursor
.getString(PodDBAdapter.KEY_REASON_DETAILED_INDEX);
String title = logCursor
.getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX);
Date completionDate = new Date(
logCursor
.getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX));
downloadLog.add(new DownloadStatus(id, title, feedfileId,
feedfileType, successful, DownloadError.fromCode(reason), completionDate,
reasonDetailed));
} while (logCursor.moveToNext());
}
logCursor.close();
Collections.sort(downloadLog, new DownloadStatusComparator());
return downloadLog;
}
/**
* Loads the FeedItemStatistics objects of all Feeds in the database. This method should be preferred over
* {@link #getFeedItemList(android.content.Context, de.danoeh.antennapod.feed.Feed)} if only metadata about
* the FeedItems is needed.
*
* @param context A context that is used for opening a database connection.
* @return A list of FeedItemStatistics objects sorted alphabetically by their Feed's title.
*/
public static List<FeedItemStatistics> getFeedStatisticsList(final Context context) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
List<FeedItemStatistics> result = new ArrayList<FeedItemStatistics>();
Cursor cursor = adapter.getFeedStatisticsCursor();
if (cursor.moveToFirst()) {
do {
result.add(new FeedItemStatistics(cursor.getLong(PodDBAdapter.IDX_FEEDSTATISTICS_FEED),
cursor.getInt(PodDBAdapter.IDX_FEEDSTATISTICS_NUM_ITEMS),
cursor.getInt(PodDBAdapter.IDX_FEEDSTATISTICS_NEW_ITEMS),
cursor.getInt(PodDBAdapter.IDX_FEEDSTATISTICS_IN_PROGRESS_EPISODES),
new Date(cursor.getLong(PodDBAdapter.IDX_FEEDSTATISTICS_LATEST_EPISODE))));
} while (cursor.moveToNext());
}
cursor.close();
adapter.close();
return result;
}
/**
* Loads a specific Feed from the database.
*
* @param context A context that is used for opening a database connection.
* @param feedId The ID of the Feed
* @return The Feed or null if the Feed could not be found. The Feeds FeedItems will also be loaded from the
* database and the items-attribute will be set correctly.
*/
public static Feed getFeed(final Context context, final long feedId) {
if (AppConfig.DEBUG)
Log.d(TAG, "Loading feed with id " + feedId);
Feed feed = null;
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
Cursor feedCursor = adapter.getFeedCursor(feedId);
if (feedCursor.moveToFirst()) {
feed = extractFeedFromCursorRow(adapter, feedCursor);
feed.setItems(getFeedItemList(context, feed));
} else {
Log.e(TAG, "getFeed could not find feed with id " + feedId);
}
adapter.close();
return feed;
}
static FeedItem getFeedItem(final Context context, final long itemId, PodDBAdapter adapter) {
if (AppConfig.DEBUG)
Log.d(TAG, "Loading feeditem with id " + itemId);
FeedItem item = null;
Cursor itemCursor = adapter.getFeedItemCursor(Long.toString(itemId));
if (itemCursor.moveToFirst()) {
List<FeedItem> list = extractItemlistFromCursor(adapter, itemCursor);
if (list.size() > 0) {
item = list.get(0);
loadFeedDataOfFeedItemlist(context, list);
}
}
return item;
}
/**
* Loads a specific FeedItem from the database.
*
* @param context A context that is used for opening a database connection.
* @param itemId The ID of the FeedItem
* @return The FeedItem or null if the FeedItem could not be found. All FeedComponent-attributes of the FeedItem will
* also be loaded from the database.
*/
public static FeedItem getFeedItem(final Context context, final long itemId) {
if (AppConfig.DEBUG)
Log.d(TAG, "Loading feeditem with id " + itemId);
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
FeedItem item = getFeedItem(context, itemId, adapter);
adapter.close();
return item;
}
/**
* Loads additional information about a FeedItem, e.g. shownotes
*
* @param context A context that is used for opening a database connection.
* @param item The FeedItem
*/
public static void loadExtraInformationOfFeedItem(final Context context, final FeedItem item) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
Cursor extraCursor = adapter.getExtraInformationOfItem(item);
if (extraCursor.moveToFirst()) {
String description = extraCursor
.getString(PodDBAdapter.IDX_FI_EXTRA_DESCRIPTION);
String contentEncoded = extraCursor
.getString(PodDBAdapter.IDX_FI_EXTRA_CONTENT_ENCODED);
item.setDescription(description);
item.setContentEncoded(contentEncoded);
}
adapter.close();
}
/**
* Returns the number of downloaded episodes.
*
* @param context A context that is used for opening a database connection.
* @return The number of downloaded episodes.
*/
public static int getNumberOfDownloadedEpisodes(final Context context) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
final int result = adapter.getNumberOfDownloadedEpisodes();
adapter.close();
return result;
}
/**
* Returns the number of unread items.
*
* @param context A context that is used for opening a database connection.
* @return The number of unread items.
*/
public static int getNumberOfUnreadItems(final Context context) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
final int result = adapter.getNumberOfUnreadItems();
adapter.close();
return result;
}
/**
* Searches the DB for a FeedImage of the given id.
*
* @param context A context that is used for opening a database connection.
* @param imageId The id of the object
* @return The found object
*/
public static FeedImage getFeedImage(final Context context, final long imageId) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
FeedImage result = getFeedImage(adapter, imageId);
adapter.close();
return result;
}
/**
* Searches the DB for a FeedImage of the given id.
*
* @param id The id of the object
* @return The found object
*/
static FeedImage getFeedImage(PodDBAdapter adapter, final long id) {
Cursor cursor = adapter.getImageOfFeedCursor(id);
if ((cursor.getCount() == 0) || !cursor.moveToFirst()) {
throw new SQLException("No FeedImage found at index: " + id);
}
FeedImage image = new FeedImage(id, cursor.getString(cursor
.getColumnIndex(PodDBAdapter.KEY_TITLE)),
cursor.getString(cursor
.getColumnIndex(PodDBAdapter.KEY_FILE_URL)),
cursor.getString(cursor
.getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL)),
cursor.getInt(cursor
.getColumnIndex(PodDBAdapter.KEY_DOWNLOADED)) > 0);
cursor.close();
return image;
}
/**
* Searches the DB for a FeedMedia of the given id.
*
* @param context A context that is used for opening a database connection.
* @param mediaId The id of the object
* @return The found object
*/
public static FeedMedia getFeedMedia(final Context context, final long mediaId) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
Cursor mediaCursor = adapter.getSingleFeedMediaCursor(mediaId);
FeedMedia media = null;
if (mediaCursor.moveToFirst()) {
final long itemId = mediaCursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX);
media = extractFeedMediaFromCursorRow(mediaCursor);
FeedItem item = getFeedItem(context, itemId);
if (media != null && item != null) {
media.setItem(item);
item.setMedia(media);
}
}
mediaCursor.close();
adapter.close();
return media;
}
}

View File

@ -0,0 +1,731 @@
package de.danoeh.antennapod.storage;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedImage;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.util.DownloadError;
import de.danoeh.antennapod.util.NetworkUtils;
import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.exception.MediaFileNotFoundException;
/**
* Provides methods for doing common tasks that use DBReader and DBWriter.
*/
public final class DBTasks {
private static final String TAG = "DBTasks";
private DBTasks() {
}
/**
* Starts playback of a FeedMedia object's file. This method will build an Intent based on the given parameters to
* start the {@link PlaybackService}.
*
* @param context Used for sending starting Services and Activities.
* @param media The FeedMedia object.
* @param showPlayer If true, starts the appropriate player activity ({@link de.danoeh.antennapod.activity.AudioplayerActivity}
* or {@link de.danoeh.antennapod.activity.VideoplayerActivity}
* @param startWhenPrepared Parameter for the {@link PlaybackService} start intent. If true, playback will start as
* soon as the PlaybackService has finished loading the FeedMedia object's file.
* @param shouldStream Parameter for the {@link PlaybackService} start intent. If true, the FeedMedia object's file
* will be streamed, otherwise the downloaded file will be used. If the downloaded file cannot be
* found, the PlaybackService will shutdown and the database entry of the FeedMedia object will be
* corrected.
*/
public static void playMedia(final Context context, final FeedMedia media,
boolean showPlayer, boolean startWhenPrepared, boolean shouldStream) {
try {
if (!shouldStream) {
if (media.fileExists() == false) {
throw new MediaFileNotFoundException(
"No episode was found at " + media.getFile_url(),
media);
}
}
// Start playback Service
Intent launchIntent = new Intent(context, PlaybackService.class);
launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
startWhenPrepared);
launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM,
shouldStream);
launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
true);
context.startService(launchIntent);
if (showPlayer) {
// Launch media player
context.startActivity(PlaybackService.getPlayerActivityIntent(
context, media));
}
DBWriter.addQueueItemAt(context, media.getItem().getId(), 0, false);
} catch (MediaFileNotFoundException e) {
e.printStackTrace();
if (media.isPlaying()) {
context.sendBroadcast(new Intent(
PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
}
notifyMissingFeedMediaFile(context, media);
}
}
private static AtomicBoolean isRefreshing = new AtomicBoolean(false);
/**
* Refreshes a given list of Feeds in a separate Thread. This method might ignore subsequent calls if it is still
* enqueuing Feeds for download from a previous call
*
* @param context Might be used for accessing the database
* @param feeds List of Feeds that should be refreshed.
*/
public static void refreshAllFeeds(final Context context,
final List<Feed> feeds) {
if (isRefreshing.compareAndSet(false, true)) {
new Thread() {
public void run() {
if (feeds != null) {
refreshFeeds(context, feeds);
} else {
refreshFeeds(context, DBReader.getFeedList(context));
}
isRefreshing.set(false);
}
}.start();
} else {
if (AppConfig.DEBUG)
Log.d(TAG,
"Ignoring request to refresh all feeds: Refresh lock is locked");
}
}
/**
* Refreshes expired Feeds in the list returned by the getExpiredFeedsList(Context, long) method in DBReader.
* The expiration date parameter is determined by the update interval specified in {@link UserPreferences}.
*
* @param context Used for DB access.
*/
public static void refreshExpiredFeeds(final Context context) {
if (AppConfig.DEBUG)
Log.d(TAG, "Refreshing expired feeds");
new Thread() {
public void run() {
long millis = UserPreferences.getUpdateInterval();
if (millis > 0) {
long now = Calendar.getInstance().getTime().getTime();
// Allow a 10 minute window
millis -= 10 * 60 * 1000;
List<Feed> feedList = DBReader.getExpiredFeedsList(context,
now - millis);
if (feedList.size() > 0) {
refreshFeeds(context, feedList);
}
}
}
}.start();
}
private static void refreshFeeds(final Context context,
final List<Feed> feedList) {
for (Feed feed : feedList) {
try {
refreshFeed(context, feed);
} catch (DownloadRequestException e) {
e.printStackTrace();
DBWriter.addDownloadStatus(
context,
new DownloadStatus(feed, feed
.getHumanReadableIdentifier(),
DownloadError.ERROR_REQUEST_ERROR, false, e
.getMessage()));
}
}
}
/**
* Updates a specific Feed.
*
* @param context Used for requesting the download.
* @param feed The Feed object.
*/
public static void refreshFeed(Context context, Feed feed)
throws DownloadRequestException {
DownloadRequester.getInstance().downloadFeed(context,
new Feed(feed.getDownload_url(), new Date(), feed.getTitle()));
}
/**
* Notifies the database about a missing FeedImage file. This method will attempt to re-download the file.
*
* @param context Used for requesting the download.
* @param image The FeedImage object.
*/
public static void notifyInvalidImageFile(final Context context,
final FeedImage image) {
Log.i(TAG,
"The DB was notified about an invalid image download. It will now try to re-download the image file");
try {
DownloadRequester.getInstance().downloadImage(context, image);
} catch (DownloadRequestException e) {
e.printStackTrace();
Log.w(TAG, "Failed to download invalid feed image");
}
}
/**
* Notifies the database about a missing FeedMedia file. This method will correct the FeedMedia object's values in the
* DB and send a FeedUpdateBroadcast.
*/
public static void notifyMissingFeedMediaFile(final Context context,
final FeedMedia media) {
Log.i(TAG,
"The feedmanager was notified about a missing episode. It will update its database now.");
media.setDownloaded(false);
media.setFile_url(null);
DBWriter.setFeedMedia(context, media);
EventDistributor.getInstance().sendFeedUpdateBroadcast();
}
/**
* Request the download of all objects in the queue. from a separate Thread.
*
* @param context Used for requesting the download an accessing the database.
*/
public static void downloadAllItemsInQueue(final Context context) {
new Thread() {
public void run() {
List<FeedItem> queue = DBReader.getQueue(context);
if (!queue.isEmpty()) {
try {
downloadFeedItems(context,
queue.toArray(new FeedItem[queue.size()]));
} catch (DownloadRequestException e) {
e.printStackTrace();
}
}
}
}.start();
}
/**
* Requests the download of a list of FeedItem objects.
*
* @param context Used for requesting the download and accessing the DB.
* @param items The FeedItem objects.
*/
public static void downloadFeedItems(final Context context,
FeedItem... items) throws DownloadRequestException {
downloadFeedItems(true, context, items);
}
private static void downloadFeedItems(boolean performAutoCleanup,
final Context context, final FeedItem... items)
throws DownloadRequestException {
final DownloadRequester requester = DownloadRequester.getInstance();
if (performAutoCleanup) {
new Thread() {
@Override
public void run() {
performAutoCleanup(context,
getPerformAutoCleanupArgs(context, items.length));
}
}.start();
}
for (FeedItem item : items) {
if (item.getMedia() != null
&& !requester.isDownloadingFile(item.getMedia())
&& !item.getMedia().isDownloaded()) {
if (items.length > 1) {
try {
requester.downloadMedia(context, item.getMedia());
} catch (DownloadRequestException e) {
e.printStackTrace();
DBWriter.addDownloadStatus(context,
new DownloadStatus(item.getMedia(), item
.getMedia()
.getHumanReadableIdentifier(),
DownloadError.ERROR_REQUEST_ERROR,
false, e.getMessage()));
}
} else {
requester.downloadMedia(context, item.getMedia());
}
}
}
}
private static int getNumberOfUndownloadedEpisodes(
final List<FeedItem> queue, final List<FeedItem> unreadItems) {
int counter = 0;
for (FeedItem item : queue) {
if (item.hasMedia() && !item.getMedia().isDownloaded()
&& !item.getMedia().isPlaying()) {
counter++;
}
}
for (FeedItem item : unreadItems) {
if (item.hasMedia() && !item.getMedia().isDownloaded()) {
counter++;
}
}
return counter;
}
/**
* Looks for undownloaded episodes in the queue or list of unread items and request a download if
* 1. Network is available
* 2. There is free space in the episode cache
* This method should NOT be executed on the GUI thread.
*
* @param context Used for accessing the DB.
*/
public static void autodownloadUndownloadedItems(final Context context) {
if (AppConfig.DEBUG)
Log.d(TAG, "Performing auto-dl of undownloaded episodes");
if (NetworkUtils.autodownloadNetworkAvailable(context)
&& UserPreferences.isEnableAutodownload()) {
final List<FeedItem> queue = DBReader.getQueue(context);
final List<FeedItem> unreadItems = DBReader
.getUnreadItemsList(context);
int undownloadedEpisodes = getNumberOfUndownloadedEpisodes(queue,
unreadItems);
int downloadedEpisodes = DBReader
.getNumberOfDownloadedEpisodes(context);
int deletedEpisodes = performAutoCleanup(context,
getPerformAutoCleanupArgs(context, undownloadedEpisodes));
int episodeSpaceLeft = undownloadedEpisodes;
boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences
.getEpisodeCacheSizeUnlimited();
if (!cacheIsUnlimited
&& UserPreferences.getEpisodeCacheSize() < downloadedEpisodes
+ undownloadedEpisodes) {
episodeSpaceLeft = UserPreferences.getEpisodeCacheSize()
- (downloadedEpisodes - deletedEpisodes);
}
List<FeedItem> itemsToDownload = new ArrayList<FeedItem>();
if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
for (int i = 0; i < queue.size(); i++) { // ignore playing item
FeedItem item = queue.get(i);
if (item.hasMedia() && !item.getMedia().isDownloaded()
&& !item.getMedia().isPlaying()) {
itemsToDownload.add(item);
episodeSpaceLeft--;
undownloadedEpisodes--;
if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
break;
}
}
}
}
if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
for (FeedItem item : unreadItems) {
if (item.hasMedia() && !item.getMedia().isDownloaded()) {
itemsToDownload.add(item);
episodeSpaceLeft--;
undownloadedEpisodes--;
if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
break;
}
}
}
}
if (AppConfig.DEBUG)
Log.d(TAG, "Enqueueing " + itemsToDownload.size()
+ " items for download");
try {
downloadFeedItems(false, context,
itemsToDownload.toArray(new FeedItem[itemsToDownload
.size()]));
} catch (DownloadRequestException e) {
e.printStackTrace();
}
}
}
private static int getPerformAutoCleanupArgs(Context context,
final int episodeNumber) {
if (episodeNumber >= 0
&& UserPreferences.getEpisodeCacheSize() != UserPreferences
.getEpisodeCacheSizeUnlimited()) {
int downloadedEpisodes = DBReader
.getNumberOfDownloadedEpisodes(context);
if (downloadedEpisodes + episodeNumber >= UserPreferences
.getEpisodeCacheSize()) {
return downloadedEpisodes + episodeNumber
- UserPreferences.getEpisodeCacheSize();
}
}
return 0;
}
/**
* Removed downloaded episodes outside of the queue if the episode cache is full. Episodes with a smaller
* 'playbackCompletionDate'-value will be deleted first.
* <p/>
* This method should NOT be executed on the GUI thread.
*
* @param context Used for accessing the DB.
*/
public static void performAutoCleanup(final Context context) {
performAutoCleanup(context, getPerformAutoCleanupArgs(context, 0));
}
private static int performAutoCleanup(final Context context,
final int episodeNumber) {
List<FeedItem> candidates = DBReader.getDownloadedItems(context);
List<FeedItem> queue = DBReader.getQueue(context);
List<FeedItem> delete;
for (FeedItem item : candidates) {
if (item.hasMedia() && item.getMedia().isDownloaded()
&& !queue.contains(item) && item.isRead()) {
candidates.add(item);
}
}
Collections.sort(candidates, new Comparator<FeedItem>() {
@Override
public int compare(FeedItem lhs, FeedItem rhs) {
Date l = lhs.getMedia().getPlaybackCompletionDate();
Date r = rhs.getMedia().getPlaybackCompletionDate();
if (l == null) {
l = new Date(0);
}
if (r == null) {
r = new Date(0);
}
return l.compareTo(r);
}
});
if (candidates.size() > episodeNumber) {
delete = candidates.subList(0, episodeNumber);
} else {
delete = candidates;
}
for (FeedItem item : delete) {
DBWriter.deleteFeedMediaOfItem(context, item.getId());
}
int counter = delete.size();
if (AppConfig.DEBUG)
Log.d(TAG, String.format(
"Auto-delete deleted %d episodes (%d requested)", counter,
episodeNumber));
return counter;
}
/**
* Adds all FeedItem objects whose 'read'-attribute is false to the queue in a separate thread.
*/
public static void enqueueAllNewItems(final Context context) {
long[] unreadItems = DBReader.getUnreadItemIds(context);
DBWriter.addQueueItem(context, unreadItems);
}
/**
* Returns the successor of a FeedItem in the queue.
*
* @param context Used for accessing the DB.
* @param itemId ID of the FeedItem
* @param queue Used for determining the successor of the item. If this parameter is null, the method will load
* the queue from the database in the same thread.
* @return Successor of the FeedItem or null if the FeedItem is not in the queue or has no successor.
*/
public static FeedItem getQueueSuccessorOfItem(Context context,
final long itemId, List<FeedItem> queue) {
FeedItem result = null;
if (queue == null) {
queue = DBReader.getQueue(context);
}
if (queue != null) {
Iterator<FeedItem> iterator = queue.iterator();
while (iterator.hasNext()) {
FeedItem item = iterator.next();
if (item.getId() == itemId) {
if (iterator.hasNext()) {
result = iterator.next();
}
break;
}
}
}
return result;
}
/**
* Loads the queue from the database and checks if the specified FeedItem is in the queue.
* This method should NOT be executed in the GUI thread.
*
* @param context Used for accessing the DB.
* @param feedItemId ID of the FeedItem
*/
public static boolean isInQueue(Context context, final long feedItemId) {
List<Long> queue = DBReader.getQueueIDList(context);
return QueueAccess.IDListAccess(queue).contains(feedItemId);
}
private static Feed searchFeedByIdentifyingValue(Context context,
String identifier) {
List<Feed> feeds = DBReader.getFeedList(context);
for (Feed feed : feeds) {
if (feed.getIdentifyingValue().equals(identifier)) {
return feed;
}
}
return null;
}
/**
* Get a FeedItem by its identifying value.
*/
private static FeedItem searchFeedItemByIdentifyingValue(Feed feed,
String identifier) {
for (FeedItem item : feed.getItems()) {
if (item.getIdentifyingValue().equals(identifier)) {
return item;
}
}
return null;
}
/**
* Adds a new Feed to the database or updates the old version if it already exists. If another Feed with the same
* identifying value already exists, this method will add new FeedItems from the new Feed to the existing Feed.
* These FeedItems will be marked as unread.
* This method should NOT be executed on the GUI thread.
*
* @param context Used for accessing the DB.
* @param newFeed The new Feed object.
* @return The updated Feed from the database if it already existed, or the new Feed from the parameters otherwise.
*/
public static synchronized Feed updateFeed(final Context context,
final Feed newFeed) {
// Look up feed in the feedslist
final Feed savedFeed = searchFeedByIdentifyingValue(context,
newFeed.getIdentifyingValue());
if (savedFeed == null) {
if (AppConfig.DEBUG)
Log.d(TAG,
"Found no existing Feed with title "
+ newFeed.getTitle() + ". Adding as new one.");
// Add a new Feed
try {
DBWriter.addNewFeed(context, newFeed).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return newFeed;
} else {
if (AppConfig.DEBUG)
Log.d(TAG, "Feed with title " + newFeed.getTitle()
+ " already exists. Syncing new with existing one.");
savedFeed.setItems(DBReader.getFeedItemList(context, savedFeed));
if (savedFeed.compareWithOther(newFeed)) {
if (AppConfig.DEBUG)
Log.d(TAG,
"Feed has updated attribute values. Updating old feed's attributes");
savedFeed.updateFromOther(newFeed);
}
// Look for new or updated Items
for (int idx = 0; idx < newFeed.getItems().size(); idx++) {
final FeedItem item = newFeed.getItems().get(idx);
FeedItem oldItem = searchFeedItemByIdentifyingValue(savedFeed,
item.getIdentifyingValue());
if (oldItem == null) {
// item is new
final int i = idx;
item.setFeed(savedFeed);
savedFeed.getItems().add(i, item);
DBWriter.markItemRead(context, item.getId(), false);
} else {
oldItem.updateFromOther(item);
}
}
// update attributes
savedFeed.setLastUpdate(newFeed.getLastUpdate());
savedFeed.setType(newFeed.getType());
try {
DBWriter.setCompleteFeed(context, savedFeed).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
new Thread() {
@Override
public void run() {
autodownloadUndownloadedItems(context);
}
}.start();
return savedFeed;
}
}
/**
* Searches the titles of FeedItems of a specific Feed for a given
* string.
*
* @param context Used for accessing the DB.
* @param feedID The id of the feed whose items should be searched.
* @param query The search string.
* @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
*/
public static FutureTask<List<FeedItem>> searchFeedItemTitle(final Context context,
final long feedID, final String query) {
return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) {
@Override
public void execute(PodDBAdapter adapter) {
Cursor searchResult = adapter.searchItemTitles(feedID,
query);
List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult);
DBReader.loadFeedDataOfFeedItemlist(context, items);
setResult(items);
searchResult.close();
}
});
}
/**
* Searches the descriptions of FeedItems of a specific Feed for a given
* string.
*
* @param context Used for accessing the DB.
* @param feedID The id of the feed whose items should be searched.
* @param query The search string
* @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
*/
public static FutureTask<List<FeedItem>> searchFeedItemDescription(final Context context,
final long feedID, final String query) {
return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) {
@Override
public void execute(PodDBAdapter adapter) {
Cursor searchResult = adapter.searchItemDescriptions(feedID,
query);
List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult);
DBReader.loadFeedDataOfFeedItemlist(context, items);
setResult(items);
searchResult.close();
}
});
}
/**
* Searches the contentEncoded-value of FeedItems of a specific Feed for a given
* string.
*
* @param context Used for accessing the DB.
* @param feedID The id of the feed whose items should be searched.
* @param query The search string
* @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
*/
public static FutureTask<List<FeedItem>> searchFeedItemContentEncoded(final Context context,
final long feedID, final String query) {
return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) {
@Override
public void execute(PodDBAdapter adapter) {
Cursor searchResult = adapter.searchItemContentEncoded(feedID,
query);
List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult);
DBReader.loadFeedDataOfFeedItemlist(context, items);
setResult(items);
searchResult.close();
}
});
}
/**
* Searches chapters of the FeedItems of a specific Feed for a given string.
*
* @param context Used for accessing the DB.
* @param feedID The id of the feed whose items should be searched.
* @param query The search string
* @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
*/
public static FutureTask<List<FeedItem>> searchFeedItemChapters(final Context context,
final long feedID, final String query) {
return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) {
@Override
public void execute(PodDBAdapter adapter) {
Cursor searchResult = adapter.searchItemChapters(feedID,
query);
List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult);
DBReader.loadFeedDataOfFeedItemlist(context, items);
setResult(items);
searchResult.close();
}
});
}
/**
* A runnable which should be used for database queries. The onCompletion
* method is executed on the database executor to handle Cursors correctly.
* This class automatically creates a PodDBAdapter object and closes it when
* it is no longer in use.
*/
static abstract class QueryTask<T> implements Callable<T> {
private T result;
private Context context;
public QueryTask(Context context) {
this.context = context;
}
@Override
public T call() throws Exception {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
execute(adapter);
adapter.close();
return result;
}
public abstract void execute(PodDBAdapter adapter);
protected void setResult(T result) {
this.result = result;
}
}
}

View File

@ -0,0 +1,724 @@
package de.danoeh.antennapod.storage;
import java.io.File;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.preference.PreferenceManager;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.feed.*;
import de.danoeh.antennapod.preferences.PlaybackPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.util.QueueAccess;
/**
* Provides methods for writing data to AntennaPod's database.
* In general, DBWriter-methods will be executed on an internal ExecutorService.
* Some methods return a Future-object which the caller can use for waiting for the method's completion. The returned Future's
* will NOT contain any results.
* The caller can also use the {@link EventDistributor} in order to be notified about the method's completion asynchronously.
* This class will use the {@link EventDistributor} to notify listeners about changes in the database.
*/
public class DBWriter {
private static final String TAG = "DBWriter";
private static final ExecutorService dbExec;
static {
dbExec = Executors.newSingleThreadExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setPriority(Thread.MIN_PRIORITY);
return t;
}
});
}
private DBWriter() {
}
/**
* Deletes a downloaded FeedMedia file from the storage device.
*
* @param context A context that is used for opening a database connection.
* @param mediaId ID of the FeedMedia object whose downloaded file should be deleted.
*/
public static Future<?> deleteFeedMediaOfItem(final Context context,
final long mediaId) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
final FeedMedia media = DBReader.getFeedMedia(context, mediaId);
if (media != null) {
boolean result = false;
if (media.isDownloaded()) {
// delete downloaded media file
File mediaFile = new File(media.getFile_url());
if (mediaFile.exists()) {
result = mediaFile.delete();
}
media.setDownloaded(false);
media.setFile_url(null);
setFeedMedia(context, media);
// If media is currently being played, change playback
// type to 'stream' and shutdown playback service
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA) {
if (media.getId() == PlaybackPreferences
.getCurrentlyPlayingFeedMediaId()) {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(
PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM,
true);
editor.commit();
}
if (PlaybackPreferences
.getCurrentlyPlayingFeedMediaId() == media
.getId()) {
context.sendBroadcast(new Intent(
PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
}
}
}
if (AppConfig.DEBUG)
Log.d(TAG, "Deleting File. Result: " + result);
}
}
});
}
/**
* Deletes a Feed and all downloaded files of its components like images and downloaded episodes.
*
* @param context A context that is used for opening a database connection.
* @param feedId ID of the Feed that should be deleted.
*/
public static Future<?> deleteFeed(final Context context, final long feedId) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
DownloadRequester requester = DownloadRequester.getInstance();
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context
.getApplicationContext());
final Feed feed = DBReader.getFeed(context, feedId);
if (feed != null) {
if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA
&& PlaybackPreferences.getLastPlayedFeedId() == feed
.getId()) {
context.sendBroadcast(new Intent(
PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
SharedPreferences.Editor editor = prefs.edit();
editor.putLong(
PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID,
-1);
editor.commit();
}
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
// delete image file
if (feed.getImage() != null) {
if (feed.getImage().isDownloaded()
&& feed.getImage().getFile_url() != null) {
File imageFile = new File(feed.getImage()
.getFile_url());
imageFile.delete();
} else if (requester.isDownloadingFile(feed.getImage())) {
requester.cancelDownload(context, feed.getImage());
}
}
// delete stored media files and mark them as read
List<FeedItem> queue = DBReader.getQueue(context);
boolean queueWasModified = false;
if (feed.getItems() == null) {
DBReader.getFeedItemList(context, feed);
}
for (FeedItem item : feed.getItems()) {
queueWasModified |= queue.remove(item);
if (item.getMedia() != null
&& item.getMedia().isDownloaded()) {
File mediaFile = new File(item.getMedia()
.getFile_url());
mediaFile.delete();
} else if (item.getMedia() != null
&& requester.isDownloadingFile(item.getMedia())) {
requester.cancelDownload(context, item.getMedia());
}
}
if (queueWasModified) {
adapter.setQueue(queue);
}
adapter.removeFeed(feed);
adapter.close();
EventDistributor.getInstance().sendFeedUpdateBroadcast();
}
}
});
}
/**
* Deletes the entire playback history.
*
* @param context A context that is used for opening a database connection.
*/
public static Future<?> clearPlaybackHistory(final Context context) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.clearPlaybackHistory();
adapter.close();
EventDistributor.getInstance()
.sendPlaybackHistoryUpdateBroadcast();
}
});
}
/**
* Adds a FeedMedia object to the playback history. A FeedMedia object is in the playback history if
* its playback completion date is set to a non-null value. This method will set the playback completion date to the
* current date regardless of the current value.
*
* @param context A context that is used for opening a database connection.
* @param media FeedMedia that should be added to the playback history.
*/
public static Future<?> addItemToPlaybackHistory(final Context context,
final FeedMedia media) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
if (AppConfig.DEBUG)
Log.d(TAG, "Adding new item to playback history");
media.setPlaybackCompletionDate(new Date());
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setMedia(media);
adapter.close();
EventDistributor.getInstance().sendPlaybackHistoryUpdateBroadcast();
}
});
}
private static void cleanupDownloadLog(final PodDBAdapter adapter) {
final long logSize = adapter.getDownloadLogSize();
if (logSize > DBReader.DOWNLOAD_LOG_SIZE) {
if (AppConfig.DEBUG)
Log.d(TAG, "Cleaning up download log");
adapter.removeDownloadLogItems(logSize - DBReader.DOWNLOAD_LOG_SIZE);
}
}
/**
* Adds a Download status object to the download log.
*
* @param context A context that is used for opening a database connection.
* @param status The DownloadStatus object.
*/
public static Future<?> addDownloadStatus(final Context context,
final DownloadStatus status) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setDownloadStatus(status);
cleanupDownloadLog(adapter);
adapter.close();
EventDistributor.getInstance().sendDownloadLogUpdateBroadcast();
}
});
}
/**
* Inserts a FeedItem in the queue at the specified index. The 'read'-attribute of the FeedItem will be set to
* true. If the FeedItem is already in the queue, the queue will not be modified.
*
* @param context A context that is used for opening a database connection.
* @param itemId ID of the FeedItem that should be added to the queue.
* @param index Destination index. Must be in range 0..queue.size()
* @param performAutoDownload True if an auto-download process should be started after the operation
* @throws IndexOutOfBoundsException if index < 0 || index >= queue.size()
*/
public static Future<?> addQueueItemAt(final Context context, final long itemId,
final int index, final boolean performAutoDownload) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
final PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
final List<FeedItem> queue = DBReader
.getQueue(context, adapter);
FeedItem item = null;
if (queue != null) {
boolean queueModified = false;
boolean unreadItemsModified = false;
if (!itemListContains(queue, itemId)) {
item = DBReader.getFeedItem(context, itemId);
if (item != null) {
queue.add(index, item);
queueModified = true;
if (!item.isRead()) {
item.setRead(true);
unreadItemsModified = true;
}
}
}
if (queueModified) {
adapter.setQueue(queue);
EventDistributor.getInstance()
.sendQueueUpdateBroadcast();
}
if (unreadItemsModified && item != null) {
adapter.setSingleFeedItem(item);
EventDistributor.getInstance()
.sendUnreadItemsUpdateBroadcast();
}
}
adapter.close();
if (performAutoDownload) {
new Thread() {
@Override
public void run() {
DBTasks.autodownloadUndownloadedItems(context);
}
}.start();
}
}
});
}
/**
* Appends FeedItem objects to the end of the queue. The 'read'-attribute of all items will be set to true.
* If a FeedItem is already in the queue, the FeedItem will not change its position in the queue.
*
* @param context A context that is used for opening a database connection.
* @param itemIds IDs of the FeedItem objects that should be added to the queue.
*/
public static Future<?> addQueueItem(final Context context,
final long... itemIds) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
if (itemIds.length > 0) {
final PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
final List<FeedItem> queue = DBReader.getQueue(context,
adapter);
if (queue != null) {
boolean queueModified = false;
boolean unreadItemsModified = false;
List<FeedItem> itemsToSave = new LinkedList<FeedItem>();
for (int i = 0; i < itemIds.length; i++) {
if (!itemListContains(queue, itemIds[i])) {
final FeedItem item = DBReader.getFeedItem(
context, itemIds[i]);
if (item != null) {
queue.add(item);
queueModified = true;
if (!item.isRead()) {
item.setRead(true);
itemsToSave.add(item);
unreadItemsModified = true;
}
}
}
}
if (queueModified) {
adapter.setQueue(queue);
EventDistributor.getInstance()
.sendQueueUpdateBroadcast();
}
if (unreadItemsModified) {
adapter.setFeedItemlist(itemsToSave);
EventDistributor.getInstance()
.sendUnreadItemsUpdateBroadcast();
}
}
adapter.close();
new Thread() {
@Override
public void run() {
DBTasks.autodownloadUndownloadedItems(context);
}
}.start();
}
}
});
}
/**
* Removes all FeedItem objects from the queue.
*
* @param context A context that is used for opening a database connection.
*/
public static Future<?> clearQueue(final Context context) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.clearQueue();
adapter.close();
EventDistributor.getInstance().sendQueueUpdateBroadcast();
}
});
}
/**
* Removes a FeedItem object from the queue.
*
* @param context A context that is used for opening a database connection.
* @param itemId ID of the FeedItem that should be removed.
* @param performAutoDownload true if an auto-download process should be started after the operation.
*/
public static Future<?> removeQueueItem(final Context context,
final long itemId, final boolean performAutoDownload) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
final PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
final List<FeedItem> queue = DBReader
.getQueue(context, adapter);
FeedItem item = null;
if (queue != null) {
boolean queueModified = false;
QueueAccess queueAccess = QueueAccess.ItemListAccess(queue);
if (queueAccess.contains(itemId)) {
item = DBReader.getFeedItem(context, itemId);
if (item != null) {
queueModified = queueAccess.remove(itemId);
}
}
if (queueModified) {
adapter.setQueue(queue);
EventDistributor.getInstance()
.sendQueueUpdateBroadcast();
} else {
Log.w(TAG, "Queue was not modified by call to removeQueueItem");
}
} else {
Log.e(TAG, "removeQueueItem: Could not load queue");
}
adapter.close();
if (performAutoDownload) {
new Thread() {
@Override
public void run() {
DBTasks.autodownloadUndownloadedItems(context);
}
}.start();
}
}
});
}
/**
* Changes the position of a FeedItem in the queue.
*
* @param context A context that is used for opening a database connection.
* @param from Source index. Must be in range 0..queue.size()-1.
* @param to Destination index. Must be in range 0..queue.size()-1.
* @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to
* false if the caller wants to avoid unexpected updates of the GUI.
* @throws IndexOutOfBoundsException if (to < 0 || to >= queue.size()) || (from < 0 || from >= queue.size())
*/
public static Future<?> moveQueueItem(final Context context, final int from,
final int to, final boolean broadcastUpdate) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
final PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
final List<FeedItem> queue = DBReader
.getQueue(context, adapter);
if (queue != null) {
if (from >= 0 && from < queue.size() && to >= 0
&& to < queue.size()) {
final FeedItem item = queue.remove(from);
queue.add(to, item);
adapter.setQueue(queue);
if (broadcastUpdate) {
EventDistributor.getInstance()
.sendQueueUpdateBroadcast();
}
}
} else {
Log.e(TAG, "moveQueueItem: Could not load queue");
}
adapter.close();
}
});
}
/**
* Sets the 'read'-attribute of a FeedItem to the specified value.
*
* @param context A context that is used for opening a database connection.
* @param item The FeedItem object
* @param read New value of the 'read'-attribute
* @param resetMediaPosition true if this method should also reset the position of the FeedItem's FeedMedia object.
* If the FeedItem has no FeedMedia object, this parameter will be ignored.
*/
public static Future<?> markItemRead(Context context, FeedItem item, boolean read, boolean resetMediaPosition) {
long mediaId = (item.hasMedia()) ? item.getMedia().getId() : 0;
return markItemRead(context, item.getId(), read, mediaId, resetMediaPosition);
}
/**
* Sets the 'read'-attribute of a FeedItem to the specified value.
*
* @param context A context that is used for opening a database connection.
* @param itemId ID of the FeedItem
* @param read New value of the 'read'-attribute
*/
public static Future<?> markItemRead(final Context context, final long itemId,
final boolean read) {
return markItemRead(context, itemId, read, 0, false);
}
private static Future<?> markItemRead(final Context context, final long itemId,
final boolean read, final long mediaId,
final boolean resetMediaPosition) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
final PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setFeedItemRead(read, itemId, mediaId,
resetMediaPosition);
adapter.close();
EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
}
});
}
/**
* Sets the 'read'-attribute of all FeedItems of a specific Feed to true.
*
* @param context A context that is used for opening a database connection.
* @param feedId ID of the Feed.
*/
public static Future<?> markFeedRead(final Context context, final long feedId) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
final PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
Cursor itemCursor = adapter.getAllItemsOfFeedCursor(feedId);
long[] itemIds = new long[itemCursor.getCount()];
itemCursor.moveToFirst();
for (int i = 0; i < itemIds.length; i++) {
itemIds[i] = itemCursor.getLong(PodDBAdapter.KEY_ID_INDEX);
}
itemCursor.close();
adapter.setFeedItemRead(true, itemIds);
adapter.close();
EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
}
});
}
/**
* Sets the 'read'-attribute of all FeedItems to true.
*
* @param context A context that is used for opening a database connection.
*/
public static Future<?> markAllItemsRead(final Context context) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
final PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
Cursor itemCursor = adapter.getUnreadItemsCursor();
long[] itemIds = new long[itemCursor.getCount()];
itemCursor.moveToFirst();
for (int i = 0; i < itemIds.length; i++) {
itemIds[i] = itemCursor.getLong(PodDBAdapter.KEY_ID_INDEX);
}
itemCursor.close();
adapter.setFeedItemRead(true, itemIds);
adapter.close();
EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
}
});
}
static Future<?> addNewFeed(final Context context, final Feed feed) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
final PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setCompleteFeed(feed);
adapter.close();
EventDistributor.getInstance().sendFeedUpdateBroadcast();
}
});
}
static Future<?> setCompleteFeed(final Context context, final Feed feed) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setCompleteFeed(feed);
adapter.close();
EventDistributor.getInstance().sendFeedUpdateBroadcast();
}
});
}
/**
* Saves a FeedMedia object in the database. This method will save all attributes of the FeedMedia object. The
* contents of FeedComponent-attributes (e.g. the FeedMedia's 'item'-attribute) will not be saved.
*
* @param context A context that is used for opening a database connection.
* @param media The FeedMedia object.
*/
public static Future<?> setFeedMedia(final Context context,
final FeedMedia media) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setMedia(media);
adapter.close();
}
});
}
/**
* Saves only value of the 'position'-attribute of a FeedMedia object.
*
* @param context A context that is used for opening a database connection.
* @param media The FeedMedia object.
*/
public static Future<?> setFeedMediaPosition(final Context context, final FeedMedia media) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setFeedMediaPosition(media);
adapter.close();
}
});
}
/**
* Saves a FeedItem object in the database. This method will save all attributes of the FeedItem object including
* the content of FeedComponent-attributes.
*
* @param context A context that is used for opening a database connection.
* @param item The FeedItem object.
*/
public static Future<?> setFeedItem(final Context context,
final FeedItem item) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setSingleFeedItem(item);
adapter.close();
}
});
}
/**
* Saves a FeedImage object in the database. This method will save all attributes of the FeedImage object. The
* contents of FeedComponent-attributes (e.g. the FeedImages's 'feed'-attribute) will not be saved.
*
* @param context A context that is used for opening a database connection.
* @param image The FeedImage object.
*/
public static Future<?> setFeedImage(final Context context,
final FeedImage image) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setImage(image);
adapter.close();
}
});
}
private static boolean itemListContains(List<FeedItem> items, long itemId) {
for (FeedItem item : items) {
if (item.getId() == itemId) {
return true;
}
}
return false;
}
}

View File

@ -5,7 +5,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.text.ExtendedMessageFormat;
import org.apache.commons.lang3.StringUtils;
import android.content.Context;
import android.content.Intent;
@ -18,301 +18,283 @@ import de.danoeh.antennapod.feed.FeedFile;
import de.danoeh.antennapod.feed.FeedImage;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.download.DownloadRequest;
import de.danoeh.antennapod.service.download.DownloadService;
import de.danoeh.antennapod.util.FileNameGenerator;
import de.danoeh.antennapod.util.URLChecker;
public class DownloadRequester {
private static final String TAG = "DownloadRequester";
private static final String TAG = "DownloadRequester";
public static String IMAGE_DOWNLOADPATH = "images/";
public static String FEED_DOWNLOADPATH = "cache/";
public static String MEDIA_DOWNLOADPATH = "media/";
public static String IMAGE_DOWNLOADPATH = "images/";
public static String FEED_DOWNLOADPATH = "cache/";
public static String MEDIA_DOWNLOADPATH = "media/";
private static DownloadRequester downloader;
private static DownloadRequester downloader;
Map<String, FeedFile> downloads;
Map<String, DownloadRequest> downloads;
private DownloadRequester() {
downloads = new ConcurrentHashMap<String, FeedFile>();
}
private DownloadRequester() {
downloads = new ConcurrentHashMap<String, DownloadRequest>();
}
public static DownloadRequester getInstance() {
if (downloader == null) {
downloader = new DownloadRequester();
}
return downloader;
}
public static DownloadRequester getInstance() {
if (downloader == null) {
downloader = new DownloadRequester();
}
return downloader;
}
private void download(Context context, FeedFile item, File dest,
boolean overwriteIfExists) {
if (!isDownloadingFile(item)) {
if (!isFilenameAvailable(dest.toString()) || dest.exists()) {
if (AppConfig.DEBUG)
Log.d(TAG, "Filename already used.");
if (isFilenameAvailable(dest.toString()) && overwriteIfExists) {
boolean result = dest.delete();
if (AppConfig.DEBUG)
Log.d(TAG, "Deleting file. Result: " + result);
} else {
// find different name
File newDest = null;
for (int i = 1; i < Integer.MAX_VALUE; i++) {
String newName = FilenameUtils.getBaseName(dest
.getName())
+ "-"
+ i
+ FilenameUtils.EXTENSION_SEPARATOR
+ FilenameUtils.getExtension(dest.getName());
if (AppConfig.DEBUG)
Log.d(TAG, "Testing filename " + newName);
newDest = new File(dest.getParent(), newName);
if (!newDest.exists()
&& isFilenameAvailable(newDest.toString())) {
if (AppConfig.DEBUG)
Log.d(TAG, "File doesn't exist yet. Using "
+ newName);
break;
}
}
if (newDest != null) {
dest = newDest;
}
}
}
if (AppConfig.DEBUG)
Log.d(TAG,
"Requesting download of url " + item.getDownload_url());
item.setDownload_url(URLChecker.prepareURL(item.getDownload_url()));
item.setFile_url(dest.toString());
downloads.put(item.getDownload_url(), item);
private void download(Context context, FeedFile item, File dest,
boolean overwriteIfExists) {
if (!isDownloadingFile(item)) {
if (!isFilenameAvailable(dest.toString()) || dest.exists()) {
if (AppConfig.DEBUG)
Log.d(TAG, "Filename already used.");
if (isFilenameAvailable(dest.toString()) && overwriteIfExists) {
boolean result = dest.delete();
if (AppConfig.DEBUG)
Log.d(TAG, "Deleting file. Result: " + result);
} else {
// find different name
File newDest = null;
for (int i = 1; i < Integer.MAX_VALUE; i++) {
String newName = FilenameUtils.getBaseName(dest
.getName())
+ "-"
+ i
+ "."
+ FilenameUtils.getExtension(dest.getName());
if (AppConfig.DEBUG)
Log.d(TAG, "Testing filename " + newName);
newDest = new File(dest.getParent(), newName);
if (!newDest.exists()
&& isFilenameAvailable(newDest.toString())) {
if (AppConfig.DEBUG)
Log.d(TAG, "File doesn't exist yet. Using "
+ newName);
break;
}
}
if (newDest != null) {
dest = newDest;
}
}
}
if (AppConfig.DEBUG)
Log.d(TAG,
"Requesting download of url " + item.getDownload_url());
item.setDownload_url(URLChecker.prepareURL(item.getDownload_url()));
DownloadService.Request request = new DownloadService.Request(
item.getFile_url(), item.getDownload_url());
DownloadRequest request = new DownloadRequest(dest.toString(),
item.getDownload_url(), item.getHumanReadableIdentifier(),
item.getId(), item.getTypeAsInt());
if (!DownloadService.isRunning) {
Intent launchIntent = new Intent(context, DownloadService.class);
launchIntent.putExtra(DownloadService.EXTRA_REQUEST, request);
context.startService(launchIntent);
} else {
Intent queueIntent = new Intent(
DownloadService.ACTION_ENQUEUE_DOWNLOAD);
queueIntent.putExtra(DownloadService.EXTRA_REQUEST, request);
context.sendBroadcast(queueIntent);
}
EventDistributor.getInstance().sendDownloadQueuedBroadcast();
} else {
Log.e(TAG, "URL " + item.getDownload_url()
+ " is already being downloaded");
}
}
downloads.put(request.getSource(), request);
/**
* Returns true if a filename is available and false if it has already been
* taken by another requested download.
*/
private boolean isFilenameAvailable(String path) {
for (String key : downloads.keySet()) {
FeedFile f = downloads.get(key);
if (f.getFile_url() != null && f.getFile_url().equals(path)) {
if (AppConfig.DEBUG)
Log.d(TAG, path
+ " is already used by another requested download");
return false;
}
}
if (AppConfig.DEBUG)
Log.d(TAG, path + " is available as a download destination");
return true;
}
Intent launchIntent = new Intent(context, DownloadService.class);
launchIntent.putExtra(DownloadService.EXTRA_REQUEST, request);
context.startService(launchIntent);
EventDistributor.getInstance().sendDownloadQueuedBroadcast();
} else {
Log.e(TAG, "URL " + item.getDownload_url()
+ " is already being downloaded");
}
}
public void downloadFeed(Context context, Feed feed)
throws DownloadRequestException {
if (feedFileValid(feed)) {
download(context, feed, new File(getFeedfilePath(context),
getFeedfileName(feed)), true);
}
}
/**
* Returns true if a filename is available and false if it has already been
* taken by another requested download.
*/
private boolean isFilenameAvailable(String path) {
for (String key : downloads.keySet()) {
DownloadRequest r = downloads.get(key);
if (StringUtils.equals(r.getDestination(), path)) {
if (AppConfig.DEBUG)
Log.d(TAG, path
+ " is already used by another requested download");
return false;
}
}
if (AppConfig.DEBUG)
Log.d(TAG, path + " is available as a download destination");
return true;
}
public void downloadImage(Context context, FeedImage image)
throws DownloadRequestException {
if (feedFileValid(image)) {
download(context, image, new File(getImagefilePath(context),
getImagefileName(image)), true);
}
}
public void downloadFeed(Context context, Feed feed)
throws DownloadRequestException {
if (feedFileValid(feed)) {
download(context, feed, new File(getFeedfilePath(context),
getFeedfileName(feed)), true);
}
}
public void downloadMedia(Context context, FeedMedia feedmedia)
throws DownloadRequestException {
if (feedFileValid(feedmedia)) {
download(context, feedmedia,
new File(getMediafilePath(context, feedmedia),
getMediafilename(feedmedia)), false);
}
}
public void downloadImage(Context context, FeedImage image)
throws DownloadRequestException {
if (feedFileValid(image)) {
download(context, image, new File(getImagefilePath(context),
getImagefileName(image)), true);
}
}
/**
* Throws a DownloadRequestException if the feedfile or the download url of
* the feedfile is null.
*
* @throws DownloadRequestException
*/
private boolean feedFileValid(FeedFile f) throws DownloadRequestException {
if (f == null) {
throw new DownloadRequestException("Feedfile was null");
} else if (f.getDownload_url() == null) {
throw new DownloadRequestException("File has no download URL");
} else {
return true;
}
}
public void downloadMedia(Context context, FeedMedia feedmedia)
throws DownloadRequestException {
if (feedFileValid(feedmedia)) {
download(context, feedmedia,
new File(getMediafilePath(context, feedmedia),
getMediafilename(feedmedia)), false);
}
}
/**
* Cancels a running download.
* */
public void cancelDownload(final Context context, final FeedFile f) {
cancelDownload(context, f.getDownload_url());
}
/**
* Throws a DownloadRequestException if the feedfile or the download url of
* the feedfile is null.
*
* @throws DownloadRequestException
*/
private boolean feedFileValid(FeedFile f) throws DownloadRequestException {
if (f == null) {
throw new DownloadRequestException("Feedfile was null");
} else if (f.getDownload_url() == null) {
throw new DownloadRequestException("File has no download URL");
} else {
return true;
}
}
/**
* Cancels a running download.
* */
public void cancelDownload(final Context context, final String downloadUrl) {
if (AppConfig.DEBUG)
Log.d(TAG, "Cancelling download with url " + downloadUrl);
Intent cancelIntent = new Intent(DownloadService.ACTION_CANCEL_DOWNLOAD);
cancelIntent.putExtra(DownloadService.EXTRA_DOWNLOAD_URL, downloadUrl);
context.sendBroadcast(cancelIntent);
}
/**
* Cancels a running download.
*/
public void cancelDownload(final Context context, final FeedFile f) {
cancelDownload(context, f.getDownload_url());
}
/** Cancels all running downloads */
public void cancelAllDownloads(Context context) {
if (AppConfig.DEBUG)
Log.d(TAG, "Cancelling all running downloads");
context.sendBroadcast(new Intent(
DownloadService.ACTION_CANCEL_ALL_DOWNLOADS));
}
/**
* Cancels a running download.
*/
public void cancelDownload(final Context context, final String downloadUrl) {
if (AppConfig.DEBUG)
Log.d(TAG, "Cancelling download with url " + downloadUrl);
Intent cancelIntent = new Intent(DownloadService.ACTION_CANCEL_DOWNLOAD);
cancelIntent.putExtra(DownloadService.EXTRA_DOWNLOAD_URL, downloadUrl);
context.sendBroadcast(cancelIntent);
}
/** Returns true if there is at least one Feed in the downloads queue. */
public boolean isDownloadingFeeds() {
for (FeedFile f : downloads.values()) {
if (f.getClass() == Feed.class) {
return true;
}
}
return false;
}
/**
* Cancels all running downloads
*/
public void cancelAllDownloads(Context context) {
if (AppConfig.DEBUG)
Log.d(TAG, "Cancelling all running downloads");
context.sendBroadcast(new Intent(
DownloadService.ACTION_CANCEL_ALL_DOWNLOADS));
}
/** Checks if feedfile is in the downloads list */
public boolean isDownloadingFile(FeedFile item) {
if (item.getDownload_url() != null) {
return downloads.containsKey(item.getDownload_url());
}
return false;
}
/**
* Returns true if there is at least one Feed in the downloads queue.
*/
public boolean isDownloadingFeeds() {
for (DownloadRequest r : downloads.values()) {
if (r.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
return true;
}
}
return false;
}
public FeedFile getDownload(String downloadUrl) {
return downloads.get(downloadUrl);
}
/**
* Checks if feedfile is in the downloads list
*/
public boolean isDownloadingFile(FeedFile item) {
if (item.getDownload_url() != null) {
return downloads.containsKey(item.getDownload_url());
}
return false;
}
/** Checks if feedfile with the given download url is in the downloads list */
public boolean isDownloadingFile(String downloadUrl) {
return downloads.get(downloadUrl) != null;
}
public DownloadRequest getDownload(String downloadUrl) {
return downloads.get(downloadUrl);
}
public boolean hasNoDownloads() {
return downloads.isEmpty();
}
/**
* Checks if feedfile with the given download url is in the downloads list
*/
public boolean isDownloadingFile(String downloadUrl) {
return downloads.get(downloadUrl) != null;
}
public FeedFile getDownloadAt(int index) {
return downloads.get(index);
}
public boolean hasNoDownloads() {
return downloads.isEmpty();
}
/** Remove an object from the downloads-list of the requester. */
public void removeDownload(FeedFile f) {
if (downloads.remove(f.getDownload_url()) == null) {
Log.e(TAG,
"Could not remove object with url " + f.getDownload_url());
}
}
/**
* Remove an object from the downloads-list of the requester.
*/
public void removeDownload(DownloadRequest r) {
if (downloads.remove(r.getSource()) == null) {
Log.e(TAG,
"Could not remove object with url " + r.getSource());
}
}
/** Get the number of uncompleted Downloads */
public int getNumberOfDownloads() {
return downloads.size();
}
/**
* Get the number of uncompleted Downloads
*/
public int getNumberOfDownloads() {
return downloads.size();
}
public String getFeedfilePath(Context context)
throws DownloadRequestException {
return getExternalFilesDirOrThrowException(context, FEED_DOWNLOADPATH)
.toString() + "/";
}
public String getFeedfilePath(Context context)
throws DownloadRequestException {
return getExternalFilesDirOrThrowException(context, FEED_DOWNLOADPATH)
.toString() + "/";
}
public String getFeedfileName(Feed feed) {
String filename = feed.getDownload_url();
if (feed.getTitle() != null && !feed.getTitle().isEmpty()) {
filename = feed.getTitle();
}
return "feed-" + FileNameGenerator.generateFileName(filename);
}
public String getFeedfileName(Feed feed) {
String filename = feed.getDownload_url();
if (feed.getTitle() != null && !feed.getTitle().isEmpty()) {
filename = feed.getTitle();
}
return "feed-" + FileNameGenerator.generateFileName(filename);
}
public String getImagefilePath(Context context)
throws DownloadRequestException {
return getExternalFilesDirOrThrowException(context, IMAGE_DOWNLOADPATH)
.toString() + "/";
}
public String getImagefilePath(Context context)
throws DownloadRequestException {
return getExternalFilesDirOrThrowException(context, IMAGE_DOWNLOADPATH)
.toString() + "/";
}
public String getImagefileName(FeedImage image) {
String filename = image.getDownload_url();
if (image.getFeed() != null && image.getFeed().getTitle() != null) {
filename = image.getFeed().getTitle();
}
return "image-" + FileNameGenerator.generateFileName(filename);
}
public String getImagefileName(FeedImage image) {
String filename = image.getDownload_url();
if (image.getFeed() != null && image.getFeed().getTitle() != null) {
filename = image.getFeed().getTitle();
}
return "image-" + FileNameGenerator.generateFileName(filename);
}
public String getMediafilePath(Context context, FeedMedia media)
throws DownloadRequestException {
File externalStorage = getExternalFilesDirOrThrowException(
context,
MEDIA_DOWNLOADPATH
+ FileNameGenerator.generateFileName(media.getItem()
.getFeed().getTitle()) + "/");
return externalStorage.toString();
}
public String getMediafilePath(Context context, FeedMedia media)
throws DownloadRequestException {
File externalStorage = getExternalFilesDirOrThrowException(
context,
MEDIA_DOWNLOADPATH
+ FileNameGenerator.generateFileName(media.getItem()
.getFeed().getTitle()) + "/");
return externalStorage.toString();
}
private File getExternalFilesDirOrThrowException(Context context,
String type) throws DownloadRequestException {
File result = UserPreferences.getDataFolder(context, type);
if (result == null) {
throw new DownloadRequestException(
"Failed to access external storage");
}
return result;
}
private File getExternalFilesDirOrThrowException(Context context,
String type) throws DownloadRequestException {
File result = UserPreferences.getDataFolder(context, type);
if (result == null) {
throw new DownloadRequestException(
"Failed to access external storage");
}
return result;
}
public String getMediafilename(FeedMedia media) {
String filename;
String titleBaseFilename = "";
// Try to generate the filename by the item title
if (media.getItem() != null && media.getItem().getTitle() != null) {
String title = media.getItem().getTitle();
// Delete reserved characters
titleBaseFilename = title.replaceAll("[\\\\/%\\?\\*:|<>\"\\p{Cntrl}]", "");
titleBaseFilename = titleBaseFilename.trim();
}
String URLBaseFilename = URLUtil.guessFileName(media.getDownload_url(),
null, media.getMime_type());;
if (titleBaseFilename != "") {
// Append extension
filename = titleBaseFilename + FilenameUtils.EXTENSION_SEPARATOR +
FilenameUtils.getExtension(URLBaseFilename);
} else {
// Fall back on URL file name
filename = URLBaseFilename;
}
return filename;
}
public String getMediafilename(FeedMedia media) {
return URLUtil.guessFileName(media.getDownload_url(), null,
media.getMime_type());
}
}

View File

@ -0,0 +1,42 @@
package de.danoeh.antennapod.storage;
import java.util.Date;
/**
* Contains information about a feed's items.
*/
public class FeedItemStatistics {
private long feedID;
private int numberOfItems;
private int numberOfNewItems;
private int numberOfInProgressItems;
private Date lastUpdate;
public FeedItemStatistics(long feedID, int numberOfItems, int numberOfNewItems, int numberOfInProgressItems, Date lastUpdate) {
this.feedID = feedID;
this.numberOfItems = numberOfItems;
this.numberOfNewItems = numberOfNewItems;
this.numberOfInProgressItems = numberOfInProgressItems;
this.lastUpdate = lastUpdate;
}
public long getFeedID() {
return feedID;
}
public int getNumberOfItems() {
return numberOfItems;
}
public int getNumberOfNewItems() {
return numberOfNewItems;
}
public int getNumberOfInProgressItems() {
return numberOfInProgressItems;
}
public Date getLastUpdate() {
return lastUpdate;
}
}

View File

@ -0,0 +1,57 @@
package de.danoeh.antennapod.storage;
import android.content.Context;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.SearchResult;
import de.danoeh.antennapod.util.comparator.SearchResultValueComparator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* Performs search on Feeds and FeedItems
*/
public class FeedSearcher {
private static final String TAG = "FeedSearcher";
/**
* Performs a search in all feeds or one specific feed.
*/
public static List<SearchResult> performSearch(final Context context,
final String query, final long selectedFeed) {
final int values[] = {0, 0, 1, 2};
final String[] subtitles = {context.getString(R.string.found_in_shownotes_label),
context.getString(R.string.found_in_shownotes_label),
context.getString(R.string.found_in_chapters_label),
context.getString(R.string.found_in_title_label)};
List<SearchResult> result = new ArrayList<SearchResult>();
FutureTask<List<FeedItem>>[] tasks = new FutureTask[4];
(tasks[0] = DBTasks.searchFeedItemContentEncoded(context, selectedFeed, query)).run();
(tasks[1] = DBTasks.searchFeedItemDescription(context, selectedFeed, query)).run();
(tasks[2] = DBTasks.searchFeedItemChapters(context, selectedFeed, query)).run();
(tasks[3] = DBTasks.searchFeedItemTitle(context, selectedFeed, query)).run();
try {
for (int i = 0; i < tasks.length; i++) {
FutureTask task = tasks[i];
List<FeedItem> items = (List<FeedItem>) task.get();
for (FeedItem item : items) {
result.add(new SearchResult(item, values[i], subtitles[i]));
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
Collections.sort(result, new SearchResultValueComparator());
return result;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,92 @@
package de.danoeh.antennapod.util;
import de.danoeh.antennapod.feed.FeedItem;
import java.util.Iterator;
import java.util.List;
/**
* Provides methods for accessing the queue. It is possible to load only a part of the information about the queue that
* is stored in the database (e.g. sometimes the user just has to test if a specific item is contained in the List.
* QueueAccess provides an interface for accessing the queue without having to care about the type of the queue
* representation.
*/
public abstract class QueueAccess {
/**
* Returns true if the item is in the queue, false otherwise.
*/
public abstract boolean contains(long id);
/**
* Removes the item from the queue.
*
* @return true if the queue was modified by this operation.
*/
public abstract boolean remove(long id);
private QueueAccess() {
}
public static QueueAccess IDListAccess(final List<Long> ids) {
return new QueueAccess() {
@Override
public boolean contains(long id) {
return (ids != null) && ids.contains(id);
}
@Override
public boolean remove(long id) {
return ids.remove(id);
}
};
}
public static QueueAccess ItemListAccess(final List<FeedItem> items) {
return new QueueAccess() {
@Override
public boolean contains(long id) {
if (items == null) {
return false;
}
Iterator<FeedItem> it = items.iterator();
for (FeedItem i = it.next(); it.hasNext(); i = it.next()) {
if (i.getId() == id) {
return true;
}
}
return false;
}
@Override
public boolean remove(long id) {
Iterator<FeedItem> it = items.iterator();
for (FeedItem i = it.next(); it.hasNext(); i = it.next()) {
if (i.getId() == id) {
it.remove();
return true;
}
}
return false;
}
};
}
public static QueueAccess NotInQueueAccess() {
return new QueueAccess() {
@Override
public boolean contains(long id) {
return false;
}
@Override
public boolean remove(long id) {
return false;
}
};
}
}

View File

@ -0,0 +1,17 @@
package de.danoeh.antennapod.util;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* Created by daniel on 04.08.13.
*/
public interface ShownotesProvider {
/**
* Loads shownotes. If the shownotes have to be loaded from a file or from a
* database, it should be done in a separate thread. After the shownotes
* have been loaded, callback.onShownotesLoaded should be called.
*/
public Callable<String> loadShownotes();
}

View File

@ -2,7 +2,7 @@ package de.danoeh.antennapod.util.comparator;
import java.util.Comparator;
import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.service.download.*;
/** Compares the completion date of two Downloadstatus objects. */
public class DownloadStatusComparator implements Comparator<DownloadStatus> {

View File

@ -7,12 +7,16 @@ import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.storage.DBTasks;
import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.ShareUtils;
import java.util.List;
/** Handles interactions with the FeedItemMenu. */
public class FeedItemMenuHandler {
private FeedItemMenuHandler() {
@ -45,11 +49,12 @@ public class FeedItemMenuHandler {
* True if MenuItems that let the user share information about
* the FeedItem and visit its website should be set visible. This
* parameter should be set to false if the menu space is limited.
* @param queueAccess
* Used for testing if the queue contains the selected item
* @return Always returns true
* */
public static boolean onPrepareMenu(MenuInterface mi,
FeedItem selectedItem, boolean showExtendedMenu) {
FeedManager manager = FeedManager.getInstance();
FeedItem selectedItem, boolean showExtendedMenu, QueueAccess queueAccess) {
DownloadRequester requester = DownloadRequester.getInstance();
boolean hasMedia = selectedItem.getMedia() != null;
boolean downloaded = hasMedia && selectedItem.getMedia().isDownloaded();
@ -79,7 +84,7 @@ public class FeedItemMenuHandler {
mi.setItemVisibility(R.id.cancel_download_item, false);
}
boolean isInQueue = manager.isInQueue(selectedItem);
boolean isInQueue = queueAccess.contains(selectedItem.getId());
if (!isInQueue || isPlaying) {
mi.setItemVisibility(R.id.remove_from_queue_item, false);
}
@ -111,39 +116,38 @@ public class FeedItemMenuHandler {
public static boolean onMenuItemClicked(Context context, int menuItemId,
FeedItem selectedItem) throws DownloadRequestException {
DownloadRequester requester = DownloadRequester.getInstance();
FeedManager manager = FeedManager.getInstance();
switch (menuItemId) {
case R.id.skip_episode_item:
context.sendBroadcast(new Intent(
PlaybackService.ACTION_SKIP_CURRENT_EPISODE));
break;
case R.id.download_item:
manager.downloadFeedItem(context, selectedItem);
DBTasks.downloadFeedItems(context, selectedItem);
break;
case R.id.play_item:
manager.playMedia(context, selectedItem.getMedia(), true, true,
DBTasks.playMedia(context, selectedItem.getMedia(), true, true,
false);
break;
case R.id.remove_item:
manager.deleteFeedMedia(context, selectedItem.getMedia());
DBWriter.deleteFeedMediaOfItem(context, selectedItem.getId());
break;
case R.id.cancel_download_item:
requester.cancelDownload(context, selectedItem.getMedia());
break;
case R.id.mark_read_item:
manager.markItemRead(context, selectedItem, true, true);
DBWriter.markItemRead(context, selectedItem, true, true);
break;
case R.id.mark_unread_item:
manager.markItemRead(context, selectedItem, false, true);
DBWriter.markItemRead(context, selectedItem, false, true);
break;
case R.id.add_to_queue_item:
manager.addQueueItem(context, selectedItem);
DBWriter.addQueueItem(context, selectedItem.getId());
break;
case R.id.remove_from_queue_item:
manager.removeQueueItem(context, selectedItem, true);
DBWriter.removeQueueItem(context, selectedItem.getId(), true);
break;
case R.id.stream_item:
manager.playMedia(context, selectedItem.getMedia(), true, true,
DBTasks.playMedia(context, selectedItem.getMedia(), true, true,
true);
break;
case R.id.visit_website_item:

View File

@ -13,8 +13,9 @@ import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.FeedInfoActivity;
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.service.download.DownloadService;
import de.danoeh.antennapod.storage.DBTasks;
import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.ShareUtils;
@ -55,7 +56,6 @@ public class FeedMenuHandler {
*/
public static boolean onOptionsItemClicked(Context context, MenuItem item,
Feed selectedFeed) throws DownloadRequestException {
FeedManager manager = FeedManager.getInstance();
switch (item.getItemId()) {
case R.id.show_info_item:
Intent startIntent = new Intent(context, FeedInfoActivity.class);
@ -64,10 +64,10 @@ public class FeedMenuHandler {
context.startActivity(startIntent);
break;
case R.id.refresh_item:
manager.refreshFeed(context, selectedFeed);
DBTasks.refreshFeed(context, selectedFeed);
break;
case R.id.mark_all_read_item:
manager.markFeedRead(context, selectedFeed);
DBWriter.markFeedRead(context, selectedFeed.getId());
break;
case R.id.visit_website_item:
Uri uri = Uri.parse(selectedFeed.getLink());

View File

@ -2,6 +2,7 @@ package de.danoeh.antennapod.util.playback;
import java.io.InputStream;
import java.util.List;
import java.util.concurrent.Callable;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
@ -95,8 +96,13 @@ public class ExternalMedia implements Playable {
}
@Override
public void loadShownotes(ShownoteLoaderCallback callback) {
callback.onShownotesLoaded(null);
public Callable<String> loadShownotes() {
return new Callable<String>() {
@Override
public String call() throws Exception {
return "";
}
};
}
@Override

View File

@ -3,7 +3,11 @@ package de.danoeh.antennapod.util.playback;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
import java.util.concurrent.FutureTask;
import android.content.Context;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.util.ShownotesProvider;
import org.apache.commons.io.IOUtils;
import android.content.SharedPreferences;
@ -12,262 +16,263 @@ import android.os.Parcelable;
import android.util.Log;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.feed.Chapter;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.feed.MediaType;
/** Interface for objects that can be played by the PlaybackService. */
/**
* Interface for objects that can be played by the PlaybackService.
*/
public interface Playable extends Parcelable,
ImageLoader.ImageWorkerTaskResource {
ImageLoader.ImageWorkerTaskResource, ShownotesProvider {
/**
* 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.
*/
public void writeToPreferences(SharedPreferences.Editor prefEditor);
/**
* 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.
*/
public 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.
*/
public void loadMetadata() throws PlayableException;
/**
* 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.
*/
public 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.
*/
public void loadChapterMarks();
/**
* 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.
*/
public void loadChapterMarks();
/** Returns the title of the episode that this playable represents */
public String getEpisodeTitle();
/**
* Returns the title of the episode that this playable represents
*/
public String getEpisodeTitle();
/**
* Loads shownotes. If the shownotes have to be loaded from a file or from a
* database, it should be done in a separate thread. After the shownotes
* have been loaded, callback.onShownotesLoaded should be called.
*/
public void loadShownotes(ShownoteLoaderCallback callback);
/**
* Returns a list of chapter marks or null if this Playable has no chapters.
*/
public List<Chapter> getChapters();
/**
* Returns a list of chapter marks or null if this Playable has no chapters.
*/
public List<Chapter> getChapters();
/**
* Returns a link to a website that is meant to be shown in a browser
*/
public String getWebsiteLink();
/** Returns a link to a website that is meant to be shown in a browser */
public String getWebsiteLink();
public String getPaymentLink();
public String getPaymentLink();
/**
* Returns the title of the feed this Playable belongs to.
*/
public String getFeedTitle();
/** Returns the title of the feed this Playable belongs to. */
public String getFeedTitle();
/**
* Returns a unique identifier, for example a file url or an ID from a
* database.
*/
public Object getIdentifier();
/**
* Returns a unique identifier, for example a file url or an ID from a
* database.
*/
public Object getIdentifier();
/**
* Return duration of object or 0 if duration is unknown.
*/
public int getDuration();
/** Return duration of object or 0 if duration is unknown. */
public int getDuration();
/**
* Return position of object or 0 if position is unknown.
*/
public int getPosition();
/** Return position of object or 0 if position is unknown. */
public int getPosition();
/**
* Returns the type of media. This method should return the correct value
* BEFORE loadMetadata() is called.
*/
public MediaType getMediaType();
/**
* Returns the type of media. This method should return the correct value
* BEFORE loadMetadata() is called.
*/
public MediaType getMediaType();
/**
* Returns an url to a local file that can be played or null if this file
* does not exist.
*/
public String getLocalMediaUrl();
/**
* Returns an url to a local file that can be played or null if this file
* does not exist.
*/
public String getLocalMediaUrl();
/**
* Returns an url to a file that can be streamed by the player or null if
* this url is not known.
*/
public String getStreamUrl();
/**
* Returns an url to a file that can be streamed by the player or null if
* this url is not known.
*/
public 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.
*/
public boolean localFileAvailable();
/**
* Returns true if a local file that can be played is available. getFileUrl
* MUST return a non-null string if this method returns true.
*/
public boolean localFileAvailable();
/**
* Returns true if a streamable file is available. getStreamUrl MUST return
* a non-null string if this method returns true.
*/
public boolean streamAvailable();
/**
* Returns true if a streamable file is available. getStreamUrl MUST return
* a non-null string if this method returns true.
*/
public 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.
*/
public void saveCurrentPosition(SharedPreferences pref, int newPosition);
/**
* Saves the current position of this object. Implementations can use the
* provided SharedPreference to save this information and retrieve it later
* via PlayableUtils.createInstanceFromPreferences.
*/
public void saveCurrentPosition(SharedPreferences pref, int newPosition);
public void setPosition(int newPosition);
public void setPosition(int newPosition);
public void setDuration(int newDuration);
public void setDuration(int newDuration);
/**
* Is called by the PlaybackService when playback starts.
*/
public void onPlaybackStart();
/** Is called by the PlaybackService when playback starts. */
public void onPlaybackStart();
/**
* Is called by the PlaybackService when playback is completed.
*/
public void onPlaybackCompleted();
/** Is called by the PlaybackService when playback is completed. */
public 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.
*/
public int getPlayableType();
/**
* 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.
*/
public int getPlayableType();
public void setChapters(List<Chapter> chapters);
public void setChapters(List<Chapter> chapters);
/**
* Provides utility methods for Playable objects.
*/
public static class PlayableUtils {
private static final String TAG = "PlayableUtils";
/** Provides utility methods for Playable objects. */
public static 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) {
// ADD new Playable types here:
switch (type) {
case FeedMedia.PLAYABLE_TYPE_FEEDMEDIA:
long mediaId = pref.getLong(FeedMedia.PREF_MEDIA_ID, -1);
if (mediaId != -1) {
return DBReader.getFeedMedia(context, mediaId);
}
break;
case ExternalMedia.PLAYABLE_TYPE_EXTERNAL_MEDIA:
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);
return new ExternalMedia(source,
MediaType.valueOf(mediaType), position);
}
break;
}
Log.e(TAG, "Could not restore Playable object from preferences");
return null;
}
}
/**
* Restores a playable object from a sharedPreferences file.
*
* @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(int type,
SharedPreferences pref) {
// ADD new Playable types here:
switch (type) {
case FeedMedia.PLAYABLE_TYPE_FEEDMEDIA:
long feedId = pref.getLong(FeedMedia.PREF_FEED_ID, -1);
long mediaId = pref.getLong(FeedMedia.PREF_MEDIA_ID, -1);
if (feedId != -1 && mediaId != -1) {
Feed feed = FeedManager.getInstance().getFeed(feedId);
if (feed != null) {
return FeedManager.getInstance().getFeedMedia(mediaId,
feed);
}
}
break;
case ExternalMedia.PLAYABLE_TYPE_EXTERNAL_MEDIA:
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);
return new ExternalMedia(source,
MediaType.valueOf(mediaType), position);
}
break;
}
Log.e(TAG, "Could not restore Playable object from preferences");
return null;
}
}
public static class PlayableException extends Exception {
private static final long serialVersionUID = 1L;
public static class PlayableException extends Exception {
private static final long serialVersionUID = 1L;
public PlayableException() {
super();
}
public PlayableException() {
super();
}
public PlayableException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
public PlayableException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
public PlayableException(String detailMessage) {
super(detailMessage);
}
public PlayableException(String detailMessage) {
super(detailMessage);
}
public PlayableException(Throwable throwable) {
super(throwable);
}
public PlayableException(Throwable throwable) {
super(throwable);
}
}
}
/**
* Uses local file as image resource if it is available.
*/
public static class DefaultPlayableImageLoader implements
ImageLoader.ImageWorkerTaskResource {
private Playable playable;
public static interface ShownoteLoaderCallback {
void onShownotesLoaded(String shownotes);
}
public DefaultPlayableImageLoader(Playable playable) {
if (playable == null) {
throw new IllegalArgumentException("Playable must not be null");
}
this.playable = playable;
}
/** Uses local file as image resource if it is available. */
public static class DefaultPlayableImageLoader implements
ImageLoader.ImageWorkerTaskResource {
private Playable playable;
@Override
public InputStream openImageInputStream() {
if (playable.localFileAvailable()
&& playable.getLocalMediaUrl() != null) {
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
try {
mmr.setDataSource(playable.getLocalMediaUrl());
} catch (IllegalArgumentException e) {
e.printStackTrace();
return null;
}
byte[] imgData = mmr.getEmbeddedPicture();
if (imgData != null) {
return new PublicByteArrayInputStream(imgData);
public DefaultPlayableImageLoader(Playable playable) {
if (playable == null) {
throw new IllegalArgumentException("Playable must not be null");
}
this.playable = playable;
}
}
}
return null;
}
@Override
public InputStream openImageInputStream() {
if (playable.localFileAvailable()
&& playable.getLocalMediaUrl() != null) {
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
try {
mmr.setDataSource(playable.getLocalMediaUrl());
} catch (IllegalArgumentException e) {
e.printStackTrace();
return null;
}
byte[] imgData = mmr.getEmbeddedPicture();
if (imgData != null) {
return new PublicByteArrayInputStream(imgData);
@Override
public String getImageLoaderCacheKey() {
return playable.getLocalMediaUrl();
}
}
}
return null;
}
@Override
public InputStream reopenImageInputStream(InputStream input) {
if (input instanceof PublicByteArrayInputStream) {
IOUtils.closeQuietly(input);
byte[] imgData = ((PublicByteArrayInputStream) input)
.getByteArray();
if (imgData != null) {
ByteArrayInputStream out = new ByteArrayInputStream(imgData);
return out;
}
@Override
public String getImageLoaderCacheKey() {
return playable.getLocalMediaUrl();
}
}
return null;
}
@Override
public InputStream reopenImageInputStream(InputStream input) {
if (input instanceof PublicByteArrayInputStream) {
IOUtils.closeQuietly(input);
byte[] imgData = ((PublicByteArrayInputStream) input)
.getByteArray();
if (imgData != null) {
ByteArrayInputStream out = new ByteArrayInputStream(imgData);
return out;
}
private static class PublicByteArrayInputStream extends
ByteArrayInputStream {
public PublicByteArrayInputStream(byte[] buf) {
super(buf);
}
}
return null;
}
private static class PublicByteArrayInputStream extends
ByteArrayInputStream {
public PublicByteArrayInputStream(byte[] buf) {
super(buf);
}
public byte[] getByteArray() {
return buf;
}
}
}
public byte[] getByteArray() {
return buf;
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -6,8 +6,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.service.download.Downloader;
import de.danoeh.antennapod.service.download.DownloaderCallback;
import de.danoeh.antennapod.service.download.HttpDownloader;