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);
+        }
+    }
+}