blob: db1931b41a9aaf16cd6963701c8968a04f2344d5 [file] [log] [blame]
Chung-yih Wang2d942312010-08-05 12:17:37 +08001/*
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
17package com.android.server.sip;
18
19import android.app.AlarmManager;
20import android.app.PendingIntent;
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.net.ConnectivityManager;
26import android.net.NetworkInfo;
27import android.net.sip.ISipService;
28import android.net.sip.ISipSession;
29import android.net.sip.ISipSessionListener;
Hung-ying Tyan3d7606a2010-09-12 23:50:38 +080030import android.net.sip.SipErrorCode;
Chung-yih Wang2d942312010-08-05 12:17:37 +080031import android.net.sip.SipManager;
32import android.net.sip.SipProfile;
Hung-ying Tyan84a357b2010-09-16 04:11:32 +080033import android.net.sip.SipSession;
Chung-yih Wang2d942312010-08-05 12:17:37 +080034import android.net.sip.SipSessionAdapter;
Chung-yih Wang2d942312010-08-05 12:17:37 +080035import android.net.wifi.WifiManager;
Chung-yih Wang5424c8d2010-08-25 19:02:18 +080036import android.os.Binder;
Chung-yih Wang2d942312010-08-05 12:17:37 +080037import android.os.Bundle;
Hung-ying Tyanb17eae92010-09-19 00:26:38 +080038import android.os.Handler;
39import android.os.HandlerThread;
40import android.os.Looper;
41import android.os.Message;
Hung-ying Tyan6a534892010-09-13 18:44:33 +080042import android.os.Process;
Chung-yih Wang2d942312010-08-05 12:17:37 +080043import android.os.RemoteException;
Hung-ying Tyan7e54ef72010-09-25 22:49:59 +080044import android.os.ServiceManager;
Chung-yih Wang2d942312010-08-05 12:17:37 +080045import android.os.SystemClock;
46import android.text.TextUtils;
47import android.util.Log;
48
49import java.io.IOException;
50import java.net.DatagramSocket;
51import java.net.InetAddress;
52import java.net.UnknownHostException;
Hung-ying Tyan6a534892010-09-13 18:44:33 +080053import java.util.ArrayList;
Chung-yih Wang2d942312010-08-05 12:17:37 +080054import java.util.Collection;
55import java.util.Comparator;
56import java.util.HashMap;
57import java.util.Iterator;
58import java.util.Map;
59import java.util.Timer;
60import java.util.TimerTask;
61import java.util.TreeSet;
62import javax.sip.SipException;
63
Chia-chi Yeh95b15c32010-09-02 22:15:26 +080064/**
65 * @hide
66 */
Chung-yih Wang2d942312010-08-05 12:17:37 +080067public final class SipService extends ISipService.Stub {
68 private static final String TAG = "SipService";
Hung-ying Tyanb0319572010-10-01 07:09:30 +080069 private static final boolean DEBUGV = false;
Hung-ying Tyanc7510582010-09-16 05:45:19 +080070 private static final boolean DEBUG = true;
71 private static final boolean DEBUG_TIMER = DEBUG && false;
Chung-yih Wang2d942312010-08-05 12:17:37 +080072 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 Tyanc4b87472010-09-19 18:23:44 +080082 private boolean mWifiOnly;
Chung-yih Wang2d942312010-08-05 12:17:37 +080083
Hung-ying Tyanb17eae92010-09-19 00:26:38 +080084 private MyExecutor mExecutor;
85
Chung-yih Wang2d942312010-08-05 12:17:37 +080086 // 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 Tyan3424c022010-08-27 18:08:19 +080096 /**
Hung-ying Tyan7e54ef72010-09-25 22:49:59 +080097 * Starts the SIP service. Do nothing if the SIP API is not supported on the
98 * device.
Hung-ying Tyan3424c022010-08-27 18:08:19 +080099 */
Hung-ying Tyan7e54ef72010-09-25 22:49:59 +0800100 public static void start(Context context) {
101 if (SipManager.isApiSupported(context)) {
102 ServiceManager.addService("sip", new SipService(context));
Hung-ying Tyan9db99a42010-10-07 09:14:57 +0800103 context.sendBroadcast(new Intent(SipManager.ACTION_SIP_SERVICE_UP));
Hung-ying Tyan7e54ef72010-09-25 22:49:59 +0800104 Log.i(TAG, "SIP service started");
105 }
Hung-ying Tyan3424c022010-08-27 18:08:19 +0800106 }
107
108 private SipService(Context context) {
Hung-ying Tyanc7510582010-09-16 05:45:19 +0800109 if (DEBUG) Log.d(TAG, " service started!");
Chung-yih Wang2d942312010-08-05 12:17:37 +0800110 mContext = context;
111 mConnectivityReceiver = new ConnectivityReceiver();
112 context.registerReceiver(mConnectivityReceiver,
113 new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
114
115 mTimer = new WakeupTimer(context);
Hung-ying Tyanc4b87472010-09-19 18:23:44 +0800116 mWifiOnly = SipManager.isSipWifiOnly(context);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800117 }
118
Hung-ying Tyanb17eae92010-09-19 00:26:38 +0800119 private MyExecutor getExecutor() {
120 // create mExecutor lazily
121 if (mExecutor == null) mExecutor = new MyExecutor();
122 return mExecutor;
123 }
124
Chung-yih Wang2d942312010-08-05 12:17:37 +0800125 public synchronized SipProfile[] getListOfProfiles() {
Hung-ying Tyanaa562ff2010-10-08 08:59:38 +0800126 mContext.enforceCallingOrSelfPermission(
127 android.Manifest.permission.USE_SIP, null);
Hung-ying Tyan6a534892010-09-13 18:44:33 +0800128 boolean isCallerRadio = isCallerRadio();
129 ArrayList<SipProfile> profiles = new ArrayList<SipProfile>();
Chung-yih Wang2d942312010-08-05 12:17:37 +0800130 for (SipSessionGroupExt group : mSipGroups.values()) {
Hung-ying Tyan6a534892010-09-13 18:44:33 +0800131 if (isCallerRadio || isCallerCreator(group)) {
132 profiles.add(group.getLocalProfile());
133 }
Chung-yih Wang2d942312010-08-05 12:17:37 +0800134 }
Hung-ying Tyan6a534892010-09-13 18:44:33 +0800135 return profiles.toArray(new SipProfile[profiles.size()]);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800136 }
137
138 public void open(SipProfile localProfile) {
Hung-ying Tyanaa562ff2010-10-08 08:59:38 +0800139 mContext.enforceCallingOrSelfPermission(
140 android.Manifest.permission.USE_SIP, null);
Chung-yih Wang5424c8d2010-08-25 19:02:18 +0800141 localProfile.setCallingUid(Binder.getCallingUid());
Chung-yih Wang2d942312010-08-05 12:17:37 +0800142 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 Wang2d942312010-08-05 12:17:37 +0800150 public synchronized void open3(SipProfile localProfile,
Hung-ying Tyan323d3672010-10-05 09:35:38 +0800151 PendingIntent incomingCallPendingIntent,
152 ISipSessionListener listener) {
Hung-ying Tyanaa562ff2010-10-08 08:59:38 +0800153 mContext.enforceCallingOrSelfPermission(
154 android.Manifest.permission.USE_SIP, null);
Chung-yih Wang5424c8d2010-08-25 19:02:18 +0800155 localProfile.setCallingUid(Binder.getCallingUid());
Hung-ying Tyan323d3672010-10-05 09:35:38 +0800156 if (incomingCallPendingIntent == null) {
157 Log.w(TAG, "incomingCallPendingIntent cannot be null; "
158 + "the profile is not opened");
Hung-ying Tyan6a534892010-09-13 18:44:33 +0800159 return;
Chung-yih Wang2d942312010-08-05 12:17:37 +0800160 }
Hung-ying Tyanc7510582010-09-16 05:45:19 +0800161 if (DEBUG) Log.d(TAG, "open3: " + localProfile.getUriString() + ": "
Hung-ying Tyan323d3672010-10-05 09:35:38 +0800162 + incomingCallPendingIntent + ": " + listener);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800163 try {
164 SipSessionGroupExt group = createGroup(localProfile,
Hung-ying Tyan323d3672010-10-05 09:35:38 +0800165 incomingCallPendingIntent, listener);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800166 if (localProfile.getAutoRegistration()) {
167 group.openToReceiveCalls();
Hung-ying Tyanaa562ff2010-10-08 08:59:38 +0800168 if (isWifiActive()) grabWifiLock();
Chung-yih Wang2d942312010-08-05 12:17:37 +0800169 }
170 } catch (SipException e) {
171 Log.e(TAG, "openToReceiveCalls()", e);
172 // TODO: how to send the exception back
173 }
174 }
175
Hung-ying Tyan6a534892010-09-13 18:44:33 +0800176 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 Wang2d942312010-08-05 12:17:37 +0800189 public synchronized void close(String localProfileUri) {
Hung-ying Tyanaa562ff2010-10-08 08:59:38 +0800190 mContext.enforceCallingOrSelfPermission(
191 android.Manifest.permission.USE_SIP, null);
Hung-ying Tyan6a534892010-09-13 18:44:33 +0800192 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 Wang2d942312010-08-05 12:17:37 +0800197 }
Hung-ying Tyan6a534892010-09-13 18:44:33 +0800198
199 group = mSipGroups.remove(localProfileUri);
200 notifyProfileRemoved(group.getLocalProfile());
201 group.close();
Hung-ying Tyanaa562ff2010-10-08 08:59:38 +0800202 if (isWifiActive() && !anyOpened()) releaseWifiLock();
Chung-yih Wang2d942312010-08-05 12:17:37 +0800203 }
204
205 public synchronized boolean isOpened(String localProfileUri) {
Hung-ying Tyanaa562ff2010-10-08 08:59:38 +0800206 mContext.enforceCallingOrSelfPermission(
207 android.Manifest.permission.USE_SIP, null);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800208 SipSessionGroupExt group = mSipGroups.get(localProfileUri);
Hung-ying Tyan6a534892010-09-13 18:44:33 +0800209 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 Wang2d942312010-08-05 12:17:37 +0800216 }
217
218 public synchronized boolean isRegistered(String localProfileUri) {
Hung-ying Tyanaa562ff2010-10-08 08:59:38 +0800219 mContext.enforceCallingOrSelfPermission(
220 android.Manifest.permission.USE_SIP, null);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800221 SipSessionGroupExt group = mSipGroups.get(localProfileUri);
Hung-ying Tyan6a534892010-09-13 18:44:33 +0800222 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 Wang2d942312010-08-05 12:17:37 +0800229 }
230
231 public synchronized void setRegistrationListener(String localProfileUri,
232 ISipSessionListener listener) {
Hung-ying Tyanaa562ff2010-10-08 08:59:38 +0800233 mContext.enforceCallingOrSelfPermission(
234 android.Manifest.permission.USE_SIP, null);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800235 SipSessionGroupExt group = mSipGroups.get(localProfileUri);
Hung-ying Tyan6a534892010-09-13 18:44:33 +0800236 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 Wang2d942312010-08-05 12:17:37 +0800242 }
243
244 public synchronized ISipSession createSession(SipProfile localProfile,
245 ISipSessionListener listener) {
Hung-ying Tyanaa562ff2010-10-08 08:59:38 +0800246 mContext.enforceCallingOrSelfPermission(
247 android.Manifest.permission.USE_SIP, null);
Chung-yih Wang5424c8d2010-08-25 19:02:18 +0800248 localProfile.setCallingUid(Binder.getCallingUid());
Chung-yih Wang2d942312010-08-05 12:17:37 +0800249 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 Tyanaa562ff2010-10-08 08:59:38 +0800260 mContext.enforceCallingOrSelfPermission(
261 android.Manifest.permission.USE_SIP, null);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800262 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 Tyan6a534892010-09-13 18:44:33 +0800286 } else if (!isCallerCreator(group)) {
287 throw new SipException("only creator can access the profile");
Chung-yih Wang2d942312010-08-05 12:17:37 +0800288 }
289 return group;
290 }
291
292 private SipSessionGroupExt createGroup(SipProfile localProfile,
Hung-ying Tyan323d3672010-10-05 09:35:38 +0800293 PendingIntent incomingCallPendingIntent,
294 ISipSessionListener listener) throws SipException {
Chung-yih Wang2d942312010-08-05 12:17:37 +0800295 String key = localProfile.getUriString();
296 SipSessionGroupExt group = mSipGroups.get(key);
297 if (group != null) {
Hung-ying Tyan6a534892010-09-13 18:44:33 +0800298 if (!isCallerCreator(group)) {
299 throw new SipException("only creator can access the profile");
300 }
Hung-ying Tyan323d3672010-10-05 09:35:38 +0800301 group.setIncomingCallPendingIntent(incomingCallPendingIntent);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800302 group.setListener(listener);
303 } else {
304 group = new SipSessionGroupExt(localProfile,
Hung-ying Tyan323d3672010-10-05 09:35:38 +0800305 incomingCallPendingIntent, listener);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800306 mSipGroups.put(key, group);
307 notifyProfileAdded(localProfile);
308 }
309 return group;
310 }
311
312 private void notifyProfileAdded(SipProfile localProfile) {
Hung-ying Tyanc7510582010-09-16 05:45:19 +0800313 if (DEBUG) Log.d(TAG, "notify: profile added: " + localProfile);
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800314 Intent intent = new Intent(SipManager.ACTION_SIP_ADD_PHONE);
315 intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
Chung-yih Wang2d942312010-08-05 12:17:37 +0800316 mContext.sendBroadcast(intent);
317 }
318
319 private void notifyProfileRemoved(SipProfile localProfile) {
Hung-ying Tyanc7510582010-09-16 05:45:19 +0800320 if (DEBUG) Log.d(TAG, "notify: profile removed: " + localProfile);
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800321 Intent intent = new Intent(SipManager.ACTION_SIP_REMOVE_PHONE);
322 intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
Chung-yih Wang2d942312010-08-05 12:17:37 +0800323 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 Tyanc7510582010-09-16 05:45:19 +0800335 if (DEBUG) Log.d(TAG, "acquire wifi lock");
Chung-yih Wang2d942312010-08-05 12:17:37 +0800336 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 Tyanc7510582010-09-16 05:45:19 +0800345 if (DEBUG) Log.d(TAG, "release wifi lock");
Chung-yih Wang2d942312010-08-05 12:17:37 +0800346 mWifiLock.release();
347 mWifiLock = null;
348 }
349 }
350
Hung-ying Tyanaa562ff2010-10-08 08:59:38 +0800351 private boolean isWifiActive() {
Chung-yih Wang2d942312010-08-05 12:17:37 +0800352 return "WIFI".equalsIgnoreCase(mNetworkType);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800353 }
354
355 private synchronized void onConnectivityChanged(
356 String type, boolean connected) {
Hung-ying Tyanc7510582010-09-16 05:45:19 +0800357 if (DEBUG) Log.d(TAG, "onConnectivityChanged(): "
Chung-yih Wang2d942312010-08-05 12:17:37 +0800358 + 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 Tyan323d3672010-10-05 09:35:38 +0800409 private PendingIntent mIncomingCallPendingIntent;
Chung-yih Wang2d942312010-08-05 12:17:37 +0800410 private boolean mOpened;
411
412 private AutoRegistrationProcess mAutoRegistration =
413 new AutoRegistrationProcess();
414
415 public SipSessionGroupExt(SipProfile localProfile,
Hung-ying Tyan323d3672010-10-05 09:35:38 +0800416 PendingIntent incomingCallPendingIntent,
Chung-yih Wang2d942312010-08-05 12:17:37 +0800417 ISipSessionListener listener) throws SipException {
418 String password = localProfile.getPassword();
419 SipProfile p = duplicate(localProfile);
420 mSipGroup = createSipSessionGroup(mLocalIp, p, password);
Hung-ying Tyan323d3672010-10-05 09:35:38 +0800421 mIncomingCallPendingIntent = incomingCallPendingIntent;
Chung-yih Wang2d942312010-08-05 12:17:37 +0800422 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 Wang5424c8d2010-08-25 19:02:18 +0800451 return new SipProfile.Builder(p).setPassword("*").build();
Chung-yih Wang2d942312010-08-05 12:17:37 +0800452 } 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 Tyan323d3672010-10-05 09:35:38 +0800462 public void setIncomingCallPendingIntent(PendingIntent pIntent) {
463 mIncomingCallPendingIntent = pIntent;
Chung-yih Wang2d942312010-08-05 12:17:37 +0800464 }
465
466 public void openToReceiveCalls() throws SipException {
467 mOpened = true;
468 if (mConnected) {
469 mSipGroup.openToReceiveCalls(this);
470 mAutoRegistration.start(mSipGroup);
471 }
Hung-ying Tyanc7510582010-09-16 05:45:19 +0800472 if (DEBUG) Log.d(TAG, " openToReceiveCalls: " + getUri() + ": "
Hung-ying Tyan323d3672010-10-05 09:35:38 +0800473 + mIncomingCallPendingIntent);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800474 }
475
476 public void onConnectivityChanged(boolean connected)
477 throws SipException {
Hung-ying Tyand231aa82010-09-14 00:17:51 +0800478 mSipGroup.onConnectivityChanged();
Chung-yih Wang2d942312010-08-05 12:17:37 +0800479 if (connected) {
480 resetGroup(mLocalIp);
481 if (mOpened) openToReceiveCalls();
482 } else {
483 // close mSipGroup but remember mOpened
Hung-ying Tyanc7510582010-09-16 05:45:19 +0800484 if (DEBUG) Log.d(TAG, " close auto reg temporarily: "
Hung-ying Tyan323d3672010-10-05 09:35:38 +0800485 + getUri() + ": " + mIncomingCallPendingIntent);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800486 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 Tyanfc51f2c2010-09-22 23:51:57 +0800507 public void close() {
Chung-yih Wang2d942312010-08-05 12:17:37 +0800508 mOpened = false;
Hung-ying Tyanfc51f2c2010-09-22 23:51:57 +0800509 mSipGroup.close();
Chung-yih Wang2d942312010-08-05 12:17:37 +0800510 mAutoRegistration.stop();
Hung-ying Tyanc7510582010-09-16 05:45:19 +0800511 if (DEBUG) Log.d(TAG, " close: " + getUri() + ": "
Hung-ying Tyan323d3672010-10-05 09:35:38 +0800512 + mIncomingCallPendingIntent);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800513 }
514
515 public ISipSession createSession(ISipSessionListener listener) {
516 return mSipGroup.createSession(listener);
517 }
518
519 @Override
Hung-ying Tyan323d3672010-10-05 09:35:38 +0800520 public void onRinging(ISipSession s, SipProfile caller,
Chia-chi Yeh95b15c32010-09-02 22:15:26 +0800521 String sessionDescription) {
Hung-ying Tyan323d3672010-10-05 09:35:38 +0800522 SipSessionGroup.SipSessionImpl session =
523 (SipSessionGroup.SipSessionImpl) s;
Chung-yih Wang2d942312010-08-05 12:17:37 +0800524 synchronized (SipService.this) {
525 try {
526 if (!isRegistered()) {
527 session.endCall();
528 return;
529 }
530
531 // send out incoming call broadcast
Chung-yih Wang2d942312010-08-05 12:17:37 +0800532 addPendingSession(session);
533 Intent intent = SipManager.createIncomingCallBroadcast(
Hung-ying Tyan323d3672010-10-05 09:35:38 +0800534 session.getCallId(), sessionDescription);
Hung-ying Tyanc7510582010-09-16 05:45:19 +0800535 if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": "
536 + caller.getUri() + ": " + session.getCallId()
Hung-ying Tyan323d3672010-10-05 09:35:38 +0800537 + " " + 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 Wang2d942312010-08-05 12:17:37 +0800543 }
544 }
545 }
546
547 @Override
Hung-ying Tyan97963792010-09-17 16:58:51 +0800548 public void onError(ISipSession session, int errorCode,
Chung-yih Wang2d942312010-08-05 12:17:37 +0800549 String message) {
Hung-ying Tyan97963792010-09-17 16:58:51 +0800550 if (DEBUG) Log.d(TAG, "sip session error: "
551 + SipErrorCode.toString(errorCode) + ": " + message);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800552 }
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 Tyanfb3a98b2010-09-30 07:49:35 +0800567 // KeepAliveProcess is controlled by AutoRegistrationProcess.
568 // All methods will be invoked in sync with SipService.this except realRun()
Chung-yih Wang2d942312010-08-05 12:17:37 +0800569 private class KeepAliveProcess implements Runnable {
570 private static final String TAG = "\\KEEPALIVE/";
Chung-yih Wang4d1e0122010-09-09 13:27:10 +0800571 private static final int INTERVAL = 10;
Chung-yih Wang2d942312010-08-05 12:17:37 +0800572 private SipSessionGroup.SipSessionImpl mSession;
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800573 private boolean mRunning = false;
Chung-yih Wang2d942312010-08-05 12:17:37 +0800574
575 public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) {
576 mSession = session;
577 }
578
579 public void start() {
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800580 if (mRunning) return;
581 mRunning = true;
Chung-yih Wang2d942312010-08-05 12:17:37 +0800582 mTimer.set(INTERVAL * 1000, this);
583 }
584
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800585 // timeout handler
Chung-yih Wang2d942312010-08-05 12:17:37 +0800586 public void run() {
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800587 if (!mRunning) return;
588 final SipSessionGroup.SipSessionImpl session = mSession;
589
Hung-ying Tyanb17eae92010-09-19 00:26:38 +0800590 // delegate to mExecutor
591 getExecutor().addTask(new Runnable() {
592 public void run() {
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800593 realRun(session);
Hung-ying Tyanb17eae92010-09-19 00:26:38 +0800594 }
595 });
596 }
597
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800598 // real timeout handler
599 private void realRun(SipSessionGroup.SipSessionImpl session) {
Chung-yih Wang2d942312010-08-05 12:17:37 +0800600 synchronized (SipService.this) {
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800601 if (notCurrentSession(session)) return;
602
603 session = session.duplicate();
Hung-ying Tyanb0319572010-10-01 07:09:30 +0800604 if (DEBUGV) Log.v(TAG, "~~~ keepalive");
Chung-yih Wang2d942312010-08-05 12:17:37 +0800605 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 Tyanfb3a98b2010-09-30 07:49:35 +0800616 mRunning = false;
617 mSession = null;
Chung-yih Wang2d942312010-08-05 12:17:37 +0800618 mTimer.cancel(this);
619 }
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800620
621 private boolean notCurrentSession(ISipSession session) {
622 return (session != mSession) || !mRunning;
623 }
Chung-yih Wang2d942312010-08-05 12:17:37 +0800624 }
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 Tyan97963792010-09-17 16:58:51 +0800634 private int mErrorCode;
Hung-ying Tyan3d7606a2010-09-12 23:50:38 +0800635 private String mErrorMessage;
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800636 private boolean mRunning = false;
Chung-yih Wang2d942312010-08-05 12:17:37 +0800637
638 private String getAction() {
639 return toString();
640 }
641
642 public void start(SipSessionGroup group) {
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800643 if (!mRunning) {
644 mRunning = true;
Chung-yih Wang2d942312010-08-05 12:17:37 +0800645 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 Tyanc7510582010-09-16 05:45:19 +0800655 if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess for "
Chung-yih Wang2d942312010-08-05 12:17:37 +0800656 + mSession.getLocalProfile().getUriString());
657 }
658 }
659
660 public void stop() {
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800661 if (!mRunning) return;
662 mRunning = false;
663 mSession.setListener(null);
Hung-ying Tyan3d7606a2010-09-12 23:50:38 +0800664 if (mConnected && mRegistered) mSession.unregister();
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800665
Chung-yih Wang2d942312010-08-05 12:17:37 +0800666 mTimer.cancel(this);
667 if (mKeepAliveProcess != null) {
668 mKeepAliveProcess.stop();
669 mKeepAliveProcess = null;
670 }
Chung-yih Wang2d942312010-08-05 12:17:37 +0800671
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800672 mRegistered = false;
673 setListener(mProxy.getListener());
Chung-yih Wang2d942312010-08-05 12:17:37 +0800674 }
675
676 public void setListener(ISipSessionListener listener) {
Hung-ying Tyan3d7606a2010-09-12 23:50:38 +0800677 synchronized (SipService.this) {
678 mProxy.setListener(listener);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800679
Hung-ying Tyan3d7606a2010-09-12 23:50:38 +0800680 try {
Hung-ying Tyan97963792010-09-17 16:58:51 +0800681 int state = (mSession == null)
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800682 ? SipSession.State.READY_TO_CALL
Hung-ying Tyan97963792010-09-17 16:58:51 +0800683 : mSession.getState();
Hung-ying Tyan84a357b2010-09-16 04:11:32 +0800684 if ((state == SipSession.State.REGISTERING)
685 || (state == SipSession.State.DEREGISTERING)) {
Hung-ying Tyan3d7606a2010-09-12 23:50:38 +0800686 mProxy.onRegistering(mSession);
687 } else if (mRegistered) {
688 int duration = (int)
689 (mExpiryTime - SystemClock.elapsedRealtime());
690 mProxy.onRegistrationDone(mSession, duration);
Hung-ying Tyan97963792010-09-17 16:58:51 +0800691 } else if (mErrorCode != SipErrorCode.NO_ERROR) {
Hung-ying Tyan3d7606a2010-09-12 23:50:38 +0800692 if (mErrorCode == SipErrorCode.TIME_OUT) {
693 mProxy.onRegistrationTimeout(mSession);
694 } else {
Hung-ying Tyan97963792010-09-17 16:58:51 +0800695 mProxy.onRegistrationFailed(mSession, mErrorCode,
696 mErrorMessage);
Hung-ying Tyan3d7606a2010-09-12 23:50:38 +0800697 }
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800698 } 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 Tyan3d7606a2010-09-12 23:50:38 +0800710 }
711 } catch (Throwable t) {
712 Log.w(TAG, "setListener(): " + t);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800713 }
Chung-yih Wang2d942312010-08-05 12:17:37 +0800714 }
715 }
716
717 public boolean isRegistered() {
718 return mRegistered;
719 }
720
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800721 // timeout handler
Chung-yih Wang2d942312010-08-05 12:17:37 +0800722 public void run() {
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800723 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 Tyanb17eae92010-09-19 00:26:38 +0800734 }
735
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800736 // real timeout handler
737 private void realRun(SipSessionGroup.SipSessionImpl session) {
Chung-yih Wang2d942312010-08-05 12:17:37 +0800738 synchronized (SipService.this) {
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800739 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 Wang2d942312010-08-05 12:17:37 +0800744 }
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 Tyanc7510582010-09-16 05:45:19 +0800764 if (DEBUG) Log.d(TAG, "Refresh registration " + duration + "s later.");
Chung-yih Wang2d942312010-08-05 12:17:37 +0800765 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 Tyanc7510582010-09-16 05:45:19 +0800781 if (DEBUG) Log.d(TAG, "onRegistering(): " + session);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800782 synchronized (SipService.this) {
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800783 if (notCurrentSession(session)) return;
784
Chung-yih Wang2d942312010-08-05 12:17:37 +0800785 mRegistered = false;
Hung-ying Tyan3d7606a2010-09-12 23:50:38 +0800786 mProxy.onRegistering(session);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800787 }
788 }
789
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800790 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 Wang2d942312010-08-05 12:17:37 +0800798 @Override
799 public void onRegistrationDone(ISipSession session, int duration) {
Hung-ying Tyanc7510582010-09-16 05:45:19 +0800800 if (DEBUG) Log.d(TAG, "onRegistrationDone(): " + session);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800801 synchronized (SipService.this) {
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800802 if (notCurrentSession(session)) return;
Hung-ying Tyan3d7606a2010-09-12 23:50:38 +0800803
804 mProxy.onRegistrationDone(session, duration);
805
Chung-yih Wang2d942312010-08-05 12:17:37 +0800806 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 Tyanc7510582010-09-16 05:45:19 +0800832 if (DEBUG) Log.d(TAG, "Refresh registration immediately");
Chung-yih Wang2d942312010-08-05 12:17:37 +0800833 run();
834 }
835 }
836 }
837
838 @Override
Hung-ying Tyan97963792010-09-17 16:58:51 +0800839 public void onRegistrationFailed(ISipSession session, int errorCode,
840 String message) {
Hung-ying Tyanc7510582010-09-16 05:45:19 +0800841 if (DEBUG) Log.d(TAG, "onRegistrationFailed(): " + session + ": "
Hung-ying Tyan97963792010-09-17 16:58:51 +0800842 + SipErrorCode.toString(errorCode) + ": " + message);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800843 synchronized (SipService.this) {
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800844 if (notCurrentSession(session)) return;
Chung-yih Wang2d942312010-08-05 12:17:37 +0800845
Hung-ying Tyanee8a8842010-10-06 08:33:47 +0800846 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 Tyan3d7606a2010-09-12 23:50:38 +0800853 }
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800854
855 mErrorCode = errorCode;
856 mErrorMessage = message;
857 mProxy.onRegistrationFailed(session, errorCode, message);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800858 }
859 }
860
861 @Override
862 public void onRegistrationTimeout(ISipSession session) {
Hung-ying Tyanc7510582010-09-16 05:45:19 +0800863 if (DEBUG) Log.d(TAG, "onRegistrationTimeout(): " + session);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800864 synchronized (SipService.this) {
Hung-ying Tyanfb3a98b2010-09-30 07:49:35 +0800865 if (notCurrentSession(session)) return;
866
Hung-ying Tyan3d7606a2010-09-12 23:50:38 +0800867 mErrorCode = SipErrorCode.TIME_OUT;
868 mProxy.onRegistrationTimeout(session);
Hung-ying Tyanee8a8842010-10-06 08:33:47 +0800869 restartLater();
Chung-yih Wang2d942312010-08-05 12:17:37 +0800870 }
871 }
872
Hung-ying Tyanee8a8842010-10-06 08:33:47 +0800873 private void restartLater() {
Chung-yih Wang2d942312010-08-05 12:17:37 +0800874 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 Tyan12bec5d2010-09-14 19:33:10 +0800897
Hung-ying Tyanc4b87472010-09-19 18:23:44 +0800898 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 Tyan12bec5d2010-09-14 19:33:10 +0800907 NetworkInfo activeNetInfo = getActiveNetworkInfo();
Hung-ying Tyanc7510582010-09-16 05:45:19 +0800908 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 Tyan12bec5d2010-09-14 19:33:10 +0800917 }
918 if ((state == NetworkInfo.State.CONNECTED)
919 && (activeNetInfo != null)
920 && (activeNetInfo.getType() != netInfo.getType())) {
Hung-ying Tyanc7510582010-09-16 05:45:19 +0800921 if (DEBUG) Log.d(TAG, "ignore connect event: " + type
Hung-ying Tyan12bec5d2010-09-14 19:33:10 +0800922 + ", active: " + activeNetInfo.getTypeName());
923 return;
924 }
925
Chung-yih Wang2d942312010-08-05 12:17:37 +0800926 if (state == NetworkInfo.State.CONNECTED) {
Hung-ying Tyanc7510582010-09-16 05:45:19 +0800927 if (DEBUG) Log.d(TAG, "Connectivity alert: CONNECTED " + type);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800928 onChanged(type, true);
929 } else if (state == NetworkInfo.State.DISCONNECTED) {
Hung-ying Tyanc7510582010-09-16 05:45:19 +0800930 if (DEBUG) Log.d(TAG, "Connectivity alert: DISCONNECTED " + type);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800931 onChanged(type, false);
932 } else {
Hung-ying Tyanc7510582010-09-16 05:45:19 +0800933 if (DEBUG) Log.d(TAG, "Connectivity alert not processed: "
934 + state + " " + type);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800935 }
936 }
937 }
938 }
939
Hung-ying Tyan12bec5d2010-09-14 19:33:10 +0800940 private NetworkInfo getActiveNetworkInfo() {
941 ConnectivityManager cm = (ConnectivityManager)
942 mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
943 return cm.getActiveNetworkInfo();
944 }
945
Chung-yih Wang2d942312010-08-05 12:17:37 +0800946 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 Tyanb17eae92010-09-19 00:26:38 +0800955 mTimer.schedule(mTask, 2 * 1000L);
Chung-yih Wang2d942312010-08-05 12:17:37 +0800956 // 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 Tyanfb3a98b2010-09-30 07:49:35 +0800976 // timeout handler
Chung-yih Wang2d942312010-08-05 12:17:37 +0800977 @Override
978 public void run() {
Hung-ying Tyanb17eae92010-09-19 00:26:38 +0800979 // delegate to mExecutor
980 getExecutor().addTask(new Runnable() {
981 public void run() {
982 realRun();
983 }
984 });
985 }
986
987 private void realRun() {
Chung-yih Wang2d942312010-08-05 12:17:37 +0800988 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 Tyanc7510582010-09-16 05:45:19 +0800995 if (DEBUG) Log.d(TAG, " deliver change for " + mNetworkType
Chung-yih Wang2d942312010-08-05 12:17:37 +0800996 + (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 Tyanc7510582010-09-16 05:45:19 +08001077 if (DEBUG_TIMER) {
1078 Log.d(TAG, "queue re-calculated");
1079 printQueue();
1080 }
Chung-yih Wang2d942312010-08-05 12:17:37 +08001081 }
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 Tyanc7510582010-09-16 05:45:19 +08001134 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 Wang2d942312010-08-05 12:17:37 +08001140 }
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 Tyanc7510582010-09-16 05:45:19 +08001149 if (DEBUG_TIMER) Log.d(TAG, "cancel:" + callback);
Chung-yih Wang2d942312010-08-05 12:17:37 +08001150
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 Tyanc7510582010-09-16 05:45:19 +08001157 if (DEBUG_TIMER) Log.d(TAG, " cancel found:" + event);
Chung-yih Wang2d942312010-08-05 12:17:37 +08001158 }
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 Tyanc7510582010-09-16 05:45:19 +08001171 if (DEBUG_TIMER) {
1172 Log.d(TAG, "after cancel:");
1173 printQueue();
1174 }
Chung-yih Wang2d942312010-08-05 12:17:37 +08001175 }
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 Tyanc7510582010-09-16 05:45:19 +08001223 if (DEBUG_TIMER) Log.d(TAG, "time's up, triggerTime = "
1224 + showTime(triggerTime) + ": " + mEventQueue.size());
Chung-yih Wang2d942312010-08-05 12:17:37 +08001225 if (stopped() || mEventQueue.isEmpty()) return;
1226
1227 for (MyEvent event : mEventQueue) {
1228 if (event.mTriggerTime != triggerTime) break;
Hung-ying Tyanc7510582010-09-16 05:45:19 +08001229 if (DEBUG_TIMER) Log.d(TAG, "execute " + event);
Chung-yih Wang2d942312010-08-05 12:17:37 +08001230
1231 event.mLastTriggerTime = event.mTriggerTime;
1232 event.mTriggerTime += event.mPeriod;
1233
1234 // run the callback in a new thread to prevent deadlock
Hung-ying Tyan84a357b2010-09-16 04:11:32 +08001235 new Thread(event.mCallback, "SipServiceTimerCallbackThread")
1236 .start();
Chung-yih Wang2d942312010-08-05 12:17:37 +08001237 }
Hung-ying Tyanc7510582010-09-16 05:45:19 +08001238 if (DEBUG_TIMER) {
1239 Log.d(TAG, "after timeout execution");
1240 printQueue();
1241 }
Chung-yih Wang2d942312010-08-05 12:17:37 +08001242 scheduleNext();
1243 }
1244
1245 private String getAction() {
1246 return toString();
1247 }
1248
Chung-yih Wang1d62c772010-08-06 11:32:24 +08001249 private String showTime(long time) {
Chung-yih Wang2d942312010-08-05 12:17:37 +08001250 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 Wang1d62c772010-08-06 11:32:24 +08001257
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 Tyanb17eae92010-09-19 00:26:38 +08001299
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 Wang2d942312010-08-05 12:17:37 +08001325}