/*
* Copyright (C) 2008 Josh Guilfoyle <jasta@devtcg.org>
*
* 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 2, 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.
*/
package org.devtcg.five.service;
import org.devtcg.five.Constants;
import org.devtcg.five.R;
import org.devtcg.five.activity.Settings;
import org.devtcg.five.provider.AbstractSyncAdapter;
import org.devtcg.five.provider.Five;
import org.devtcg.five.provider.FiveProvider;
import org.devtcg.five.provider.util.AcquireProvider;
import org.devtcg.five.provider.util.SourceItem;
import org.devtcg.five.provider.util.Sources;
import org.devtcg.five.util.Stopwatch;
import org.devtcg.util.CancelableThread;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.Process;
import android.os.SystemClock;
import android.util.Log;
public class MetaService extends Service
{
public static final String TAG = "MetaService";
private static final int NOTIF_SYNCING = 0;
static volatile boolean sSyncing;
SyncThread mSyncThread;
PowerManager.WakeLock mWakeLock;
NotificationManager mNM;
final Handler mHandler = new Handler();
public static void startSync(Context context)
{
context.startService(new Intent(Constants.ACTION_START_SYNC, null,
context, MetaService.class));
}
public static void stopSync(Context context)
{
context.startService(new Intent(Constants.ACTION_STOP_SYNC, null,
context, MetaService.class));
}
@Override
public void onCreate()
{
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mNM = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
}
@Override
public void onStart(Intent intent, int startId)
{
String action = intent.getAction();
if (Constants.ACTION_START_SYNC.equals(action))
{
if (sSyncing == true)
{
if (Constants.DEBUG)
Log.e(Constants.TAG, "ACTION_START_SYNC, but already syncing");
}
else
{
sSyncing = true;
mSyncThread = new SyncThread();
mSyncThread.start();
}
}
else if (Constants.ACTION_STOP_SYNC.equals(action))
{
if (sSyncing == false)
{
if (Constants.DEBUG)
Log.e(Constants.TAG, "ACTION_STOP_SYNC, but not syncing...");
stopSelf(startId);
}
else
{
sSyncing = false;
mSyncThread.requestCancelAndWait();
}
}
}
public static void scheduleAutoSync(Context context)
{
SharedPreferences prefs = context.getSharedPreferences("settings", Context.MODE_PRIVATE);
rescheduleAutoSync(context, prefs.getLong("autosync",
context.getResources().getInteger(R.integer.defaultAutoSyncInterval)));
}
public static void rescheduleAutoSync(Context context, long interval)
{
if (interval < 0)
throw new IllegalArgumentException("Interval must be non-negative.");
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
PendingIntent syncIntent = PendingIntent.getService(context, 0,
new Intent(Constants.ACTION_START_SYNC, null, context, MetaService.class), 0);
/* Cancel any previously scheduled auto-syncs. */
am.cancel(syncIntent);
/* Make sure that we actually have sources to sync. */
if (Sources.isEmpty(context))
{
if (Constants.DEBUG)
Log.i(Constants.TAG, "No auto-sync scheduling (no sources to sync)");
}
else
{
if (Constants.DEBUG)
Log.i(Constants.TAG, "Rescheduling with auto sync interval of " + interval + " ms");
am.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + interval, interval, syncIntent);
}
}
@Override
public IBinder onBind(Intent intent)
{
return null;
}
public static boolean isSyncing()
{
return sSyncing;
}
private class SyncThread extends CancelableThread
{
private static final String TAG = "SyncThread";
private volatile SyncContext mContext;
public SyncThread()
{
super("SyncThread");
}
public void run()
{
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mWakeLock.acquire();
try {
sendBeginSync();
showNotification();
SourceItem item = new SourceItem(Sources.getSources(MetaService.this));
try {
int count = item.getCount();
if (count == 0)
Log.w(TAG, "No sync sources.");
else
{
Log.i(TAG, "Starting sync with " + count + " sources");
while (hasCanceled() == false && item.moveToNext() == true)
{
long sourceId = item.getId();
sendBeginSource(sourceId);
SyncContext context = new SyncContext();
context.observer = new SourceSyncObserver(sourceId);
try {
mContext = context;
runSyncLoop(item);
if (context.hasSuccess())
recordSuccess(item);
if (context.hasError())
context.observer.onStatusChanged(context.errorMessage);
else
context.observer.onStatusChanged(null);
} finally {
mContext = null;
sendEndSource(sourceId);
}
}
}
} finally {
item.close();
}
} finally {
cancelNotification();
cleanupAndStopService();
sendEndSync();
}
}
private void runSyncLoop(SourceItem source)
{
AcquireProvider ap = AcquireProvider.getInstance();
AcquireProvider.ProviderInterface client =
ap.acquireProvider(getContentResolver(), Five.AUTHORITY);
try {
FiveProvider provider = (FiveProvider)client.getLocalContentProvider();
provider.mSource = source;
AbstractSyncAdapter adapter = provider.getSyncAdapter();
Stopwatch watch = Stopwatch.getInstance();
watch.start();
adapter.runSyncLoop(mContext);
watch.stopAndDebugElapsed(TAG, "runSyncLoop");
} finally {
ap.releaseProvider(getContentResolver(), client);
}
}
private void recordSuccess(SourceItem source)
{
Log.d(TAG, "recordSuccess: newestSyncTime=" + mContext.newestSyncTime);
/*
* Record the last successful sync time with the provider. This
* field is used only for display purposes. Normally during sync,
* the last modified since argument is derived from the latest
* synced change either in the temp provider or the main provider,
* or 0 if no data has been synced previously.
*/
ContentValues v = new ContentValues();
v.put(Five.Sources.LAST_SYNC_TIME, System.currentTimeMillis());
getContentResolver().update(source.getUri(), v, null, null);
}
public void cleanupAndStopService()
{
sSyncing = false;
mSyncThread = null;
Log.i(Constants.TAG, "Done syncing, stopping service...");
stopSelf();
mWakeLock.release();
}
@Override
protected void onRequestCancel()
{
SyncContext context = mContext;
if (context != null) {
context.cancel();
}
interrupt();
}
private class SourceSyncObserver implements SyncObserver
{
private final Uri mSourceUri;
private final ContentValues mTmpValues = new ContentValues();
public SourceSyncObserver(long sourceId) {
mSourceUri = Sources.makeUri(sourceId);
}
public void onStatusChanged(String statusMessage)
{
ContentValues values = mTmpValues;
values.clear();
values.put(Five.Sources.STATUS, statusMessage);
getContentResolver().update(mSourceUri, values, null, null);
}
}
}
public void showNotification()
{
Notification n = new Notification(android.R.drawable.stat_notify_sync, null,
System.currentTimeMillis());
n.setLatestEventInfo(this, getString(R.string.syncing_notif_title),
getString(R.string.syncing_notif_content), PendingIntent.getActivity(this,
0, new Intent(this, Settings.class), 0));
n.flags = Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT;
mNM.notify(NOTIF_SYNCING, n);
}
public void cancelNotification()
{
mNM.cancel(NOTIF_SYNCING);
}
public void sendBeginSync()
{
sendBroadcast(new Intent(Constants.ACTION_SYNC_BEGIN));
}
public void sendEndSync()
{
sendBroadcast(new Intent(Constants.ACTION_SYNC_END));
}
public void sendBeginSource(long sourceId)
{
sendBroadcast(new Intent(Constants.ACTION_SYNC_BEGIN)
.putExtra(Constants.EXTRA_SOURCE_ID, sourceId));
}
public void sendEndSource(long sourceId)
{
sendBroadcast(new Intent(Constants.ACTION_SYNC_END)
.putExtra(Constants.EXTRA_SOURCE_ID, sourceId));
}
}