/*
* Copyright 2012 Google Inc.
*
* 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.google.android.apps.iosched.sync;
import com.google.android.apps.iosched.io.HandlerException;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.widget.Toast;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import static com.google.android.apps.iosched.util.LogUtils.LOGE;
import static com.google.android.apps.iosched.util.LogUtils.LOGI;
import static com.google.android.apps.iosched.util.LogUtils.LOGW;
import static com.google.android.apps.iosched.util.LogUtils.makeLogTag;
/**
* Background {@link android.app.Service} that adds or removes sessions from your calendar via the
* Conference API.
*
* @see com.google.android.apps.iosched.sync.SyncHelper
*/
public class ScheduleUpdaterService extends Service {
private static final String TAG = makeLogTag(ScheduleUpdaterService.class);
public static final String EXTRA_SESSION_ID
= "com.google.android.apps.iosched.extra.SESSION_ID";
public static final String EXTRA_IN_SCHEDULE
= "com.google.android.apps.iosched.extra.IN_SCHEDULE";
private static final int SCHEDULE_UPDATE_DELAY_MILLIS = 5000;
private final Handler mUiThreadHandler = new Handler();
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private final LinkedList<Intent> mScheduleUpdates = new LinkedList<Intent>();
// Handler pattern copied from IntentService
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
processPendingScheduleUpdates();
int numRemainingUpdates;
synchronized (mScheduleUpdates) {
numRemainingUpdates = mScheduleUpdates.size();
}
if (numRemainingUpdates == 0) {
stopSelf();
} else {
// More updates were added since the current pending set was processed. Reschedule
// another pass.
removeMessages(0);
sendEmptyMessageDelayed(0, SCHEDULE_UPDATE_DELAY_MILLIS);
}
}
}
public ScheduleUpdaterService() {
}
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread(ScheduleUpdaterService.class.getSimpleName());
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// When receiving a new intent, delay the schedule until 5 seconds from now.
mServiceHandler.removeMessages(0);
mServiceHandler.sendEmptyMessageDelayed(0, SCHEDULE_UPDATE_DELAY_MILLIS);
// Remove pending updates involving this session ID.
String sessionId = intent.getStringExtra(EXTRA_SESSION_ID);
Iterator<Intent> updatesIterator = mScheduleUpdates.iterator();
while (updatesIterator.hasNext()) {
Intent existingIntent = updatesIterator.next();
if (sessionId.equals(existingIntent.getStringExtra(EXTRA_SESSION_ID))) {
updatesIterator.remove();
}
}
// Queue this schedule update.
synchronized (mScheduleUpdates) {
mScheduleUpdates.add(intent);
}
return START_REDELIVER_INTENT;
}
@Override
public void onDestroy() {
mServiceLooper.quit();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
public void processPendingScheduleUpdates() {
try {
// Operate on a local copy of the schedule update list so as not to block
// the main thread adding to this list
List<Intent> scheduleUpdates = new ArrayList<Intent>();
synchronized (mScheduleUpdates) {
scheduleUpdates.addAll(mScheduleUpdates);
mScheduleUpdates.clear();
}
SyncHelper syncHelper = new SyncHelper(this);
for (Intent updateIntent : scheduleUpdates) {
String sessionId = updateIntent.getStringExtra(EXTRA_SESSION_ID);
boolean inSchedule = updateIntent.getBooleanExtra(EXTRA_IN_SCHEDULE, false);
LOGI(TAG, "addOrRemoveSessionFromSchedule:"
+ " sessionId=" + sessionId
+ " inSchedule=" + inSchedule);
syncHelper.addOrRemoveSessionFromSchedule(this, sessionId, inSchedule);
}
} catch (HandlerException.NoDevsiteProfileException e) {
// The user doesn't have a profile, message this to them.
// TODO: better UX for this type of error
LOGW(TAG, "To sync your schedule to the web, login to developers.google.com.", e);
mUiThreadHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(ScheduleUpdaterService.this,
"To sync your schedule to the web, login to developers.google.com.",
Toast.LENGTH_LONG).show();
}
});
} catch (IOException e) {
// TODO: do something useful here, like revert the changes locally in the
// content provider to maintain client/server sync
LOGE(TAG, "Error processing schedule update", e);
}
}
}