/*
* Copyright (C) 2010 mAPPn.Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mappn.gfan.common.download;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;
import android.view.View;
import android.widget.RemoteViews;
import com.mappn.gfan.R;
import com.mappn.gfan.Session;
import com.mappn.gfan.common.util.Utils;
/**
* This class handles the updating of the Notification Manager for the
* cases where there is an ongoing download. Once the download is complete
* (be it successful or unsuccessful) it is no longer the responsibility
* of this component to show the download in the notification manager.
*
*/
class DownloadNotification {
Context mContext;
HashMap <String, NotificationItem> mNotifications;
NotificationManager mNotificationManager;
static final String LOGTAG = "DownloadNotification";
static final String WHERE_RUNNING =
"(" + DownloadManager.Impl.COLUMN_STATUS + " >= '100') AND (" +
DownloadManager.Impl.COLUMN_STATUS + " <= '199') AND (" +
DownloadManager.Impl.COLUMN_VISIBILITY + " IS NULL OR " +
DownloadManager.Impl.COLUMN_VISIBILITY + " == '" +
DownloadManager.Impl.VISIBILITY_VISIBLE + "' OR " +
DownloadManager.Impl.COLUMN_VISIBILITY +
" == '" + DownloadManager.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED + "')";
static final String WHERE_COMPLETED =
DownloadManager.Impl.COLUMN_STATUS + " >= '200' AND " +
DownloadManager.Impl.COLUMN_VISIBILITY +
" == '" + DownloadManager.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED + "'";
/**
* This inner class is used to collate downloads that are owned by
* the same application. This is so that only one notification line
* item is used for all downloads of a given application.
*
*/
static class NotificationItem {
// This first db _id for the download for the app
int mId;
// current downloaded bytes
long mCurrentBytes = 0;
// total size
long mTotalBytes = 0;
// the number of title
int mTitleCount = 0;
// App package name
String mPackageName;
// download titles.
String[] mTitles = new String[2];
String mPausedText = null;
/*
* Add a second download to this notification item.
*/
void addItem(String title, long currentBytes, long totalBytes) {
mCurrentBytes += currentBytes;
if (totalBytes <= 0 || mTotalBytes == -1) {
mTotalBytes = -1;
} else {
mTotalBytes += totalBytes;
}
if (mTitleCount < 2) {
mTitles[mTitleCount] = title;
}
mTitleCount++;
}
}
/**
* Constructor
* @param ctx The context to use to obtain access to the
* Notification Service
*/
DownloadNotification(Context ctx) {
mContext = ctx;
mNotifications = new HashMap<String, NotificationItem>();
mNotificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
}
/*
* Clear all notifications
*/
public void clearAllNotification() {
if(mNotificationManager != null) {
mNotificationManager.cancelAll();
}
}
/*
* Cancel notification use id
*/
public void cancelNotification(long id) {
if(mNotificationManager != null) {
mNotificationManager.cancel((int) id);
}
}
/*
* Update the notification ui.
*/
public void updateNotification(Collection<DownloadInfo> downloads) {
// Collate the notifications
mNotifications.clear();
for (DownloadInfo download : downloads) {
if (isActiveAndVisible(download)) {
// downloading items
updateActivieNotification(download);
} else if(isCompleteAndVisible(download)) {
// downloaded items
updateCompletedNotification(download);
} else if(isCompleteAndInstall(download)) {
// inatall OTA
startInstallOta(download);
} else {
// others
}
}
addActiviteNotifications();
}
private void startInstallOta(DownloadInfo download) {
try {
File root = new File(Environment.getExternalStorageDirectory(),
com.mappn.gfan.Constants.IMAGE_CACHE_DIR);
root.mkdirs();
File output = new File(root, "aMarket.apk");
Utils.copyFile(new FileInputStream(new File(download.mFileName)),
new FileOutputStream(output));
Utils.installApk(mContext, output);
Session.get(mContext).getDownloadManager().hideDownload(download.mId);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/*
* update the ongoing notification item
*/
private void updateActivieNotification(DownloadInfo download) {
String packageName = download.mPackage;
long max = download.mTotalBytes;
long progress = download.mCurrentBytes;
long id = download.mId;
String title = download.mTitle;
if (TextUtils.isEmpty(title)) {
Utils.D("don't get any title information");
title = mContext.getResources().getString(R.string.download_unknown_title);
}
NotificationItem item;
if (mNotifications.containsKey(packageName)) {
item = mNotifications.get(packageName);
item.addItem(title, progress, max);
Utils.D("just update the notification which already exist and title is " + title);
} else {
item = new NotificationItem();
item.mId = (int) id;
item.addItem(title, progress, max);
mNotifications.put(packageName, item);
Utils.D("just create one new notification and title is " + title);
}
// This item paused by user
if (download.mStatus == DownloadManager.Impl.STATUS_PAUSED_BY_APP
&& item.mPausedText == null) {
item.mPausedText = mContext.getResources().getString(
R.string.notification_paused_by_app);
}
}
/*
* Add notification items
*/
private void addActiviteNotifications() {
// Add the notifications
for (NotificationItem item : mNotifications.values()) {
// Build the notification object
Notification n = new Notification();
boolean hasPausedText = (item.mPausedText != null);
int iconResource = android.R.drawable.stat_sys_download;
if (hasPausedText) {
iconResource = android.R.drawable.stat_sys_warning;
}
n.icon = iconResource;
n.flags |= Notification.FLAG_ONGOING_EVENT;
// Build the RemoteView object
RemoteViews expandedView = new RemoteViews("com.mappn.gfan",
R.layout.status_bar_ongoing_event_progress_bar);
StringBuilder title = new StringBuilder(item.mTitles[0]);
if (item.mTitleCount > 1) {
title.append(mContext.getString(R.string.notification_filename_separator));
title.append(item.mTitles[1]);
n.number = item.mTitleCount;
if (item.mTitleCount > 2) {
title.append(mContext.getString(R.string.notification_filename_extras,
new Object[] { Integer.valueOf(item.mTitleCount - 2) }));
}
}
expandedView.setTextViewText(R.id.title, title);
if (hasPausedText) {
expandedView.setViewVisibility(R.id.progress_bar, View.GONE);
expandedView.setTextViewText(R.id.paused_text, item.mPausedText);
} else {
expandedView.setViewVisibility(R.id.paused_text, View.GONE);
expandedView.setProgressBar(R.id.progress_bar, (int) item.mTotalBytes,
(int) item.mCurrentBytes, item.mTotalBytes == -1);
}
expandedView.setTextViewText(R.id.progress_text,
getDownloadingText(item.mTotalBytes, item.mCurrentBytes));
expandedView.setImageViewResource(R.id.appIcon, iconResource);
n.contentView = expandedView;
Intent intent = new Intent(Constants.ACTION_LIST);
intent.setClassName("com.mappn.gfan", DownloadReceiver.class.getName());
intent.setData(ContentUris.withAppendedId(DownloadManager.Impl.CONTENT_URI, item.mId));
intent.putExtra("multiple", item.mTitleCount > 1);
n.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
mNotificationManager.notify((int) item.mId, n);
}
}
/*
* Update the completed notification item
*/
private void updateCompletedNotification(DownloadInfo download) {
// Add the notifications
Notification n = new Notification();
n.icon = android.R.drawable.stat_sys_download_done;
long id = download.mId;
String title = download.mTitle;
if (TextUtils.isEmpty(title)) {
title = mContext.getResources().getString(R.string.download_unknown_title);
}
Uri contentUri = ContentUris.withAppendedId(DownloadManager.Impl.CONTENT_URI, id);
String caption;
Intent intent = new Intent(Constants.ACTION_OPEN);
if (DownloadManager.Impl.isStatusError(download.mStatus)) {
// download have some troubles, when user click this notification direct goto the
// product's details page
caption = handleErrorMessage(download.mStatus);
intent.putExtra(DownloadManager.Impl.COLUMN_STATUS,
DownloadManager.Impl.STATUS_UNKNOWN_ERROR);
Session.get(mContext).getDownloadingList().remove(download.mPackageName);
Session.get(mContext).updateDownloading();
} else {
// download success
intent.putExtra(DownloadManager.Impl.COLUMN_STATUS,
DownloadManager.Impl.STATUS_SUCCESS);
caption = mContext.getResources().getString(R.string.notification_download_complete);
}
intent.setClassName("com.mappn.gfan", DownloadReceiver.class.getName());
intent.setData(contentUri);
n.when = download.mLastMod;
n.setLatestEventInfo(mContext, title, caption,
PendingIntent.getBroadcast(mContext, 0, intent, 0));
// make this item invisible after click event
intent = new Intent(Constants.ACTION_HIDE);
intent.setClassName("com.mappn.gfan", DownloadReceiver.class.getName());
intent.setData(contentUri);
n.deleteIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
// update the download status
mNotificationManager.notify((int) download.mId, n);
}
private String handleErrorMessage(int status) {
if (DownloadManager.Impl.STATUS_BAD_REQUEST == status) {
return mContext.getString(R.string.download_alert_url);
} else if (DownloadManager.Impl.STATUS_NOT_ACCEPTABLE == status) {
return mContext.getString(R.string.download_error_file_type);
} else if (DownloadManager.Impl.STATUS_LENGTH_REQUIRED == status
|| DownloadManager.Impl.STATUS_PRECONDITION_FAILED == status
|| DownloadManager.Impl.STATUS_UNKNOWN_ERROR == status) {
return mContext.getString(R.string.download_alert_service);
} else if (DownloadManager.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR == status
|| DownloadManager.Impl.STATUS_FILE_ERROR == status) {
return mContext.getString(R.string.download_alert_client);
} else if (DownloadManager.Impl.STATUS_CANCELED == status) {
return mContext.getString(R.string.download_alert_cancel);
} else if (DownloadManager.Impl.STATUS_UNHANDLED_REDIRECT == status
|| DownloadManager.Impl.STATUS_UNHANDLED_HTTP_CODE == status
|| DownloadManager.Impl.STATUS_HTTP_EXCEPTION == status
|| DownloadManager.Impl.STATUS_HTTP_DATA_ERROR == status
|| DownloadManager.Impl.STATUS_TOO_MANY_REDIRECTS == status) {
return mContext.getString(R.string.download_alert_network);
} else if (DownloadManager.Impl.STATUS_DEVICE_NOT_FOUND_ERROR == status) {
return mContext.getString(R.string.download_alert_no_sdcard);
} else if (DownloadManager.Impl.STATUS_INSUFFICIENT_SPACE_ERROR == status) {
return mContext.getString(R.string.download_alert_no_space);
} else {
return mContext.getString(R.string.download_error);
}
}
private boolean isActiveAndVisible(DownloadInfo download) {
return 100 <= download.mStatus && download.mStatus < 200
&& download.mVisibility != DownloadManager.Impl.VISIBILITY_HIDDEN;
}
private boolean isCompleteAndVisible(DownloadInfo download) {
return download.mStatus >= 200
&& download.mVisibility == DownloadManager.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED;
}
private boolean isCompleteAndInstall(DownloadInfo download) {
return download.mStatus == 200
&& download.mSource == Constants.DOWNLOAD_FROM_OTA
&& download.mVisibility == DownloadManager.Impl.VISIBILITY_VISIBLE
&& Constants.MIMETYPE_APK.equals(download.mMimeType);
}
/*
* Helper function to build the downloading text.
*/
private String getDownloadingText(long totalBytes, long currentBytes) {
if (totalBytes <= 0) {
return "";
}
long progress = currentBytes * 100 / totalBytes;
StringBuilder sb = new StringBuilder();
sb.append(progress);
sb.append('%');
return sb.toString();
}
}