/** * Copyright (c) 2012 Todoroo Inc * * See the file "LICENSE" for the full license governing this code. */ package com.todoroo.astrid.sync; import java.util.concurrent.atomic.AtomicBoolean; import android.app.AlarmManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.util.Log; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.andlib.utility.Preferences; /** * Performs synchronization service logic in background service to avoid * ANR (application not responding) messages. * <p> * Starting this service * schedules a repeating alarm which handles * synchronization with your serv * * @author Tim Su * */ abstract public class SyncBackgroundService extends Service { /** Minimum time before an auto-sync */ private static final long AUTO_SYNC_MIN_OFFSET = 5*60*1000L; @Autowired private ExceptionService exceptionService; // --- abstract methods abstract protected SyncProvider<?> getSyncProvider(); abstract protected SyncProviderUtilities getSyncUtilities(); // --- implementation public SyncBackgroundService() { DependencyInjectionService.getInstance().inject(this); } private final AtomicBoolean started = new AtomicBoolean(false); /** Receive the alarm - start the synchronize service! */ @Override public void onStart(Intent intent, int startId) { try { if(intent != null && !started.getAndSet(true)) { startSynchronization(this); } } catch (Exception e) { exceptionService.reportError(getSyncUtilities().getIdentifier() + "-bg-sync", e); //$NON-NLS-1$ } } /** Start the actual synchronization */ private void startSynchronization(Context context) { if(context == null || context.getResources() == null) return; ContextManager.setContext(context); if(!getSyncUtilities().isLoggedIn()) return; getSyncProvider().synchronize(context); } @Override public IBinder onBind(Intent intent) { return null; } public synchronized void stop() { started.set(false); stopSelf(); } // --- alarm management /** * Schedules repeating alarm for auto-synchronization */ public void scheduleService() { int syncFrequencySeconds = 0; try { syncFrequencySeconds = Preferences.getIntegerFromString( getSyncUtilities().getSyncIntervalKey(), -1); } catch(ClassCastException e) { Preferences.setStringFromInteger(getSyncUtilities().getSyncIntervalKey(), 0); } Context context = ContextManager.getContext(); if(syncFrequencySeconds <= 0) { unscheduleService(context); return; } // figure out synchronization frequency long interval = 1000L * syncFrequencySeconds; long offset = computeNextSyncOffset(interval); // give a little padding offset = Math.max(offset, AUTO_SYNC_MIN_OFFSET); AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); PendingIntent pendingIntent = PendingIntent.getService(context, getSyncUtilities().getSyncIntervalKey(), createAlarmIntent(context), PendingIntent.FLAG_UPDATE_CURRENT); Log.i("Astrid", "Autosync set for " + offset / 1000 //$NON-NLS-1$ //$NON-NLS-2$ + " seconds repeating every " + syncFrequencySeconds); //$NON-NLS-1$ // cancel all existing am.cancel(pendingIntent); // schedule new am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + offset, interval, pendingIntent); } /** * Removes repeating alarm for auto-synchronization */ private void unscheduleService(Context context) { AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); PendingIntent pendingIntent = PendingIntent.getService(context, getSyncUtilities().getSyncIntervalKey(), createAlarmIntent(context), PendingIntent.FLAG_UPDATE_CURRENT); am.cancel(pendingIntent); } /** Create the alarm intent */ private Intent createAlarmIntent(Context context) { Intent intent = new Intent(context, getClass()); return intent; } // --- utility methods private long computeNextSyncOffset(long interval) { // figure out last synchronize time long lastSyncDate = getSyncUtilities().getLastSyncDate(); // if user never synchronized, give them a full offset period before bg sync if(lastSyncDate != 0) return Math.max(0, lastSyncDate + interval - DateUtilities.now()); else return interval; } }