Java tutorial
/* * Everychan Android (Meta Imageboard Client) * Copyright (C) 2014-2016 miku-nyan <https://github.com/miku-nyan> * * 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 3 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, see <http://www.gnu.org/licenses/>. */ package com.nttec.everychan.ui.posting; import com.nttec.everychan.R; import com.nttec.everychan.api.interfaces.CancellableTask; import com.nttec.everychan.api.interfaces.ProgressListener; import com.nttec.everychan.api.models.BoardModel; import com.nttec.everychan.api.models.SendPostModel; import com.nttec.everychan.api.models.UrlPageModel; import com.nttec.everychan.common.Async; import com.nttec.everychan.common.Logger; import com.nttec.everychan.common.MainApplication; import com.nttec.everychan.http.interactive.InteractiveException; import com.nttec.everychan.ui.MainActivity; import java.lang.ref.WeakReference; import android.annotation.SuppressLint; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.IBinder; import android.support.v4.app.NotificationCompat; /** * ? ? ( ?) * @author miku-nyan */ public class PostingService extends Service { private static final String TAG = "PostingService"; public static final String EXTRA_PAGE_HASH = "Hash"; public static final String EXTRA_BOARD_MODEL = "BoardModel"; public static final String EXTRA_SEND_POST_MODEL = "SendPostModel"; public static final String EXTRA_IS_POSTING_THREAD = "IsPostingThread"; public static final String EXTRA_RETURN_FROM_SERVICE = "ReturnFromService"; public static final String EXTRA_RETURN_REASON = "ReturnReason"; public static final String EXTRA_RETURN_REASON_ERROR = "ReturnReasonError"; public static final String EXTRA_RETURN_REASON_INTERACTIVE_EXCEPTION = "ReturnReasonInteractiveException"; public static final String EXTRA_TARGET_URL = "TargetUrl"; public static final String EXTRA_BROADCAST_PROGRESS_STATUS = "BroadcastProgress"; public static final String BROADCAST_ACTION_PROGRESS = "com.nttec.everychan.BROADCAST_ACTION_POSTING_PROGRESS"; public static final String BROADCAST_ACTION_STATUS = "com.nttec.everychan.BROADCAST_ACTION_POSTING_STATUS"; public static final int BROADCAST_STATUS_SUCCESS = 201; public static final int BROADCAST_STATUS_ERROR = 202; public static final int REASON_INTERACTIVE_EXCEPTION = 1; public static final int REASON_ERROR = 2; public static final int POSTING_NOTIFICATION_ID = 10; private static volatile boolean nowPosting = false; private PostingServiceBinder binder; private NotificationManager notificationManager; private PostingTask currentTask; public static boolean isNowPosting() { return nowPosting; } @Override public void onCreate() { super.onCreate(); binder = new PostingServiceBinder(this); notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); Logger.d(TAG, "created posting service"); } @Override public void onDestroy() { super.onDestroy(); Logger.d(TAG, "destroyed posting service"); } @Override public IBinder onBind(Intent intent) { return binder; } @Override @SuppressLint("InlinedApi") public int onStartCommand(Intent intent, int flags, int startId) { onStart(intent, startId); return Service.START_REDELIVER_INTENT; } @Override public void onStart(Intent intent, int startId) { if (intent == null) { nowPosting = false; stopSelf(startId); return; } currentTask = new PostingTask(startId, intent.getStringExtra(EXTRA_PAGE_HASH), (SendPostModel) intent.getSerializableExtra(EXTRA_SEND_POST_MODEL), (BoardModel) intent.getSerializableExtra(EXTRA_BOARD_MODEL)); Async.runAsync(currentTask); } public class PostingTask extends CancellableTask.BaseCancellableTask implements Runnable { private final int startId; private final String hash; private final SendPostModel sendPostModel; private final BoardModel boardModel; private long maxProgressValue = 100; private int curProgress = -1; public PostingTask(int startId, String hash, SendPostModel sendPostModel, BoardModel boardModel) { this.startId = startId; this.hash = hash; this.sendPostModel = sendPostModel; this.boardModel = boardModel; } public int getCurrentProgress() { return curProgress; } @Override public void run() { if (sendPostModel == null) { Logger.e(TAG, "sendPostModel == null"); return; } Logger.d(TAG, "start; nowPosting = true"); nowPosting = true; Intent intentToProgressDialog = new Intent(PostingService.this, PostingProgressActivity.class); intentToProgressDialog.putExtra(EXTRA_IS_POSTING_THREAD, sendPostModel.threadNumber == null); PendingIntent pIntentToProgressDialog = PendingIntent.getActivity(PostingService.this, 0, intentToProgressDialog, PendingIntent.FLAG_CANCEL_CURRENT); final String notifTitle = sendPostModel.threadNumber == null ? getString(R.string.posting_thread) : getString(R.string.posting_post); String notifText = sendPostModel.threadNumber == null ? getString(R.string.posting_thread_format, sendPostModel.chanName, sendPostModel.boardName) : getString(R.string.posting_post_format, sendPostModel.chanName, sendPostModel.boardName, sendPostModel.threadNumber); final NotificationCompat.Builder progressNotifBuilder = new NotificationCompat.Builder( PostingService.this).setSmallIcon(android.R.drawable.stat_sys_upload).setTicker(notifTitle) .setContentTitle(notifTitle).setContentText(notifText) .setContentIntent(pIntentToProgressDialog).setOngoing(true) .setCategory(NotificationCompat.CATEGORY_PROGRESS).setProgress(100, 0, true); notificationManager.notify(POSTING_NOTIFICATION_ID, progressNotifBuilder.build()); boolean success = false; String targetUrl = null; try { targetUrl = MainApplication.getInstance().getChanModule(sendPostModel.chanName) .sendPost(sendPostModel, new ProgressListener() { @Override public void setProgress(long value) { int newProgress = (int) (100 * (double) value / maxProgressValue); if (newProgress == curProgress) return; curProgress = newProgress; progressNotifBuilder.setProgress(100, newProgress, false); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { progressNotifBuilder.setContentTitle("(" + newProgress + "%) " + notifTitle); } notificationManager.notify(POSTING_NOTIFICATION_ID, progressNotifBuilder.build()); Intent broadcastIntent = new Intent(BROADCAST_ACTION_PROGRESS); broadcastIntent.putExtra(EXTRA_BROADCAST_PROGRESS_STATUS, newProgress); sendBroadcast(broadcastIntent); } @Override public void setMaxValue(long value) { if (value > 0) maxProgressValue = value; } @Override public void setIndeterminate() { if (curProgress == -1) return; progressNotifBuilder.setProgress(100, 0, true); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { progressNotifBuilder.setContentTitle(notifTitle); } notificationManager.notify(POSTING_NOTIFICATION_ID, progressNotifBuilder.build()); curProgress = -1; Intent broadcastIntent = new Intent(BROADCAST_ACTION_PROGRESS); broadcastIntent.putExtra(EXTRA_BROADCAST_PROGRESS_STATUS, curProgress); sendBroadcast(broadcastIntent); } }, this); success = true; } catch (Exception e) { Logger.e(TAG, "exception while posting", e); if (!isCancelled()) { Intent broadcastIntent = new Intent(BROADCAST_ACTION_STATUS); broadcastIntent.putExtra(EXTRA_BROADCAST_PROGRESS_STATUS, BROADCAST_STATUS_ERROR); Intent intentToPostingForm = new Intent(PostingService.this, PostFormActivity.class); intentToPostingForm.putExtra(EXTRA_PAGE_HASH, hash); intentToPostingForm.putExtra(EXTRA_SEND_POST_MODEL, sendPostModel); intentToPostingForm.putExtra(EXTRA_BOARD_MODEL, boardModel); intentToPostingForm.putExtra(EXTRA_RETURN_FROM_SERVICE, true); broadcastIntent.putExtra(EXTRA_PAGE_HASH, hash); broadcastIntent.putExtra(EXTRA_SEND_POST_MODEL, sendPostModel); broadcastIntent.putExtra(EXTRA_BOARD_MODEL, boardModel); String errorMessage = null; if (e instanceof InteractiveException) { errorMessage = getString(R.string.posting_error_interactive_format, ((InteractiveException) e).getServiceName()); InteractiveException cfException = (InteractiveException) e; intentToPostingForm.putExtra(EXTRA_RETURN_REASON, REASON_INTERACTIVE_EXCEPTION); intentToPostingForm.putExtra(EXTRA_RETURN_REASON_INTERACTIVE_EXCEPTION, cfException); broadcastIntent.putExtra(EXTRA_RETURN_REASON, REASON_INTERACTIVE_EXCEPTION); broadcastIntent.putExtra(EXTRA_RETURN_REASON_INTERACTIVE_EXCEPTION, cfException); } else { errorMessage = e.getMessage() == null ? getString(R.string.posting_error_default) : e.getMessage(); intentToPostingForm.putExtra(EXTRA_RETURN_REASON, REASON_ERROR); intentToPostingForm.putExtra(EXTRA_RETURN_REASON_ERROR, errorMessage); broadcastIntent.putExtra(EXTRA_RETURN_REASON, REASON_ERROR); broadcastIntent.putExtra(EXTRA_RETURN_REASON_ERROR, errorMessage); } PendingIntent pIntentToPostingForm = PendingIntent.getActivity(PostingService.this, 0, intentToPostingForm, PendingIntent.FLAG_CANCEL_CURRENT); NotificationCompat.Builder errorNotifBuilder = new NotificationCompat.Builder( PostingService.this) .setSmallIcon(android.R.drawable.stat_notify_error) .setTicker(e instanceof InteractiveException ? errorMessage : getString(R.string.posting_error)) .setContentTitle(getString(R.string.posting_error)).setContentText(errorMessage) .setContentIntent(pIntentToPostingForm).setOngoing(false).setAutoCancel(true) .setCategory(NotificationCompat.CATEGORY_ERROR); notificationManager.notify(POSTING_NOTIFICATION_ID, errorNotifBuilder.build()); sendBroadcast(broadcastIntent); } } if (success && !isCancelled()) { MainApplication.getInstance().draftsCache.remove(hash); Intent intentSuccess = new Intent(PostingService.this, MainActivity.class); if (targetUrl == null) { UrlPageModel model = new UrlPageModel(); model.chanName = sendPostModel.chanName; model.boardName = sendPostModel.boardName; if (sendPostModel.threadNumber != null) { model.type = UrlPageModel.TYPE_THREADPAGE; model.threadNumber = sendPostModel.threadNumber; } else { model.type = UrlPageModel.TYPE_BOARDPAGE; model.boardPage = boardModel.firstPage; } targetUrl = MainApplication.getInstance().getChanModule(sendPostModel.chanName).buildUrl(model); } if (MainApplication.getInstance().settings.subscribeOwnPosts()) { try { UrlPageModel pageModel = null; try { pageModel = MainApplication.getInstance().getChanModule(sendPostModel.chanName) .parseUrl(targetUrl); } catch (Exception e) { Logger.e(TAG, e); } if (sendPostModel.threadNumber == null) { if (pageModel != null && pageModel.type == UrlPageModel.TYPE_THREADPAGE) { String postNumber = pageModel.postNumber != null ? pageModel.postNumber : pageModel.threadNumber; MainApplication.getInstance().subscriptions.addSubscription(pageModel.chanName, pageModel.boardName, pageModel.threadNumber, postNumber); } } else { if (pageModel != null && pageModel.type == UrlPageModel.TYPE_THREADPAGE && pageModel.postNumber != null) { MainApplication.getInstance().subscriptions.addSubscription(pageModel.chanName, pageModel.boardName, pageModel.threadNumber, pageModel.postNumber); } else { MainApplication.getInstance().subscriptions.detectOwnPost(sendPostModel.chanName, sendPostModel.boardName, sendPostModel.threadNumber, sendPostModel.comment); } } } catch (Exception e) { Logger.e(TAG, e); } } intentSuccess.setData(Uri.parse(targetUrl)); PendingIntent pIntentSuccess = PendingIntent.getActivity(PostingService.this, 0, intentSuccess, PendingIntent.FLAG_CANCEL_CURRENT); NotificationCompat.Builder successNotifBuilder = new NotificationCompat.Builder(PostingService.this) .setSmallIcon(android.R.drawable.stat_sys_upload_done) .setTicker(getString(R.string.posting_success)) .setContentTitle(getString(R.string.posting_success)) .setContentText( getString(sendPostModel.threadNumber == null ? R.string.posting_success_thread : R.string.posting_success_post)) .setContentIntent(pIntentSuccess).setOngoing(false).setAutoCancel(true); notificationManager.notify(POSTING_NOTIFICATION_ID, successNotifBuilder.build()); Intent broadcastIntent = new Intent(BROADCAST_ACTION_STATUS); broadcastIntent.putExtra(EXTRA_BROADCAST_PROGRESS_STATUS, BROADCAST_STATUS_SUCCESS); broadcastIntent.putExtra(EXTRA_TARGET_URL, targetUrl); sendBroadcast(broadcastIntent); } else if (isCancelled()) { notificationManager.notify(POSTING_NOTIFICATION_ID, new NotificationCompat.Builder(PostingService.this) .setSmallIcon(android.R.drawable.ic_delete) .setTicker(getString(R.string.posting_cancelled)) .setContentTitle(getString(R.string.posting_cancelled)) .setContentText(getString(R.string.posting_cancelled)) .setContentIntent( PendingIntent.getActivity(PostingService.this, 0, new Intent(), 0)) .build()); notificationManager.cancel(POSTING_NOTIFICATION_ID); } Logger.d(TAG, "stop; nowPosting = false"); nowPosting = false; stopSelf(startId); } } public static class PostingServiceBinder extends Binder { private final WeakReference<PostingService> service; private PostingServiceBinder(PostingService service) { this.service = new WeakReference<>(service); } public void cancel() { PostingService service = this.service.get(); if (service == null) return; if (service.currentTask != null) service.currentTask.cancel(); } public int getCurrentProgress() { PostingService service = this.service.get(); if (service == null) return -1; if (service.currentTask == null) return -1; return service.currentTask.getCurrentProgress(); } } }