/**
* Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
* This file is part of CSipSimple.
*
* CSipSimple 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.
* If you own a pjsip commercial license you can also redistribute it
* and/or modify it under the terms of the GNU Lesser General Public License
* as an android library.
*
* CSipSimple 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 CSipSimple. If not, see <http://www.gnu.org/licenses/>.
*/
package com.csipsimple.utils;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.SystemClock;
import com.csipsimple.service.SipService;
import com.csipsimple.service.SipWakeLock;
import org.pjsip.pjsua.pjsua;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
public class TimerWrapper extends BroadcastReceiver {
private static final String THIS_FILE = "Timer wrap";
private static final String TIMER_ACTION = "com.csipsimple.PJ_TIMER";
private static final String EXTRA_TIMER_SCHEME = "timer";
private SipService service;
private AlarmManager alarmManager;
private SipWakeLock wakeLock;
//private WakeLock wakeLock;
private static TimerWrapper singleton;
private static HandlerThread executorThread;
private boolean serviceRegistered = false;
private final List<Integer> scheduleEntries = new ArrayList<Integer>();
private final List<Long> scheduleTimes = new ArrayList<Long>();
private SipTimersExecutor mExecutor;
private TimerWrapper(SipService ctxt) {
super();
setContext(ctxt);
}
private synchronized void setContext(SipService ctxt) {
// If we have a new context, restart bindings
if(service != ctxt) {
// Reset
quit();
// Set new service
service = ctxt;
alarmManager = (AlarmManager) service.getSystemService(Context.ALARM_SERVICE);
wakeLock = new SipWakeLock((PowerManager) ctxt.getSystemService(Context.POWER_SERVICE));
}
if(!serviceRegistered) {
IntentFilter filter = new IntentFilter(TIMER_ACTION);
filter.addDataScheme(EXTRA_TIMER_SCHEME);
service.registerReceiver(this, filter);
serviceRegistered = true;
}
}
private synchronized void quit() {
Log.v(THIS_FILE, "Quit this wrapper");
if(serviceRegistered) {
serviceRegistered = false;
try {
service.unregisterReceiver(this);
} catch (IllegalArgumentException e) {
Log.e(THIS_FILE, "Impossible to destroy timer wrapper", e);
}
}
/*
List<Integer> keys = getPendingsKeys();
//Log.v(THIS_FILE, "In buffer : "+keys);
for(Integer key : keys) {
int heapId = (key & 0xFFF000) >> 8;
int timerId = key & 0x000FFF;
doCancel(heapId, timerId);
}
*/
if(wakeLock != null) {
wakeLock.reset();
}
if(alarmManager != null) {
for(Integer entryId : scheduleEntries) {
alarmManager.cancel(getPendingIntentForTimer(entryId));
}
}
scheduleEntries.clear();
scheduleTimes.clear();
// hashOffset ++;
// hashOffset = hashOffset % 10;
}
/*
private synchronized List<Integer> getPendingsKeys(){
ArrayList<Integer> keys = new ArrayList<Integer>();
for(Integer key : pendings.keySet()) {
keys.add(key);
}
return keys;
}
*/
private PendingIntent getPendingIntentForTimer(int entryId) {
return getPendingIntentForTimer(entryId, null);
}
private PendingIntent getPendingIntentForTimer(int entryId, Long expires) {
Intent intent = new Intent(TIMER_ACTION);
String toSend = EXTRA_TIMER_SCHEME + "://" + Integer.toString(entryId);
intent.setData(Uri.parse(toSend));
intent.putExtra(EXTRA_TIMER_ENTRY, entryId);
if(expires != null) {
intent.putExtra(EXTRA_TIMER_EXPIRATION, expires);
}
return PendingIntent.getBroadcast(service, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
}
private synchronized int doSchedule(int entryId, int intervalMs) {
//Log.d(THIS_FILE, "SCHED add " + entryId + " in " + intervalMs);
long firstTime = SystemClock.elapsedRealtime();
// Clamp min
if(intervalMs < 10) {
firstTime += 10;
}else {
firstTime += intervalMs;
}
PendingIntent pendingIntent = getPendingIntentForTimer(entryId, firstTime);
// If less than 1 sec, do not wake up -- that's probably stun check so useless to wake up about that
//int alarmType = (intervalMs < 1000) ? AlarmManager.ELAPSED_REALTIME : AlarmManager.ELAPSED_REALTIME_WAKEUP;
int alarmType = AlarmManager.ELAPSED_REALTIME_WAKEUP;
// Cancel previous reg anyway
alarmManager.cancel(pendingIntent);
int existingReg = scheduleEntries.indexOf((Integer) entryId);
if(existingReg != -1) {
scheduleEntries.remove(existingReg);
scheduleTimes.remove(existingReg);
}
// Push next
Log.v(THIS_FILE, "Schedule timer " + entryId + " in " + intervalMs + "ms @ " + firstTime);
Compatibility.setExactAlarm(alarmManager, alarmType, firstTime, pendingIntent);
scheduleEntries.add((Integer) entryId);
scheduleTimes.add((Long) firstTime);
return 1;
}
private synchronized int doCancel(int entryId) {
Log.v(THIS_FILE, "Cancel timer " + entryId);
alarmManager.cancel(getPendingIntentForTimer(entryId));
int existingReg = scheduleEntries.indexOf((Integer) entryId);
if(existingReg != -1) {
scheduleEntries.remove(existingReg);
scheduleTimes.remove(existingReg);
return 1;
}
return 0;
}
private final static String EXTRA_TIMER_ENTRY = "entry";
private final static String EXTRA_TIMER_EXPIRATION = "expires";
@Override
public void onReceive(Context context, Intent intent) {
if(TIMER_ACTION.equalsIgnoreCase(intent.getAction())) {
if(singleton == null) {
Log.w(THIS_FILE, "Not found singleton");
return;
}
int timerEntry = intent.getIntExtra(EXTRA_TIMER_ENTRY, -1);
Log.v(THIS_FILE, "FIRE Received TIMER " + timerEntry + " " + intent.getLongExtra(EXTRA_TIMER_EXPIRATION, 0) + " vs " + SystemClock.elapsedRealtime());
singleton.treatAlarm(timerEntry, intent.getLongExtra(EXTRA_TIMER_EXPIRATION, 0));
}
}
public void treatAlarm(int entry, long fireTime) {
getExecutor().execute(new TimerJob(entry, fireTime));
}
//private final Handler handler = new Handler();
private final static Object singletonLock = new Object();
// Public API
public static void create(SipService ctxt) {
synchronized (singletonLock) {
if(singleton == null) {
singleton = new TimerWrapper(ctxt);
}else {
singleton.setContext(ctxt);
}
}
}
public static void destroy() {
synchronized (singletonLock) {
if(singleton != null) {
singleton.quit();
}
}
}
public static int schedule(int entry, int entryId, int time) {
if(singleton == null) {
Log.e(THIS_FILE, "Timer NOT initialized");
return -1;
}
return singleton.doSchedule(entryId, time);
}
public static int cancel(int entry, int entryId) {
return singleton.doCancel(entryId);
}
private static Looper createLooper() {
if (executorThread == null) {
Log.d(THIS_FILE, "Creating new handler thread");
executorThread = new HandlerThread("SipTimers.Executor");
executorThread.start();
}
return executorThread.getLooper();
}
private SipTimersExecutor getExecutor() {
// create mExecutor lazily
if (mExecutor == null) {
mExecutor = new SipTimersExecutor(this);
}
return mExecutor;
}
// Executes immediate tasks in a single executorThread.
public static class SipTimersExecutor extends Handler {
WeakReference<TimerWrapper> handlerService;
SipTimersExecutor(TimerWrapper s) {
super(createLooper());
handlerService = new WeakReference<TimerWrapper>(s);
}
public void execute(Runnable task) {
Message.obtain(this, 0/* don't care */, task).sendToTarget();
}
@Override
public void handleMessage(Message msg) {
if (msg.obj instanceof Runnable) {
executeInternal((Runnable) msg.obj);
} else {
Log.w(THIS_FILE, "can't handle msg: " + msg);
}
}
private void executeInternal(Runnable task) {
try {
task.run();
} catch (Throwable t) {
Log.e(THIS_FILE, "run task: " + task, t);
}
}
}
private class TimerJob implements Runnable {
private final int entryId;
private final long fireTime;
public TimerJob(int anEntry, long aFireTime) {
entryId = anEntry;
fireTime = aFireTime;
wakeLock.acquire(this);
}
@Override
public void run() {
// From now, the timer can't be cancelled anymore
Log.v(THIS_FILE, "FIRE START " + entryId);
try {
boolean doFire = false;
synchronized (TimerWrapper.this) {
int existingReg = scheduleEntries.indexOf((Integer) entryId);
if(existingReg != -1) {
if(scheduleTimes.get(existingReg) == fireTime) {
doFire = true;
scheduleEntries.remove(existingReg);
scheduleTimes.remove(existingReg);
}
}
}
if(doFire) {
pjsua.pj_timer_fire(entryId);
}else {
Log.w(THIS_FILE, "Fire from old run " + entryId);
}
}catch(Exception e) {
Log.e(THIS_FILE, "Native error ", e);
}finally {
wakeLock.release(this);
}
Log.v(THIS_FILE, "FIRE DONE " + entryId);
}
}
}