public class AuthenticatorActivity extends FragmentActivity {
	public static final String PARAM_CONFIRMCREDENTIALS = "confirmCredentials";
	public static final String PARAM_PASSWORD = "password";
	public static final String PARAM_USERNAME = "username";
	public static final String PARAM_AUTHTOKEN_TYPE = "authtokenType";
	private final Handler _handler = new Handler();
	String _username;
	String _password;
	String _authtokenType;
	boolean _requestNewAccount;
	boolean _confirmCredentials;
	private Thread _authThread;
	private TextView _messageText;
	private EditText _usernameEdit;
	private EditText _passwordEdit;
	private EditText _deviceNameEdit;
	private RadioGroup _deviceTypeList;
	private AccountAuthenticatorResponse _accountAuthenticatorResponse;
	private Bundle _resultBundle;
	private ProgressDialogFragment _progressDialog;

	@Override
	protected void onCreate(Bundle icicle) {
		super.onCreate(icicle);

		final Intent intent = getIntent();
		_username = intent.getStringExtra(PARAM_USERNAME);
		_authtokenType = intent.getStringExtra(PARAM_AUTHTOKEN_TYPE);
		_requestNewAccount = _username == null;
		_confirmCredentials = intent.getBooleanExtra(PARAM_CONFIRMCREDENTIALS, false);

		setContentView(R.layout.gpodder_login);
		_messageText = (TextView) findViewById(R.id.message);
		_usernameEdit = (EditText) findViewById(R.id.username);
		_passwordEdit = (EditText) findViewById(R.id.password);
		_deviceNameEdit = (EditText) findViewById(R.id.devicename);
		_deviceTypeList = (RadioGroup) findViewById(R.id.devicetype);
		_deviceTypeList.check(Helper.isTablet(this) ? R.id.radioLaptop : R.id.radioMobile);

		// taken from AccountAuthenticatorActivity source code
		_accountAuthenticatorResponse = getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
		if (_accountAuthenticatorResponse != null)
			_accountAuthenticatorResponse.onRequestContinued();
	}

	private CharSequence getMessage() {
		if (TextUtils.isEmpty(_username)) {
			// If no username, then we ask the user to log in using an
			// appropriate service.
			return "New Account";
		}
		if (TextUtils.isEmpty(_password)) {
			// We have an account but no password
			return "Password missing";
		}
		return null;
	}

	protected void showProgress() {
		FragmentManager fm = getSupportFragmentManager();
		_progressDialog = ProgressDialogFragment.newInstance();
		_progressDialog.show(fm, "progress");
	}

	protected void hideProgress() {
		_progressDialog.dismiss();
	}

	public void handleLogin(View view) {
		if (_requestNewAccount) {
			_username = _usernameEdit.getText().toString();
		}
		_password = _passwordEdit.getText().toString();
		if (TextUtils.isEmpty(_username) || TextUtils.isEmpty(_password)) {
			_messageText.setText(getMessage());
		} else {
			showProgress();
			final Client client = new Client(this, _username, _password);

			_authThread = new Thread() {
				@Override
				public void run() {
					new Runnable() {
						public void run() {
							final boolean isValid = client.authenticate();
							SharedPreferences gpodderPrefs = getSharedPreferences("gpodder", MODE_PRIVATE);
							gpodderPrefs.edit()
									.putString("caption", _deviceNameEdit.getText().toString())
									.putString("type", getCheckedDeviceType())
									.commit();

							_handler.post(new Runnable() {
								public void run() {
									onAuthenticationResult(isValid);
								}
							});
						}
					}.run();
				}
			};
			_authThread.start();
		}
	}

	private String getCheckedDeviceType() {
		switch (_deviceTypeList.getCheckedRadioButtonId()) {
			case R.id.radioDesktop: return "desktop";
			case R.id.radioLaptop: return "laptop";
			case R.id.radioMobile: return "mobile";
			case R.id.radioServer: return "server";
			case R.id.radioOther: return "other";
			default: return "mobile";
		}
	}

	public void onAuthenticationResult(boolean isValid) {
		hideProgress();
		if (!isValid) {
			if (_requestNewAccount) {
				_messageText.setText("That username and password did not work on gpodder.net.");
			} else {
				_messageText.setText("That password did not work on gpodder.net.");
			}
		} else {
			if (_confirmCredentials) {
				finishConfirmCredentials(isValid);
			} else {
				finishLogin();
			}
		}
	}

