/*
* Copyright 2012 The Stanford MobiSocial Laboratory
*
* 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 mobisocial.musubi.service;
import mobisocial.musubi.App;
import mobisocial.musubi.R;
import mobisocial.musubi.model.MPendingUpload;
import mobisocial.musubi.model.helpers.ObjectManager;
import mobisocial.musubi.model.helpers.PendingUploadManager;
import mobisocial.socialkit.musubi.DbObj;
import mobisocial.socialkit.musubi.Musubi;
import org.json.JSONObject;
import org.mobisocial.corral.CorralDownloadClient;
import org.mobisocial.corral.CorralHelper;
import org.mobisocial.corral.CorralHelper.UploadProgressCallback;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
/**
* Monitors the pending_uploads table for files that should be sent
*/
public class CorralUploadProcessor extends ContentObserver {
private static final String TAG = "UploadProcessor";
final int NOTIFICATION_ID = 1024;
final Context mContext;
final NotificationManager mNotificationManager;
final HandlerThread mThread;
final SQLiteOpenHelper mDb;
final Musubi mMusubi;
final ObjectManager mObjectManager;
public static CorralUploadProcessor newInstance(Context context, SQLiteOpenHelper db) {
HandlerThread thread = new HandlerThread("CorralUploadThread");
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
return new CorralUploadProcessor(context, db, thread);
}
private CorralUploadProcessor(Context context, SQLiteOpenHelper db, HandlerThread thread) {
super(new Handler(thread.getLooper()));
mThread = thread;
mContext = context;
mNotificationManager = (NotificationManager)context.getSystemService(
Context.NOTIFICATION_SERVICE);
mDb = db;
mMusubi = App.getMusubi(mContext);
mObjectManager = new ObjectManager(mDb);
}
@Override
public void onChange(boolean selfChange) {
long[] ids = new PendingUploadManager(mDb).getPendingUploadObjects();
for (final long objId : ids) {
DbObj obj = mMusubi.objForId(objId);
if (obj == null) {
Log.w(TAG, "object " + objId + " does not exist");
deletePendingUploadByObjectId(objId);
continue;
}
final String universalHash = obj.getUniversalHashString();
JSONObject json = obj.getJson();
if (json == null || !json.has(CorralDownloadClient.OBJ_LOCAL_URI)
|| !json.has(CorralDownloadClient.OBJ_MIME_TYPE)) {
Log.w(TAG, "not enough info for upload of " + objId);
deletePendingUploadByObjectId(objId);
continue;
}
if (!json.has(CorralDownloadClient.OBJ_PRESHARED_KEY)) {
Log.w(TAG, "no shared secret for encryption");
deletePendingUploadByObjectId(objId);
continue;
}
String ticker = "Upload in progress...";
long time = System.currentTimeMillis();
final Notification notification = new Notification(R.drawable.icon, ticker, time);
notification.setLatestEventInfo(mContext, "Upload in progress", "Preparing Upload...", null);
Intent musubi = new Intent(Intent.ACTION_MAIN);
musubi.addCategory(Intent.CATEGORY_LAUNCHER);
musubi.setPackage(mContext.getPackageName());
final PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, musubi, 0);
notification.contentIntent = contentIntent;
notification.flags = Notification.FLAG_ONGOING_EVENT;
mNotificationManager.notify(NOTIFICATION_ID, notification);
boolean uploadSuccessful;
Log.d(TAG, "starting upload...");
UploadProgressCallback callback = new UploadProgressCallback() {
@Override
public void onProgress(UploadState state, int progress) {
String message = "Please wait.";
switch (state) {
case TRANSFER_IN_PROGRESS:
message = "Uploading file (" + progress + "%)";
break;
case FINISHING_UP:
message = "Finishing up...";
}
notification.setLatestEventInfo(mContext, "Upload in progress",
message, null);
notification.contentIntent = contentIntent;
notification.flags = Notification.FLAG_ONGOING_EVENT;
mNotificationManager.notify(NOTIFICATION_ID, notification);
}
@Override
public boolean isCancelled() {
DbObj obj = mMusubi.objForId(objId);
return (obj == null || !universalHash.equals(obj.getUniversalHashString()));
}
};
try {
uploadSuccessful = CorralHelper.uploadContent(mContext, obj, callback);
} catch (Throwable t) {
// TODO: there is a potential NPE in the upload mechanism.
// TODO: differentiate local errors and network errors for retries.
Log.e(TAG, "exception during upload", t);
deletePendingUploadByObjectId(objId);
uploadSuccessful = false;
}
Log.d(TAG, "upload complete: " + uploadSuccessful);
if (uploadSuccessful) {
mNotificationManager.cancel(NOTIFICATION_ID);
deletePendingUploadByObjectId(objId);
mContext.getContentResolver().notifyChange(MusubiService.PREPARED_ENCODED, this);
} else if (callback.isCancelled()) {
mNotificationManager.cancel(NOTIFICATION_ID);
} else {
notification.setLatestEventInfo(mContext, "Upload failed.", "There was a problem uploading your file.", contentIntent);
notification.flags = 0;
mNotificationManager.notify(NOTIFICATION_ID, notification);
}
}
}
void deletePendingUploadByObjectId(long objId) {
String table = MPendingUpload.TABLE;
String whereClause = MPendingUpload.COL_OBJECT_ID + "=" + objId;
String[] whereArgs = null;
mDb.getWritableDatabase().delete(table, whereClause, whereArgs);
}
}