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