Merge "Move WakeupTimer out of SipService."
diff --git a/java/com/android/server/sip/SipService.java b/java/com/android/server/sip/SipService.java
index afa696c..5ad5d26 100644
--- a/java/com/android/server/sip/SipService.java
+++ b/java/com/android/server/sip/SipService.java
@@ -60,6 +60,7 @@
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeSet;
+import java.util.concurrent.Executor;
import javax.sip.SipException;
/**
@@ -68,8 +69,7 @@
public final class SipService extends ISipService.Stub {
static final String TAG = "SipService";
static final boolean DEBUGV = false;
- private static final boolean DEBUG = false;
- private static final boolean DEBUG_TIMER = DEBUG && false;
+ static final boolean DEBUG = false;
private static final int EXPIRY_TIME = 3600;
private static final int SHORT_EXPIRY_TIME = 10;
private static final int MIN_EXPIRY_TIME = 60;
@@ -78,13 +78,13 @@
private String mLocalIp;
private String mNetworkType;
private boolean mConnected;
- private WakeupTimer mTimer;
+ private SipWakeupTimer mTimer;
private WifiScanProcess mWifiScanProcess;
private WifiManager.WifiLock mWifiLock;
private boolean mWifiOnly;
private IntervalMeasurementProcess mIntervalMeasurementProcess;
- private MyExecutor mExecutor;
+ private MyExecutor mExecutor = new MyExecutor();
// SipProfile URI --> group
private Map<String, SipSessionGroupExt> mSipGroups =
@@ -118,7 +118,7 @@
mMyWakeLock = new SipWakeLock((PowerManager)
context.getSystemService(Context.POWER_SERVICE));
- mTimer = new WakeupTimer(context);
+ mTimer = new SipWakeupTimer(context, mExecutor);
mWifiOnly = SipManager.isSipWifiOnly(context);
}
@@ -159,12 +159,6 @@
if (DEBUG) Log.d(TAG, " --- unregister receivers");
}
- private MyExecutor getExecutor() {
- // create mExecutor lazily
- if (mExecutor == null) mExecutor = new MyExecutor();
- return mExecutor;
- }
-
public synchronized SipProfile[] getListOfProfiles() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.USE_SIP, null);
@@ -716,8 +710,8 @@
private int mMaxInterval = MAX_INTERVAL;
private int mInterval = MAX_INTERVAL / 2;
private int mPassCounter = 0;
- private WakeupTimer mTimer = new WakeupTimer(mContext);
-
+ private SipWakeupTimer mTimer = new SipWakeupTimer(mContext, mExecutor);
+ // TODO: fix SipWakeupTimer so that we only use one instance of the timer
public IntervalMeasurementProcess(SipSessionGroup group) {
try {
@@ -1123,7 +1117,7 @@
@Override
public void onReceive(final Context context, final Intent intent) {
// Run the handler in MyExecutor to be protected by wake lock
- getExecutor().execute(new Runnable() {
+ mExecutor.execute(new Runnable() {
public void run() {
onReceiveInternal(context, intent);
}
@@ -1227,7 +1221,7 @@
@Override
public void run() {
// delegate to mExecutor
- getExecutor().execute(new Runnable() {
+ mExecutor.execute(new Runnable() {
public void run() {
realRun();
}
@@ -1252,300 +1246,6 @@
}
}
- /**
- * Timer that can schedule events to occur even when the device is in sleep.
- * Only used internally in this package.
- */
- class WakeupTimer extends BroadcastReceiver {
- private static final String TAG = "_SIP.WkTimer_";
- private static final String TRIGGER_TIME = "TriggerTime";
-
- private Context mContext;
- private AlarmManager mAlarmManager;
-
- // runnable --> time to execute in SystemClock
- private TreeSet<MyEvent> mEventQueue =
- new TreeSet<MyEvent>(new MyEventComparator());
-
- private PendingIntent mPendingIntent;
-
- public WakeupTimer(Context context) {
- mContext = context;
- mAlarmManager = (AlarmManager)
- context.getSystemService(Context.ALARM_SERVICE);
-
- IntentFilter filter = new IntentFilter(getAction());
- context.registerReceiver(this, filter);
- }
-
- /**
- * Stops the timer. No event can be scheduled after this method is called.
- */
- public synchronized void stop() {
- mContext.unregisterReceiver(this);
- if (mPendingIntent != null) {
- mAlarmManager.cancel(mPendingIntent);
- mPendingIntent = null;
- }
- mEventQueue.clear();
- mEventQueue = null;
- }
-
- private synchronized boolean stopped() {
- if (mEventQueue == null) {
- Log.w(TAG, "Timer stopped");
- return true;
- } else {
- return false;
- }
- }
-
- private void cancelAlarm() {
- mAlarmManager.cancel(mPendingIntent);
- mPendingIntent = null;
- }
-
- private void recalculatePeriods() {
- if (mEventQueue.isEmpty()) return;
-
- MyEvent firstEvent = mEventQueue.first();
- int minPeriod = firstEvent.mMaxPeriod;
- long minTriggerTime = firstEvent.mTriggerTime;
- for (MyEvent e : mEventQueue) {
- e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod;
- int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod
- - minTriggerTime);
- interval = interval / minPeriod * minPeriod;
- e.mTriggerTime = minTriggerTime + interval;
- }
- TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>(
- mEventQueue.comparator());
- newQueue.addAll((Collection<MyEvent>) mEventQueue);
- mEventQueue.clear();
- mEventQueue = newQueue;
- if (DEBUG_TIMER) {
- Log.d(TAG, "queue re-calculated");
- printQueue();
- }
- }
-
- // Determines the period and the trigger time of the new event and insert it
- // to the queue.
- private void insertEvent(MyEvent event) {
- long now = SystemClock.elapsedRealtime();
- if (mEventQueue.isEmpty()) {
- event.mTriggerTime = now + event.mPeriod;
- mEventQueue.add(event);
- return;
- }
- MyEvent firstEvent = mEventQueue.first();
- int minPeriod = firstEvent.mPeriod;
- if (minPeriod <= event.mMaxPeriod) {
- event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod;
- int interval = event.mMaxPeriod;
- interval -= (int) (firstEvent.mTriggerTime - now);
- interval = interval / minPeriod * minPeriod;
- event.mTriggerTime = firstEvent.mTriggerTime + interval;
- mEventQueue.add(event);
- } else {
- long triggerTime = now + event.mPeriod;
- if (firstEvent.mTriggerTime < triggerTime) {
- event.mTriggerTime = firstEvent.mTriggerTime;
- event.mLastTriggerTime -= event.mPeriod;
- } else {
- event.mTriggerTime = triggerTime;
- }
- mEventQueue.add(event);
- recalculatePeriods();
- }
- }
-
- /**
- * Sets a periodic timer.
- *
- * @param period the timer period; in milli-second
- * @param callback is called back when the timer goes off; the same callback
- * can be specified in multiple timer events
- */
- public synchronized void set(int period, Runnable callback) {
- if (stopped()) return;
-
- long now = SystemClock.elapsedRealtime();
- MyEvent event = new MyEvent(period, callback, now);
- insertEvent(event);
-
- if (mEventQueue.first() == event) {
- if (mEventQueue.size() > 1) cancelAlarm();
- scheduleNext();
- }
-
- long triggerTime = event.mTriggerTime;
- if (DEBUG_TIMER) {
- Log.d(TAG, " add event " + event + " scheduled at "
- + showTime(triggerTime) + " at " + showTime(now)
- + ", #events=" + mEventQueue.size());
- printQueue();
- }
- }
-
- /**
- * Cancels all the timer events with the specified callback.
- *
- * @param callback the callback
- */
- public synchronized void cancel(Runnable callback) {
- if (stopped() || mEventQueue.isEmpty()) return;
- if (DEBUG_TIMER) Log.d(TAG, "cancel:" + callback);
-
- MyEvent firstEvent = mEventQueue.first();
- for (Iterator<MyEvent> iter = mEventQueue.iterator();
- iter.hasNext();) {
- MyEvent event = iter.next();
- if (event.mCallback == callback) {
- iter.remove();
- if (DEBUG_TIMER) Log.d(TAG, " cancel found:" + event);
- }
- }
- if (mEventQueue.isEmpty()) {
- cancelAlarm();
- } else if (mEventQueue.first() != firstEvent) {
- cancelAlarm();
- firstEvent = mEventQueue.first();
- firstEvent.mPeriod = firstEvent.mMaxPeriod;
- firstEvent.mTriggerTime = firstEvent.mLastTriggerTime
- + firstEvent.mPeriod;
- recalculatePeriods();
- scheduleNext();
- }
- if (DEBUG_TIMER) {
- Log.d(TAG, "after cancel:");
- printQueue();
- }
- }
-
- private void scheduleNext() {
- if (stopped() || mEventQueue.isEmpty()) return;
-
- if (mPendingIntent != null) {
- throw new RuntimeException("pendingIntent is not null!");
- }
-
- MyEvent event = mEventQueue.first();
- Intent intent = new Intent(getAction());
- intent.putExtra(TRIGGER_TIME, event.mTriggerTime);
- PendingIntent pendingIntent = mPendingIntent =
- PendingIntent.getBroadcast(mContext, 0, intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- event.mTriggerTime, pendingIntent);
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- // This callback is already protected by AlarmManager's wake lock.
- String action = intent.getAction();
- if (getAction().equals(action)
- && intent.getExtras().containsKey(TRIGGER_TIME)) {
- mPendingIntent = null;
- long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L);
- execute(triggerTime);
- } else {
- Log.d(TAG, "unrecognized intent: " + intent);
- }
- }
-
- private void printQueue() {
- int count = 0;
- for (MyEvent event : mEventQueue) {
- Log.d(TAG, " " + event + ": scheduled at "
- + showTime(event.mTriggerTime) + ": last at "
- + showTime(event.mLastTriggerTime));
- if (++count >= 5) break;
- }
- if (mEventQueue.size() > count) {
- Log.d(TAG, " .....");
- } else if (count == 0) {
- Log.d(TAG, " <empty>");
- }
- }
-
- private synchronized void execute(long triggerTime) {
- if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = "
- + showTime(triggerTime) + ": " + mEventQueue.size());
- if (stopped() || mEventQueue.isEmpty()) return;
-
- for (MyEvent event : mEventQueue) {
- if (event.mTriggerTime != triggerTime) break;
- if (DEBUG_TIMER) Log.d(TAG, "execute " + event);
-
- event.mLastTriggerTime = event.mTriggerTime;
- event.mTriggerTime += event.mPeriod;
-
- // run the callback in the handler thread to prevent deadlock
- getExecutor().execute(event.mCallback);
- }
- if (DEBUG_TIMER) {
- Log.d(TAG, "after timeout execution");
- printQueue();
- }
- scheduleNext();
- }
-
- private String getAction() {
- return toString();
- }
-
- private String showTime(long time) {
- int ms = (int) (time % 1000);
- int s = (int) (time / 1000);
- int m = s / 60;
- s %= 60;
- return String.format("%d.%d.%d", m, s, ms);
- }
- }
-
- private static class MyEvent {
- int mPeriod;
- int mMaxPeriod;
- long mTriggerTime;
- long mLastTriggerTime;
- Runnable mCallback;
-
- MyEvent(int period, Runnable callback, long now) {
- mPeriod = mMaxPeriod = period;
- mCallback = callback;
- mLastTriggerTime = now;
- }
-
- @Override
- public String toString() {
- String s = super.toString();
- s = s.substring(s.indexOf("@"));
- return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":"
- + toString(mCallback);
- }
-
- private String toString(Object o) {
- String s = o.toString();
- int index = s.indexOf("$");
- if (index > 0) s = s.substring(index + 1);
- return s;
- }
- }
-
- private static class MyEventComparator implements Comparator<MyEvent> {
- public int compare(MyEvent e1, MyEvent e2) {
- if (e1 == e2) return 0;
- int diff = e1.mMaxPeriod - e2.mMaxPeriod;
- if (diff == 0) diff = -1;
- return diff;
- }
-
- public boolean equals(Object that) {
- return (this == that);
- }
- }
-
private static Looper createLooper() {
HandlerThread thread = new HandlerThread("SipService.Executor");
thread.start();
@@ -1554,12 +1254,13 @@
// Executes immediate tasks in a single thread.
// Hold/release wake lock for running tasks
- private class MyExecutor extends Handler {
+ private class MyExecutor extends Handler implements Executor {
MyExecutor() {
super(createLooper());
}
- void execute(Runnable task) {
+ @Override
+ public void execute(Runnable task) {
mMyWakeLock.acquire(task);
Message.obtain(this, 0/* don't care */, task).sendToTarget();
}
diff --git a/java/com/android/server/sip/SipWakeupTimer.java b/java/com/android/server/sip/SipWakeupTimer.java
new file mode 100644
index 0000000..9cc26b0
--- /dev/null
+++ b/java/com/android/server/sip/SipWakeupTimer.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2011, The Android Open Source Project
+ *
+ * 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.android.server.sip;
+
+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.os.SystemClock;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.TreeSet;
+import java.util.concurrent.Executor;
+import javax.sip.SipException;
+
+/**
+ * Timer that can schedule events to occur even when the device is in sleep.
+ */
+class SipWakeupTimer extends BroadcastReceiver {
+ private static final String TAG = "_SIP.WkTimer_";
+ private static final String TRIGGER_TIME = "TriggerTime";
+ private static final boolean DEBUG_TIMER = SipService.DEBUG && false;
+
+ private Context mContext;
+ private AlarmManager mAlarmManager;
+
+ // runnable --> time to execute in SystemClock
+ private TreeSet<MyEvent> mEventQueue =
+ new TreeSet<MyEvent>(new MyEventComparator());
+
+ private PendingIntent mPendingIntent;
+
+ private Executor mExecutor;
+
+ public SipWakeupTimer(Context context, Executor executor) {
+ mContext = context;
+ mAlarmManager = (AlarmManager)
+ context.getSystemService(Context.ALARM_SERVICE);
+
+ IntentFilter filter = new IntentFilter(getAction());
+ context.registerReceiver(this, filter);
+ mExecutor = executor;
+ }
+
+ /**
+ * Stops the timer. No event can be scheduled after this method is called.
+ */
+ public synchronized void stop() {
+ mContext.unregisterReceiver(this);
+ if (mPendingIntent != null) {
+ mAlarmManager.cancel(mPendingIntent);
+ mPendingIntent = null;
+ }
+ mEventQueue.clear();
+ mEventQueue = null;
+ }
+
+ private synchronized boolean stopped() {
+ if (mEventQueue == null) {
+ Log.w(TAG, "Timer stopped");
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void cancelAlarm() {
+ mAlarmManager.cancel(mPendingIntent);
+ mPendingIntent = null;
+ }
+
+ private void recalculatePeriods() {
+ if (mEventQueue.isEmpty()) return;
+
+ MyEvent firstEvent = mEventQueue.first();
+ int minPeriod = firstEvent.mMaxPeriod;
+ long minTriggerTime = firstEvent.mTriggerTime;
+ for (MyEvent e : mEventQueue) {
+ e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod;
+ int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod
+ - minTriggerTime);
+ interval = interval / minPeriod * minPeriod;
+ e.mTriggerTime = minTriggerTime + interval;
+ }
+ TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>(
+ mEventQueue.comparator());
+ newQueue.addAll((Collection<MyEvent>) mEventQueue);
+ mEventQueue.clear();
+ mEventQueue = newQueue;
+ if (DEBUG_TIMER) {
+ Log.d(TAG, "queue re-calculated");
+ printQueue();
+ }
+ }
+
+ // Determines the period and the trigger time of the new event and insert it
+ // to the queue.
+ private void insertEvent(MyEvent event) {
+ long now = SystemClock.elapsedRealtime();
+ if (mEventQueue.isEmpty()) {
+ event.mTriggerTime = now + event.mPeriod;
+ mEventQueue.add(event);
+ return;
+ }
+ MyEvent firstEvent = mEventQueue.first();
+ int minPeriod = firstEvent.mPeriod;
+ if (minPeriod <= event.mMaxPeriod) {
+ event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod;
+ int interval = event.mMaxPeriod;
+ interval -= (int) (firstEvent.mTriggerTime - now);
+ interval = interval / minPeriod * minPeriod;
+ event.mTriggerTime = firstEvent.mTriggerTime + interval;
+ mEventQueue.add(event);
+ } else {
+ long triggerTime = now + event.mPeriod;
+ if (firstEvent.mTriggerTime < triggerTime) {
+ event.mTriggerTime = firstEvent.mTriggerTime;
+ event.mLastTriggerTime -= event.mPeriod;
+ } else {
+ event.mTriggerTime = triggerTime;
+ }
+ mEventQueue.add(event);
+ recalculatePeriods();
+ }
+ }
+
+ /**
+ * Sets a periodic timer.
+ *
+ * @param period the timer period; in milli-second
+ * @param callback is called back when the timer goes off; the same callback
+ * can be specified in multiple timer events
+ */
+ public synchronized void set(int period, Runnable callback) {
+ if (stopped()) return;
+
+ long now = SystemClock.elapsedRealtime();
+ MyEvent event = new MyEvent(period, callback, now);
+ insertEvent(event);
+
+ if (mEventQueue.first() == event) {
+ if (mEventQueue.size() > 1) cancelAlarm();
+ scheduleNext();
+ }
+
+ long triggerTime = event.mTriggerTime;
+ if (DEBUG_TIMER) {
+ Log.d(TAG, " add event " + event + " scheduled at "
+ + showTime(triggerTime) + " at " + showTime(now)
+ + ", #events=" + mEventQueue.size());
+ printQueue();
+ }
+ }
+
+ /**
+ * Cancels all the timer events with the specified callback.
+ *
+ * @param callback the callback
+ */
+ public synchronized void cancel(Runnable callback) {
+ if (stopped() || mEventQueue.isEmpty()) return;
+ if (DEBUG_TIMER) Log.d(TAG, "cancel:" + callback);
+
+ MyEvent firstEvent = mEventQueue.first();
+ for (Iterator<MyEvent> iter = mEventQueue.iterator();
+ iter.hasNext();) {
+ MyEvent event = iter.next();
+ if (event.mCallback == callback) {
+ iter.remove();
+ if (DEBUG_TIMER) Log.d(TAG, " cancel found:" + event);
+ }
+ }
+ if (mEventQueue.isEmpty()) {
+ cancelAlarm();
+ } else if (mEventQueue.first() != firstEvent) {
+ cancelAlarm();
+ firstEvent = mEventQueue.first();
+ firstEvent.mPeriod = firstEvent.mMaxPeriod;
+ firstEvent.mTriggerTime = firstEvent.mLastTriggerTime
+ + firstEvent.mPeriod;
+ recalculatePeriods();
+ scheduleNext();
+ }
+ if (DEBUG_TIMER) {
+ Log.d(TAG, "after cancel:");
+ printQueue();
+ }
+ }
+
+ private void scheduleNext() {
+ if (stopped() || mEventQueue.isEmpty()) return;
+
+ if (mPendingIntent != null) {
+ throw new RuntimeException("pendingIntent is not null!");
+ }
+
+ MyEvent event = mEventQueue.first();
+ Intent intent = new Intent(getAction());
+ intent.putExtra(TRIGGER_TIME, event.mTriggerTime);
+ PendingIntent pendingIntent = mPendingIntent =
+ PendingIntent.getBroadcast(mContext, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ event.mTriggerTime, pendingIntent);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // This callback is already protected by AlarmManager's wake lock.
+ String action = intent.getAction();
+ if (getAction().equals(action)
+ && intent.getExtras().containsKey(TRIGGER_TIME)) {
+ mPendingIntent = null;
+ long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L);
+ execute(triggerTime);
+ } else {
+ Log.d(TAG, "unrecognized intent: " + intent);
+ }
+ }
+
+ private void printQueue() {
+ int count = 0;
+ for (MyEvent event : mEventQueue) {
+ Log.d(TAG, " " + event + ": scheduled at "
+ + showTime(event.mTriggerTime) + ": last at "
+ + showTime(event.mLastTriggerTime));
+ if (++count >= 5) break;
+ }
+ if (mEventQueue.size() > count) {
+ Log.d(TAG, " .....");
+ } else if (count == 0) {
+ Log.d(TAG, " <empty>");
+ }
+ }
+
+ private synchronized void execute(long triggerTime) {
+ if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = "
+ + showTime(triggerTime) + ": " + mEventQueue.size());
+ if (stopped() || mEventQueue.isEmpty()) return;
+
+ for (MyEvent event : mEventQueue) {
+ if (event.mTriggerTime != triggerTime) break;
+ if (DEBUG_TIMER) Log.d(TAG, "execute " + event);
+
+ event.mLastTriggerTime = event.mTriggerTime;
+ event.mTriggerTime += event.mPeriod;
+
+ // run the callback in the handler thread to prevent deadlock
+ mExecutor.execute(event.mCallback);
+ }
+ if (DEBUG_TIMER) {
+ Log.d(TAG, "after timeout execution");
+ printQueue();
+ }
+ scheduleNext();
+ }
+
+ private String getAction() {
+ return toString();
+ }
+
+ private String showTime(long time) {
+ int ms = (int) (time % 1000);
+ int s = (int) (time / 1000);
+ int m = s / 60;
+ s %= 60;
+ return String.format("%d.%d.%d", m, s, ms);
+ }
+
+ private static class MyEvent {
+ int mPeriod;
+ int mMaxPeriod;
+ long mTriggerTime;
+ long mLastTriggerTime;
+ Runnable mCallback;
+
+ MyEvent(int period, Runnable callback, long now) {
+ mPeriod = mMaxPeriod = period;
+ mCallback = callback;
+ mLastTriggerTime = now;
+ }
+
+ @Override
+ public String toString() {
+ String s = super.toString();
+ s = s.substring(s.indexOf("@"));
+ return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":"
+ + toString(mCallback);
+ }
+
+ private String toString(Object o) {
+ String s = o.toString();
+ int index = s.indexOf("$");
+ if (index > 0) s = s.substring(index + 1);
+ return s;
+ }
+ }
+
+ private static class MyEventComparator implements Comparator<MyEvent> {
+ public int compare(MyEvent e1, MyEvent e2) {
+ if (e1 == e2) return 0;
+ int diff = e1.mMaxPeriod - e2.mMaxPeriod;
+ if (diff == 0) diff = -1;
+ return diff;
+ }
+
+ public boolean equals(Object that) {
+ return (this == that);
+ }
+ }
+}