• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.AppOpsManager;
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;
30 import android.net.sip.SipErrorCode;
31 import android.net.sip.SipManager;
32 import android.net.sip.SipProfile;
33 import android.net.sip.SipSession;
34 import android.net.sip.SipSessionAdapter;
35 import android.net.wifi.WifiManager;
36 import android.os.Binder;
37 import android.os.Bundle;
38 import android.os.Handler;
39 import android.os.HandlerThread;
40 import android.os.Looper;
41 import android.os.Message;
42 import android.os.PowerManager;
43 import android.os.Process;
44 import android.os.RemoteException;
45 import android.os.ServiceManager;
46 import android.os.SystemClock;
47 import android.os.UserHandle;
48 import android.telephony.Rlog;
49 
50 import java.io.IOException;
51 import java.net.DatagramSocket;
52 import java.net.InetAddress;
53 import java.net.UnknownHostException;
54 import java.util.ArrayList;
55 import java.util.HashMap;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.concurrent.Executor;
59 
60 import javax.sip.SipException;
61 
62 /**
63  * @hide
64  */
65 public final class SipService extends ISipService.Stub {
66     static final String TAG = "SipService";
67     static final boolean DBG = true;
68     private static final int EXPIRY_TIME = 3600;
69     private static final int SHORT_EXPIRY_TIME = 10;
70     private static final int MIN_EXPIRY_TIME = 60;
71     private static final int DEFAULT_KEEPALIVE_INTERVAL = 10; // in seconds
72     private static final int DEFAULT_MAX_KEEPALIVE_INTERVAL = 120; // in seconds
73 
74     private Context mContext;
75     private String mLocalIp;
76     private int mNetworkType = -1;
77     private SipWakeupTimer mTimer;
78     private WifiManager.WifiLock mWifiLock;
79     private boolean mSipOnWifiOnly;
80 
81     private final AppOpsManager mAppOps;
82 
83     private SipKeepAliveProcessCallback mSipKeepAliveProcessCallback;
84 
85     private MyExecutor mExecutor = new MyExecutor();
86 
87     // SipProfile URI --> group
88     private Map<String, SipSessionGroupExt> mSipGroups =
89             new HashMap<String, SipSessionGroupExt>();
90 
91     // session ID --> session
92     private Map<String, ISipSession> mPendingSessions =
93             new HashMap<String, ISipSession>();
94 
95     private ConnectivityReceiver mConnectivityReceiver;
96     private SipWakeLock mMyWakeLock;
97     private int mKeepAliveInterval;
98     private int mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL;
99 
100     /**
101      * Starts the SIP service. Do nothing if the SIP API is not supported on the
102      * device.
103      */
start(Context context)104     public static void start(Context context) {
105         if (SipManager.isApiSupported(context)) {
106             if (ServiceManager.getService("sip") == null) {
107                 ServiceManager.addService("sip", new SipService(context));
108                 context.sendBroadcast(new Intent(SipManager.ACTION_SIP_SERVICE_UP));
109                 if (DBG) slog("start:");
110             }
111         }
112     }
113 
SipService(Context context)114     private SipService(Context context) {
115         if (DBG) log("SipService: started!");
116         mContext = context;
117         mConnectivityReceiver = new ConnectivityReceiver();
118 
119         mWifiLock = ((WifiManager)
120                 context.getSystemService(Context.WIFI_SERVICE))
121                 .createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
122         mWifiLock.setReferenceCounted(false);
123         mSipOnWifiOnly = SipManager.isSipWifiOnly(context);
124 
125         mMyWakeLock = new SipWakeLock((PowerManager)
126                 context.getSystemService(Context.POWER_SERVICE));
127 
128         mTimer = new SipWakeupTimer(context, mExecutor);
129         mAppOps = mContext.getSystemService(AppOpsManager.class);
130     }
131 
132     @Override
getProfiles(String opPackageName)133     public synchronized List<SipProfile> getProfiles(String opPackageName) throws RemoteException {
134         if (!canUseSip(opPackageName, "getProfiles")) {
135             throw new RemoteException(String.format("Package %s cannot use Sip service",
136                     opPackageName));
137         }
138         boolean isCallerRadio = isCallerRadio();
139         ArrayList<SipProfile> profiles = new ArrayList<>();
140         for (SipSessionGroupExt group : mSipGroups.values()) {
141             if (isCallerRadio || isCallerCreator(group)) {
142                 profiles.add(group.getLocalProfile());
143             }
144         }
145         return profiles;
146     }
147 
148     @Override
open(SipProfile localProfile, String opPackageName)149     public synchronized void open(SipProfile localProfile, String opPackageName) {
150         if (!canUseSip(opPackageName, "open")) {
151             return;
152         }
153         localProfile.setCallingUid(Binder.getCallingUid());
154         try {
155             createGroup(localProfile);
156         } catch (SipException e) {
157             loge("openToMakeCalls()", e);
158             // TODO: how to send the exception back
159         }
160     }
161 
162     @Override
open3(SipProfile localProfile, PendingIntent incomingCallPendingIntent, ISipSessionListener listener, String opPackageName)163     public synchronized void open3(SipProfile localProfile,
164             PendingIntent incomingCallPendingIntent,
165             ISipSessionListener listener,
166             String opPackageName) {
167         if (!canUseSip(opPackageName, "open3")) {
168             return;
169         }
170         localProfile.setCallingUid(Binder.getCallingUid());
171         if (incomingCallPendingIntent == null) {
172             if (DBG) log("open3: incomingCallPendingIntent cannot be null; "
173                     + "the profile is not opened");
174             return;
175         }
176         if (DBG) log("open3: " + obfuscateSipUri(localProfile.getUriString()) + ": "
177                 + incomingCallPendingIntent + ": " + listener);
178         try {
179             SipSessionGroupExt group = createGroup(localProfile,
180                     incomingCallPendingIntent, listener);
181             if (localProfile.getAutoRegistration()) {
182                 group.openToReceiveCalls();
183                 updateWakeLocks();
184             }
185         } catch (SipException e) {
186             loge("open3:", e);
187             // TODO: how to send the exception back
188         }
189     }
190 
isCallerCreator(SipSessionGroupExt group)191     private boolean isCallerCreator(SipSessionGroupExt group) {
192         SipProfile profile = group.getLocalProfile();
193         return (profile.getCallingUid() == Binder.getCallingUid());
194     }
195 
isCallerCreatorOrRadio(SipSessionGroupExt group)196     private boolean isCallerCreatorOrRadio(SipSessionGroupExt group) {
197         return (isCallerRadio() || isCallerCreator(group));
198     }
199 
isCallerRadio()200     private boolean isCallerRadio() {
201         return UserHandle.isSameApp(Binder.getCallingUid(), Process.PHONE_UID);
202     }
203 
204     @Override
close(String localProfileUri, String opPackageName)205     public synchronized void close(String localProfileUri, String opPackageName) {
206         if (!canUseSip(opPackageName, "close")) {
207             return;
208         }
209         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
210         if (group == null) return;
211         if (!isCallerCreatorOrRadio(group)) {
212             if (DBG) log("only creator or radio can close this profile");
213             return;
214         }
215 
216         group = mSipGroups.remove(localProfileUri);
217         notifyProfileRemoved(group.getLocalProfile());
218         group.close();
219 
220         updateWakeLocks();
221     }
222 
223     @Override
isOpened(String localProfileUri, String opPackageName)224     public synchronized boolean isOpened(String localProfileUri, String opPackageName) {
225         if (!canUseSip(opPackageName, "isOpened")) {
226             return false;
227         }
228         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
229         if (group == null) return false;
230         if (isCallerCreatorOrRadio(group)) {
231             return true;
232         } else {
233             if (DBG) log("only creator or radio can query on the profile");
234             return false;
235         }
236     }
237 
238     @Override
isRegistered(String localProfileUri, String opPackageName)239     public synchronized boolean isRegistered(String localProfileUri, String opPackageName) {
240         if (!canUseSip(opPackageName, "isRegistered")) {
241             return false;
242         }
243         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
244         if (group == null) return false;
245         if (isCallerCreatorOrRadio(group)) {
246             return group.isRegistered();
247         } else {
248             if (DBG) log("only creator or radio can query on the profile");
249             return false;
250         }
251     }
252 
253     @Override
setRegistrationListener(String localProfileUri, ISipSessionListener listener, String opPackageName)254     public synchronized void setRegistrationListener(String localProfileUri,
255             ISipSessionListener listener, String opPackageName) {
256         if (!canUseSip(opPackageName, "setRegistrationListener")) {
257             return;
258         }
259         SipSessionGroupExt group = mSipGroups.get(localProfileUri);
260         if (group == null) return;
261         if (isCallerCreator(group)) {
262             group.setListener(listener);
263         } else {
264             if (DBG) log("only creator can set listener on the profile");
265         }
266     }
267 
268     @Override
createSession(SipProfile localProfile, ISipSessionListener listener, String opPackageName)269     public synchronized ISipSession createSession(SipProfile localProfile,
270             ISipSessionListener listener, String opPackageName) {
271         if (DBG) log("createSession: profile" + localProfile);
272         if (!canUseSip(opPackageName, "createSession")) {
273             return null;
274         }
275         localProfile.setCallingUid(Binder.getCallingUid());
276         if (mNetworkType == -1) {
277             if (DBG) log("createSession: mNetworkType==-1 ret=null");
278             return null;
279         }
280         try {
281             SipSessionGroupExt group = createGroup(localProfile);
282             return group.createSession(listener);
283         } catch (SipException e) {
284             if (DBG) loge("createSession;", e);
285             return null;
286         }
287     }
288 
289     @Override
getPendingSession(String callId, String opPackageName)290     public synchronized ISipSession getPendingSession(String callId, String opPackageName) {
291         if (!canUseSip(opPackageName, "getPendingSession")) {
292             return null;
293         }
294         if (callId == null) return null;
295         return mPendingSessions.get(callId);
296     }
297 
determineLocalIp()298     private String determineLocalIp() {
299         try {
300             DatagramSocket s = new DatagramSocket();
301             s.connect(InetAddress.getByName("192.168.1.1"), 80);
302             return s.getLocalAddress().getHostAddress();
303         } catch (IOException e) {
304             if (DBG) loge("determineLocalIp()", e);
305             // dont do anything; there should be a connectivity change going
306             return null;
307         }
308     }
309 
createGroup(SipProfile localProfile)310     private SipSessionGroupExt createGroup(SipProfile localProfile)
311             throws SipException {
312         String key = localProfile.getUriString();
313         SipSessionGroupExt group = mSipGroups.get(key);
314         if (group == null) {
315             group = new SipSessionGroupExt(localProfile, null, null);
316             mSipGroups.put(key, group);
317             notifyProfileAdded(localProfile);
318         } else if (!isCallerCreator(group)) {
319             throw new SipException("only creator can access the profile");
320         }
321         return group;
322     }
323 
createGroup(SipProfile localProfile, PendingIntent incomingCallPendingIntent, ISipSessionListener listener)324     private SipSessionGroupExt createGroup(SipProfile localProfile,
325             PendingIntent incomingCallPendingIntent,
326             ISipSessionListener listener) throws SipException {
327         String key = localProfile.getUriString();
328         SipSessionGroupExt group = mSipGroups.get(key);
329         if (group != null) {
330             if (!isCallerCreator(group)) {
331                 throw new SipException("only creator can access the profile");
332             }
333             group.setIncomingCallPendingIntent(incomingCallPendingIntent);
334             group.setListener(listener);
335         } else {
336             group = new SipSessionGroupExt(localProfile,
337                     incomingCallPendingIntent, listener);
338             mSipGroups.put(key, group);
339             notifyProfileAdded(localProfile);
340         }
341         return group;
342     }
343 
notifyProfileAdded(SipProfile localProfile)344     private void notifyProfileAdded(SipProfile localProfile) {
345         if (DBG) log("notify: profile added: " + localProfile);
346         Intent intent = new Intent(SipManager.ACTION_SIP_ADD_PHONE);
347         intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
348         mContext.sendBroadcast(intent, android.Manifest.permission.USE_SIP);
349         if (mSipGroups.size() == 1) {
350             registerReceivers();
351         }
352     }
353 
notifyProfileRemoved(SipProfile localProfile)354     private void notifyProfileRemoved(SipProfile localProfile) {
355         if (DBG) log("notify: profile removed: " + localProfile);
356         Intent intent = new Intent(SipManager.ACTION_SIP_REMOVE_PROFILE);
357         intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
358         mContext.sendBroadcast(intent, android.Manifest.permission.USE_SIP);
359         if (mSipGroups.size() == 0) {
360             unregisterReceivers();
361         }
362     }
363 
stopPortMappingMeasurement()364     private void stopPortMappingMeasurement() {
365         if (mSipKeepAliveProcessCallback != null) {
366             mSipKeepAliveProcessCallback.stop();
367             mSipKeepAliveProcessCallback = null;
368         }
369     }
370 
startPortMappingLifetimeMeasurement( SipProfile localProfile)371     private void startPortMappingLifetimeMeasurement(
372             SipProfile localProfile) {
373         startPortMappingLifetimeMeasurement(localProfile,
374                 DEFAULT_MAX_KEEPALIVE_INTERVAL);
375     }
376 
startPortMappingLifetimeMeasurement( SipProfile localProfile, int maxInterval)377     private void startPortMappingLifetimeMeasurement(
378             SipProfile localProfile, int maxInterval) {
379         if ((mSipKeepAliveProcessCallback == null)
380                 && (mKeepAliveInterval == -1)
381                 && isBehindNAT(mLocalIp)) {
382             if (DBG) log("startPortMappingLifetimeMeasurement: profile="
383                     + localProfile.getUriString());
384 
385             int minInterval = mLastGoodKeepAliveInterval;
386             if (minInterval >= maxInterval) {
387                 // If mLastGoodKeepAliveInterval also does not work, reset it
388                 // to the default min
389                 minInterval = mLastGoodKeepAliveInterval
390                         = DEFAULT_KEEPALIVE_INTERVAL;
391                 log("  reset min interval to " + minInterval);
392             }
393             mSipKeepAliveProcessCallback = new SipKeepAliveProcessCallback(
394                     localProfile, minInterval, maxInterval);
395             mSipKeepAliveProcessCallback.start();
396         }
397     }
398 
restartPortMappingLifetimeMeasurement( SipProfile localProfile, int maxInterval)399     private void restartPortMappingLifetimeMeasurement(
400             SipProfile localProfile, int maxInterval) {
401         stopPortMappingMeasurement();
402         mKeepAliveInterval = -1;
403         startPortMappingLifetimeMeasurement(localProfile, maxInterval);
404     }
405 
addPendingSession(ISipSession session)406     private synchronized void addPendingSession(ISipSession session) {
407         try {
408             cleanUpPendingSessions();
409             mPendingSessions.put(session.getCallId(), session);
410             if (DBG) log("#pending sess=" + mPendingSessions.size());
411         } catch (RemoteException e) {
412             // should not happen with a local call
413             loge("addPendingSession()", e);
414         }
415     }
416 
cleanUpPendingSessions()417     private void cleanUpPendingSessions() throws RemoteException {
418         Map.Entry<String, ISipSession>[] entries =
419                 mPendingSessions.entrySet().toArray(
420                 new Map.Entry[mPendingSessions.size()]);
421         for (Map.Entry<String, ISipSession> entry : entries) {
422             if (entry.getValue().getState() != SipSession.State.INCOMING_CALL) {
423                 mPendingSessions.remove(entry.getKey());
424             }
425         }
426     }
427 
callingSelf(SipSessionGroupExt ringingGroup, SipSessionGroup.SipSessionImpl ringingSession)428     private synchronized boolean callingSelf(SipSessionGroupExt ringingGroup,
429             SipSessionGroup.SipSessionImpl ringingSession) {
430         String callId = ringingSession.getCallId();
431         for (SipSessionGroupExt group : mSipGroups.values()) {
432             if ((group != ringingGroup) && group.containsSession(callId)) {
433                 if (DBG) log("call self: "
434                         + ringingSession.getLocalProfile().getUriString()
435                         + " -> " + group.getLocalProfile().getUriString());
436                 return true;
437             }
438         }
439         return false;
440     }
441 
onKeepAliveIntervalChanged()442     private synchronized void onKeepAliveIntervalChanged() {
443         for (SipSessionGroupExt group : mSipGroups.values()) {
444             group.onKeepAliveIntervalChanged();
445         }
446     }
447 
getKeepAliveInterval()448     private int getKeepAliveInterval() {
449         return (mKeepAliveInterval < 0)
450                 ? mLastGoodKeepAliveInterval
451                 : mKeepAliveInterval;
452     }
453 
isBehindNAT(String address)454     private boolean isBehindNAT(String address) {
455         try {
456             // TODO: How is isBehindNAT used and why these constanst address:
457             //       10.x.x.x | 192.168.x.x | 172.16.x.x .. 172.19.x.x
458             byte[] d = InetAddress.getByName(address).getAddress();
459             if ((d[0] == 10) ||
460                     (((0x000000FF & d[0]) == 172) &&
461                     ((0x000000F0 & d[1]) == 16)) ||
462                     (((0x000000FF & d[0]) == 192) &&
463                     ((0x000000FF & d[1]) == 168))) {
464                 return true;
465             }
466         } catch (UnknownHostException e) {
467             loge("isBehindAT()" + address, e);
468         }
469         return false;
470     }
471 
canUseSip(String packageName, String message)472     private boolean canUseSip(String packageName, String message) {
473         mContext.enforceCallingOrSelfPermission(
474                 android.Manifest.permission.USE_SIP, message);
475 
476         return mAppOps.noteOp(AppOpsManager.OPSTR_USE_SIP, Binder.getCallingUid(),
477                 packageName, null, message) == AppOpsManager.MODE_ALLOWED;
478     }
479 
480     private class SipSessionGroupExt extends SipSessionAdapter {
481         private static final String SSGE_TAG = "SipSessionGroupExt";
482         private static final boolean SSGE_DBG = true;
483         private SipSessionGroup mSipGroup;
484         private PendingIntent mIncomingCallPendingIntent;
485         private boolean mOpenedToReceiveCalls;
486 
487         private SipAutoReg mAutoRegistration =
488                 new SipAutoReg();
489 
SipSessionGroupExt(SipProfile localProfile, PendingIntent incomingCallPendingIntent, ISipSessionListener listener)490         public SipSessionGroupExt(SipProfile localProfile,
491                 PendingIntent incomingCallPendingIntent,
492                 ISipSessionListener listener) throws SipException {
493             if (SSGE_DBG) log("SipSessionGroupExt: profile=" + localProfile);
494             mSipGroup = new SipSessionGroup(duplicate(localProfile),
495                     localProfile.getPassword(), mTimer, mMyWakeLock);
496             mIncomingCallPendingIntent = incomingCallPendingIntent;
497             mAutoRegistration.setListener(listener);
498         }
499 
getLocalProfile()500         public SipProfile getLocalProfile() {
501             return mSipGroup.getLocalProfile();
502         }
503 
containsSession(String callId)504         public boolean containsSession(String callId) {
505             return mSipGroup.containsSession(callId);
506         }
507 
onKeepAliveIntervalChanged()508         public void onKeepAliveIntervalChanged() {
509             mAutoRegistration.onKeepAliveIntervalChanged();
510         }
511 
512         // TODO: remove this method once SipWakeupTimer can better handle variety
513         // of timeout values
setWakeupTimer(SipWakeupTimer timer)514         void setWakeupTimer(SipWakeupTimer timer) {
515             mSipGroup.setWakeupTimer(timer);
516         }
517 
duplicate(SipProfile p)518         private SipProfile duplicate(SipProfile p) {
519             try {
520                 return new SipProfile.Builder(p).setPassword("*").build();
521             } catch (Exception e) {
522                 loge("duplicate()", e);
523                 throw new RuntimeException("duplicate profile", e);
524             }
525         }
526 
setListener(ISipSessionListener listener)527         public void setListener(ISipSessionListener listener) {
528             mAutoRegistration.setListener(listener);
529         }
530 
setIncomingCallPendingIntent(PendingIntent pIntent)531         public void setIncomingCallPendingIntent(PendingIntent pIntent) {
532             mIncomingCallPendingIntent = pIntent;
533         }
534 
openToReceiveCalls()535         public void openToReceiveCalls() {
536             mOpenedToReceiveCalls = true;
537             if (mNetworkType != -1) {
538                 mSipGroup.openToReceiveCalls(this);
539                 mAutoRegistration.start(mSipGroup);
540             }
541             if (SSGE_DBG) log("openToReceiveCalls: " + obfuscateSipUri(getUri()) + ": "
542                     + mIncomingCallPendingIntent);
543         }
544 
onConnectivityChanged(boolean connected)545         public void onConnectivityChanged(boolean connected)
546                 throws SipException {
547             if (SSGE_DBG) {
548                 log("onConnectivityChanged: connected=" + connected + " uri="
549                     + obfuscateSipUri(getUri()) + ": " + mIncomingCallPendingIntent);
550             }
551             mSipGroup.onConnectivityChanged();
552             if (connected) {
553                 mSipGroup.reset();
554                 if (mOpenedToReceiveCalls) openToReceiveCalls();
555             } else {
556                 mSipGroup.close();
557                 mAutoRegistration.stop();
558             }
559         }
560 
close()561         public void close() {
562             mOpenedToReceiveCalls = false;
563             mSipGroup.close();
564             mAutoRegistration.stop();
565             if (SSGE_DBG) log("close: " + obfuscateSipUri(getUri()) + ": "
566                     + mIncomingCallPendingIntent);
567         }
568 
createSession(ISipSessionListener listener)569         public ISipSession createSession(ISipSessionListener listener) {
570             if (SSGE_DBG) log("createSession");
571             return mSipGroup.createSession(listener);
572         }
573 
574         @Override
onRinging(ISipSession s, SipProfile caller, String sessionDescription)575         public void onRinging(ISipSession s, SipProfile caller,
576                 String sessionDescription) {
577             SipSessionGroup.SipSessionImpl session =
578                     (SipSessionGroup.SipSessionImpl) s;
579             synchronized (SipService.this) {
580                 try {
581                     if (!isRegistered() || callingSelf(this, session)) {
582                         if (SSGE_DBG) log("onRinging: end notReg or self");
583                         session.endCall();
584                         return;
585                     }
586 
587                     // send out incoming call broadcast
588                     addPendingSession(session);
589                     Intent intent = SipManager.createIncomingCallBroadcast(
590                             session.getCallId(), sessionDescription);
591                     if (SSGE_DBG) log("onRinging: uri=" + getUri() + ": "
592                             + caller.getUri() + ": " + session.getCallId()
593                             + " " + mIncomingCallPendingIntent);
594                     mIncomingCallPendingIntent.send(mContext,
595                             SipManager.INCOMING_CALL_RESULT_CODE, intent);
596                 } catch (PendingIntent.CanceledException e) {
597                     loge("onRinging: pendingIntent is canceled, drop incoming call", e);
598                     session.endCall();
599                 }
600             }
601         }
602 
603         @Override
onError(ISipSession session, int errorCode, String message)604         public void onError(ISipSession session, int errorCode,
605                 String message) {
606             if (SSGE_DBG) log("onError: errorCode=" + errorCode + " desc="
607                     + SipErrorCode.toString(errorCode) + ": " + message);
608         }
609 
isOpenedToReceiveCalls()610         public boolean isOpenedToReceiveCalls() {
611             return mOpenedToReceiveCalls;
612         }
613 
isRegistered()614         public boolean isRegistered() {
615             return mAutoRegistration.isRegistered();
616         }
617 
getUri()618         private String getUri() {
619             return mSipGroup.getLocalProfileUri();
620         }
621 
log(String s)622         private void log(String s) {
623             Rlog.d(SSGE_TAG, s);
624         }
625 
loge(String s, Throwable t)626         private void loge(String s, Throwable t) {
627             Rlog.e(SSGE_TAG, s, t);
628         }
629 
630     }
631 
632     private class SipKeepAliveProcessCallback implements Runnable,
633             SipSessionGroup.KeepAliveProcessCallback {
634         private static final String SKAI_TAG = "SipKeepAliveProcessCallback";
635         private static final boolean SKAI_DBG = true;
636         private static final int MIN_INTERVAL = 5; // in seconds
637         private static final int PASS_THRESHOLD = 10;
638         private static final int NAT_MEASUREMENT_RETRY_INTERVAL = 120; // in seconds
639         private SipProfile mLocalProfile;
640         private SipSessionGroupExt mGroup;
641         private SipSessionGroup.SipSessionImpl mSession;
642         private int mMinInterval;
643         private int mMaxInterval;
644         private int mInterval;
645         private int mPassCount;
646 
SipKeepAliveProcessCallback(SipProfile localProfile, int minInterval, int maxInterval)647         public SipKeepAliveProcessCallback(SipProfile localProfile,
648                 int minInterval, int maxInterval) {
649             mMaxInterval = maxInterval;
650             mMinInterval = minInterval;
651             mLocalProfile = localProfile;
652         }
653 
start()654         public void start() {
655             synchronized (SipService.this) {
656                 if (mSession != null) {
657                     return;
658                 }
659 
660                 mInterval = (mMaxInterval + mMinInterval) / 2;
661                 mPassCount = 0;
662 
663                 // Don't start measurement if the interval is too small
664                 if (mInterval < DEFAULT_KEEPALIVE_INTERVAL || checkTermination()) {
665                     if (SKAI_DBG) log("start: measurement aborted; interval=[" +
666                             mMinInterval + "," + mMaxInterval + "]");
667                     return;
668                 }
669 
670                 try {
671                     if (SKAI_DBG) log("start: interval=" + mInterval);
672 
673                     mGroup = new SipSessionGroupExt(mLocalProfile, null, null);
674                     // TODO: remove this line once SipWakeupTimer can better handle
675                     // variety of timeout values
676                     mGroup.setWakeupTimer(new SipWakeupTimer(mContext, mExecutor));
677 
678                     mSession = (SipSessionGroup.SipSessionImpl)
679                             mGroup.createSession(null);
680                     mSession.startKeepAliveProcess(mInterval, this);
681                 } catch (Throwable t) {
682                     onError(SipErrorCode.CLIENT_ERROR, t.toString());
683                 }
684             }
685         }
686 
stop()687         public void stop() {
688             synchronized (SipService.this) {
689                 if (mSession != null) {
690                     mSession.stopKeepAliveProcess();
691                     mSession = null;
692                 }
693                 if (mGroup != null) {
694                     mGroup.close();
695                     mGroup = null;
696                 }
697                 mTimer.cancel(this);
698                 if (SKAI_DBG) log("stop");
699             }
700         }
701 
restart()702         private void restart() {
703             synchronized (SipService.this) {
704                 // Return immediately if the measurement process is stopped
705                 if (mSession == null) return;
706 
707                 if (SKAI_DBG) log("restart: interval=" + mInterval);
708                 try {
709                     mSession.stopKeepAliveProcess();
710                     mPassCount = 0;
711                     mSession.startKeepAliveProcess(mInterval, this);
712                 } catch (SipException e) {
713                     loge("restart", e);
714                 }
715             }
716         }
717 
checkTermination()718         private boolean checkTermination() {
719             return ((mMaxInterval - mMinInterval) < MIN_INTERVAL);
720         }
721 
722         // SipSessionGroup.KeepAliveProcessCallback
723         @Override
onResponse(boolean portChanged)724         public void onResponse(boolean portChanged) {
725             synchronized (SipService.this) {
726                 if (!portChanged) {
727                     if (++mPassCount != PASS_THRESHOLD) return;
728                     // update the interval, since the current interval is good to
729                     // keep the port mapping.
730                     if (mKeepAliveInterval > 0) {
731                         mLastGoodKeepAliveInterval = mKeepAliveInterval;
732                     }
733                     mKeepAliveInterval = mMinInterval = mInterval;
734                     if (SKAI_DBG) {
735                         log("onResponse: portChanged=" + portChanged + " mKeepAliveInterval="
736                                 + mKeepAliveInterval);
737                     }
738                     onKeepAliveIntervalChanged();
739                 } else {
740                     // Since the rport is changed, shorten the interval.
741                     mMaxInterval = mInterval;
742                 }
743                 if (checkTermination()) {
744                     // update mKeepAliveInterval and stop measurement.
745                     stop();
746                     // If all the measurements failed, we still set it to
747                     // mMinInterval; If mMinInterval still doesn't work, a new
748                     // measurement with min interval=DEFAULT_KEEPALIVE_INTERVAL
749                     // will be conducted.
750                     mKeepAliveInterval = mMinInterval;
751                     if (SKAI_DBG) {
752                         log("onResponse: checkTermination mKeepAliveInterval="
753                                 + mKeepAliveInterval);
754                     }
755                 } else {
756                     // calculate the new interval and continue.
757                     mInterval = (mMaxInterval + mMinInterval) / 2;
758                     if (SKAI_DBG) {
759                         log("onResponse: mKeepAliveInterval=" + mKeepAliveInterval
760                                 + ", new mInterval=" + mInterval);
761                     }
762                     restart();
763                 }
764             }
765         }
766 
767         // SipSessionGroup.KeepAliveProcessCallback
768         @Override
onError(int errorCode, String description)769         public void onError(int errorCode, String description) {
770             if (SKAI_DBG) loge("onError: errorCode=" + errorCode + " desc=" + description);
771             restartLater();
772         }
773 
774         // timeout handler
775         @Override
run()776         public void run() {
777             mTimer.cancel(this);
778             restart();
779         }
780 
restartLater()781         private void restartLater() {
782             synchronized (SipService.this) {
783                 int interval = NAT_MEASUREMENT_RETRY_INTERVAL;
784                 mTimer.cancel(this);
785                 mTimer.set(interval * 1000, this);
786             }
787         }
788 
log(String s)789         private void log(String s) {
790             Rlog.d(SKAI_TAG, s);
791         }
792 
loge(String s)793         private void loge(String s) {
794             Rlog.d(SKAI_TAG, s);
795         }
796 
loge(String s, Throwable t)797         private void loge(String s, Throwable t) {
798             Rlog.d(SKAI_TAG, s, t);
799         }
800     }
801 
802     private class SipAutoReg extends SipSessionAdapter
803             implements Runnable, SipSessionGroup.KeepAliveProcessCallback {
804         private String SAR_TAG;
805         private static final boolean SAR_DBG = true;
806         private static final int MIN_KEEPALIVE_SUCCESS_COUNT = 10;
807 
808         private SipSessionGroup.SipSessionImpl mSession;
809         private SipSessionGroup.SipSessionImpl mKeepAliveSession;
810         private SipSessionListenerProxy mProxy = new SipSessionListenerProxy();
811         private int mBackoff = 1;
812         private boolean mRegistered;
813         private long mExpiryTime;
814         private int mErrorCode;
815         private String mErrorMessage;
816         private boolean mRunning = false;
817 
818         private int mKeepAliveSuccessCount = 0;
819 
start(SipSessionGroup group)820         public void start(SipSessionGroup group) {
821             if (!mRunning) {
822                 mRunning = true;
823                 mBackoff = 1;
824                 mSession = (SipSessionGroup.SipSessionImpl)
825                         group.createSession(this);
826                 // return right away if no active network connection.
827                 if (mSession == null) return;
828 
829                 // start unregistration to clear up old registration at server
830                 // TODO: when rfc5626 is deployed, use reg-id and sip.instance
831                 // in registration to avoid adding duplicate entries to server
832                 mMyWakeLock.acquire(mSession);
833                 mSession.unregister();
834                 SAR_TAG = "SipAutoReg:" +
835                         obfuscateSipUri(mSession.getLocalProfile().getUriString());
836                 if (SAR_DBG) log("start: group=" + group);
837             }
838         }
839 
startKeepAliveProcess(int interval)840         private void startKeepAliveProcess(int interval) {
841             if (SAR_DBG) log("startKeepAliveProcess: interval=" + interval);
842             if (mKeepAliveSession == null) {
843                 mKeepAliveSession = mSession.duplicate();
844             } else {
845                 mKeepAliveSession.stopKeepAliveProcess();
846             }
847             try {
848                 mKeepAliveSession.startKeepAliveProcess(interval, this);
849             } catch (SipException e) {
850                 loge("startKeepAliveProcess: interval=" + interval, e);
851             }
852         }
853 
stopKeepAliveProcess()854         private void stopKeepAliveProcess() {
855             if (mKeepAliveSession != null) {
856                 mKeepAliveSession.stopKeepAliveProcess();
857                 mKeepAliveSession = null;
858             }
859             mKeepAliveSuccessCount = 0;
860         }
861 
862         // SipSessionGroup.KeepAliveProcessCallback
863         @Override
onResponse(boolean portChanged)864         public void onResponse(boolean portChanged) {
865             synchronized (SipService.this) {
866                 if (portChanged) {
867                     int interval = getKeepAliveInterval();
868                     if (mKeepAliveSuccessCount < MIN_KEEPALIVE_SUCCESS_COUNT) {
869                         if (SAR_DBG) {
870                             log("onResponse: keepalive doesn't work with interval "
871                                     + interval + ", past success count="
872                                     + mKeepAliveSuccessCount);
873                         }
874                         if (interval > DEFAULT_KEEPALIVE_INTERVAL) {
875                             restartPortMappingLifetimeMeasurement(
876                                     mSession.getLocalProfile(), interval);
877                             mKeepAliveSuccessCount = 0;
878                         }
879                     } else {
880                         if (SAR_DBG) {
881                             log("keep keepalive going with interval "
882                                     + interval + ", past success count="
883                                     + mKeepAliveSuccessCount);
884                         }
885                         mKeepAliveSuccessCount /= 2;
886                     }
887                 } else {
888                     // Start keep-alive interval measurement on the first
889                     // successfully kept-alive SipSessionGroup
890                     startPortMappingLifetimeMeasurement(
891                             mSession.getLocalProfile());
892                     mKeepAliveSuccessCount++;
893                 }
894 
895                 if (!mRunning || !portChanged) return;
896 
897                 // The keep alive process is stopped when port is changed;
898                 // Nullify the session so that the process can be restarted
899                 // again when the re-registration is done
900                 mKeepAliveSession = null;
901 
902                 // Acquire wake lock for the registration process. The
903                 // lock will be released when registration is complete.
904                 mMyWakeLock.acquire(mSession);
905                 mSession.register(EXPIRY_TIME);
906             }
907         }
908 
909         // SipSessionGroup.KeepAliveProcessCallback
910         @Override
onError(int errorCode, String description)911         public void onError(int errorCode, String description) {
912             if (SAR_DBG) {
913                 loge("onError: errorCode=" + errorCode + " desc=" + description);
914             }
915             onResponse(true); // re-register immediately
916         }
917 
stop()918         public void stop() {
919             if (!mRunning) return;
920             mRunning = false;
921             mMyWakeLock.release(mSession);
922             if (mSession != null) {
923                 mSession.setListener(null);
924                 if (mNetworkType != -1 && mRegistered) mSession.unregister();
925             }
926 
927             mTimer.cancel(this);
928             stopKeepAliveProcess();
929 
930             mRegistered = false;
931             setListener(mProxy.getListener());
932         }
933 
onKeepAliveIntervalChanged()934         public void onKeepAliveIntervalChanged() {
935             if (mKeepAliveSession != null) {
936                 int newInterval = getKeepAliveInterval();
937                 if (SAR_DBG) {
938                     log("onKeepAliveIntervalChanged: interval=" + newInterval);
939                 }
940                 mKeepAliveSuccessCount = 0;
941                 startKeepAliveProcess(newInterval);
942             }
943         }
944 
setListener(ISipSessionListener listener)945         public void setListener(ISipSessionListener listener) {
946             synchronized (SipService.this) {
947                 mProxy.setListener(listener);
948 
949                 try {
950                     int state = (mSession == null)
951                             ? SipSession.State.READY_TO_CALL
952                             : mSession.getState();
953                     if ((state == SipSession.State.REGISTERING)
954                             || (state == SipSession.State.DEREGISTERING)) {
955                         mProxy.onRegistering(mSession);
956                     } else if (mRegistered) {
957                         int duration = (int)
958                                 (mExpiryTime - SystemClock.elapsedRealtime());
959                         mProxy.onRegistrationDone(mSession, duration);
960                     } else if (mErrorCode != SipErrorCode.NO_ERROR) {
961                         if (mErrorCode == SipErrorCode.TIME_OUT) {
962                             mProxy.onRegistrationTimeout(mSession);
963                         } else {
964                             mProxy.onRegistrationFailed(mSession, mErrorCode,
965                                     mErrorMessage);
966                         }
967                     } else if (mNetworkType == -1) {
968                         mProxy.onRegistrationFailed(mSession,
969                                 SipErrorCode.DATA_CONNECTION_LOST,
970                                 "no data connection");
971                     } else if (!mRunning) {
972                         mProxy.onRegistrationFailed(mSession,
973                                 SipErrorCode.CLIENT_ERROR,
974                                 "registration not running");
975                     } else {
976                         mProxy.onRegistrationFailed(mSession,
977                                 SipErrorCode.IN_PROGRESS,
978                                 String.valueOf(state));
979                     }
980                 } catch (Throwable t) {
981                     loge("setListener: ", t);
982                 }
983             }
984         }
985 
isRegistered()986         public boolean isRegistered() {
987             return mRegistered;
988         }
989 
990         // timeout handler: re-register
991         @Override
run()992         public void run() {
993             synchronized (SipService.this) {
994                 if (!mRunning) return;
995 
996                 mErrorCode = SipErrorCode.NO_ERROR;
997                 mErrorMessage = null;
998                 if (SAR_DBG) log("run: registering");
999                 if (mNetworkType != -1) {
1000                     mMyWakeLock.acquire(mSession);
1001                     mSession.register(EXPIRY_TIME);
1002                 }
1003             }
1004         }
1005 
restart(int duration)1006         private void restart(int duration) {
1007             if (SAR_DBG) log("restart: duration=" + duration + "s later.");
1008             mTimer.cancel(this);
1009             mTimer.set(duration * 1000, this);
1010         }
1011 
backoffDuration()1012         private int backoffDuration() {
1013             int duration = SHORT_EXPIRY_TIME * mBackoff;
1014             if (duration > 3600) {
1015                 duration = 3600;
1016             } else {
1017                 mBackoff *= 2;
1018             }
1019             return duration;
1020         }
1021 
1022         @Override
onRegistering(ISipSession session)1023         public void onRegistering(ISipSession session) {
1024             if (SAR_DBG) log("onRegistering: " + session);
1025             synchronized (SipService.this) {
1026                 if (notCurrentSession(session)) return;
1027 
1028                 mRegistered = false;
1029                 mProxy.onRegistering(session);
1030             }
1031         }
1032 
notCurrentSession(ISipSession session)1033         private boolean notCurrentSession(ISipSession session) {
1034             if (session != mSession) {
1035                 ((SipSessionGroup.SipSessionImpl) session).setListener(null);
1036                 mMyWakeLock.release(session);
1037                 return true;
1038             }
1039             return !mRunning;
1040         }
1041 
1042         @Override
onRegistrationDone(ISipSession session, int duration)1043         public void onRegistrationDone(ISipSession session, int duration) {
1044             if (SAR_DBG) log("onRegistrationDone: " + session);
1045             synchronized (SipService.this) {
1046                 if (notCurrentSession(session)) return;
1047 
1048                 mProxy.onRegistrationDone(session, duration);
1049 
1050                 if (duration > 0) {
1051                     mExpiryTime = SystemClock.elapsedRealtime()
1052                             + (duration * 1000);
1053 
1054                     if (!mRegistered) {
1055                         mRegistered = true;
1056                         // allow some overlap to avoid call drop during renew
1057                         duration -= MIN_EXPIRY_TIME;
1058                         if (duration < MIN_EXPIRY_TIME) {
1059                             duration = MIN_EXPIRY_TIME;
1060                         }
1061                         restart(duration);
1062 
1063                         SipProfile localProfile = mSession.getLocalProfile();
1064                         if ((mKeepAliveSession == null) && (isBehindNAT(mLocalIp)
1065                                 || localProfile.getSendKeepAlive())) {
1066                             startKeepAliveProcess(getKeepAliveInterval());
1067                         }
1068                     }
1069                     mMyWakeLock.release(session);
1070                 } else {
1071                     mRegistered = false;
1072                     mExpiryTime = -1L;
1073                     if (SAR_DBG) log("Refresh registration immediately");
1074                     run();
1075                 }
1076             }
1077         }
1078 
1079         @Override
onRegistrationFailed(ISipSession session, int errorCode, String message)1080         public void onRegistrationFailed(ISipSession session, int errorCode,
1081                 String message) {
1082             if (SAR_DBG) log("onRegistrationFailed: " + session + ": "
1083                     + SipErrorCode.toString(errorCode) + ": " + message);
1084             synchronized (SipService.this) {
1085                 if (notCurrentSession(session)) return;
1086 
1087                 switch (errorCode) {
1088                     case SipErrorCode.INVALID_CREDENTIALS:
1089                     case SipErrorCode.SERVER_UNREACHABLE:
1090                         if (SAR_DBG) log("   pause auto-registration");
1091                         stop();
1092                         break;
1093                     default:
1094                         restartLater();
1095                 }
1096 
1097                 mErrorCode = errorCode;
1098                 mErrorMessage = message;
1099                 mProxy.onRegistrationFailed(session, errorCode, message);
1100                 mMyWakeLock.release(session);
1101             }
1102         }
1103 
1104         @Override
onRegistrationTimeout(ISipSession session)1105         public void onRegistrationTimeout(ISipSession session) {
1106             if (SAR_DBG) log("onRegistrationTimeout: " + session);
1107             synchronized (SipService.this) {
1108                 if (notCurrentSession(session)) return;
1109 
1110                 mErrorCode = SipErrorCode.TIME_OUT;
1111                 mProxy.onRegistrationTimeout(session);
1112                 restartLater();
1113                 mMyWakeLock.release(session);
1114             }
1115         }
1116 
restartLater()1117         private void restartLater() {
1118             if (SAR_DBG) loge("restartLater");
1119             mRegistered = false;
1120             restart(backoffDuration());
1121         }
1122 
log(String s)1123         private void log(String s) {
1124             Rlog.d(SAR_TAG, s);
1125         }
1126 
loge(String s)1127         private void loge(String s) {
1128             Rlog.e(SAR_TAG, s);
1129         }
1130 
loge(String s, Throwable e)1131         private void loge(String s, Throwable e) {
1132             Rlog.e(SAR_TAG, s, e);
1133         }
1134     }
1135 
1136     private class ConnectivityReceiver extends BroadcastReceiver {
1137         @Override
onReceive(Context context, Intent intent)1138         public void onReceive(Context context, Intent intent) {
1139             Bundle bundle = intent.getExtras();
1140             if (bundle != null) {
1141                 final NetworkInfo info = (NetworkInfo)
1142                         bundle.get(ConnectivityManager.EXTRA_NETWORK_INFO);
1143 
1144                 // Run the handler in MyExecutor to be protected by wake lock
1145                 mExecutor.execute(new Runnable() {
1146                     @Override
1147                     public void run() {
1148                         onConnectivityChanged(info);
1149                     }
1150                 });
1151             }
1152         }
1153     }
1154 
registerReceivers()1155     private void registerReceivers() {
1156         mContext.registerReceiver(mConnectivityReceiver,
1157                 new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
1158         if (DBG) log("registerReceivers:");
1159     }
1160 
unregisterReceivers()1161     private void unregisterReceivers() {
1162         mContext.unregisterReceiver(mConnectivityReceiver);
1163         if (DBG) log("unregisterReceivers:");
1164 
1165         // Reset variables maintained by ConnectivityReceiver.
1166         mWifiLock.release();
1167         mNetworkType = -1;
1168     }
1169 
updateWakeLocks()1170     private void updateWakeLocks() {
1171         for (SipSessionGroupExt group : mSipGroups.values()) {
1172             if (group.isOpenedToReceiveCalls()) {
1173                 // Also grab the WifiLock when we are disconnected, so the
1174                 // system will keep trying to reconnect. It will be released
1175                 // when the system eventually connects to something else.
1176                 if (mNetworkType == ConnectivityManager.TYPE_WIFI || mNetworkType == -1) {
1177                     mWifiLock.acquire();
1178                 } else {
1179                     mWifiLock.release();
1180                 }
1181                 return;
1182             }
1183         }
1184         mWifiLock.release();
1185         mMyWakeLock.reset(); // in case there's a leak
1186     }
1187 
onConnectivityChanged(NetworkInfo info)1188     private synchronized void onConnectivityChanged(NetworkInfo info) {
1189         // We only care about the default network, and getActiveNetworkInfo()
1190         // is the only way to distinguish them. However, as broadcasts are
1191         // delivered asynchronously, we might miss DISCONNECTED events from
1192         // getActiveNetworkInfo(), which is critical to our SIP stack. To
1193         // solve this, if it is a DISCONNECTED event to our current network,
1194         // respect it. Otherwise get a new one from getActiveNetworkInfo().
1195         if (info == null || info.isConnected() || info.getType() != mNetworkType) {
1196             ConnectivityManager cm = (ConnectivityManager)
1197                     mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
1198             info = cm.getActiveNetworkInfo();
1199         }
1200 
1201         // Some devices limit SIP on Wi-Fi. In this case, if we are not on
1202         // Wi-Fi, treat it as a DISCONNECTED event.
1203         int networkType = (info != null && info.isConnected()) ? info.getType() : -1;
1204         if (mSipOnWifiOnly && networkType != ConnectivityManager.TYPE_WIFI) {
1205             networkType = -1;
1206         }
1207 
1208         // Ignore the event if the current active network is not changed.
1209         if (mNetworkType == networkType) {
1210             // TODO: Maybe we need to send seq/generation number
1211             return;
1212         }
1213         if (DBG) {
1214             log("onConnectivityChanged: " + mNetworkType +
1215                     " -> " + networkType);
1216         }
1217 
1218         try {
1219             if (mNetworkType != -1) {
1220                 mLocalIp = null;
1221                 stopPortMappingMeasurement();
1222                 for (SipSessionGroupExt group : mSipGroups.values()) {
1223                     group.onConnectivityChanged(false);
1224                 }
1225             }
1226             mNetworkType = networkType;
1227 
1228             if (mNetworkType != -1) {
1229                 mLocalIp = determineLocalIp();
1230                 mKeepAliveInterval = -1;
1231                 mLastGoodKeepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL;
1232                 for (SipSessionGroupExt group : mSipGroups.values()) {
1233                     group.onConnectivityChanged(true);
1234                 }
1235             }
1236             updateWakeLocks();
1237         } catch (SipException e) {
1238             loge("onConnectivityChanged()", e);
1239         }
1240     }
1241 
createLooper()1242     private static Looper createLooper() {
1243         HandlerThread thread = new HandlerThread("SipService.Executor");
1244         thread.start();
1245         return thread.getLooper();
1246     }
1247 
1248     // Executes immediate tasks in a single thread.
1249     // Hold/release wake lock for running tasks
1250     private class MyExecutor extends Handler implements Executor {
MyExecutor()1251         MyExecutor() {
1252             super(createLooper());
1253         }
1254 
1255         @Override
execute(Runnable task)1256         public void execute(Runnable task) {
1257             mMyWakeLock.acquire(task);
1258             Message.obtain(this, 0/* don't care */, task).sendToTarget();
1259         }
1260 
1261         @Override
handleMessage(Message msg)1262         public void handleMessage(Message msg) {
1263             if (msg.obj instanceof Runnable) {
1264                 executeInternal((Runnable) msg.obj);
1265             } else {
1266                 if (DBG) log("handleMessage: not Runnable ignore msg=" + msg);
1267             }
1268         }
1269 
executeInternal(Runnable task)1270         private void executeInternal(Runnable task) {
1271             try {
1272                 task.run();
1273             } catch (Throwable t) {
1274                 loge("run task: " + task, t);
1275             } finally {
1276                 mMyWakeLock.release(task);
1277             }
1278         }
1279     }
1280 
log(String s)1281     private void log(String s) {
1282         Rlog.d(TAG, s);
1283     }
1284 
slog(String s)1285     private static void slog(String s) {
1286         Rlog.d(TAG, s);
1287     }
1288 
loge(String s, Throwable e)1289     private void loge(String s, Throwable e) {
1290         Rlog.e(TAG, s, e);
1291     }
1292 
obfuscateSipUri(String sipUri)1293     public static String obfuscateSipUri(String sipUri) {
1294         StringBuilder sb = new StringBuilder();
1295         int start = 0;
1296         sipUri = sipUri.trim();
1297         if (sipUri.startsWith("sip:")) {
1298             start = 4;
1299             sb.append("sip:");
1300         }
1301 
1302         char prevC = '\0';
1303         int len = sipUri.length();
1304         for (int i = start; i < len; i++) {
1305             char c = sipUri.charAt(i);
1306             char nextC = (i + 1 < len) ? sipUri.charAt(i + 1) : '\0';
1307             char charToAppend = '*';
1308 
1309             // This logic allows the first and last letter before an '@' sign to show up without
1310             // obfuscation as well as the first and last letter an '@' sign.
1311             // e.g.: brad@comment.it => b**d@c******.*t
1312             if ((i - start < 1) ||
1313                     (i + 1 == len) ||
1314                     isAllowedCharacter(c) ||
1315                     (prevC == '@') ||
1316                     (nextC == '@')) {
1317                 charToAppend = c;
1318             }
1319             sb.append(charToAppend);
1320             prevC = c;
1321         }
1322         return sb.toString();
1323     }
1324 
isAllowedCharacter(char c)1325     private static boolean isAllowedCharacter(char c) {
1326         return c == '@' || c == '.';
1327     }
1328 }
1329