	private void finishLogin() {
		final Account account = new Account(_username, Constants.GPODDER_ACCOUNT_TYPE);

		AccountManager accountManager = AccountManager.get(this);
		if (_requestNewAccount) {
			String rdm = Long.toHexString(Double.doubleToLongBits(Math.random()));
			SharedPreferences gpodderPrefs = getSharedPreferences("gpodder", MODE_PRIVATE);
			gpodderPrefs.edit()
					.putString("deviceId", "podax_" + rdm)
					.putBoolean("configurationNeedsUpdate", true)
					.commit();

			accountManager.addAccountExplicitly(account, _password, null);
			ContentResolver.requestSync(account, GPodderProvider.AUTHORITY, new Bundle());
			ContentResolver.setSyncAutomatically(account, GPodderProvider.AUTHORITY, true);
		} else {
			accountManager.setPassword(account, _password);
		}
		final Intent intent = new Intent();
		intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, _username);
		intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, Constants.GPODDER_ACCOUNT_TYPE);
		setAccountAuthenticatorResult(intent.getExtras());
		setResult(RESULT_OK, intent);
		finish();
	}

	protected void finishConfirmCredentials(boolean result) {
		final Account account = new Account(_username, Constants.GPODDER_ACCOUNT_TYPE);
		AccountManager accountManager = AccountManager.get(this);
		accountManager.setPassword(account, _password);
		final Intent intent = new Intent();
		intent.putExtra(AccountManager.KEY_BOOLEAN_RESULT, result);
		setAccountAuthenticatorResult(intent.getExtras());
		setResult(RESULT_OK, intent);
		finish();
	}

	public void setAccountAuthenticatorResult(Bundle accountAuthenticatorResult) {
		this._resultBundle = accountAuthenticatorResult;
	}

	@Override
	public void finish() {
		// copied from AccountAuthenticatorActivity source code
		if (_accountAuthenticatorResponse != null) {
			// send the result bundle back if set, otherwise send an error.
			if (_resultBundle != null)
				_accountAuthenticatorResponse.onResult(_resultBundle);
			else
				_accountAuthenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
			_accountAuthenticatorResponse = null;
		}
		super.finish();
	}

} public class BootReceiver extends BroadcastReceiver {

	@Override
	public void onReceive(Context context, Intent intent) {
		setupAlarms(context);
	}

	public static void setupAlarms(Context context) {
		// intent will always be BOOT_COMPLETED
		AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

		// refresh the feeds
		Intent refreshIntent = new Intent(context, UpdateService.class);
		refreshIntent.setAction(Constants.ACTION_REFRESH_ALL_SUBSCRIPTIONS);
		PendingIntent pendingRefreshIntent = PendingIntent.getService(context, 0, refreshIntent, 0);
		alarmManager.cancel(pendingRefreshIntent);
		alarmManager.setInexactRepeating(AlarmManager.RTC,
				System.currentTimeMillis(), AlarmManager.INTERVAL_HOUR * 6, pendingRefreshIntent);
	}

} public class Constants {
	public static final int NOTIFICATION_UPDATE = 1;
	public static final int SUBSCRIPTION_UPDATE_ERROR = 2;
	public static final int NOTIFICATION_PLAYING = 3;
	public static final int NOTIFICATION_GPODDER_ERROR = 4;
	public static final int NOTIFICATION_DOWNLOAD_ERROR = 5;

	// active podcast related
	public static final Uri ACTIVE_PODCAST_DATA_RESTART = Uri.parse("podax://activepodcast/restart");
	public static final Uri ACTIVE_PODCAST_DATA_BACK = Uri.parse("podax://activepodcast/back");
	public static final Uri ACTIVE_PODCAST_DATA_FORWARD = Uri.parse("podax://activepodcast/forward");
	public static final Uri ACTIVE_PODCAST_DATA_END = Uri.parse("podax://activepodcast/end");

	public static final String ACTION_REFRESH_ALL_SUBSCRIPTIONS = "com.axelby.podax.REFRESH_ALL_SUBSCRIPTIONS";
	public static final String ACTION_REFRESH_SUBSCRIPTION = "com.axelby.podax.REFRESH_SUBSCRIPTION";
	public static final String ACTION_DOWNLOAD_PODCAST = "com.axelby.podax.DOWNLOAD_PODCAST";
	public static final String ACTION_DOWNLOAD_PODCASTS = "com.axelby.podax.DOWNLOAD_PODCASTS";

	public static final String EXTRA_PODCAST_ID = "com.axelby.podax.podcastId";
	public static final String EXTRA_SUBSCRIPTION_ID = "com.axelby.podax.subscriptionId";
	public static final String EXTRA_TITLE = "com.axelby.podax.title";
	public static final String EXTRA_URL = "com.axelby.podax.url";
	public static final String EXTRA_MANUAL_REFRESH = "com.axelby.podax.manual_refresh";
	public static final String EXTRA_FRAGMENT = "com.axelby.podax.fragmentId";

	public static final String EXTRA_PLAYER_COMMAND = "com.axelby.podax.player_command";
	public static final String EXTRA_PLAYER_COMMAND_ARG = "com.axelby.podax.player_command_arg";
	public static final int PLAYER_COMMAND_PLAYPAUSE = 0;
	public static final int PLAYER_COMMAND_PLAY = 1;
	public static final int PLAYER_COMMAND_PAUSE = 2;
	public static final int PLAYER_COMMAND_STOP = 3;
	public static final int PLAYER_COMMAND_PLAYSTOP = 4;
	public static final int PLAYER_COMMAND_RESUME = 5;
	public static final int PLAYER_COMMAND_REFRESHPODCAST = 6;

	// reasons for pausing
	public static final int PAUSE_AUDIOFOCUS = 0;
	public static final int PAUSE_MEDIABUTTON = 1;
	public static final int PAUSE_COUNT = 2;

	// gpodder constants
	public static final String GPODDER_ACCOUNT_TYPE = "com.axelby.gpodder";
	public static final int GPODDER_UPDATE_NONE = 0;
	public static final int GPODDER_UPDATE_POSITION = 1;
	// podax automatically downloads and deletes
	// so it doesn't make sense to send these events to gpodder
	public static final int GPODDER_UPDATE_DOWNLOAD = 2;
	public static final int GPODDER_UPDATE_DELETE = 4;
	public static final int GPODDER_UPDATE_NEW = 8;
} public class GPodderProvider extends ContentProvider {
	public static String AUTHORITY = "com.axelby.podax.gpodder_sync";
	public static Uri URI = Uri.parse("content://" + AUTHORITY);
	public static final String ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/vnd.axelby.podax.gpoddersync";
	public static final String DIR_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/vnd.axelby.podax.gpoddersync";

	private DBAdapter _dbAdapter;

	public static Uri TO_REMOVE_URI = Uri.withAppendedPath(URI, "to_remove");
	public static Uri TO_ADD_URI = Uri.withAppendedPath(URI, "to_add");
	public static Uri DEVICE_URI = Uri.withAppendedPath(URI, "to_add");

	static final int ALL = 0;
	static final int TO_ADD = 1;
	static final int TO_REMOVE = 2;
	static final int DEVICE = 3;

	static UriMatcher _uriMatcher;

	static {
		_uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
		_uriMatcher.addURI(AUTHORITY, "", ALL);
		_uriMatcher.addURI(AUTHORITY, "to_add", TO_ADD);
		_uriMatcher.addURI(AUTHORITY, "to_remove", TO_REMOVE);
		_uriMatcher.addURI(AUTHORITY, "device", DEVICE);
	}

	@Override
	public boolean onCreate() {
		_dbAdapter = new DBAdapter(this.getContext());
		return true;
	}

	@Override
	public String getType(Uri uri) {
		return DIR_TYPE;
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
		SQLiteDatabase db = _dbAdapter.getReadableDatabase();

		if (selection == null)
			selection = "1=1";
		switch (_uriMatcher.match(uri)) {
			case ALL:
				return db.query("gpodder_sync", projection, selection, selectionArgs, null, null, sortOrder);
			case TO_ADD:
				return db.query("gpodder_sync", projection, selection + " AND to_add = 1", selectionArgs, null, null, sortOrder);
			case TO_REMOVE:
				return db.query("gpodder_sync", projection, selection + " AND to_remove = 1", selectionArgs, null, null, sortOrder);
			case DEVICE:
				return db.query("gpodder_device", projection, selection, selectionArgs, null, null, sortOrder);
			default:
				return null;
		}
	}

	// not a valid operation
	@Override
	public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
		return 0;
	}

	public static ContentValues makeValuesToAdd(String url) {
		ContentValues values = new ContentValues(2);
		values.put("url", url);
		values.put("to_add", 1);
		return values;
	}

	public static ContentValues makeValuesToRemove(String url) {
		ContentValues values = new ContentValues(2);
		values.put("url", url);
		values.put("to_remove", 1);
		return values;
	}

	@Override
	public Uri insert(Uri uri, ContentValues values) {
		if (!uri.equals(URI) || !values.containsKey("url"))
			return null;
		if (!values.containsKey("to_add") && !values.containsKey("to_remove"))
			return null;

		SQLiteDatabase db = _dbAdapter.getWritableDatabase();
		long id = db.insert("gpodder_sync", null, values);
		return ContentUris.withAppendedId(URI, id);
	}

	@Override
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		if (!uri.equals(URI))
			return 0;

		SQLiteDatabase db = _dbAdapter.getWritableDatabase();
		return db.delete("gpodder_sync", selection, selectionArgs);
	}

	private ContentValues makeUrlValues(String url) {
		ContentValues values = new ContentValues();
		values.put("url", url);
		return values;
	}

	public void fakeSync() {
		SQLiteDatabase db = _dbAdapter.getWritableDatabase();

		Cursor c = db.query("pending_remove", new String[]{"url"}, null, null, null, null, null);
		while (c.moveToNext())
			db.delete("subscriptions", "url = ?", new String[]{c.getString(0)});
		c.close();
		db.delete("pending_remove", null, null);

		c = db.query("pending_add", new String[]{"url"}, null, null, null, null, null);
		while (c.moveToNext())
			db.insert("subscriptions", null, makeUrlValues(c.getString(0)));
		c.close();
		db.delete("pending_add", null, null);
	}
} public class Helper {

	private static RequestQueue _requestQueue = null;
	private static ImageLoader _imageLoader = null;
	private static LruCache<String, Bitmap> _imageCache = new LruCache<String, Bitmap>(10);
	private static DiskBasedCache _diskCache = null;

	public static boolean ensureWifi(Context context) {
		ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
		NetworkInfo netInfo = cm.getActiveNetworkInfo();
		if (netInfo == null || !netInfo.isConnectedOrConnecting())
			return false;
		// always OK if we're on wifi
		if (netInfo.getType() == ConnectivityManager.TYPE_WIFI)
			return true;
		// check for wifi only pref
		if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("wifiPref", true))
			return false;
		// check for 3g data turned off
		return netInfo.isConnected();
	}

	public static String getTimeString(int milliseconds) {
		int seconds = milliseconds / 1000;
		final int SECONDSPERHOUR = 60 * 60;
		final int SECONDSPERMINUTE = 60;
		int hours = seconds / SECONDSPERHOUR;
		int minutes = seconds % SECONDSPERHOUR / SECONDSPERMINUTE;
		seconds = seconds % SECONDSPERMINUTE;

		StringBuilder builder = new StringBuilder();
		if (hours > 0) {
			builder.append(hours);
			builder.append(":");
			if (minutes < 10)
				builder.append("0");
		}
		builder.append(minutes);
		builder.append(":");
		if (seconds < 10)
			builder.append("0");
		builder.append(seconds);
		return builder.toString();
	}

	public static void registerMediaButtons(Context context) {
		AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
		audioManager.registerMediaButtonEventReceiver(new ComponentName(context, MediaButtonIntentReceiver.class));
	}

	public static void unregisterMediaButtons(Context context) {
		AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
		audioManager.unregisterMediaButtonEventReceiver(new ComponentName(context, MediaButtonIntentReceiver.class));
	}

	public static RequestQueue getRequestQueue(Context context) {
		if (_requestQueue == null)
			_requestQueue = Volley.newRequestQueue(context);
		return _requestQueue;
	}

	public static ImageLoader getImageLoader(Context context) {
		if (_diskCache == null)
			_diskCache = new DiskBasedCache(context.getExternalCacheDir());
		if (_imageLoader == null) {
			_imageLoader = new ImageLoader(getRequestQueue(context), new ImageLoader.ImageCache() {
				@Override
				public Bitmap getBitmap(String key) {
					if (_imageCache.get(key) != null)
						return _imageCache.get(key);
					if (_diskCache.getFileForKey(key).exists())
						return BitmapFactory.decodeFile(_diskCache.getFileForKey(key).getAbsolutePath());
					return null;
				}

				@Override
				public void putBitmap(String key, Bitmap bitmap) {
					_imageCache.put(key, bitmap);

					Cache.Entry entry = new Cache.Entry();
					// only put a max 512x512 image in the disk cache
					if (bitmap.getWidth() > 512 && bitmap.getHeight() > 512)
						bitmap = Bitmap.createScaledBitmap(bitmap, 512, 512, false);
					ByteBuffer buffer = ByteBuffer.allocate(bitmap.getRowBytes() * bitmap.getHeight());
					bitmap.copyPixelsToBuffer(buffer);
					entry.data = buffer.array();
					_diskCache.put(key, entry);
				}
			});
		}
		return _imageLoader;
	}

	public static Bitmap getCachedImage(Context context, String url) {
		if (_diskCache == null)
			_diskCache = new DiskBasedCache(context.getExternalCacheDir());
		String key = "#W0#H0" + url;
		if (_imageCache.get(key) != null)
			return _imageCache.get(key);
		if (_diskCache.getFileForKey(key).exists())
			return BitmapFactory.decodeFile(_diskCache.getFileForKey(url).getAbsolutePath());
		return null;
	}

	public static Bitmap getCachedImage(Context context, String url, int width, int height) {
		if (_diskCache == null)
			_diskCache = new DiskBasedCache(context.getExternalCacheDir());

		String key = "#W" + width + "#H" + height + url;
		if (_imageCache.get(key) != null)
			return _imageCache.get(key);

		Bitmap fullSize = getCachedImage(context, url);
		if (fullSize == null)
			return null;

		Bitmap scaled = Bitmap.createScaledBitmap(fullSize, width, height, false);
		_imageCache.put(key, scaled);
		return scaled;
	}

	public static boolean isTablet(Context context) {
		return (context.getResources().getConfiguration().screenLayout
				& Configuration.SCREENLAYOUT_SIZE_MASK)
				>= Configuration.SCREENLAYOUT_SIZE_LARGE;
	}
} public class PlayerService extends Service {
	private long _currentPodcastId;
	protected PodcastPlayer _player;
	private LockscreenManager _lockscreenManager = new LockscreenManager();

	private ContentObserver _podcastChangeObserver = new ContentObserver(new Handler()) {
		@Override
		public boolean deliverSelfNotifications() {
			return false;
		}

		@Override
		public void onChange(boolean selfChange) {
			onChange(selfChange, null);
		}

		@Override
		public void onChange(boolean selfChange, Uri uri) {
			ensurePlayerStatus();
		}
	};

	private BroadcastReceiver _stopReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			PodaxLog.log(PlayerService.this, "stopping for intent " + intent.getAction());
			PlayerService.stop(PlayerService.this);
		}
	};

	// static functions for easier controls
	public static void play(Context context) {
		PlayerService.sendCommand(context, Constants.PLAYER_COMMAND_PLAY);
	}

	public static void play(Context context, long podcastId) {
		QueueManager.changeActivePodcast(context, podcastId);
		PlayerService.sendCommand(context, Constants.PLAYER_COMMAND_PLAY);
	}

	public static void pause(Context context, int pause_reason) {
		PlayerService.sendCommand(context, Constants.PLAYER_COMMAND_PAUSE, pause_reason);
	}

	public static void stop(Context context) {
		PlayerService.sendCommand(context, Constants.PLAYER_COMMAND_STOP);
	}

	public static void playpause(Context context, int pause_reason) {
		PlayerService.sendCommand(context, Constants.PLAYER_COMMAND_PLAYPAUSE, pause_reason);
	}

	private static void sendCommand(Context context, int command) {
		Intent intent = new Intent(context, PlayerService.class);
		intent.putExtra(Constants.EXTRA_PLAYER_COMMAND, command);
		context.startService(intent);
	}

	private static void sendCommand(Context context, int command, int arg) {
		Intent intent = new Intent(context, PlayerService.class);
		intent.putExtra(Constants.EXTRA_PLAYER_COMMAND, command);
		intent.putExtra(Constants.EXTRA_PLAYER_COMMAND_ARG, arg);
		context.startService(intent);
	}

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	@Override
	public void onCreate() {
		registerReceiver(_stopReceiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY));
	}

	@Override
	public void onDestroy() {
		unregisterReceiver(_stopReceiver);
	}

	private class PodcastEventHandler implements PodcastPlayer.OnCompletionListener,
			PodcastPlayer.OnPauseListener,
			PodcastPlayer.OnPlayListener,
			PodcastPlayer.OnChangeListener,
			PodcastPlayer.OnSeekListener,
			PodcastPlayer.OnStopListener {
		@Override
		public void onPlay(float positionInSeconds, float playbackRate) {
			updateActivePodcast();

			// listen for changes to the podcast
			getContentResolver().registerContentObserver(PodcastProvider.PLAYER_UPDATE_URI, false, _podcastChangeObserver);

			// grab the media button
			AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
			ComponentName eventReceiver = new ComponentName(PlayerService.this, MediaButtonIntentReceiver.class);
			audioManager.registerMediaButtonEventReceiver(eventReceiver);

			PlayerStatus.updateState(PlayerService.this, PlayerStates.PLAYING);
			showNotification();
			_lockscreenManager.setLockscreenPlaying(positionInSeconds, playbackRate);
		}

		@Override
		public void onPause(float positionInSeconds) {
			updateActivePodcastPosition(positionInSeconds);
			PlayerStatus.updateState(PlayerService.this, PlayerStatus.PlayerStates.PAUSED);
			_lockscreenManager.setLockscreenPaused(positionInSeconds);
			showNotification();
		}

		@Override
		public void onStop(float positionInSeconds) {
			updateActivePodcastPosition(positionInSeconds);
			_lockscreenManager.removeLockscreenControls(positionInSeconds);
			removeNotification();
			getContentResolver().unregisterContentObserver(_podcastChangeObserver);
			PlayerStatus.updateState(PlayerService.this, PlayerStatus.PlayerStates.STOPPED);
			stopSelf();
		}

		@Override
		public void onCompletion() {
			QueueManager.moveToNextInQueue(PlayerService.this);
		}

		@Override
		public void onChange() {
			// assume that podcast playing is the active podcast
			PlayerStatus status = PlayerStatus.getCurrentState(PlayerService.this);
			_currentPodcastId = status.getPodcastId();
			if (status.getState() == PlayerStates.PLAYING)
				_player.play();
			QueueManager.changeActivePodcast(PlayerService.this, status.getPodcastId());
			_lockscreenManager.setupLockscreenControls(PlayerService.this, status);
			showNotification();
		}

		@Override
		public void onSeek(float positionInSeconds) {
			updateActivePodcastPosition(positionInSeconds);
		}
	}
	private PodcastEventHandler _podcastEventHandler = new PodcastEventHandler();

	private void createPlayer() {
		if (_player == null) {
			_player = new PodcastPlayer(this);
			_player.setOnCompletionListener(_podcastEventHandler);
			_player.setOnPlayListener(_podcastEventHandler);
			_player.setOnPauseListener(_podcastEventHandler);
			_player.setOnStopListener(_podcastEventHandler);
			_player.setOnChangeListener(_podcastEventHandler);
			_player.setOnSeekListener(_podcastEventHandler);
			ensurePlayerStatus();
		}
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		if (intent == null || intent.getExtras() == null)
			return START_NOT_STICKY;

		if (!intent.getExtras().containsKey(Constants.EXTRA_PLAYER_COMMAND))
			return START_NOT_STICKY;

		int pauseReason = intent.getIntExtra(Constants.EXTRA_PLAYER_COMMAND_ARG, -1);

		if (_player == null && intent.getExtras().containsKey(Constants.EXTRA_PLAYER_COMMAND))
			createPlayer();
		switch (intent.getIntExtra(Constants.EXTRA_PLAYER_COMMAND, -1)) {
			case -1:
				break;
			case Constants.PLAYER_COMMAND_PLAYPAUSE:
				_player.playPause(pauseReason);
				break;
			case Constants.PLAYER_COMMAND_PLAY:
                ensurePlayerStatus();
				_player.play();
				break;
			case Constants.PLAYER_COMMAND_PLAYSTOP:
				_player.playStop();
				break;
			case Constants.PLAYER_COMMAND_PAUSE:
				_player.pause(pauseReason);
				break;
			case Constants.PLAYER_COMMAND_RESUME:
				_player.unpause(pauseReason);
				break;
			case Constants.PLAYER_COMMAND_STOP:
				_player.stop();
				break;
			case Constants.PLAYER_COMMAND_REFRESHPODCAST:
				ensurePlayerStatus();
				break;
		}

		return START_NOT_STICKY;
	}

	private void ensurePlayerStatus() {
		if (_player == null)
			return;

		PlayerStatus status = PlayerStatus.getCurrentState(this);
		if (!status.hasActivePodcast()) {
			_player.stop();
			return;
		}

		if (status.getPodcastId() != _currentPodcastId) {
			_player.changePodcast(status.getFilename(), status.getPosition() / 1000.0f);
		} else
			_player.seekTo(status.getPosition() / 1000.0f);
	}

	private void showNotification() {
		PlayerStatus playerStatus = PlayerStatus.getCurrentState(this);

		// both paths use the pendingintent
		Intent showIntent = new Intent(this, MainActivity.class);
		PendingIntent showPendingIntent = PendingIntent.getActivity(this, 0, showIntent, 0);

		NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
				.setSmallIcon(R.drawable.icon)
				.setWhen(0)
				.setContentTitle(playerStatus.getTitle())
				.setContentText(playerStatus.getSubscriptionTitle())
				.setContentIntent(showPendingIntent)
				.setOngoing(true)
				.setPriority(NotificationCompat.PRIORITY_DEFAULT);

		// set up pause intent
		Intent pauseIntent = new Intent(this, PlayerService.class);
		// use data to make intent unique
		pauseIntent.setData(Uri.parse("podax://playercommand/playpause"));
		pauseIntent.putExtra(Constants.EXTRA_PLAYER_COMMAND, Constants.PLAYER_COMMAND_PLAYPAUSE);
		pauseIntent.putExtra(Constants.EXTRA_PLAYER_COMMAND_ARG, Constants.PAUSE_MEDIABUTTON);
		PendingIntent pausePendingIntent = PendingIntent.getService(this, 0, pauseIntent, 0);

		// set up forward intent
		Intent forwardIntent = new Intent(this, ActivePodcastReceiver.class);
		forwardIntent.setData(Constants.ACTIVE_PODCAST_DATA_FORWARD);
		PendingIntent forwardPendingIntent = PendingIntent.getService(this, 0, forwardIntent, 0);

		Bitmap subscriptionBitmap = SubscriptionCursor.getThumbnailImage(this, playerStatus.getSubscriptionId());
		if (subscriptionBitmap != null)
			builder.setLargeIcon(subscriptionBitmap);

		if (playerStatus.getState() == PlayerStates.PLAYING)
			builder.addAction(R.drawable.ic_media_pause_normal, getString(R.string.pause), pausePendingIntent);
		else
			builder.addAction(R.drawable.ic_media_play_normal, getString(R.string.play), pausePendingIntent);
		builder.addAction(R.drawable.ic_media_ff_normal, getString(R.string.fast_forward), forwardPendingIntent);

		Notification notification = builder.build();
		startForeground(Constants.NOTIFICATION_PLAYING, notification);
	}

	private void removeNotification() {
		stopForeground(true);
	}

	private void updateActivePodcast() {
		ContentValues values = new ContentValues(1);
		values.put(PodcastProvider.COLUMN_ID, _currentPodcastId);
		getContentResolver().update(PodcastProvider.ACTIVE_PODCAST_URI, values, null, null);
	}

	private void updateActivePodcastPosition(float positionInSeconds) {
		ContentValues values = new ContentValues();
		values.put(PodcastProvider.COLUMN_LAST_POSITION, (int)(positionInSeconds * 1000));
		getContentResolver().update(PodcastProvider.PLAYER_UPDATE_URI, values, null, null);
	}
} public class PlayerStatus {

	public static PlayerStatus getCurrentState(Context context) {
		String[] projection = {
				PodcastProvider.COLUMN_ID,
				PodcastProvider.COLUMN_TITLE,
				PodcastProvider.COLUMN_SUBSCRIPTION_ID,
				PodcastProvider.COLUMN_SUBSCRIPTION_TITLE,
				PodcastProvider.COLUMN_LAST_POSITION,
				PodcastProvider.COLUMN_DURATION,
				PodcastProvider.COLUMN_MEDIA_URL,
		};
		Cursor cursor = context.getContentResolver().query(PodcastProvider.ACTIVE_PODCAST_URI, projection, null, null, null);
		PlayerStatus status = new PlayerStatus();
		if (cursor == null) {
			status._state = PlayerStates.QUEUEEMPTY;
			return status;
		}
		if (cursor.moveToNext()) {
			PodcastCursor podcast = new PodcastCursor(cursor);
			SharedPreferences prefs = context.getSharedPreferences("player", Context.MODE_PRIVATE);
			status._state = PlayerStates.fromInt(prefs.getInt("playingState", PlayerStates.STOPPED.toInt()));
			status._podcastId = podcast.getId();
			status._subscriptionId = podcast.getSubscriptionId();
			status._title = podcast.getTitle();
			status._subscriptionTitle = podcast.getSubscriptionTitle();
			status._position = podcast.getLastPosition();
			status._duration = podcast.getDuration();
			status._filename = podcast.getFilename(context);
		}
		cursor.close();
		return status;
	}

	public static void updateState(Context context, PlayerStates state) {
		SharedPreferences prefs = context.getSharedPreferences("player", Context.MODE_PRIVATE);
		prefs.edit().putInt("playingState", state.toInt()).commit();
		ActivePodcastReceiver.notifyExternal(context);
	}

	public static PlayerStates getPlayerState(Context context) {
		SharedPreferences prefs = context.getSharedPreferences("player", Context.MODE_PRIVATE);
		return PlayerStates.fromInt(prefs.getInt("playingState", -1));
	}

	// instance variables
	public enum PlayerStates {
		INVALID(-1),
		QUEUEEMPTY(0),
		STOPPED(1),
		PAUSED(2),
		PLAYING(3);

		private final int _code;

		PlayerStates(int code) {
			_code = code;
		}

		public int toInt() {
			return _code;
		}

		public static PlayerStates fromInt(int code) {
			for (PlayerStates p : PlayerStates.values())
				if (p.toInt() == code)
					return p;
			return QUEUEEMPTY;
		}
	}

	private PlayerStates _state;
	private long _podcastId;
	private long _subscriptionId;
	private int _position;
	private int _duration;
	private String _title;
	private String _subscriptionTitle;
	private String _filename;

	private PlayerStatus() {
		_state = PlayerStates.QUEUEEMPTY;
		_podcastId = -1;
	}

	public PlayerStates getState() {
		return _state;
	}

	public boolean isPlaying() { return _state == PlayerStates.PLAYING; }

	public long getPodcastId() {
		return _podcastId;
	}

	public long getSubscriptionId() {
		return _subscriptionId;
	}

	public int getPosition() { return _position; }

	public int getDuration() {
		return _duration;
	}

	public String getTitle() {
		return _title;
	}

	public String getSubscriptionTitle() {
		return _subscriptionTitle;
	}

	public String getFilename() { return _filename; }

	public boolean hasActivePodcast() {
		return getState() != PlayerStates.QUEUEEMPTY;
	}

	public boolean isPlayerServiceActive() {
		return getState() == PlayerStates.PLAYING || getState() == PlayerStates.PAUSED;
	}
} public class PodaxLog {

	public static void ensureRemoved(Context context) {
		if (isDebuggable(context))
			return;
		File file = new File(context.getExternalFilesDir(null), "podax.log");
		if (file.exists())
			file.delete();
	}

	public static void log(Context context, String format, Object... args) {
		try {
			if (!isDebuggable(context))
				return;

			String message = String.format(format, args);
			message = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).format(new Date()) + " " + message;
			message += "\n";
			File file = new File(context.getExternalFilesDir(null), "podax.log");
			FileWriter out = new FileWriter(file, true);
			out.write(message);
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static boolean isDebuggable(Context context) {
		// make sure debuggable flag is set before writing to the log
		PackageInfo packageInfo;
		try {
			packageInfo = context.getPackageManager().getPackageInfo(
					context.getApplicationInfo().packageName,
					PackageManager.GET_CONFIGURATIONS);
		} catch (NameNotFoundException e) {
			return false;
		}
		if (packageInfo == null)
			return false;

		int flags = packageInfo.applicationInfo.flags;
		if ((flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0)
			return false;

		return true;
	}

} public class SubscriptionProvider extends ContentProvider {
	public static String AUTHORITY = "com.axelby.podax.subscriptionprovider";
	public static Uri URI = Uri.parse("content://" + AUTHORITY + "/subscriptions");
	public static final Uri SEARCH_URI = Uri.withAppendedPath(URI, "search");
	public static final Uri FROM_GPODDER_URI = Uri.withAppendedPath(URI, "from_gpodder");

	public static final String ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/vnd.axelby.subscription";
	public static final String DIR_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/vnd.axelby.subscription";
	public static final String PODCAST_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/vnd.axelby.podcast";

	public static final String COLUMN_ID = "_id";
	public static final String COLUMN_TITLE = "title";
	public static final String COLUMN_URL = "url";
	public static final String COLUMN_LAST_MODIFIED = "lastModified";
	public static final String COLUMN_LAST_UPDATE = "lastUpdate";
	public static final String COLUMN_ETAG = "eTag";
	public static final String COLUMN_THUMBNAIL = "thumbnail";
	public static final String COLUMN_TITLE_OVERRIDE = "titleOverride";
	public static final String COLUMN_QUEUE_NEW = "queueNew";
	public static final String COLUMN_EXPIRATION = "expirationDays";

	private static final int SUBSCRIPTIONS = 1;
	private static final int SUBSCRIPTION_ID = 2;
	private static final int PODCASTS = 3;
	private static final int SUBSCRIPTIONS_SEARCH = 4;
	private static final int FROM_GPODDER = 5;

	static UriMatcher _uriMatcher;
	static HashMap<String, String> _columnMap;

	static {
		_uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
		_uriMatcher.addURI(AUTHORITY, "subscriptions", SUBSCRIPTIONS);
		_uriMatcher.addURI(AUTHORITY, "subscriptions/#", SUBSCRIPTION_ID);
		_uriMatcher.addURI(AUTHORITY, "subscriptions/#/podcasts", PODCASTS);
		_uriMatcher.addURI(AUTHORITY, "subscriptions/search", SUBSCRIPTIONS_SEARCH);
		_uriMatcher.addURI(AUTHORITY, "subscriptions/from_gpodder", FROM_GPODDER);

		_columnMap = new HashMap<String, String>();
		_columnMap.put(COLUMN_ID, "_id");
		_columnMap.put(COLUMN_TITLE, "title");
		_columnMap.put(COLUMN_URL, "url");
		_columnMap.put(COLUMN_LAST_MODIFIED, "lastModified");
		_columnMap.put(COLUMN_LAST_UPDATE, "lastUpdate");
		_columnMap.put(COLUMN_ETAG, "eTag");
		_columnMap.put(COLUMN_THUMBNAIL, "thumbnail");
		_columnMap.put(COLUMN_TITLE_OVERRIDE, "titleOverride");
		_columnMap.put(COLUMN_QUEUE_NEW, "queueNew");
		_columnMap.put(COLUMN_EXPIRATION, "expirationDays");
	}

	DBAdapter _dbAdapter;

	@Override
	public boolean onCreate() {
		_dbAdapter = new DBAdapter(getContext());
		return true;
	}

	@Override
	public String getType(Uri uri) {
		switch (_uriMatcher.match(uri)) {
			case SUBSCRIPTIONS:
				return DIR_TYPE;
			case SUBSCRIPTION_ID:
				return ITEM_TYPE;
			case PODCASTS:
				return PODCAST_ITEM_TYPE;
			default:
				throw new IllegalArgumentException("Unknown URI");
		}
	}

	@Override
	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
		int uriMatch = _uriMatcher.match(uri);

		if (uriMatch == PODCASTS) {
			return getContext().getContentResolver().query(PodcastProvider.URI,
					projection, "subscriptionId = ?",
					new String[]{uri.getPathSegments().get(1)},
					PodcastProvider.COLUMN_PUB_DATE + " DESC");
		}

		// make sure that title_override is in the query set if title is in there
		if (projection != null) {
			boolean hasTitle = false, hasOverride = false;
			for (String p : projection) {
				if (p.equals(COLUMN_TITLE))
					hasTitle = true;
				if (p.equals(COLUMN_TITLE_OVERRIDE))
					hasOverride = true;
			}
			if (hasTitle && !hasOverride) {
				ArrayList<String> list = new ArrayList<String>(Arrays.asList(projection));
				list.add(COLUMN_TITLE_OVERRIDE);
				projection = list.toArray(new String[list.size()]);
			}
		}

		SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder();
		sqlBuilder.setProjectionMap(_columnMap);
		sqlBuilder.setTables("subscriptions");

		switch (uriMatch) {
			case SUBSCRIPTIONS:
				if (sortOrder == null)
					sortOrder = "title IS NULL, COALESCE(titleOverride, title)";
				break;
			case SUBSCRIPTION_ID:
				sqlBuilder.appendWhere("_id = " + uri.getLastPathSegment());
				break;
			case SUBSCRIPTIONS_SEARCH:
				sqlBuilder.appendWhere("LOWER(title) LIKE ? OR LOWER(titleOverride) LIKE ?");
				if (!selectionArgs[0].startsWith("%"))
					selectionArgs[0] = "%" + selectionArgs[0] + "%";
				selectionArgs = new String[]{selectionArgs[0], selectionArgs[0]};
				if (sortOrder == null)
					sortOrder = "COALESCE(titleOverride, title)";
				break;
			default:
				throw new IllegalArgumentException("Unknown URI");
		}

		SQLiteDatabase db = _dbAdapter.getReadableDatabase();
		Cursor c = sqlBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
		c.setNotificationUri(getContext().getContentResolver(), uri);
		return c;
	}

	@Override
	public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
		// the url is not allowed to be changed -- insert a new one instead
		if (values.containsKey(COLUMN_URL))
			return 0;

		switch (_uriMatcher.match(uri)) {
			case SUBSCRIPTIONS:
				break;
			case SUBSCRIPTION_ID:
				String extraWhere = COLUMN_ID + " = " + uri.getLastPathSegment();
				if (where != null)
					where = extraWhere + " AND " + where;
				else
					where = extraWhere;
				break;
			default:
				throw new IllegalArgumentException("Unknown URI");
		}

		SQLiteDatabase db = _dbAdapter.getWritableDatabase();
		int count = db.update("subscriptions", values, where, whereArgs);
		if (!URI.equals(uri))
			getContext().getContentResolver().notifyChange(uri, null);
		getContext().getContentResolver().notifyChange(URI, null);
		return count;
	}

	@Override
	public Uri insert(Uri uri, ContentValues values) {
		// url is required
		if (!values.containsKey(COLUMN_URL))
			return null;

		boolean from_gpodder = false;

		switch (_uriMatcher.match(uri)) {
			case SUBSCRIPTIONS:
				break;
			case FROM_GPODDER:
				from_gpodder = true;
				break;
			default:
				throw new IllegalArgumentException("Unknown URI");
		}

		String url = values.getAsString(COLUMN_URL);
		SQLiteDatabase db = _dbAdapter.getWritableDatabase();

		// don't duplicate a url
		Cursor c = db.rawQuery("SELECT _id FROM subscriptions WHERE url = ?", new String[]{url});
		if (c.moveToNext()) {
			long oldId = c.getLong(0);
			c.close();
			return ContentUris.withAppendedId(URI, oldId);
		}
		c.close();

		long id = db.insert("subscriptions", null, values);

		// add during next gpodder sync
		if (!from_gpodder)
			getContext().getContentResolver().insert(GPodderProvider.URI, GPodderProvider.makeValuesToAdd(url));

		getContext().getContentResolver().notifyChange(URI, null);
		return ContentUris.withAppendedId(URI, id);
	}

	@Override
	public int delete(Uri uri, String where, String[] whereArgs) {
		ContentResolver contentResolver = getContext().getContentResolver();

		boolean from_gpodder = false;

		switch (_uriMatcher.match(uri)) {
			case SUBSCRIPTIONS:
				break;
			case FROM_GPODDER:
				from_gpodder = true;
				break;
			case SUBSCRIPTION_ID:
				String extraWhere = COLUMN_ID + " = " + uri.getLastPathSegment();
				if (where != null)
					where = extraWhere + " AND " + where;
				else
					where = extraWhere;
				break;
			default:
				throw new IllegalArgumentException("Unknown URI");
		}

		SQLiteDatabase db = _dbAdapter.getWritableDatabase();

		// go through subscriptions about to be deleted and remove podcasts
		Cursor c = db.query("subscriptions", new String[]{COLUMN_ID}, where, whereArgs, null, null, null);
		ArrayList<String> subIds = new ArrayList<String>();
		String in = "";
		while (c.moveToNext()) {
			in += ",?";
			subIds.add(String.valueOf(c.getInt(0)));
		}
		c.close();
		if (!in.equals("")) {
			in = "(" + in.substring(1) + ")";
			contentResolver.delete(PodcastProvider.URI, "subscriptionId IN " + in, subIds.toArray(new String[subIds.size()]));
		}

		// remove during next gpodder sync
		if (!from_gpodder) {
			c = db.query("subscriptions", new String[]{COLUMN_URL}, where, whereArgs, null, null, null);
			while (c.moveToNext()) {
				String url = c.getString(0);
				getContext().getContentResolver().insert(GPodderProvider.URI, GPodderProvider.makeValuesToRemove(url));
			}
			c.close();
		}

		// delete subscription
		int count = db.delete("subscriptions", where, whereArgs);

		contentResolver.notifyChange(URI, null);
		return count;
	}

} public class UpdateService extends IntentService {
	Handler _uiHandler = new Handler();

	public UpdateService() {
		super("UpdateService");
	}

	public static void updateSubscriptions(Context context) {
		Intent intent = new Intent(context, UpdateService.class);
		intent.setAction(Constants.ACTION_REFRESH_ALL_SUBSCRIPTIONS);
		intent.putExtra(Constants.EXTRA_MANUAL_REFRESH, true);
		context.startService(intent);
	}

	public static void updateSubscription(Context context, long subscriptionId) {
		Intent intent = createUpdateSubscriptionIntent(context, subscriptionId);
		intent.putExtra(Constants.EXTRA_MANUAL_REFRESH, true);
		context.startService(intent);
	}

	public static void updateSubscription(Context context, Uri subscriptionUri) {
		Integer subscriptionId = Integer.valueOf(subscriptionUri.getLastPathSegment());
		Intent intent = createUpdateSubscriptionIntent(context, subscriptionId);
		intent.putExtra(Constants.EXTRA_MANUAL_REFRESH, true);
		context.startService(intent);
	}

	public static void downloadPodcasts(Context context) {
		Intent intent = new Intent(context, UpdateService.class);
		intent.setAction(Constants.ACTION_DOWNLOAD_PODCASTS);
		intent.putExtra(Constants.EXTRA_MANUAL_REFRESH, true);
		context.startService(intent);
	}

	public static void downloadPodcastsSilently(Context context) {
		Intent intent = new Intent(context, UpdateService.class);
		intent.setAction(Constants.ACTION_DOWNLOAD_PODCASTS);
		context.startService(intent);
	}

	private static Intent createUpdateSubscriptionIntent(Context context, long subscriptionId) {
		Intent intent = new Intent(context, UpdateService.class);
		intent.setAction(Constants.ACTION_REFRESH_SUBSCRIPTION);
		intent.putExtra(Constants.EXTRA_SUBSCRIPTION_ID, subscriptionId);
		return intent;
	}

	private static Intent createDownloadPodcastIntent(Context context, long subscriptionId) {
		Intent intent = new Intent(context, UpdateService.class);
		intent.setAction(Constants.ACTION_DOWNLOAD_PODCAST);
		intent.putExtra(Constants.EXTRA_PODCAST_ID, subscriptionId);
		return intent;
	}

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	@Override
	public void onHandleIntent(Intent intent) {
		handleIntent(intent);
	}

	public void handleIntent(Intent intent) {
		String action = intent.getAction();
		if (action == null)
			return;

		if (intent.getBooleanExtra(Constants.EXTRA_MANUAL_REFRESH, false) && !Helper.ensureWifi(this)) {
			_uiHandler.post(new Runnable() {
				public void run() {
					Toast.makeText(UpdateService.this,
							R.string.update_request_no_wifi,
							Toast.LENGTH_SHORT).show();
				}
			});
			return;
		}

		if (action.equals(Constants.ACTION_REFRESH_ALL_SUBSCRIPTIONS)) {
			String[] projection = new String[]{SubscriptionProvider.COLUMN_ID};
			Cursor c = getContentResolver().query(SubscriptionProvider.URI, projection, null, null, null);
			if (c != null) {
				while (c.moveToNext())
					handleIntent(createUpdateSubscriptionIntent(this, c.getLong(0)));
				c.close();
			}
		} else if (action.equals(Constants.ACTION_REFRESH_SUBSCRIPTION)) {
			long subscriptionId = intent.getLongExtra(Constants.EXTRA_SUBSCRIPTION_ID, -1);
			if (subscriptionId == -1)
				return;
			new SubscriptionUpdater(this).update(subscriptionId);
		} else if (action.equals(Constants.ACTION_DOWNLOAD_PODCASTS)) {
			verifyDownloadedFiles();
			expireDownloadedFiles();

			String[] projection = {PodcastProvider.COLUMN_ID};
			Cursor c = getContentResolver().query(PodcastProvider.QUEUE_URI, projection, null, null, null);
			if (c != null) {
				while (c.moveToNext())
					handleIntent(createDownloadPodcastIntent(this, c.getLong(0)));
				c.close();
			}
		} else if (action.equals(Constants.ACTION_DOWNLOAD_PODCAST)) {
			long podcastId = intent.getLongExtra(Constants.EXTRA_PODCAST_ID, -1L);
			if (podcastId == -1)
				return;
			float maxPodcasts = PreferenceManager.getDefaultSharedPreferences(this).getFloat("queueMaxNumPodcasts", 10000);
			if (getQueueNumDownloadedItems() >= maxPodcasts)
				return;
			PodcastDownloader.download(this, podcastId);
		}

		removeNotification();
	}

	// make sure all media files in the folder are for existing podcasts
	private void verifyDownloadedFiles() {
		ArrayList<String> validMediaFilenames = new ArrayList<String>();
		String[] projection = new String[]{
				PodcastProvider.COLUMN_ID,
				PodcastProvider.COLUMN_MEDIA_URL,
		};
		Uri queueUri = Uri.withAppendedPath(PodcastProvider.URI, "queue");
		if (queueUri == null)
			return;
		Cursor c = getContentResolver().query(queueUri, projection, null, null, null);
		if (c == null)
			return;
		while (c.moveToNext())
			validMediaFilenames.add(new PodcastCursor(c).getFilename(this));
		c.close();

		File dir = new File(PodcastCursor.getStoragePath(this));
		File[] files = dir.listFiles();
		// this is possible if the directory does not exist
		if (files == null)
			return;
		for (File f : files) {
			// make sure the file is a media file
			String extension = PodcastCursor.getExtension(f.getName());
			String[] mediaExtensions = new String[]{"mp3", "ogg", "wma", "m4a",};
			if (Arrays.binarySearch(mediaExtensions, extension) < 0)
				continue;
			if (!validMediaFilenames.contains(f.getAbsolutePath())) {
				Log.w("Podax", "deleting file " + f.getName());
				f.delete();
			}
		}
	}

	private void expireDownloadedFiles() {
		String[] projection = new String[]{
				PodcastProvider.COLUMN_ID,
				PodcastProvider.COLUMN_MEDIA_URL,
		};
		Cursor c = getContentResolver().query(PodcastProvider.EXPIRED_URI, projection, null, null, null);
		if (c == null)
			return;
		while (c.moveToNext())
			new PodcastCursor(c).removeFromQueue(this);
		c.close();
	}

	private int getQueueNumDownloadedItems() {
		String[] projection = {
				PodcastProvider.COLUMN_ID,
				PodcastProvider.COLUMN_TITLE,
				PodcastProvider.COLUMN_SUBSCRIPTION_TITLE,
				PodcastProvider.COLUMN_MEDIA_URL,
				PodcastProvider.COLUMN_FILE_SIZE,
		};
		Cursor c = getContentResolver().query(PodcastProvider.QUEUE_URI, projection, null, null, null);
		if (c == null)
			return 0;
		int ret = 0;

		while (c.moveToNext()) {
			PodcastCursor podcast = new PodcastCursor(c);
			if (podcast.isDownloaded(this))
				ret++;
		}
		c.close();

		return ret;
	}

	private void removeNotification() {
		NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		notificationManager.cancel(Constants.NOTIFICATION_UPDATE);
	}
}
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.NotificationManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarActivity;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.axelby.gpodder.AuthenticatorActivity;
import com.axelby.podax.BootReceiver;
import com.axelby.podax.Constants;
import com.axelby.podax.GPodderProvider;
import com.axelby.podax.Helper;
import com.axelby.podax.PlayerService;
import com.axelby.podax.PlayerStatus;
import com.axelby.podax.PodaxLog;
import com.axelby.podax.R;
import com.axelby.podax.SubscriptionProvider;
import com.axelby.podax.UpdateService;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
								}
							})
							.setNegativeButton(R.string.no_thanks, new DialogInterface.OnClickListener() {
								@Override
								public void onClick(DialogInterface dialogInterface, int i) {
								}
							})
							.create()
							.show();
					preferences.edit().putInt("lastReleaseNoteDialog", versionCode).commit();
				}
			}
		} catch (PackageManager.NameNotFoundException ignored) {
		}

		// ui initialization
		setContentView(R.layout.app);
		_drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
		_drawerToggle = new ActionBarDrawerToggle(this, _drawerLayout, R.drawable.ic_drawer,
				R.string.open_drawer, R.string.close_drawer);
		_drawerLayout.setDrawerListener(_drawerToggle);

		getSupportActionBar().setDisplayHomeAsUpEnabled(true);
		getSupportActionBar().setHomeButtonEnabled(true);

		_drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
		_drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);

		ListView drawer = (ListView) findViewById(R.id.drawer);
		PodaxDrawerAdapter _drawerAdapter = new PodaxDrawerAdapter(this);
		drawer.setAdapter(_drawerAdapter);
		drawer.setOnItemClickListener(new OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
				changeFragment(position);
			}
		});

		if (intent.hasExtra(Constants.EXTRA_FRAGMENT)) {
			handleIntent(intent);
		} else if (savedInstanceState == null) {
			Cursor c = getContentResolver().query(SubscriptionProvider.URI, null, null, null, null);
			if (c != null) {
				int subscriptionCount = c.getCount();
				c.close();
				if (subscriptionCount == 0) {
					replaceFragment(WelcomeFragment.class);
					_fragmentId = 1;
				} else {
					replaceFragment(PodcastDetailFragment.class);
					_fragmentId = 2;
				}
			}
		} else {
			_fragmentId = savedInstanceState.getInt("fragmentId");
		}
	}

	private void changeFragment(int position) {
		_drawerLayout.closeDrawer(GravityCompat.START);

		_fragmentId = position;

		switch (position) {
			case 1:
				replaceFragment(WelcomeFragment.class);
				break;
			case 2:
				replaceFragment(PodcastDetailFragment.class);
				break;
			case 3:
				replaceFragment(QueueFragment.class);
				break;
			case 4:
				replaceFragment(SubscriptionListFragment.class);
				break;
			case 5:
				replaceFragment(SearchFragment.class);
				break;
			case 7:
				askForRSSUrl();
				break;
			case 8:
				replaceFragment(ITunesPopularListFragment.class);
				break;
			case 9:
				replaceFragment(GPodderPopularListFragment.class);
				break;
			case 10:
				handleGPodder();
				break;
			case 12:
				replaceFragment(StatsFragment.class);
				break;
			case 13:
				replaceFragment(PodaxPreferenceFragment.class);
				break;
			case 14:
				replaceFragment(AboutFragment.class);
				break;
			case 15:
				replaceFragment(LogViewerFragment.class);
				break;
		}
	}

	private void handleIntent(Intent intent) {
		if (intent == null || intent.getExtras() == null)
			return;
		if (intent.hasExtra("fragmentId"))
			changeFragment(intent.getIntExtra("fragmentId", 2));
	}

	private void handleGPodder() {
		AccountManager am = AccountManager.get(this);
		Account[] gpodder_accounts = am.getAccountsByType(Constants.GPODDER_ACCOUNT_TYPE);
		if (gpodder_accounts == null || gpodder_accounts.length == 0)
			startActivity(new Intent(this, AuthenticatorActivity.class));
		else {
			Toast.makeText(this, "Refreshing from gpodder.net as " + gpodder_accounts[0].name, Toast.LENGTH_SHORT).show();
			ContentResolver.requestSync(gpodder_accounts[0], GPodderProvider.AUTHORITY, new Bundle());