Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2010, The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.server.sip; |
| 18 | |
| 19 | import android.app.AlarmManager; |
| 20 | import android.app.PendingIntent; |
| 21 | import android.content.BroadcastReceiver; |
| 22 | import android.content.Context; |
| 23 | import android.content.Intent; |
| 24 | import android.content.IntentFilter; |
| 25 | import android.net.ConnectivityManager; |
| 26 | import android.net.NetworkInfo; |
| 27 | import android.net.sip.ISipService; |
| 28 | import android.net.sip.ISipSession; |
| 29 | import android.net.sip.ISipSessionListener; |
Hung-ying Tyan | 3d7606a | 2010-09-12 23:50:38 +0800 | [diff] [blame] | 30 | import android.net.sip.SipErrorCode; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 31 | import android.net.sip.SipManager; |
| 32 | import android.net.sip.SipProfile; |
Hung-ying Tyan | 84a357b | 2010-09-16 04:11:32 +0800 | [diff] [blame] | 33 | import android.net.sip.SipSession; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 34 | import android.net.sip.SipSessionAdapter; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 35 | import android.net.wifi.WifiManager; |
Chung-yih Wang | 5424c8d | 2010-08-25 19:02:18 +0800 | [diff] [blame] | 36 | import android.os.Binder; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 37 | import android.os.Bundle; |
Hung-ying Tyan | b17eae9 | 2010-09-19 00:26:38 +0800 | [diff] [blame] | 38 | import android.os.Handler; |
| 39 | import android.os.HandlerThread; |
| 40 | import android.os.Looper; |
| 41 | import android.os.Message; |
Hung-ying Tyan | 6a53489 | 2010-09-13 18:44:33 +0800 | [diff] [blame] | 42 | import android.os.Process; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 43 | import android.os.RemoteException; |
Hung-ying Tyan | 7e54ef7 | 2010-09-25 22:49:59 +0800 | [diff] [blame] | 44 | import android.os.ServiceManager; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 45 | import android.os.SystemClock; |
| 46 | import android.text.TextUtils; |
| 47 | import android.util.Log; |
| 48 | |
| 49 | import java.io.IOException; |
| 50 | import java.net.DatagramSocket; |
| 51 | import java.net.InetAddress; |
| 52 | import java.net.UnknownHostException; |
Hung-ying Tyan | 6a53489 | 2010-09-13 18:44:33 +0800 | [diff] [blame] | 53 | import java.util.ArrayList; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 54 | import java.util.Collection; |
| 55 | import java.util.Comparator; |
| 56 | import java.util.HashMap; |
| 57 | import java.util.Iterator; |
| 58 | import java.util.Map; |
| 59 | import java.util.Timer; |
| 60 | import java.util.TimerTask; |
| 61 | import java.util.TreeSet; |
| 62 | import javax.sip.SipException; |
| 63 | |
Chia-chi Yeh | 95b15c3 | 2010-09-02 22:15:26 +0800 | [diff] [blame] | 64 | /** |
| 65 | * @hide |
| 66 | */ |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 67 | public final class SipService extends ISipService.Stub { |
| 68 | private static final String TAG = "SipService"; |
Hung-ying Tyan | b031957 | 2010-10-01 07:09:30 +0800 | [diff] [blame] | 69 | private static final boolean DEBUGV = false; |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 70 | private static final boolean DEBUG = true; |
| 71 | private static final boolean DEBUG_TIMER = DEBUG && false; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 72 | private static final int EXPIRY_TIME = 3600; |
| 73 | private static final int SHORT_EXPIRY_TIME = 10; |
| 74 | private static final int MIN_EXPIRY_TIME = 60; |
| 75 | |
| 76 | private Context mContext; |
| 77 | private String mLocalIp; |
| 78 | private String mNetworkType; |
| 79 | private boolean mConnected; |
| 80 | private WakeupTimer mTimer; |
| 81 | private WifiManager.WifiLock mWifiLock; |
Hung-ying Tyan | c4b8747 | 2010-09-19 18:23:44 +0800 | [diff] [blame] | 82 | private boolean mWifiOnly; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 83 | |
Hung-ying Tyan | b17eae9 | 2010-09-19 00:26:38 +0800 | [diff] [blame] | 84 | private MyExecutor mExecutor; |
| 85 | |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 86 | // SipProfile URI --> group |
| 87 | private Map<String, SipSessionGroupExt> mSipGroups = |
| 88 | new HashMap<String, SipSessionGroupExt>(); |
| 89 | |
| 90 | // session ID --> session |
| 91 | private Map<String, ISipSession> mPendingSessions = |
| 92 | new HashMap<String, ISipSession>(); |
| 93 | |
| 94 | private ConnectivityReceiver mConnectivityReceiver; |
| 95 | |
Hung-ying Tyan | 3424c02 | 2010-08-27 18:08:19 +0800 | [diff] [blame] | 96 | /** |
Hung-ying Tyan | 7e54ef7 | 2010-09-25 22:49:59 +0800 | [diff] [blame] | 97 | * Starts the SIP service. Do nothing if the SIP API is not supported on the |
| 98 | * device. |
Hung-ying Tyan | 3424c02 | 2010-08-27 18:08:19 +0800 | [diff] [blame] | 99 | */ |
Hung-ying Tyan | 7e54ef7 | 2010-09-25 22:49:59 +0800 | [diff] [blame] | 100 | public static void start(Context context) { |
| 101 | if (SipManager.isApiSupported(context)) { |
| 102 | ServiceManager.addService("sip", new SipService(context)); |
Hung-ying Tyan | 9db99a4 | 2010-10-07 09:14:57 +0800 | [diff] [blame] | 103 | context.sendBroadcast(new Intent(SipManager.ACTION_SIP_SERVICE_UP)); |
Hung-ying Tyan | 7e54ef7 | 2010-09-25 22:49:59 +0800 | [diff] [blame] | 104 | Log.i(TAG, "SIP service started"); |
| 105 | } |
Hung-ying Tyan | 3424c02 | 2010-08-27 18:08:19 +0800 | [diff] [blame] | 106 | } |
| 107 | |
| 108 | private SipService(Context context) { |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 109 | if (DEBUG) Log.d(TAG, " service started!"); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 110 | mContext = context; |
| 111 | mConnectivityReceiver = new ConnectivityReceiver(); |
| 112 | context.registerReceiver(mConnectivityReceiver, |
| 113 | new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); |
| 114 | |
| 115 | mTimer = new WakeupTimer(context); |
Hung-ying Tyan | c4b8747 | 2010-09-19 18:23:44 +0800 | [diff] [blame] | 116 | mWifiOnly = SipManager.isSipWifiOnly(context); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 117 | } |
| 118 | |
Hung-ying Tyan | b17eae9 | 2010-09-19 00:26:38 +0800 | [diff] [blame] | 119 | private MyExecutor getExecutor() { |
| 120 | // create mExecutor lazily |
| 121 | if (mExecutor == null) mExecutor = new MyExecutor(); |
| 122 | return mExecutor; |
| 123 | } |
| 124 | |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 125 | public synchronized SipProfile[] getListOfProfiles() { |
Hung-ying Tyan | aa562ff | 2010-10-08 08:59:38 +0800 | [diff] [blame^] | 126 | mContext.enforceCallingOrSelfPermission( |
| 127 | android.Manifest.permission.USE_SIP, null); |
Hung-ying Tyan | 6a53489 | 2010-09-13 18:44:33 +0800 | [diff] [blame] | 128 | boolean isCallerRadio = isCallerRadio(); |
| 129 | ArrayList<SipProfile> profiles = new ArrayList<SipProfile>(); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 130 | for (SipSessionGroupExt group : mSipGroups.values()) { |
Hung-ying Tyan | 6a53489 | 2010-09-13 18:44:33 +0800 | [diff] [blame] | 131 | if (isCallerRadio || isCallerCreator(group)) { |
| 132 | profiles.add(group.getLocalProfile()); |
| 133 | } |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 134 | } |
Hung-ying Tyan | 6a53489 | 2010-09-13 18:44:33 +0800 | [diff] [blame] | 135 | return profiles.toArray(new SipProfile[profiles.size()]); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 136 | } |
| 137 | |
| 138 | public void open(SipProfile localProfile) { |
Hung-ying Tyan | aa562ff | 2010-10-08 08:59:38 +0800 | [diff] [blame^] | 139 | mContext.enforceCallingOrSelfPermission( |
| 140 | android.Manifest.permission.USE_SIP, null); |
Chung-yih Wang | 5424c8d | 2010-08-25 19:02:18 +0800 | [diff] [blame] | 141 | localProfile.setCallingUid(Binder.getCallingUid()); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 142 | try { |
| 143 | createGroup(localProfile); |
| 144 | } catch (SipException e) { |
| 145 | Log.e(TAG, "openToMakeCalls()", e); |
| 146 | // TODO: how to send the exception back |
| 147 | } |
| 148 | } |
| 149 | |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 150 | public synchronized void open3(SipProfile localProfile, |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 151 | PendingIntent incomingCallPendingIntent, |
| 152 | ISipSessionListener listener) { |
Hung-ying Tyan | aa562ff | 2010-10-08 08:59:38 +0800 | [diff] [blame^] | 153 | mContext.enforceCallingOrSelfPermission( |
| 154 | android.Manifest.permission.USE_SIP, null); |
Chung-yih Wang | 5424c8d | 2010-08-25 19:02:18 +0800 | [diff] [blame] | 155 | localProfile.setCallingUid(Binder.getCallingUid()); |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 156 | if (incomingCallPendingIntent == null) { |
| 157 | Log.w(TAG, "incomingCallPendingIntent cannot be null; " |
| 158 | + "the profile is not opened"); |
Hung-ying Tyan | 6a53489 | 2010-09-13 18:44:33 +0800 | [diff] [blame] | 159 | return; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 160 | } |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 161 | if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": " |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 162 | + incomingCallPendingIntent + ": " + listener); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 163 | try { |
| 164 | SipSessionGroupExt group = createGroup(localProfile, |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 165 | incomingCallPendingIntent, listener); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 166 | if (localProfile.getAutoRegistration()) { |
| 167 | group.openToReceiveCalls(); |
Hung-ying Tyan | aa562ff | 2010-10-08 08:59:38 +0800 | [diff] [blame^] | 168 | if (isWifiActive()) grabWifiLock(); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 169 | } |
| 170 | } catch (SipException e) { |
| 171 | Log.e(TAG, "openToReceiveCalls()", e); |
| 172 | // TODO: how to send the exception back |
| 173 | } |
| 174 | } |
| 175 | |
Hung-ying Tyan | 6a53489 | 2010-09-13 18:44:33 +0800 | [diff] [blame] | 176 | private boolean isCallerCreator(SipSessionGroupExt group) { |
| 177 | SipProfile profile = group.getLocalProfile(); |
| 178 | return (profile.getCallingUid() == Binder.getCallingUid()); |
| 179 | } |
| 180 | |
| 181 | private boolean isCallerCreatorOrRadio(SipSessionGroupExt group) { |
| 182 | return (isCallerRadio() || isCallerCreator(group)); |
| 183 | } |
| 184 | |
| 185 | private boolean isCallerRadio() { |
| 186 | return (Binder.getCallingUid() == Process.PHONE_UID); |
| 187 | } |
| 188 | |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 189 | public synchronized void close(String localProfileUri) { |
Hung-ying Tyan | aa562ff | 2010-10-08 08:59:38 +0800 | [diff] [blame^] | 190 | mContext.enforceCallingOrSelfPermission( |
| 191 | android.Manifest.permission.USE_SIP, null); |
Hung-ying Tyan | 6a53489 | 2010-09-13 18:44:33 +0800 | [diff] [blame] | 192 | SipSessionGroupExt group = mSipGroups.get(localProfileUri); |
| 193 | if (group == null) return; |
| 194 | if (!isCallerCreatorOrRadio(group)) { |
| 195 | Log.d(TAG, "only creator or radio can close this profile"); |
| 196 | return; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 197 | } |
Hung-ying Tyan | 6a53489 | 2010-09-13 18:44:33 +0800 | [diff] [blame] | 198 | |
| 199 | group = mSipGroups.remove(localProfileUri); |
| 200 | notifyProfileRemoved(group.getLocalProfile()); |
| 201 | group.close(); |
Hung-ying Tyan | aa562ff | 2010-10-08 08:59:38 +0800 | [diff] [blame^] | 202 | if (isWifiActive() && !anyOpened()) releaseWifiLock(); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 203 | } |
| 204 | |
| 205 | public synchronized boolean isOpened(String localProfileUri) { |
Hung-ying Tyan | aa562ff | 2010-10-08 08:59:38 +0800 | [diff] [blame^] | 206 | mContext.enforceCallingOrSelfPermission( |
| 207 | android.Manifest.permission.USE_SIP, null); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 208 | SipSessionGroupExt group = mSipGroups.get(localProfileUri); |
Hung-ying Tyan | 6a53489 | 2010-09-13 18:44:33 +0800 | [diff] [blame] | 209 | if (group == null) return false; |
| 210 | if (isCallerCreatorOrRadio(group)) { |
| 211 | return group.isOpened(); |
| 212 | } else { |
| 213 | Log.i(TAG, "only creator or radio can query on the profile"); |
| 214 | return false; |
| 215 | } |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 216 | } |
| 217 | |
| 218 | public synchronized boolean isRegistered(String localProfileUri) { |
Hung-ying Tyan | aa562ff | 2010-10-08 08:59:38 +0800 | [diff] [blame^] | 219 | mContext.enforceCallingOrSelfPermission( |
| 220 | android.Manifest.permission.USE_SIP, null); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 221 | SipSessionGroupExt group = mSipGroups.get(localProfileUri); |
Hung-ying Tyan | 6a53489 | 2010-09-13 18:44:33 +0800 | [diff] [blame] | 222 | if (group == null) return false; |
| 223 | if (isCallerCreatorOrRadio(group)) { |
| 224 | return group.isRegistered(); |
| 225 | } else { |
| 226 | Log.i(TAG, "only creator or radio can query on the profile"); |
| 227 | return false; |
| 228 | } |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 229 | } |
| 230 | |
| 231 | public synchronized void setRegistrationListener(String localProfileUri, |
| 232 | ISipSessionListener listener) { |
Hung-ying Tyan | aa562ff | 2010-10-08 08:59:38 +0800 | [diff] [blame^] | 233 | mContext.enforceCallingOrSelfPermission( |
| 234 | android.Manifest.permission.USE_SIP, null); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 235 | SipSessionGroupExt group = mSipGroups.get(localProfileUri); |
Hung-ying Tyan | 6a53489 | 2010-09-13 18:44:33 +0800 | [diff] [blame] | 236 | if (group == null) return; |
| 237 | if (isCallerCreator(group)) { |
| 238 | group.setListener(listener); |
| 239 | } else { |
| 240 | Log.i(TAG, "only creator can set listener on the profile"); |
| 241 | } |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 242 | } |
| 243 | |
| 244 | public synchronized ISipSession createSession(SipProfile localProfile, |
| 245 | ISipSessionListener listener) { |
Hung-ying Tyan | aa562ff | 2010-10-08 08:59:38 +0800 | [diff] [blame^] | 246 | mContext.enforceCallingOrSelfPermission( |
| 247 | android.Manifest.permission.USE_SIP, null); |
Chung-yih Wang | 5424c8d | 2010-08-25 19:02:18 +0800 | [diff] [blame] | 248 | localProfile.setCallingUid(Binder.getCallingUid()); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 249 | if (!mConnected) return null; |
| 250 | try { |
| 251 | SipSessionGroupExt group = createGroup(localProfile); |
| 252 | return group.createSession(listener); |
| 253 | } catch (SipException e) { |
| 254 | Log.w(TAG, "createSession()", e); |
| 255 | return null; |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | public synchronized ISipSession getPendingSession(String callId) { |
Hung-ying Tyan | aa562ff | 2010-10-08 08:59:38 +0800 | [diff] [blame^] | 260 | mContext.enforceCallingOrSelfPermission( |
| 261 | android.Manifest.permission.USE_SIP, null); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 262 | if (callId == null) return null; |
| 263 | return mPendingSessions.get(callId); |
| 264 | } |
| 265 | |
| 266 | private String determineLocalIp() { |
| 267 | try { |
| 268 | DatagramSocket s = new DatagramSocket(); |
| 269 | s.connect(InetAddress.getByName("192.168.1.1"), 80); |
| 270 | return s.getLocalAddress().getHostAddress(); |
| 271 | } catch (IOException e) { |
| 272 | Log.w(TAG, "determineLocalIp()", e); |
| 273 | // dont do anything; there should be a connectivity change going |
| 274 | return null; |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | private SipSessionGroupExt createGroup(SipProfile localProfile) |
| 279 | throws SipException { |
| 280 | String key = localProfile.getUriString(); |
| 281 | SipSessionGroupExt group = mSipGroups.get(key); |
| 282 | if (group == null) { |
| 283 | group = new SipSessionGroupExt(localProfile, null, null); |
| 284 | mSipGroups.put(key, group); |
| 285 | notifyProfileAdded(localProfile); |
Hung-ying Tyan | 6a53489 | 2010-09-13 18:44:33 +0800 | [diff] [blame] | 286 | } else if (!isCallerCreator(group)) { |
| 287 | throw new SipException("only creator can access the profile"); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 288 | } |
| 289 | return group; |
| 290 | } |
| 291 | |
| 292 | private SipSessionGroupExt createGroup(SipProfile localProfile, |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 293 | PendingIntent incomingCallPendingIntent, |
| 294 | ISipSessionListener listener) throws SipException { |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 295 | String key = localProfile.getUriString(); |
| 296 | SipSessionGroupExt group = mSipGroups.get(key); |
| 297 | if (group != null) { |
Hung-ying Tyan | 6a53489 | 2010-09-13 18:44:33 +0800 | [diff] [blame] | 298 | if (!isCallerCreator(group)) { |
| 299 | throw new SipException("only creator can access the profile"); |
| 300 | } |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 301 | group.setIncomingCallPendingIntent(incomingCallPendingIntent); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 302 | group.setListener(listener); |
| 303 | } else { |
| 304 | group = new SipSessionGroupExt(localProfile, |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 305 | incomingCallPendingIntent, listener); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 306 | mSipGroups.put(key, group); |
| 307 | notifyProfileAdded(localProfile); |
| 308 | } |
| 309 | return group; |
| 310 | } |
| 311 | |
| 312 | private void notifyProfileAdded(SipProfile localProfile) { |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 313 | if (DEBUG) Log.d(TAG, "notify: profile added: " + localProfile); |
Hung-ying Tyan | 84a357b | 2010-09-16 04:11:32 +0800 | [diff] [blame] | 314 | Intent intent = new Intent(SipManager.ACTION_SIP_ADD_PHONE); |
| 315 | intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString()); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 316 | mContext.sendBroadcast(intent); |
| 317 | } |
| 318 | |
| 319 | private void notifyProfileRemoved(SipProfile localProfile) { |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 320 | if (DEBUG) Log.d(TAG, "notify: profile removed: " + localProfile); |
Hung-ying Tyan | 84a357b | 2010-09-16 04:11:32 +0800 | [diff] [blame] | 321 | Intent intent = new Intent(SipManager.ACTION_SIP_REMOVE_PHONE); |
| 322 | intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString()); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 323 | mContext.sendBroadcast(intent); |
| 324 | } |
| 325 | |
| 326 | private boolean anyOpened() { |
| 327 | for (SipSessionGroupExt group : mSipGroups.values()) { |
| 328 | if (group.isOpened()) return true; |
| 329 | } |
| 330 | return false; |
| 331 | } |
| 332 | |
| 333 | private void grabWifiLock() { |
| 334 | if (mWifiLock == null) { |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 335 | if (DEBUG) Log.d(TAG, "acquire wifi lock"); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 336 | mWifiLock = ((WifiManager) |
| 337 | mContext.getSystemService(Context.WIFI_SERVICE)) |
| 338 | .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG); |
| 339 | mWifiLock.acquire(); |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | private void releaseWifiLock() { |
| 344 | if (mWifiLock != null) { |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 345 | if (DEBUG) Log.d(TAG, "release wifi lock"); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 346 | mWifiLock.release(); |
| 347 | mWifiLock = null; |
| 348 | } |
| 349 | } |
| 350 | |
Hung-ying Tyan | aa562ff | 2010-10-08 08:59:38 +0800 | [diff] [blame^] | 351 | private boolean isWifiActive() { |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 352 | return "WIFI".equalsIgnoreCase(mNetworkType); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 353 | } |
| 354 | |
| 355 | private synchronized void onConnectivityChanged( |
| 356 | String type, boolean connected) { |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 357 | if (DEBUG) Log.d(TAG, "onConnectivityChanged(): " |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 358 | + mNetworkType + (mConnected? " CONNECTED" : " DISCONNECTED") |
| 359 | + " --> " + type + (connected? " CONNECTED" : " DISCONNECTED")); |
| 360 | |
| 361 | boolean sameType = type.equals(mNetworkType); |
| 362 | if (!sameType && !connected) return; |
| 363 | |
| 364 | boolean wasWifi = "WIFI".equalsIgnoreCase(mNetworkType); |
| 365 | boolean isWifi = "WIFI".equalsIgnoreCase(type); |
| 366 | boolean wifiOff = (isWifi && !connected) || (wasWifi && !sameType); |
| 367 | boolean wifiOn = isWifi && connected; |
| 368 | if (wifiOff) { |
| 369 | releaseWifiLock(); |
| 370 | } else if (wifiOn) { |
| 371 | if (anyOpened()) grabWifiLock(); |
| 372 | } |
| 373 | |
| 374 | try { |
| 375 | boolean wasConnected = mConnected; |
| 376 | mNetworkType = type; |
| 377 | mConnected = connected; |
| 378 | |
| 379 | if (wasConnected) { |
| 380 | mLocalIp = null; |
| 381 | for (SipSessionGroupExt group : mSipGroups.values()) { |
| 382 | group.onConnectivityChanged(false); |
| 383 | } |
| 384 | } |
| 385 | |
| 386 | if (connected) { |
| 387 | mLocalIp = determineLocalIp(); |
| 388 | for (SipSessionGroupExt group : mSipGroups.values()) { |
| 389 | group.onConnectivityChanged(true); |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | } catch (SipException e) { |
| 394 | Log.e(TAG, "onConnectivityChanged()", e); |
| 395 | } |
| 396 | } |
| 397 | |
| 398 | private synchronized void addPendingSession(ISipSession session) { |
| 399 | try { |
| 400 | mPendingSessions.put(session.getCallId(), session); |
| 401 | } catch (RemoteException e) { |
| 402 | // should not happen with a local call |
| 403 | Log.e(TAG, "addPendingSession()", e); |
| 404 | } |
| 405 | } |
| 406 | |
| 407 | private class SipSessionGroupExt extends SipSessionAdapter { |
| 408 | private SipSessionGroup mSipGroup; |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 409 | private PendingIntent mIncomingCallPendingIntent; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 410 | private boolean mOpened; |
| 411 | |
| 412 | private AutoRegistrationProcess mAutoRegistration = |
| 413 | new AutoRegistrationProcess(); |
| 414 | |
| 415 | public SipSessionGroupExt(SipProfile localProfile, |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 416 | PendingIntent incomingCallPendingIntent, |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 417 | ISipSessionListener listener) throws SipException { |
| 418 | String password = localProfile.getPassword(); |
| 419 | SipProfile p = duplicate(localProfile); |
| 420 | mSipGroup = createSipSessionGroup(mLocalIp, p, password); |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 421 | mIncomingCallPendingIntent = incomingCallPendingIntent; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 422 | mAutoRegistration.setListener(listener); |
| 423 | } |
| 424 | |
| 425 | public SipProfile getLocalProfile() { |
| 426 | return mSipGroup.getLocalProfile(); |
| 427 | } |
| 428 | |
| 429 | // network connectivity is tricky because network can be disconnected |
| 430 | // at any instant so need to deal with exceptions carefully even when |
| 431 | // you think you are connected |
| 432 | private SipSessionGroup createSipSessionGroup(String localIp, |
| 433 | SipProfile localProfile, String password) throws SipException { |
| 434 | try { |
| 435 | return new SipSessionGroup(localIp, localProfile, password); |
| 436 | } catch (IOException e) { |
| 437 | // network disconnected |
| 438 | Log.w(TAG, "createSipSessionGroup(): network disconnected?"); |
| 439 | if (localIp != null) { |
| 440 | return createSipSessionGroup(null, localProfile, password); |
| 441 | } else { |
| 442 | // recursive |
| 443 | Log.wtf(TAG, "impossible!"); |
| 444 | throw new RuntimeException("createSipSessionGroup"); |
| 445 | } |
| 446 | } |
| 447 | } |
| 448 | |
| 449 | private SipProfile duplicate(SipProfile p) { |
| 450 | try { |
Chung-yih Wang | 5424c8d | 2010-08-25 19:02:18 +0800 | [diff] [blame] | 451 | return new SipProfile.Builder(p).setPassword("*").build(); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 452 | } catch (Exception e) { |
| 453 | Log.wtf(TAG, "duplicate()", e); |
| 454 | throw new RuntimeException("duplicate profile", e); |
| 455 | } |
| 456 | } |
| 457 | |
| 458 | public void setListener(ISipSessionListener listener) { |
| 459 | mAutoRegistration.setListener(listener); |
| 460 | } |
| 461 | |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 462 | public void setIncomingCallPendingIntent(PendingIntent pIntent) { |
| 463 | mIncomingCallPendingIntent = pIntent; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 464 | } |
| 465 | |
| 466 | public void openToReceiveCalls() throws SipException { |
| 467 | mOpened = true; |
| 468 | if (mConnected) { |
| 469 | mSipGroup.openToReceiveCalls(this); |
| 470 | mAutoRegistration.start(mSipGroup); |
| 471 | } |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 472 | if (DEBUG) Log.d(TAG, " openToReceiveCalls: " + getUri() + ": " |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 473 | + mIncomingCallPendingIntent); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 474 | } |
| 475 | |
| 476 | public void onConnectivityChanged(boolean connected) |
| 477 | throws SipException { |
Hung-ying Tyan | d231aa8 | 2010-09-14 00:17:51 +0800 | [diff] [blame] | 478 | mSipGroup.onConnectivityChanged(); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 479 | if (connected) { |
| 480 | resetGroup(mLocalIp); |
| 481 | if (mOpened) openToReceiveCalls(); |
| 482 | } else { |
| 483 | // close mSipGroup but remember mOpened |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 484 | if (DEBUG) Log.d(TAG, " close auto reg temporarily: " |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 485 | + getUri() + ": " + mIncomingCallPendingIntent); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 486 | mSipGroup.close(); |
| 487 | mAutoRegistration.stop(); |
| 488 | } |
| 489 | } |
| 490 | |
| 491 | private void resetGroup(String localIp) throws SipException { |
| 492 | try { |
| 493 | mSipGroup.reset(localIp); |
| 494 | } catch (IOException e) { |
| 495 | // network disconnected |
| 496 | Log.w(TAG, "resetGroup(): network disconnected?"); |
| 497 | if (localIp != null) { |
| 498 | resetGroup(null); // reset w/o local IP |
| 499 | } else { |
| 500 | // recursive |
| 501 | Log.wtf(TAG, "impossible!"); |
| 502 | throw new RuntimeException("resetGroup"); |
| 503 | } |
| 504 | } |
| 505 | } |
| 506 | |
Hung-ying Tyan | fc51f2c | 2010-09-22 23:51:57 +0800 | [diff] [blame] | 507 | public void close() { |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 508 | mOpened = false; |
Hung-ying Tyan | fc51f2c | 2010-09-22 23:51:57 +0800 | [diff] [blame] | 509 | mSipGroup.close(); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 510 | mAutoRegistration.stop(); |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 511 | if (DEBUG) Log.d(TAG, " close: " + getUri() + ": " |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 512 | + mIncomingCallPendingIntent); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 513 | } |
| 514 | |
| 515 | public ISipSession createSession(ISipSessionListener listener) { |
| 516 | return mSipGroup.createSession(listener); |
| 517 | } |
| 518 | |
| 519 | @Override |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 520 | public void onRinging(ISipSession s, SipProfile caller, |
Chia-chi Yeh | 95b15c3 | 2010-09-02 22:15:26 +0800 | [diff] [blame] | 521 | String sessionDescription) { |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 522 | SipSessionGroup.SipSessionImpl session = |
| 523 | (SipSessionGroup.SipSessionImpl) s; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 524 | synchronized (SipService.this) { |
| 525 | try { |
| 526 | if (!isRegistered()) { |
| 527 | session.endCall(); |
| 528 | return; |
| 529 | } |
| 530 | |
| 531 | // send out incoming call broadcast |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 532 | addPendingSession(session); |
| 533 | Intent intent = SipManager.createIncomingCallBroadcast( |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 534 | session.getCallId(), sessionDescription); |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 535 | if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": " |
| 536 | + caller.getUri() + ": " + session.getCallId() |
Hung-ying Tyan | 323d367 | 2010-10-05 09:35:38 +0800 | [diff] [blame] | 537 | + " " + mIncomingCallPendingIntent); |
| 538 | mIncomingCallPendingIntent.send(mContext, |
| 539 | SipManager.INCOMING_CALL_RESULT_CODE, intent); |
| 540 | } catch (PendingIntent.CanceledException e) { |
| 541 | Log.w(TAG, "pendingIntent is canceled, drop incoming call"); |
| 542 | session.endCall(); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 543 | } |
| 544 | } |
| 545 | } |
| 546 | |
| 547 | @Override |
Hung-ying Tyan | 9796379 | 2010-09-17 16:58:51 +0800 | [diff] [blame] | 548 | public void onError(ISipSession session, int errorCode, |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 549 | String message) { |
Hung-ying Tyan | 9796379 | 2010-09-17 16:58:51 +0800 | [diff] [blame] | 550 | if (DEBUG) Log.d(TAG, "sip session error: " |
| 551 | + SipErrorCode.toString(errorCode) + ": " + message); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 552 | } |
| 553 | |
| 554 | public boolean isOpened() { |
| 555 | return mOpened; |
| 556 | } |
| 557 | |
| 558 | public boolean isRegistered() { |
| 559 | return mAutoRegistration.isRegistered(); |
| 560 | } |
| 561 | |
| 562 | private String getUri() { |
| 563 | return mSipGroup.getLocalProfileUri(); |
| 564 | } |
| 565 | } |
| 566 | |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 567 | // KeepAliveProcess is controlled by AutoRegistrationProcess. |
| 568 | // All methods will be invoked in sync with SipService.this except realRun() |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 569 | private class KeepAliveProcess implements Runnable { |
| 570 | private static final String TAG = "\\KEEPALIVE/"; |
Chung-yih Wang | 4d1e012 | 2010-09-09 13:27:10 +0800 | [diff] [blame] | 571 | private static final int INTERVAL = 10; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 572 | private SipSessionGroup.SipSessionImpl mSession; |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 573 | private boolean mRunning = false; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 574 | |
| 575 | public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) { |
| 576 | mSession = session; |
| 577 | } |
| 578 | |
| 579 | public void start() { |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 580 | if (mRunning) return; |
| 581 | mRunning = true; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 582 | mTimer.set(INTERVAL * 1000, this); |
| 583 | } |
| 584 | |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 585 | // timeout handler |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 586 | public void run() { |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 587 | if (!mRunning) return; |
| 588 | final SipSessionGroup.SipSessionImpl session = mSession; |
| 589 | |
Hung-ying Tyan | b17eae9 | 2010-09-19 00:26:38 +0800 | [diff] [blame] | 590 | // delegate to mExecutor |
| 591 | getExecutor().addTask(new Runnable() { |
| 592 | public void run() { |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 593 | realRun(session); |
Hung-ying Tyan | b17eae9 | 2010-09-19 00:26:38 +0800 | [diff] [blame] | 594 | } |
| 595 | }); |
| 596 | } |
| 597 | |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 598 | // real timeout handler |
| 599 | private void realRun(SipSessionGroup.SipSessionImpl session) { |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 600 | synchronized (SipService.this) { |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 601 | if (notCurrentSession(session)) return; |
| 602 | |
| 603 | session = session.duplicate(); |
Hung-ying Tyan | b031957 | 2010-10-01 07:09:30 +0800 | [diff] [blame] | 604 | if (DEBUGV) Log.v(TAG, "~~~ keepalive"); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 605 | mTimer.cancel(this); |
| 606 | session.sendKeepAlive(); |
| 607 | if (session.isReRegisterRequired()) { |
| 608 | mSession.register(EXPIRY_TIME); |
| 609 | } else { |
| 610 | mTimer.set(INTERVAL * 1000, this); |
| 611 | } |
| 612 | } |
| 613 | } |
| 614 | |
| 615 | public void stop() { |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 616 | mRunning = false; |
| 617 | mSession = null; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 618 | mTimer.cancel(this); |
| 619 | } |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 620 | |
| 621 | private boolean notCurrentSession(ISipSession session) { |
| 622 | return (session != mSession) || !mRunning; |
| 623 | } |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 624 | } |
| 625 | |
| 626 | private class AutoRegistrationProcess extends SipSessionAdapter |
| 627 | implements Runnable { |
| 628 | private SipSessionGroup.SipSessionImpl mSession; |
| 629 | private SipSessionListenerProxy mProxy = new SipSessionListenerProxy(); |
| 630 | private KeepAliveProcess mKeepAliveProcess; |
| 631 | private int mBackoff = 1; |
| 632 | private boolean mRegistered; |
| 633 | private long mExpiryTime; |
Hung-ying Tyan | 9796379 | 2010-09-17 16:58:51 +0800 | [diff] [blame] | 634 | private int mErrorCode; |
Hung-ying Tyan | 3d7606a | 2010-09-12 23:50:38 +0800 | [diff] [blame] | 635 | private String mErrorMessage; |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 636 | private boolean mRunning = false; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 637 | |
| 638 | private String getAction() { |
| 639 | return toString(); |
| 640 | } |
| 641 | |
| 642 | public void start(SipSessionGroup group) { |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 643 | if (!mRunning) { |
| 644 | mRunning = true; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 645 | mBackoff = 1; |
| 646 | mSession = (SipSessionGroup.SipSessionImpl) |
| 647 | group.createSession(this); |
| 648 | // return right away if no active network connection. |
| 649 | if (mSession == null) return; |
| 650 | |
| 651 | // start unregistration to clear up old registration at server |
| 652 | // TODO: when rfc5626 is deployed, use reg-id and sip.instance |
| 653 | // in registration to avoid adding duplicate entries to server |
| 654 | mSession.unregister(); |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 655 | if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess for " |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 656 | + mSession.getLocalProfile().getUriString()); |
| 657 | } |
| 658 | } |
| 659 | |
| 660 | public void stop() { |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 661 | if (!mRunning) return; |
| 662 | mRunning = false; |
| 663 | mSession.setListener(null); |
Hung-ying Tyan | 3d7606a | 2010-09-12 23:50:38 +0800 | [diff] [blame] | 664 | if (mConnected && mRegistered) mSession.unregister(); |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 665 | |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 666 | mTimer.cancel(this); |
| 667 | if (mKeepAliveProcess != null) { |
| 668 | mKeepAliveProcess.stop(); |
| 669 | mKeepAliveProcess = null; |
| 670 | } |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 671 | |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 672 | mRegistered = false; |
| 673 | setListener(mProxy.getListener()); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 674 | } |
| 675 | |
| 676 | public void setListener(ISipSessionListener listener) { |
Hung-ying Tyan | 3d7606a | 2010-09-12 23:50:38 +0800 | [diff] [blame] | 677 | synchronized (SipService.this) { |
| 678 | mProxy.setListener(listener); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 679 | |
Hung-ying Tyan | 3d7606a | 2010-09-12 23:50:38 +0800 | [diff] [blame] | 680 | try { |
Hung-ying Tyan | 9796379 | 2010-09-17 16:58:51 +0800 | [diff] [blame] | 681 | int state = (mSession == null) |
Hung-ying Tyan | 84a357b | 2010-09-16 04:11:32 +0800 | [diff] [blame] | 682 | ? SipSession.State.READY_TO_CALL |
Hung-ying Tyan | 9796379 | 2010-09-17 16:58:51 +0800 | [diff] [blame] | 683 | : mSession.getState(); |
Hung-ying Tyan | 84a357b | 2010-09-16 04:11:32 +0800 | [diff] [blame] | 684 | if ((state == SipSession.State.REGISTERING) |
| 685 | || (state == SipSession.State.DEREGISTERING)) { |
Hung-ying Tyan | 3d7606a | 2010-09-12 23:50:38 +0800 | [diff] [blame] | 686 | mProxy.onRegistering(mSession); |
| 687 | } else if (mRegistered) { |
| 688 | int duration = (int) |
| 689 | (mExpiryTime - SystemClock.elapsedRealtime()); |
| 690 | mProxy.onRegistrationDone(mSession, duration); |
Hung-ying Tyan | 9796379 | 2010-09-17 16:58:51 +0800 | [diff] [blame] | 691 | } else if (mErrorCode != SipErrorCode.NO_ERROR) { |
Hung-ying Tyan | 3d7606a | 2010-09-12 23:50:38 +0800 | [diff] [blame] | 692 | if (mErrorCode == SipErrorCode.TIME_OUT) { |
| 693 | mProxy.onRegistrationTimeout(mSession); |
| 694 | } else { |
Hung-ying Tyan | 9796379 | 2010-09-17 16:58:51 +0800 | [diff] [blame] | 695 | mProxy.onRegistrationFailed(mSession, mErrorCode, |
| 696 | mErrorMessage); |
Hung-ying Tyan | 3d7606a | 2010-09-12 23:50:38 +0800 | [diff] [blame] | 697 | } |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 698 | } else if (!mConnected) { |
| 699 | mProxy.onRegistrationFailed(mSession, |
| 700 | SipErrorCode.DATA_CONNECTION_LOST, |
| 701 | "no data connection"); |
| 702 | } else if (!mRunning) { |
| 703 | mProxy.onRegistrationFailed(mSession, |
| 704 | SipErrorCode.CLIENT_ERROR, |
| 705 | "registration not running"); |
| 706 | } else { |
| 707 | mProxy.onRegistrationFailed(mSession, |
| 708 | SipErrorCode.IN_PROGRESS, |
| 709 | String.valueOf(state)); |
Hung-ying Tyan | 3d7606a | 2010-09-12 23:50:38 +0800 | [diff] [blame] | 710 | } |
| 711 | } catch (Throwable t) { |
| 712 | Log.w(TAG, "setListener(): " + t); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 713 | } |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 714 | } |
| 715 | } |
| 716 | |
| 717 | public boolean isRegistered() { |
| 718 | return mRegistered; |
| 719 | } |
| 720 | |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 721 | // timeout handler |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 722 | public void run() { |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 723 | synchronized (SipService.this) { |
| 724 | if (!mRunning) return; |
| 725 | final SipSessionGroup.SipSessionImpl session = mSession; |
| 726 | |
| 727 | // delegate to mExecutor |
| 728 | getExecutor().addTask(new Runnable() { |
| 729 | public void run() { |
| 730 | realRun(session); |
| 731 | } |
| 732 | }); |
| 733 | } |
Hung-ying Tyan | b17eae9 | 2010-09-19 00:26:38 +0800 | [diff] [blame] | 734 | } |
| 735 | |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 736 | // real timeout handler |
| 737 | private void realRun(SipSessionGroup.SipSessionImpl session) { |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 738 | synchronized (SipService.this) { |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 739 | if (notCurrentSession(session)) return; |
| 740 | mErrorCode = SipErrorCode.NO_ERROR; |
| 741 | mErrorMessage = null; |
| 742 | if (DEBUG) Log.d(TAG, "~~~ registering"); |
| 743 | if (mConnected) session.register(EXPIRY_TIME); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 744 | } |
| 745 | } |
| 746 | |
| 747 | private boolean isBehindNAT(String address) { |
| 748 | try { |
| 749 | byte[] d = InetAddress.getByName(address).getAddress(); |
| 750 | if ((d[0] == 10) || |
| 751 | (((0x000000FF & ((int)d[0])) == 172) && |
| 752 | ((0x000000F0 & ((int)d[1])) == 16)) || |
| 753 | (((0x000000FF & ((int)d[0])) == 192) && |
| 754 | ((0x000000FF & ((int)d[1])) == 168))) { |
| 755 | return true; |
| 756 | } |
| 757 | } catch (UnknownHostException e) { |
| 758 | Log.e(TAG, "isBehindAT()" + address, e); |
| 759 | } |
| 760 | return false; |
| 761 | } |
| 762 | |
| 763 | private void restart(int duration) { |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 764 | if (DEBUG) Log.d(TAG, "Refresh registration " + duration + "s later."); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 765 | mTimer.cancel(this); |
| 766 | mTimer.set(duration * 1000, this); |
| 767 | } |
| 768 | |
| 769 | private int backoffDuration() { |
| 770 | int duration = SHORT_EXPIRY_TIME * mBackoff; |
| 771 | if (duration > 3600) { |
| 772 | duration = 3600; |
| 773 | } else { |
| 774 | mBackoff *= 2; |
| 775 | } |
| 776 | return duration; |
| 777 | } |
| 778 | |
| 779 | @Override |
| 780 | public void onRegistering(ISipSession session) { |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 781 | if (DEBUG) Log.d(TAG, "onRegistering(): " + session); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 782 | synchronized (SipService.this) { |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 783 | if (notCurrentSession(session)) return; |
| 784 | |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 785 | mRegistered = false; |
Hung-ying Tyan | 3d7606a | 2010-09-12 23:50:38 +0800 | [diff] [blame] | 786 | mProxy.onRegistering(session); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 787 | } |
| 788 | } |
| 789 | |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 790 | private boolean notCurrentSession(ISipSession session) { |
| 791 | if (session != mSession) { |
| 792 | ((SipSessionGroup.SipSessionImpl) session).setListener(null); |
| 793 | return true; |
| 794 | } |
| 795 | return !mRunning; |
| 796 | } |
| 797 | |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 798 | @Override |
| 799 | public void onRegistrationDone(ISipSession session, int duration) { |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 800 | if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 801 | synchronized (SipService.this) { |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 802 | if (notCurrentSession(session)) return; |
Hung-ying Tyan | 3d7606a | 2010-09-12 23:50:38 +0800 | [diff] [blame] | 803 | |
| 804 | mProxy.onRegistrationDone(session, duration); |
| 805 | |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 806 | if (duration > 0) { |
| 807 | mSession.clearReRegisterRequired(); |
| 808 | mExpiryTime = SystemClock.elapsedRealtime() |
| 809 | + (duration * 1000); |
| 810 | |
| 811 | if (!mRegistered) { |
| 812 | mRegistered = true; |
| 813 | // allow some overlap to avoid call drop during renew |
| 814 | duration -= MIN_EXPIRY_TIME; |
| 815 | if (duration < MIN_EXPIRY_TIME) { |
| 816 | duration = MIN_EXPIRY_TIME; |
| 817 | } |
| 818 | restart(duration); |
| 819 | |
| 820 | if (isBehindNAT(mLocalIp) || |
| 821 | mSession.getLocalProfile().getSendKeepAlive()) { |
| 822 | if (mKeepAliveProcess == null) { |
| 823 | mKeepAliveProcess = |
| 824 | new KeepAliveProcess(mSession); |
| 825 | } |
| 826 | mKeepAliveProcess.start(); |
| 827 | } |
| 828 | } |
| 829 | } else { |
| 830 | mRegistered = false; |
| 831 | mExpiryTime = -1L; |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 832 | if (DEBUG) Log.d(TAG, "Refresh registration immediately"); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 833 | run(); |
| 834 | } |
| 835 | } |
| 836 | } |
| 837 | |
| 838 | @Override |
Hung-ying Tyan | 9796379 | 2010-09-17 16:58:51 +0800 | [diff] [blame] | 839 | public void onRegistrationFailed(ISipSession session, int errorCode, |
| 840 | String message) { |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 841 | if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": " |
Hung-ying Tyan | 9796379 | 2010-09-17 16:58:51 +0800 | [diff] [blame] | 842 | + SipErrorCode.toString(errorCode) + ": " + message); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 843 | synchronized (SipService.this) { |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 844 | if (notCurrentSession(session)) return; |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 845 | |
Hung-ying Tyan | ee8a884 | 2010-10-06 08:33:47 +0800 | [diff] [blame] | 846 | switch (errorCode) { |
| 847 | case SipErrorCode.INVALID_CREDENTIALS: |
| 848 | case SipErrorCode.SERVER_UNREACHABLE: |
| 849 | if (DEBUG) Log.d(TAG, " pause auto-registration"); |
| 850 | stop(); |
| 851 | default: |
| 852 | restartLater(); |
Hung-ying Tyan | 3d7606a | 2010-09-12 23:50:38 +0800 | [diff] [blame] | 853 | } |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 854 | |
| 855 | mErrorCode = errorCode; |
| 856 | mErrorMessage = message; |
| 857 | mProxy.onRegistrationFailed(session, errorCode, message); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 858 | } |
| 859 | } |
| 860 | |
| 861 | @Override |
| 862 | public void onRegistrationTimeout(ISipSession session) { |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 863 | if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 864 | synchronized (SipService.this) { |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 865 | if (notCurrentSession(session)) return; |
| 866 | |
Hung-ying Tyan | 3d7606a | 2010-09-12 23:50:38 +0800 | [diff] [blame] | 867 | mErrorCode = SipErrorCode.TIME_OUT; |
| 868 | mProxy.onRegistrationTimeout(session); |
Hung-ying Tyan | ee8a884 | 2010-10-06 08:33:47 +0800 | [diff] [blame] | 869 | restartLater(); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 870 | } |
| 871 | } |
| 872 | |
Hung-ying Tyan | ee8a884 | 2010-10-06 08:33:47 +0800 | [diff] [blame] | 873 | private void restartLater() { |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 874 | mRegistered = false; |
| 875 | restart(backoffDuration()); |
| 876 | if (mKeepAliveProcess != null) { |
| 877 | mKeepAliveProcess.stop(); |
| 878 | mKeepAliveProcess = null; |
| 879 | } |
| 880 | } |
| 881 | } |
| 882 | |
| 883 | private class ConnectivityReceiver extends BroadcastReceiver { |
| 884 | private Timer mTimer = new Timer(); |
| 885 | private MyTimerTask mTask; |
| 886 | |
| 887 | @Override |
| 888 | public void onReceive(Context context, Intent intent) { |
| 889 | String action = intent.getAction(); |
| 890 | if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { |
| 891 | Bundle b = intent.getExtras(); |
| 892 | if (b != null) { |
| 893 | NetworkInfo netInfo = (NetworkInfo) |
| 894 | b.get(ConnectivityManager.EXTRA_NETWORK_INFO); |
| 895 | String type = netInfo.getTypeName(); |
| 896 | NetworkInfo.State state = netInfo.getState(); |
Hung-ying Tyan | 12bec5d | 2010-09-14 19:33:10 +0800 | [diff] [blame] | 897 | |
Hung-ying Tyan | c4b8747 | 2010-09-19 18:23:44 +0800 | [diff] [blame] | 898 | if (mWifiOnly && (netInfo.getType() != |
| 899 | ConnectivityManager.TYPE_WIFI)) { |
| 900 | if (DEBUG) { |
| 901 | Log.d(TAG, "Wifi only, other connectivity ignored: " |
| 902 | + type); |
| 903 | } |
| 904 | return; |
| 905 | } |
| 906 | |
Hung-ying Tyan | 12bec5d | 2010-09-14 19:33:10 +0800 | [diff] [blame] | 907 | NetworkInfo activeNetInfo = getActiveNetworkInfo(); |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 908 | if (DEBUG) { |
| 909 | if (activeNetInfo != null) { |
| 910 | Log.d(TAG, "active network: " |
| 911 | + activeNetInfo.getTypeName() |
| 912 | + ((activeNetInfo.getState() == NetworkInfo.State.CONNECTED) |
| 913 | ? " CONNECTED" : " DISCONNECTED")); |
| 914 | } else { |
| 915 | Log.d(TAG, "active network: null"); |
| 916 | } |
Hung-ying Tyan | 12bec5d | 2010-09-14 19:33:10 +0800 | [diff] [blame] | 917 | } |
| 918 | if ((state == NetworkInfo.State.CONNECTED) |
| 919 | && (activeNetInfo != null) |
| 920 | && (activeNetInfo.getType() != netInfo.getType())) { |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 921 | if (DEBUG) Log.d(TAG, "ignore connect event: " + type |
Hung-ying Tyan | 12bec5d | 2010-09-14 19:33:10 +0800 | [diff] [blame] | 922 | + ", active: " + activeNetInfo.getTypeName()); |
| 923 | return; |
| 924 | } |
| 925 | |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 926 | if (state == NetworkInfo.State.CONNECTED) { |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 927 | if (DEBUG) Log.d(TAG, "Connectivity alert: CONNECTED " + type); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 928 | onChanged(type, true); |
| 929 | } else if (state == NetworkInfo.State.DISCONNECTED) { |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 930 | if (DEBUG) Log.d(TAG, "Connectivity alert: DISCONNECTED " + type); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 931 | onChanged(type, false); |
| 932 | } else { |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 933 | if (DEBUG) Log.d(TAG, "Connectivity alert not processed: " |
| 934 | + state + " " + type); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 935 | } |
| 936 | } |
| 937 | } |
| 938 | } |
| 939 | |
Hung-ying Tyan | 12bec5d | 2010-09-14 19:33:10 +0800 | [diff] [blame] | 940 | private NetworkInfo getActiveNetworkInfo() { |
| 941 | ConnectivityManager cm = (ConnectivityManager) |
| 942 | mContext.getSystemService(Context.CONNECTIVITY_SERVICE); |
| 943 | return cm.getActiveNetworkInfo(); |
| 944 | } |
| 945 | |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 946 | private void onChanged(String type, boolean connected) { |
| 947 | synchronized (SipService.this) { |
| 948 | // When turning on WIFI, it needs some time for network |
| 949 | // connectivity to get stabile so we defer good news (because |
| 950 | // we want to skip the interim ones) but deliver bad news |
| 951 | // immediately |
| 952 | if (connected) { |
| 953 | if (mTask != null) mTask.cancel(); |
| 954 | mTask = new MyTimerTask(type, connected); |
Hung-ying Tyan | b17eae9 | 2010-09-19 00:26:38 +0800 | [diff] [blame] | 955 | mTimer.schedule(mTask, 2 * 1000L); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 956 | // TODO: hold wakup lock so that we can finish change before |
| 957 | // the device goes to sleep |
| 958 | } else { |
| 959 | if ((mTask != null) && mTask.mNetworkType.equals(type)) { |
| 960 | mTask.cancel(); |
| 961 | } |
| 962 | onConnectivityChanged(type, false); |
| 963 | } |
| 964 | } |
| 965 | } |
| 966 | |
| 967 | private class MyTimerTask extends TimerTask { |
| 968 | private boolean mConnected; |
| 969 | private String mNetworkType; |
| 970 | |
| 971 | public MyTimerTask(String type, boolean connected) { |
| 972 | mNetworkType = type; |
| 973 | mConnected = connected; |
| 974 | } |
| 975 | |
Hung-ying Tyan | fb3a98b | 2010-09-30 07:49:35 +0800 | [diff] [blame] | 976 | // timeout handler |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 977 | @Override |
| 978 | public void run() { |
Hung-ying Tyan | b17eae9 | 2010-09-19 00:26:38 +0800 | [diff] [blame] | 979 | // delegate to mExecutor |
| 980 | getExecutor().addTask(new Runnable() { |
| 981 | public void run() { |
| 982 | realRun(); |
| 983 | } |
| 984 | }); |
| 985 | } |
| 986 | |
| 987 | private void realRun() { |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 988 | synchronized (SipService.this) { |
| 989 | if (mTask != this) { |
| 990 | Log.w(TAG, " unexpected task: " + mNetworkType |
| 991 | + (mConnected ? " CONNECTED" : "DISCONNECTED")); |
| 992 | return; |
| 993 | } |
| 994 | mTask = null; |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 995 | if (DEBUG) Log.d(TAG, " deliver change for " + mNetworkType |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 996 | + (mConnected ? " CONNECTED" : "DISCONNECTED")); |
| 997 | onConnectivityChanged(mNetworkType, mConnected); |
| 998 | } |
| 999 | } |
| 1000 | } |
| 1001 | } |
| 1002 | |
| 1003 | // TODO: clean up pending SipSession(s) periodically |
| 1004 | |
| 1005 | |
| 1006 | /** |
| 1007 | * Timer that can schedule events to occur even when the device is in sleep. |
| 1008 | * Only used internally in this package. |
| 1009 | */ |
| 1010 | class WakeupTimer extends BroadcastReceiver { |
| 1011 | private static final String TAG = "_SIP.WkTimer_"; |
| 1012 | private static final String TRIGGER_TIME = "TriggerTime"; |
| 1013 | |
| 1014 | private Context mContext; |
| 1015 | private AlarmManager mAlarmManager; |
| 1016 | |
| 1017 | // runnable --> time to execute in SystemClock |
| 1018 | private TreeSet<MyEvent> mEventQueue = |
| 1019 | new TreeSet<MyEvent>(new MyEventComparator()); |
| 1020 | |
| 1021 | private PendingIntent mPendingIntent; |
| 1022 | |
| 1023 | public WakeupTimer(Context context) { |
| 1024 | mContext = context; |
| 1025 | mAlarmManager = (AlarmManager) |
| 1026 | context.getSystemService(Context.ALARM_SERVICE); |
| 1027 | |
| 1028 | IntentFilter filter = new IntentFilter(getAction()); |
| 1029 | context.registerReceiver(this, filter); |
| 1030 | } |
| 1031 | |
| 1032 | /** |
| 1033 | * Stops the timer. No event can be scheduled after this method is called. |
| 1034 | */ |
| 1035 | public synchronized void stop() { |
| 1036 | mContext.unregisterReceiver(this); |
| 1037 | if (mPendingIntent != null) { |
| 1038 | mAlarmManager.cancel(mPendingIntent); |
| 1039 | mPendingIntent = null; |
| 1040 | } |
| 1041 | mEventQueue.clear(); |
| 1042 | mEventQueue = null; |
| 1043 | } |
| 1044 | |
| 1045 | private synchronized boolean stopped() { |
| 1046 | if (mEventQueue == null) { |
| 1047 | Log.w(TAG, "Timer stopped"); |
| 1048 | return true; |
| 1049 | } else { |
| 1050 | return false; |
| 1051 | } |
| 1052 | } |
| 1053 | |
| 1054 | private void cancelAlarm() { |
| 1055 | mAlarmManager.cancel(mPendingIntent); |
| 1056 | mPendingIntent = null; |
| 1057 | } |
| 1058 | |
| 1059 | private void recalculatePeriods() { |
| 1060 | if (mEventQueue.isEmpty()) return; |
| 1061 | |
| 1062 | MyEvent firstEvent = mEventQueue.first(); |
| 1063 | int minPeriod = firstEvent.mMaxPeriod; |
| 1064 | long minTriggerTime = firstEvent.mTriggerTime; |
| 1065 | for (MyEvent e : mEventQueue) { |
| 1066 | e.mPeriod = e.mMaxPeriod / minPeriod * minPeriod; |
| 1067 | int interval = (int) (e.mLastTriggerTime + e.mMaxPeriod |
| 1068 | - minTriggerTime); |
| 1069 | interval = interval / minPeriod * minPeriod; |
| 1070 | e.mTriggerTime = minTriggerTime + interval; |
| 1071 | } |
| 1072 | TreeSet<MyEvent> newQueue = new TreeSet<MyEvent>( |
| 1073 | mEventQueue.comparator()); |
| 1074 | newQueue.addAll((Collection<MyEvent>) mEventQueue); |
| 1075 | mEventQueue.clear(); |
| 1076 | mEventQueue = newQueue; |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 1077 | if (DEBUG_TIMER) { |
| 1078 | Log.d(TAG, "queue re-calculated"); |
| 1079 | printQueue(); |
| 1080 | } |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 1081 | } |
| 1082 | |
| 1083 | // Determines the period and the trigger time of the new event and insert it |
| 1084 | // to the queue. |
| 1085 | private void insertEvent(MyEvent event) { |
| 1086 | long now = SystemClock.elapsedRealtime(); |
| 1087 | if (mEventQueue.isEmpty()) { |
| 1088 | event.mTriggerTime = now + event.mPeriod; |
| 1089 | mEventQueue.add(event); |
| 1090 | return; |
| 1091 | } |
| 1092 | MyEvent firstEvent = mEventQueue.first(); |
| 1093 | int minPeriod = firstEvent.mPeriod; |
| 1094 | if (minPeriod <= event.mMaxPeriod) { |
| 1095 | event.mPeriod = event.mMaxPeriod / minPeriod * minPeriod; |
| 1096 | int interval = event.mMaxPeriod; |
| 1097 | interval -= (int) (firstEvent.mTriggerTime - now); |
| 1098 | interval = interval / minPeriod * minPeriod; |
| 1099 | event.mTriggerTime = firstEvent.mTriggerTime + interval; |
| 1100 | mEventQueue.add(event); |
| 1101 | } else { |
| 1102 | long triggerTime = now + event.mPeriod; |
| 1103 | if (firstEvent.mTriggerTime < triggerTime) { |
| 1104 | event.mTriggerTime = firstEvent.mTriggerTime; |
| 1105 | event.mLastTriggerTime -= event.mPeriod; |
| 1106 | } else { |
| 1107 | event.mTriggerTime = triggerTime; |
| 1108 | } |
| 1109 | mEventQueue.add(event); |
| 1110 | recalculatePeriods(); |
| 1111 | } |
| 1112 | } |
| 1113 | |
| 1114 | /** |
| 1115 | * Sets a periodic timer. |
| 1116 | * |
| 1117 | * @param period the timer period; in milli-second |
| 1118 | * @param callback is called back when the timer goes off; the same callback |
| 1119 | * can be specified in multiple timer events |
| 1120 | */ |
| 1121 | public synchronized void set(int period, Runnable callback) { |
| 1122 | if (stopped()) return; |
| 1123 | |
| 1124 | long now = SystemClock.elapsedRealtime(); |
| 1125 | MyEvent event = new MyEvent(period, callback, now); |
| 1126 | insertEvent(event); |
| 1127 | |
| 1128 | if (mEventQueue.first() == event) { |
| 1129 | if (mEventQueue.size() > 1) cancelAlarm(); |
| 1130 | scheduleNext(); |
| 1131 | } |
| 1132 | |
| 1133 | long triggerTime = event.mTriggerTime; |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 1134 | if (DEBUG_TIMER) { |
| 1135 | Log.d(TAG, " add event " + event + " scheduled at " |
| 1136 | + showTime(triggerTime) + " at " + showTime(now) |
| 1137 | + ", #events=" + mEventQueue.size()); |
| 1138 | printQueue(); |
| 1139 | } |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 1140 | } |
| 1141 | |
| 1142 | /** |
| 1143 | * Cancels all the timer events with the specified callback. |
| 1144 | * |
| 1145 | * @param callback the callback |
| 1146 | */ |
| 1147 | public synchronized void cancel(Runnable callback) { |
| 1148 | if (stopped() || mEventQueue.isEmpty()) return; |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 1149 | if (DEBUG_TIMER) Log.d(TAG, "cancel:" + callback); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 1150 | |
| 1151 | MyEvent firstEvent = mEventQueue.first(); |
| 1152 | for (Iterator<MyEvent> iter = mEventQueue.iterator(); |
| 1153 | iter.hasNext();) { |
| 1154 | MyEvent event = iter.next(); |
| 1155 | if (event.mCallback == callback) { |
| 1156 | iter.remove(); |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 1157 | if (DEBUG_TIMER) Log.d(TAG, " cancel found:" + event); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 1158 | } |
| 1159 | } |
| 1160 | if (mEventQueue.isEmpty()) { |
| 1161 | cancelAlarm(); |
| 1162 | } else if (mEventQueue.first() != firstEvent) { |
| 1163 | cancelAlarm(); |
| 1164 | firstEvent = mEventQueue.first(); |
| 1165 | firstEvent.mPeriod = firstEvent.mMaxPeriod; |
| 1166 | firstEvent.mTriggerTime = firstEvent.mLastTriggerTime |
| 1167 | + firstEvent.mPeriod; |
| 1168 | recalculatePeriods(); |
| 1169 | scheduleNext(); |
| 1170 | } |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 1171 | if (DEBUG_TIMER) { |
| 1172 | Log.d(TAG, "after cancel:"); |
| 1173 | printQueue(); |
| 1174 | } |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 1175 | } |
| 1176 | |
| 1177 | private void scheduleNext() { |
| 1178 | if (stopped() || mEventQueue.isEmpty()) return; |
| 1179 | |
| 1180 | if (mPendingIntent != null) { |
| 1181 | throw new RuntimeException("pendingIntent is not null!"); |
| 1182 | } |
| 1183 | |
| 1184 | MyEvent event = mEventQueue.first(); |
| 1185 | Intent intent = new Intent(getAction()); |
| 1186 | intent.putExtra(TRIGGER_TIME, event.mTriggerTime); |
| 1187 | PendingIntent pendingIntent = mPendingIntent = |
| 1188 | PendingIntent.getBroadcast(mContext, 0, intent, |
| 1189 | PendingIntent.FLAG_UPDATE_CURRENT); |
| 1190 | mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, |
| 1191 | event.mTriggerTime, pendingIntent); |
| 1192 | } |
| 1193 | |
| 1194 | @Override |
| 1195 | public synchronized void onReceive(Context context, Intent intent) { |
| 1196 | String action = intent.getAction(); |
| 1197 | if (getAction().equals(action) |
| 1198 | && intent.getExtras().containsKey(TRIGGER_TIME)) { |
| 1199 | mPendingIntent = null; |
| 1200 | long triggerTime = intent.getLongExtra(TRIGGER_TIME, -1L); |
| 1201 | execute(triggerTime); |
| 1202 | } else { |
| 1203 | Log.d(TAG, "unrecognized intent: " + intent); |
| 1204 | } |
| 1205 | } |
| 1206 | |
| 1207 | private void printQueue() { |
| 1208 | int count = 0; |
| 1209 | for (MyEvent event : mEventQueue) { |
| 1210 | Log.d(TAG, " " + event + ": scheduled at " |
| 1211 | + showTime(event.mTriggerTime) + ": last at " |
| 1212 | + showTime(event.mLastTriggerTime)); |
| 1213 | if (++count >= 5) break; |
| 1214 | } |
| 1215 | if (mEventQueue.size() > count) { |
| 1216 | Log.d(TAG, " ....."); |
| 1217 | } else if (count == 0) { |
| 1218 | Log.d(TAG, " <empty>"); |
| 1219 | } |
| 1220 | } |
| 1221 | |
| 1222 | private void execute(long triggerTime) { |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 1223 | if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = " |
| 1224 | + showTime(triggerTime) + ": " + mEventQueue.size()); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 1225 | if (stopped() || mEventQueue.isEmpty()) return; |
| 1226 | |
| 1227 | for (MyEvent event : mEventQueue) { |
| 1228 | if (event.mTriggerTime != triggerTime) break; |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 1229 | if (DEBUG_TIMER) Log.d(TAG, "execute " + event); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 1230 | |
| 1231 | event.mLastTriggerTime = event.mTriggerTime; |
| 1232 | event.mTriggerTime += event.mPeriod; |
| 1233 | |
| 1234 | // run the callback in a new thread to prevent deadlock |
Hung-ying Tyan | 84a357b | 2010-09-16 04:11:32 +0800 | [diff] [blame] | 1235 | new Thread(event.mCallback, "SipServiceTimerCallbackThread") |
| 1236 | .start(); |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 1237 | } |
Hung-ying Tyan | c751058 | 2010-09-16 05:45:19 +0800 | [diff] [blame] | 1238 | if (DEBUG_TIMER) { |
| 1239 | Log.d(TAG, "after timeout execution"); |
| 1240 | printQueue(); |
| 1241 | } |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 1242 | scheduleNext(); |
| 1243 | } |
| 1244 | |
| 1245 | private String getAction() { |
| 1246 | return toString(); |
| 1247 | } |
| 1248 | |
Chung-yih Wang | 1d62c77 | 2010-08-06 11:32:24 +0800 | [diff] [blame] | 1249 | private String showTime(long time) { |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 1250 | int ms = (int) (time % 1000); |
| 1251 | int s = (int) (time / 1000); |
| 1252 | int m = s / 60; |
| 1253 | s %= 60; |
| 1254 | return String.format("%d.%d.%d", m, s, ms); |
| 1255 | } |
| 1256 | } |
Chung-yih Wang | 1d62c77 | 2010-08-06 11:32:24 +0800 | [diff] [blame] | 1257 | |
| 1258 | private static class MyEvent { |
| 1259 | int mPeriod; |
| 1260 | int mMaxPeriod; |
| 1261 | long mTriggerTime; |
| 1262 | long mLastTriggerTime; |
| 1263 | Runnable mCallback; |
| 1264 | |
| 1265 | MyEvent(int period, Runnable callback, long now) { |
| 1266 | mPeriod = mMaxPeriod = period; |
| 1267 | mCallback = callback; |
| 1268 | mLastTriggerTime = now; |
| 1269 | } |
| 1270 | |
| 1271 | @Override |
| 1272 | public String toString() { |
| 1273 | String s = super.toString(); |
| 1274 | s = s.substring(s.indexOf("@")); |
| 1275 | return s + ":" + (mPeriod / 1000) + ":" + (mMaxPeriod / 1000) + ":" |
| 1276 | + toString(mCallback); |
| 1277 | } |
| 1278 | |
| 1279 | private String toString(Object o) { |
| 1280 | String s = o.toString(); |
| 1281 | int index = s.indexOf("$"); |
| 1282 | if (index > 0) s = s.substring(index + 1); |
| 1283 | return s; |
| 1284 | } |
| 1285 | } |
| 1286 | |
| 1287 | private static class MyEventComparator implements Comparator<MyEvent> { |
| 1288 | public int compare(MyEvent e1, MyEvent e2) { |
| 1289 | if (e1 == e2) return 0; |
| 1290 | int diff = e1.mMaxPeriod - e2.mMaxPeriod; |
| 1291 | if (diff == 0) diff = -1; |
| 1292 | return diff; |
| 1293 | } |
| 1294 | |
| 1295 | public boolean equals(Object that) { |
| 1296 | return (this == that); |
| 1297 | } |
| 1298 | } |
Hung-ying Tyan | b17eae9 | 2010-09-19 00:26:38 +0800 | [diff] [blame] | 1299 | |
| 1300 | // Single-threaded executor |
| 1301 | private static class MyExecutor extends Handler { |
| 1302 | MyExecutor() { |
| 1303 | super(createLooper()); |
| 1304 | } |
| 1305 | |
| 1306 | private static Looper createLooper() { |
| 1307 | HandlerThread thread = new HandlerThread("SipService"); |
| 1308 | thread.start(); |
| 1309 | return thread.getLooper(); |
| 1310 | } |
| 1311 | |
| 1312 | void addTask(Runnable task) { |
| 1313 | Message.obtain(this, 0/* don't care */, task).sendToTarget(); |
| 1314 | } |
| 1315 | |
| 1316 | @Override |
| 1317 | public void handleMessage(Message msg) { |
| 1318 | if (msg.obj instanceof Runnable) { |
| 1319 | ((Runnable) msg.obj).run(); |
| 1320 | } else { |
| 1321 | Log.w(TAG, "can't handle msg: " + msg); |
| 1322 | } |
| 1323 | } |
| 1324 | } |
Chung-yih Wang | 2d94231 | 2010-08-05 12:17:37 +0800 | [diff] [blame] | 1325 | } |