/*
* Copyright (C) 2010-2011 Geometer Plus <contact@geometerplus.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
package org.geometerplus.android.fbreader.network;
import java.util.*;
import java.io.*;
import java.net.URLConnection;
import android.os.IBinder;
import android.os.Handler;
import android.os.Message;
import android.app.Service;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.net.Uri;
import android.content.Intent;
import android.widget.RemoteViews;
import android.widget.Toast;
import org.geometerplus.zlibrary.ui.androidfly.R;
import org.geometerplus.zlibrary.core.resources.ZLResource;
import org.geometerplus.zlibrary.core.network.*;
import org.geometerplus.fbreader.network.BookReference;
import org.geometerplus.android.fbreader.FBReader;
public class BookDownloaderService extends Service {
public static final String BOOK_FORMAT_KEY = "org.geometerplus.android.fbreader.network.BookFormat";
public static final String REFERENCE_TYPE_KEY = "org.geometerplus.android.fbreader.network.ReferenceType";
public static final String CLEAN_URL_KEY = "org.geometerplus.android.fbreader.network.CleanURL";
public static final String TITLE_KEY = "org.geometerplus.android.fbreader.network.Title";
public static final String SSL_CERTIFICATE_KEY = "org.geometerplus.android.fbreader.network.SSLCertificate";
public static final String SHOW_NOTIFICATIONS_KEY = "org.geometerplus.android.fbreader.network.ShowNotifications";
public interface Notifications {
int DOWNLOADING_STARTED = 0x0001;
int ALREADY_DOWNLOADING = 0x0002;
int ALL = 0x0003;
}
private Set<String> myDownloadingURLs = Collections.synchronizedSet(new HashSet<String>());
private Set<Integer> myOngoingNotifications = new HashSet<Integer>();
private volatile int myServiceCounter;
private void doStart() {
++myServiceCounter;
}
private void doStop() {
if (--myServiceCounter == 0) {
stopSelf();
}
}
public static ZLResource getResource() {
return ZLResource.resource("bookDownloader");
}
@Override
public IBinder onBind(Intent intent) {
return new BookDownloaderInterface.Stub() {
public boolean isBeingDownloaded(String url) {
return myDownloadingURLs.contains(url);
}
};
}
@Override
public void onDestroy() {
final NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
for (int notificationId: myOngoingNotifications) {
notificationManager.cancel(notificationId);
}
myOngoingNotifications.clear();
super.onDestroy();
}
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
doStart();
final Uri uri = intent.getData();
if (uri == null) {
doStop();
return;
}
intent.setData(null);
final int notifications = intent.getIntExtra(SHOW_NOTIFICATIONS_KEY, 0);
final String url = uri.toString();
final int bookFormat = intent.getIntExtra(BOOK_FORMAT_KEY, BookReference.Format.NONE);
final int referenceType = intent.getIntExtra(REFERENCE_TYPE_KEY, BookReference.Type.UNKNOWN);
String cleanURL = intent.getStringExtra(CLEAN_URL_KEY);
if (cleanURL == null) {
cleanURL = url;
}
if (myDownloadingURLs.contains(url)) {
if ((notifications & Notifications.ALREADY_DOWNLOADING) != 0) {
showMessage("alreadyDownloading");
}
doStop();
return;
}
String fileName = BookReference.makeBookFileName(cleanURL, bookFormat, referenceType);
if (fileName == null) {
doStop();
return;
}
int index = fileName.lastIndexOf(File.separator);
if (index != -1) {
final String dir = fileName.substring(0, index);
final File dirFile = new File(dir);
if (!dirFile.exists() && !dirFile.mkdirs()) {
showMessage("cannotCreateDirectory", dirFile.getPath());
doStop();
return;
}
if (!dirFile.exists() || !dirFile.isDirectory()) {
showMessage("cannotCreateDirectory", dirFile.getPath());
doStop();
return;
}
}
final File fileFile = new File(fileName);
if (fileFile.exists()) {
if (!fileFile.isFile()) {
showMessage("cannotCreateFile", fileFile.getPath());
doStop();
return;
}
// TODO: question box: redownload?
doStop();
startActivity(getFBReaderIntent(fileFile));
return;
}
String title = intent.getStringExtra(TITLE_KEY);
if (title == null || title.length() == 0) {
title = fileFile.getName();
}
if ((notifications & Notifications.DOWNLOADING_STARTED) != 0) {
showMessage("downloadingStarted");
}
final String sslCertificate = intent.getStringExtra(SSL_CERTIFICATE_KEY);
startFileDownload(url, sslCertificate, fileFile, title);
}
private void showMessage(String key) {
Toast.makeText(
getApplicationContext(),
getResource().getResource(key).getValue(),
Toast.LENGTH_SHORT
).show();
}
private void showMessage(String key, String parameter) {
Toast.makeText(
getApplicationContext(),
getResource().getResource(key).getValue().replace("%s", parameter),
Toast.LENGTH_SHORT
).show();
}
private Intent getFBReaderIntent(final File file) {
final Intent intent = new Intent(getApplicationContext(), FBReader.class);
if (file != null) {
intent.setAction(Intent.ACTION_VIEW).setData(Uri.fromFile(file));
}
return intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
}
private Notification createDownloadFinishNotification(File file, String title, boolean success) {
final ZLResource resource = getResource();
final String tickerText = success ?
resource.getResource("tickerSuccess").getValue() :
resource.getResource("tickerError").getValue();
final String contentText = success ?
resource.getResource("contentSuccess").getValue() :
resource.getResource("contentError").getValue();
final Notification notification = new Notification(
android.R.drawable.stat_sys_download_done,
tickerText,
System.currentTimeMillis()
);
notification.flags |= Notification.FLAG_AUTO_CANCEL;
final Intent intent = success ? getFBReaderIntent(file) : new Intent();
final PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, 0);
notification.setLatestEventInfo(getApplicationContext(), title, contentText, contentIntent);
return notification;
}
private Notification createDownloadProgressNotification(String title) {
final RemoteViews contentView = new RemoteViews(getPackageName(), R.layout.download_notification);
contentView.setTextViewText(R.id.download_notification_title, title);
contentView.setTextViewText(R.id.download_notification_progress_text, "");
contentView.setProgressBar(R.id.download_notification_progress_bar, 100, 0, true);
final PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(), 0);
final Notification notification = new Notification();
notification.icon = android.R.drawable.stat_sys_download;
notification.flags |= Notification.FLAG_ONGOING_EVENT;
notification.contentView = contentView;
notification.contentIntent = contentIntent;
return notification;
}
private void sendDownloaderCallback() {
sendBroadcast(
new Intent(getApplicationContext(), BookDownloaderCallback.class)
);
}
private void startFileDownload(final String urlString, final String sslCertificate, final File file, final String title) {
myDownloadingURLs.add(urlString);
sendDownloaderCallback();
final int notificationId = NetworkNotifications.Instance().getBookDownloadingId();
final Notification progressNotification = createDownloadProgressNotification(title);
final NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
myOngoingNotifications.add(Integer.valueOf(notificationId));
notificationManager.notify(notificationId, progressNotification);
final Handler progressHandler = new Handler() {
public void handleMessage(Message message) {
final int progress = message.what;
final RemoteViews contentView = (RemoteViews)progressNotification.contentView;
if (progress < 0) {
contentView.setTextViewText(R.id.download_notification_progress_text, "");
contentView.setProgressBar(R.id.download_notification_progress_bar, 100, 0, true);
} else {
contentView.setTextViewText(R.id.download_notification_progress_text, "" + progress + "%");
contentView.setProgressBar(R.id.download_notification_progress_bar, 100, progress, false);
}
final NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(notificationId, progressNotification);
}
};
final Handler downloadFinishHandler = new Handler() {
public void handleMessage(Message message) {
myDownloadingURLs.remove(urlString);
final NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.cancel(notificationId);
myOngoingNotifications.remove(Integer.valueOf(notificationId));
notificationManager.notify(
notificationId,
createDownloadFinishNotification(file, title, message.what != 0)
);
sendDownloaderCallback();
doStop();
}
};
final ZLNetworkRequest request = new ZLNetworkRequest(urlString, sslCertificate, null) {
public void handleStream(URLConnection connection, InputStream inputStream) throws IOException, ZLNetworkException {
final int updateIntervalMillis = 1000; // FIXME: remove hardcoded time constant
final int fileLength = connection.getContentLength();
int downloadedPart = 0;
long progressTime = System.currentTimeMillis() + updateIntervalMillis;
if (fileLength <= 0) {
progressHandler.sendEmptyMessage(-1);
}
OutputStream outStream;
try {
outStream = new FileOutputStream(file);
} catch (FileNotFoundException ex) {
throw new ZLNetworkException(ZLNetworkException.ERROR_CREATE_FILE, file.getPath());
}
try {
final byte[] buffer = new byte[8192];
while (true) {
final int size = inputStream.read(buffer);
if (size <= 0) {
break;
}
downloadedPart += size;
if (fileLength > 0) {
final long currentTime = System.currentTimeMillis();
if (currentTime > progressTime) {
progressTime = currentTime + updateIntervalMillis;
progressHandler.sendEmptyMessage(downloadedPart * 100 / fileLength);
}
/*if (downloadedPart * 100 / fileLength > 95) {
throw new IOException("debug exception");
}*/
}
outStream.write(buffer, 0, size);
/*try {
Thread.currentThread().sleep(200);
} catch (InterruptedException ex) {
}*/
}
} finally {
outStream.close();
}
}
};
final Thread downloader = new Thread(new Runnable() {
public void run() {
boolean success = false;
try {
ZLNetworkManager.Instance().perform(request);
success = true;
} catch (ZLNetworkException e) {
// TODO: show error message to User
file.delete();
} finally {
downloadFinishHandler.sendEmptyMessage(success ? 1 : 0);
}
}
});
downloader.setPriority(Thread.MIN_PRIORITY);
downloader.start();
}
}