Java tutorial
/* Copyright (c) 2009 Christoph Studer <chstuder@gmail.com> * Copyright (c) 2010 Jan Berkel <jan.berkel@gmail.com> * * 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.zegoggles.smssync.service; import android.content.Intent; import android.net.NetworkInfo; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.text.format.DateFormat; import android.util.Log; import com.firebase.jobdispatcher.Job; import com.firebase.jobdispatcher.JobTrigger; import com.fsck.k9.mail.MessagingException; import com.squareup.otto.Produce; import com.squareup.otto.Subscribe; import com.zegoggles.smssync.App; import com.zegoggles.smssync.R; import com.zegoggles.smssync.activity.MainActivity; import com.zegoggles.smssync.mail.BackupImapStore; import com.zegoggles.smssync.mail.DataType; import com.zegoggles.smssync.service.exception.BackupDisabledException; import com.zegoggles.smssync.service.exception.ConnectivityException; import com.zegoggles.smssync.service.exception.MissingPermissionException; import com.zegoggles.smssync.service.exception.NoConnectionException; import com.zegoggles.smssync.service.exception.RequiresLoginException; import com.zegoggles.smssync.service.exception.RequiresWifiException; import com.zegoggles.smssync.service.state.BackupState; import com.zegoggles.smssync.service.state.SmsSyncState; import java.util.Date; import java.util.EnumSet; import java.util.HashSet; import java.util.Set; import static android.R.drawable.stat_sys_warning; import static com.zegoggles.smssync.App.LOCAL_LOGV; import static com.zegoggles.smssync.App.TAG; import static com.zegoggles.smssync.activity.AppPermission.formatMissingPermissionDetails; import static com.zegoggles.smssync.service.BackupType.MANUAL; import static com.zegoggles.smssync.service.BackupType.REGULAR; import static com.zegoggles.smssync.service.BackupType.SKIP; import static com.zegoggles.smssync.service.state.SmsSyncState.ERROR; import static com.zegoggles.smssync.service.state.SmsSyncState.FINISHED_BACKUP; import static com.zegoggles.smssync.service.state.SmsSyncState.INITIAL; public class SmsBackupService extends ServiceBase { private static final int BACKUP_ID = 1; private static final int NOTIFICATION_ID_WARNING = 1; @Nullable private static SmsBackupService service; @NonNull private BackupState state = new BackupState(); @Override @NonNull public BackupState getState() { return state; } @Override public void onCreate() { super.onCreate(); if (LOCAL_LOGV) Log.v(TAG, "SmsBackupService#onCreate"); service = this; } @Override public void onDestroy() { super.onDestroy(); if (LOCAL_LOGV) Log.v(TAG, "SmsBackupService#onDestroy(state=" + getState() + ")"); service = null; } @Override protected void handleIntent(final Intent intent) { if (intent == null) return; // NB: should not happen with START_NOT_STICKY final BackupType backupType = BackupType.fromIntent(intent); if (LOCAL_LOGV) { Log.v(TAG, "handleIntent(" + intent + ", " + (intent.getExtras() == null ? "null" : intent.getExtras().keySet()) + ", " + intent.getAction() + ", type=" + backupType + ")"); } appLog(R.string.app_log_backup_requested, getString(backupType.resId)); // Only start a backup if there's no other operation going on at this time. if (!isWorking() && SmsRestoreService.isServiceIdle()) { backup(backupType); } else { appLog(R.string.app_log_skip_backup_already_running); } } private void backup(BackupType backupType) { getNotifier().cancel(NOTIFICATION_ID_WARNING); try { // set initial state state = new BackupState(INITIAL, 0, 0, backupType, null, null); EnumSet<DataType> enabledTypes = getEnabledBackupTypes(); checkPermissions(enabledTypes); if (backupType != SKIP) { checkCredentials(); if (getPreferences().isUseOldScheduler()) { legacyCheckConnectivity(); } } appLog(R.string.app_log_start_backup, backupType); getBackupTask().execute(getBackupConfig(backupType, enabledTypes, getBackupImapStore())); } catch (MessagingException e) { Log.w(TAG, e); moveToState(state.transition(ERROR, e)); } catch (ConnectivityException e) { moveToState(state.transition(ERROR, e)); } catch (RequiresLoginException e) { appLog(R.string.app_log_missing_credentials); moveToState(state.transition(ERROR, e)); } catch (BackupDisabledException e) { moveToState(state.transition(FINISHED_BACKUP, e)); } catch (MissingPermissionException e) { moveToState(state.transition(ERROR, e)); } } private void checkPermissions(EnumSet<DataType> enabledTypes) throws MissingPermissionException { Set<String> missing = new HashSet<String>(); for (DataType dataType : enabledTypes) { missing.addAll(dataType.checkPermissions(this)); } if (!missing.isEmpty()) { throw new MissingPermissionException(missing); } } private BackupConfig getBackupConfig(BackupType backupType, EnumSet<DataType> enabledTypes, BackupImapStore imapStore) { return new BackupConfig(imapStore, 0, getPreferences().getMaxItemsPerSync(), getPreferences().getBackupContactGroup(), backupType, enabledTypes, getPreferences().isAppLogDebug()); } private EnumSet<DataType> getEnabledBackupTypes() throws BackupDisabledException { EnumSet<DataType> dataTypes = getPreferences().getDataTypePreferences().enabled(); if (dataTypes.isEmpty()) { throw new BackupDisabledException(); } return dataTypes; } private void checkCredentials() throws RequiresLoginException { if (!getAuthPreferences().isLoginInformationSet()) { throw new RequiresLoginException(); } } private void legacyCheckConnectivity() throws ConnectivityException { NetworkInfo active = getConnectivityManager().getActiveNetworkInfo(); if (active == null || !active.isConnectedOrConnecting()) { throw new NoConnectionException(); } if (getPreferences().isWifiOnly() && isBackgroundTask() && !isConnectedViaWifi()) { throw new RequiresWifiException(); } } protected BackupTask getBackupTask() { return new BackupTask(this); } private void moveToState(BackupState state) { backupStateChanged(state); App.post(state); } @Override protected boolean isBackgroundTask() { return state.backupType.isBackground(); } @Produce public BackupState produceLastState() { return state; } @Subscribe public void backupStateChanged(BackupState state) { if (this.state == state) return; this.state = state; if (this.state.isInitialState()) return; if (state.isError()) { handleErrorState(state); } if (state.isRunning()) { if (state.backupType == MANUAL) { notifyAboutBackup(state); } } else { appLogDebug(state.toString()); appLog(state.isCanceled() ? R.string.app_log_backup_canceled : R.string.app_log_backup_finished); scheduleNextBackup(state); stopForeground(true); stopSelf(); } } private void handleErrorState(BackupState state) { if (state.isAuthException()) { appLog(R.string.app_log_backup_failed_authentication, state.getDetailedErrorMessage(getResources())); if (shouldNotifyUser(state)) { notifyUser(NOTIFICATION_ID_WARNING, notificationBuilder(stat_sys_warning, getString(R.string.notification_auth_failure), getString(getAuthPreferences().useXOAuth() ? R.string.status_auth_failure_details_xoauth : R.string.status_auth_failure_details_plain))); } } else if (state.isConnectivityError()) { appLog(R.string.app_log_backup_failed_connectivity, state.getDetailedErrorMessage(getResources())); } else if (state.isPermissionException()) { if (state.backupType != MANUAL) { Bundle extras = new Bundle(); extras.putStringArray(MainActivity.EXTRA_PERMISSIONS, state.getMissingPermissions()); notifyUser(NOTIFICATION_ID_WARNING, notificationBuilder(R.drawable.ic_notification, getString(R.string.notification_missing_permission), formatMissingPermissionDetails(getResources(), state.getMissingPermissions())) .setContentIntent(getPendingIntent(extras))); } } else { appLog(R.string.app_log_backup_failed_general_error, state.getDetailedErrorMessage(getResources())); if (shouldNotifyUser(state)) { notifyUser(NOTIFICATION_ID_WARNING, notificationBuilder(stat_sys_warning, getString(R.string.notification_general_error), state.getErrorMessage(getResources()))); } } } private boolean shouldNotifyUser(BackupState state) { return state.backupType == MANUAL || (getPreferences().isNotificationEnabled() && !state.isConnectivityError()); } private void notifyAboutBackup(BackupState state) { NotificationCompat.Builder builder = createNotification(R.string.status_backup); notification = builder.setContentTitle(getString(R.string.status_backup)) .setContentText(state.getNotificationLabel(getResources())).setContentIntent(getPendingIntent(null)) .build(); startForeground(BACKUP_ID, notification); } private void scheduleNextBackup(BackupState state) { if (state.backupType == REGULAR && getPreferences().isUseOldScheduler()) { final Job nextSync = getBackupJobs().scheduleRegular(); if (nextSync != null) { JobTrigger.ExecutionWindowTrigger trigger = (JobTrigger.ExecutionWindowTrigger) nextSync .getTrigger(); Date date = new Date(System.currentTimeMillis() + (trigger.getWindowStart() * 1000)); appLog(R.string.app_log_scheduled_next_sync, DateFormat.format("kk:mm", date)); } else { appLog(R.string.app_log_no_next_sync); } } // else job already persisted } void notifyUser(int notificationId, NotificationCompat.Builder builder) { getNotifier().notify(notificationId, builder.build()); } private NotificationCompat.Builder notificationBuilder(int icon, String title, String text) { return new NotificationCompat.Builder(this).setSmallIcon(icon).setWhen(System.currentTimeMillis()) .setOnlyAlertOnce(true).setAutoCancel(true).setContentText(text) .setTicker(getString(R.string.app_name)).setContentTitle(title) .setContentIntent(getPendingIntent(null)); } protected BackupJobs getBackupJobs() { return new BackupJobs(this); } public static boolean isServiceWorking() { return service != null && service.isWorking(); } public BackupState transition(SmsSyncState newState, Exception e) { return state.transition(newState, e); } }