1 /* 2 * Copyright (C) 2008 Esmertec AG. 3 * Copyright (C) 2008 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.im.service; 19 20 import java.util.concurrent.ExecutorService; 21 import java.util.concurrent.Executors; 22 23 import android.app.AlarmManager; 24 import android.app.PendingIntent; 25 import android.content.BroadcastReceiver; 26 import android.content.ContentUris; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.net.Uri; 31 import android.os.PowerManager; 32 import android.os.SystemClock; 33 import android.util.SparseArray; 34 35 import com.android.im.engine.HeartbeatService; 36 37 public class AndroidHeartBeatService extends BroadcastReceiver 38 implements HeartbeatService { 39 40 private static final String WAKELOCK_TAG = "IM_HEARTBEAT"; 41 42 private static final String HEARTBEAT_INTENT_ACTION 43 = "com.android.im.intent.action.HEARTBEAT"; 44 private static final Uri HEARTBEAT_CONTENT_URI 45 = Uri.parse("content://im/heartbeat"); 46 private static final String HEARTBEAT_CONTENT_TYPE 47 = "vnd.android.im/heartbeat"; 48 49 private static final ExecutorService sExecutor = Executors.newSingleThreadExecutor(); 50 51 private final Context mContext; 52 private final AlarmManager mAlarmManager; 53 /*package*/ PowerManager.WakeLock mWakeLock; 54 55 static class Alarm { 56 public PendingIntent mAlaramSender; 57 public Callback mCallback; 58 } 59 60 private final SparseArray<Alarm> mAlarms; 61 AndroidHeartBeatService(Context context)62 public AndroidHeartBeatService(Context context) { 63 mContext = context; 64 mAlarmManager = (AlarmManager)context.getSystemService( 65 Context.ALARM_SERVICE); 66 PowerManager powerManager = (PowerManager)context.getSystemService( 67 Context.POWER_SERVICE); 68 mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 69 WAKELOCK_TAG); 70 mAlarms = new SparseArray<Alarm>(); 71 } 72 startHeartbeat(Callback callback, long triggerTime)73 public synchronized void startHeartbeat(Callback callback, long triggerTime) { 74 Alarm alarm = findAlarm(callback); 75 if (alarm == null) { 76 alarm = new Alarm(); 77 int id = nextId(); 78 alarm.mCallback = callback; 79 Uri data = ContentUris.withAppendedId(HEARTBEAT_CONTENT_URI, id); 80 Intent i = new Intent(HEARTBEAT_INTENT_ACTION) 81 .setDataAndType(data, HEARTBEAT_CONTENT_TYPE); 82 alarm.mAlaramSender = PendingIntent.getBroadcast(mContext, 0, i, 0); 83 if (mAlarms.size() == 0) { 84 mContext.registerReceiver(this, IntentFilter.create( 85 HEARTBEAT_INTENT_ACTION, HEARTBEAT_CONTENT_TYPE)); 86 } 87 mAlarms.append(id, alarm); 88 } 89 setAlarm(alarm, triggerTime); 90 } 91 stopHeartbeat(Callback callback)92 public synchronized void stopHeartbeat(Callback callback) { 93 Alarm alarm = findAlarm(callback); 94 if (alarm != null) { 95 cancelAlarm(alarm); 96 } 97 } 98 stopAll()99 public synchronized void stopAll() { 100 for (int i = 0; i < mAlarms.size(); i++) { 101 Alarm alarm = mAlarms.valueAt(i); 102 cancelAlarm(alarm); 103 } 104 } 105 106 @Override onReceive(Context context, Intent intent)107 public void onReceive(Context context, Intent intent) { 108 int id = (int)ContentUris.parseId(intent.getData()); 109 Alarm alarm = mAlarms.get(id); 110 if (alarm == null) { 111 return; 112 } 113 sExecutor.execute(new Worker(alarm)); 114 } 115 116 private class Worker implements Runnable { 117 private final Alarm mAlarm; 118 Worker(Alarm alarm)119 public Worker(Alarm alarm) { 120 mAlarm = alarm; 121 } 122 run()123 public void run() { 124 mWakeLock.acquire(); 125 try { 126 Callback callback = mAlarm.mCallback; 127 long nextSchedule = callback.sendHeartbeat(); 128 if (nextSchedule <= 0) { 129 cancelAlarm(mAlarm); 130 } else { 131 setAlarm(mAlarm, nextSchedule); 132 } 133 } finally { 134 mWakeLock.release(); 135 } 136 } 137 } 138 findAlarm(Callback callback)139 private Alarm findAlarm(Callback callback) { 140 for (int i = 0; i < mAlarms.size(); i++) { 141 Alarm alarm = mAlarms.valueAt(i); 142 if (alarm.mCallback == callback) { 143 return alarm; 144 } 145 } 146 return null; 147 } 148 setAlarm(Alarm alarm, long offset)149 /*package*/ synchronized void setAlarm(Alarm alarm, long offset) { 150 long triggerAtTime = SystemClock.elapsedRealtime() + offset; 151 mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, 152 alarm.mAlaramSender); 153 } 154 cancelAlarm(Alarm alarm)155 /*package*/ synchronized void cancelAlarm(Alarm alarm) { 156 mAlarmManager.cancel(alarm.mAlaramSender); 157 int index = mAlarms.indexOfValue(alarm); 158 if (index >= 0) { 159 mAlarms.delete(mAlarms.keyAt(index)); 160 } 161 162 // Unregister the BroadcastReceiver if there isn't a alarm anymore. 163 if (mAlarms.size() == 0) { 164 mContext.unregisterReceiver(this); 165 } 166 } 167 168 private static int sNextId = 0; nextId()169 private static synchronized int nextId() { 170 return sNextId++; 171 } 172 } 173