package com.kenny.openimgur.services; import android.app.IntentService; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.PowerManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.text.TextUtils; import com.kenny.openimgur.R; import com.kenny.openimgur.api.ApiClient; import com.kenny.openimgur.api.responses.BasicObjectResponse; import com.kenny.openimgur.api.responses.BasicResponse; import com.kenny.openimgur.api.responses.PhotoResponse; import com.kenny.openimgur.classes.ImgurAlbum; import com.kenny.openimgur.classes.ImgurBaseObject; import com.kenny.openimgur.classes.ImgurPhoto; import com.kenny.openimgur.classes.ImgurTopic; import com.kenny.openimgur.classes.Upload; import com.kenny.openimgur.ui.BaseNotification; import com.kenny.openimgur.util.FileUtil; import com.kenny.openimgur.util.LogUtil; import com.kenny.openimgur.util.NetworkUtils; import com.kenny.openimgur.util.RequestCodes; import com.kenny.openimgur.util.SqlHelper; import java.io.File; import java.util.ArrayList; import java.util.List; import okhttp3.MediaType; import okhttp3.RequestBody; import retrofit2.Response; /** * Created by kcampagna on 6/30/15. */ public class UploadService extends IntentService { private static final String TAG = UploadService.class.getSimpleName(); private static final String KEY_UPLOADS = TAG + ".uploads"; private static final String KEY_TITLE = TAG + ".title"; private static final String KEY_TOPIC = TAG + ".topic"; private static final String KEY_DESC = TAG + ".desc"; private static final String KEY_SUBMIT_TO_GALLERY = TAG + ".submit.to.gallery"; // No Topic private static final int FALLBACK_TOPIC = 29; private UploadNotification mNotification; /** * Creates service for uploading photos. * * @param context App context * @param uploads Photos being uploaded * @param submitToGallery If the upload is being submitted to the Imgur Gallery * @param title The title for the Gallery * @param desc The description for the upload * @param topic The Topic for the Gallery * @return */ public static Intent createIntent(Context context, @NonNull ArrayList<Upload> uploads, boolean submitToGallery, @Nullable String title, @Nullable String desc, @Nullable ImgurTopic topic) { Intent intent = new Intent(context, UploadService.class) .putExtra(KEY_UPLOADS, uploads) .putExtra(KEY_SUBMIT_TO_GALLERY, submitToGallery); if (!TextUtils.isEmpty(title)) intent.putExtra(KEY_TITLE, title); if (!TextUtils.isEmpty(desc)) intent.putExtra(KEY_DESC, desc); if (topic != null) intent.putExtra(KEY_TOPIC, topic); return intent; } public UploadService() { super(TAG); } @Override protected void onHandleIntent(Intent intent) { if (intent == null || !intent.hasExtra(KEY_UPLOADS)) { LogUtil.w(TAG, "Did not receive any arguments"); stopSelf(); return; } // Get a wake lock so any long uploads will not be interrupted PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); wakeLock.acquire(); try { mNotification = new UploadNotification(getApplicationContext()); ImgurTopic topic = intent.getParcelableExtra(KEY_TOPIC); String title = intent.getStringExtra(KEY_TITLE); String desc = intent.getStringExtra(KEY_DESC); ArrayList<Upload> uploads = intent.getParcelableArrayListExtra(KEY_UPLOADS); boolean submitToGallery = intent.getBooleanExtra(KEY_SUBMIT_TO_GALLERY, false); int totalUploads = uploads.size(); LogUtil.v(TAG, "Starting upload of " + totalUploads + " images"); List<ImgurPhoto> uploadedPhotos = new ArrayList<>(totalUploads); SqlHelper sql = SqlHelper.getInstance(getApplicationContext()); for (int i = 0; i < totalUploads; i++) { Upload u = uploads.get(i); mNotification.onPhotoUploading(totalUploads, i + 1); Response<PhotoResponse> response = null; try { LogUtil.v(TAG, "Uploading photo " + (i + 1) + " of " + totalUploads); if (u.isLink()) { response = ApiClient.getService().uploadLink(u.getLocation(), u.getTitle(), u.getDescription(), "URL").execute(); } else { File file = new File(u.getLocation()); if (FileUtil.isFileValid(file)) { String type; if (file.getAbsolutePath().endsWith("png")) { type = "image/png"; } else if (file.getAbsolutePath().endsWith("gif")) { type = "image/gif"; } else { type = "image/jpeg"; } RequestBody uploadFile = RequestBody.create(MediaType.parse(type), file); RequestBody uploadTitle = null; RequestBody uploadDesc = null; RequestBody uploadType = RequestBody.create(MediaType.parse("text/plain"), "file"); if (!TextUtils.isEmpty(u.getTitle())) { uploadTitle = RequestBody.create(MediaType.parse("text/plain"), u.getTitle()); } if (!TextUtils.isEmpty(u.getDescription())) { uploadDesc = RequestBody.create(MediaType.parse("text/plain"), u.getDescription()); } response = ApiClient.getService().uploadPhoto(uploadFile, uploadTitle, uploadDesc, uploadType).execute(); } else { LogUtil.w(TAG, "Unable to find file at location " + u.getLocation()); } } } catch (Exception ex) { LogUtil.e(TAG, "Error uploading image", ex); response = null; } if (response != null && response.body() != null && response.body().data != null) { sql.insertUploadedPhoto(response.body().data); uploadedPhotos.add(response.body().data); } } if (uploads.size() == uploadedPhotos.size()) { // All photos uploaded correctly LogUtil.v(TAG, "All photos successfully uploaded, number of photos uploaded " + uploadedPhotos.size()); onPhotosUploaded(uploadedPhotos, submitToGallery, title, desc, topic); } else if (uploadedPhotos.size() > 1) { // Some of the photos did not upload correctly LogUtil.w(TAG, uploadedPhotos.size() + " of " + uploads.size() + " photos were uploaded successfully"); onPartialPhotoUpload(uploadedPhotos, uploads.size(), title, desc); } else { // No photos were uploaded, double you tee eff mate LogUtil.w(TAG, "No photos were uploaded"); mNotification.onPhotoUploadFailed(); } } finally { NetworkUtils.releaseWakeLock(wakeLock); } } /** * Called when not every photo was uploaded successfully. This <b><i>will not</i></b> upload to the gallery if requested to * * @param uploadedPhotos The photos that were uploaded successfully * @param total The total number of photos that should have been uploaded * @param title The optional title of the upload * @param desc The optional title of the upload */ private void onPartialPhotoUpload(List<ImgurPhoto> uploadedPhotos, int total, @Nullable String title, @Nullable String desc) { if (uploadedPhotos.size() == 1) { mNotification.onPartialPhotoUpload(uploadedPhotos.get(0), total); } else { createAlbum(uploadedPhotos, false, title, desc, null); } } /** * Called when all photos have successfully been uploaded. Will attempt to create an album and/or upload to the gallery if desired * * @param uploadedPhotos The photos that were uploaded * @param submitToGallery If submitting to the gallery * @param title The optional title of the upload * @param desc The optional title of the upload * @param topic The optional topic when submitting to the gallery */ private void onPhotosUploaded(List<ImgurPhoto> uploadedPhotos, boolean submitToGallery, @Nullable String title, @Nullable String desc, @Nullable ImgurTopic topic) { if (uploadedPhotos.size() == 1) { ImgurPhoto photo = uploadedPhotos.get(0); if (!submitToGallery) { LogUtil.v(TAG, "Image uploaded successfully, not submitting to gallery"); mNotification.onSuccessfulUpload(photo); } else { // Upload to gallery LogUtil.v(TAG, "Uploading image to gallery with title " + title); submitToGallery(title, topic != null ? topic.getId() : FALLBACK_TOPIC, photo); } } else { LogUtil.v(TAG, "Creating album"); createAlbum(uploadedPhotos, submitToGallery, title, desc, topic); } } /** * Creates the album from the uploaded photos * * @param uploadedPhotos The photos that were uploaded * @param submitToGallery If submitting to the gallery * @param title The optional title of the upload * @param desc The optional title of the upload * @param topic The optional topic when submitting to the gallery * @return */ private void createAlbum(@NonNull List<ImgurPhoto> uploadedPhotos, boolean submitToGallery, @Nullable String title, @Nullable String desc, @Nullable ImgurTopic topic) { try { String coverId = uploadedPhotos.get(0).getId(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < uploadedPhotos.size(); i++) { sb.append(uploadedPhotos.get(i).getId()); if (i != uploadedPhotos.size() - 1) sb.append(","); } Response<BasicObjectResponse> response = ApiClient.getService().createAlbum(sb.toString(), coverId, title, desc).execute(); if (response != null && response.body() != null && response.body().data != null) { ImgurBaseObject object = response.body().data; // The response only contains the id and the delete hash, we need to construct the object from them String link = ApiClient.IMGUR_URL + "a/" + object.getId(); ImgurAlbum album = new ImgurAlbum(object.getId(), title, link, object.getDeleteHash()); album.setCoverId(coverId); SqlHelper.getInstance(getApplicationContext()).insertUploadedAlbum(album); if (!submitToGallery) { LogUtil.v(TAG, "Album creation successful"); mNotification.onSuccessfulUpload(album); } else { LogUtil.v(TAG, "Submitting album to gallery with title " + title); submitToGallery(title, topic != null ? topic.getId() : FALLBACK_TOPIC, album); } } else { LogUtil.w(TAG, "Response did not receive an object"); mNotification.onAlbumCreationFailed(); } } catch (Exception ex) { LogUtil.e(TAG, "Error while creating album", ex); mNotification.onAlbumCreationFailed(); } } /** * Submits the photo/album to the Imgur Gallery * * @param title The title for the gallery * @param topicId The topic id for the gallery * @param upload The item being submitted to the gallery */ private void submitToGallery(@NonNull String title, int topicId, ImgurBaseObject upload) { mNotification.onSubmitToGallery(); try { Response<BasicResponse> response = ApiClient.getService().submitToGallery(upload.getId(), title, topicId, "1").execute(); if (response != null && response.body() != null && response.body().data) { mNotification.onSuccessfulUpload(upload); } else { mNotification.onGallerySubmitFailed(upload); } } catch (Exception ex) { LogUtil.e(TAG, "Error while submitting to gallery", ex); mNotification.onGallerySubmitFailed(upload); } } @Override public void onDestroy() { LogUtil.v(TAG, "onDestroy"); mNotification = null; super.onDestroy(); } private static class UploadNotification extends BaseNotification { private int notificationId; public UploadNotification(Context context) { super(context); notificationId = (int) System.currentTimeMillis(); builder.setContentTitle(app.getString(R.string.upload_notif_starting)) .setContentText(app.getString(R.string.upload_notif_starting_content)); } /** * Called when a photo has begun to upload to the API * * @param totalUploads The total number of photos to upload * @param uploadNumber The current photo number that is being uploaded */ public void onPhotoUploading(int totalUploads, int uploadNumber) { String contentText = app.getResources().getQuantityString(R.plurals.upload_notif_photos, totalUploads, uploadNumber, totalUploads); builder.setContentTitle(app.getString(R.string.upload_notif_in_progress)) .setContentText(contentText); if (totalUploads > 1) { builder.setProgress(totalUploads, uploadNumber, false); } else { builder.setProgress(0, 0, true); } postNotification(); } /** * Called when everything has been successfully uploaded/created and the success notification should be displayed * * @param obj */ public void onSuccessfulUpload(ImgurBaseObject obj) { String url = obj.getLink(); Intent intent = NotificationReceiver.createCopyIntent(app, url, getNotificationId()); PendingIntent pIntent = PendingIntent.getBroadcast(app, RequestCodes.UPLOADS, intent, PendingIntent.FLAG_CANCEL_CURRENT); builder.setContentTitle(app.getString(R.string.upload_complete)) .setContentText(app.getString(R.string.upload_success, url)) .addAction(R.drawable.ic_action_copy_24dp, app.getString(R.string.copy_link), pIntent) .setProgress(0, 0, false) .setContentInfo(null); postNotification(); } /** * Called when submitting to the gallery has failed * * @param upload */ public void onGallerySubmitFailed(ImgurBaseObject upload) { String url = upload.getLink(); Intent intent = NotificationReceiver.createCopyIntent(app, url, getNotificationId()); PendingIntent pIntent = PendingIntent.getBroadcast(app, RequestCodes.UPLOADS, intent, PendingIntent.FLAG_CANCEL_CURRENT); builder.setContentTitle(app.getString(R.string.error)) .setContentText(app.getString(R.string.upload_gallery_failed)) .addAction(R.drawable.ic_action_copy_24dp, app.getString(R.string.copy_link), pIntent) .setProgress(0, 0, false) .setContentInfo(null) .setStyle(new NotificationCompat.BigTextStyle().bigText(app.getString(R.string.upload_gallery_failed_long))); postNotification(); } /** * Called when creating the album failed */ public void onAlbumCreationFailed() { builder.setContentTitle(app.getString(R.string.error)) .setContentText(app.getString(R.string.upload_album_failed)) .setProgress(0, 0, false) .setContentInfo(null) .setStyle(new NotificationCompat.BigTextStyle().bigText(app.getString(R.string.upload_album_failed_long))); postNotification(); } /** * Called when there is an error while uploading */ public void onPhotoUploadFailed() { builder.setContentTitle(app.getString(R.string.error)) .setContentText(app.getString(R.string.upload_notif_error)) .setProgress(0, 0, false) .setAutoCancel(true); postNotification(); } /** * Called when submitting to gallery */ public void onSubmitToGallery() { builder.setContentTitle(app.getString(R.string.upload_gallery_title)) .setContentText(app.getString(R.string.upload_gallery_message)) .setProgress(0, 0, true); postNotification(); } /** * Called when not every photo was uploaded successfully. * * @param photo The photo that was uploaded * @param total How many photos were supposed to be uploaded */ public void onPartialPhotoUpload(ImgurPhoto photo, int total) { // Show the notification immediately as an album will not be created for only 1 photo String msg = app.getString(R.string.upload_notif_error_upload_complete_long, 1, total); String url = photo.getLink(); Intent intent = NotificationReceiver.createCopyIntent(app, url, getNotificationId()); PendingIntent pIntent = PendingIntent.getBroadcast(app, RequestCodes.UPLOADS, intent, PendingIntent.FLAG_CANCEL_CURRENT); builder.setContentTitle(app.getString(R.string.error)) .setContentText(app.getString(R.string.upload_notif_error_upload_incomplete_short)) .setProgress(0, 0, false) .setAutoCancel(true) .addAction(R.drawable.ic_action_copy_24dp, app.getString(R.string.copy_link), pIntent) .setStyle(new NotificationCompat .BigTextStyle() .bigText(msg)); postNotification(); } @NonNull @Override protected String getTitle() { return ""; } @Override protected int getNotificationId() { return notificationId; } } }