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.services.telephony.sip;
18 
19 import com.android.internal.telephony.Phone;
20 import com.android.internal.telephony.PhoneConstants;
21 
22 import android.app.ActionBar;
23 import android.app.AlertDialog;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.Intent;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageManager;
29 import android.net.sip.SipErrorCode;
30 import android.net.sip.SipException;
31 import android.net.sip.SipManager;
32 import android.net.sip.SipProfile;
33 import android.net.sip.SipRegistrationListener;
34 import android.os.Bundle;
35 import android.os.Parcelable;
36 import android.os.Process;
37 import android.preference.CheckBoxPreference;
38 import android.preference.Preference;
39 import android.preference.Preference.OnPreferenceClickListener;
40 import android.preference.PreferenceActivity;
41 import android.preference.PreferenceCategory;
42 import android.telecom.PhoneAccount;
43 import android.telecom.TelecomManager;
44 import android.text.TextUtils;
45 import android.util.Log;
46 import android.view.Menu;
47 import android.view.MenuItem;
48 
49 import java.io.IOException;
50 import java.util.Collections;
51 import java.util.Comparator;
52 import java.util.LinkedHashMap;
53 import java.util.List;
54 import java.util.Map;
55 
56 /**
57  * The PreferenceActivity class for managing sip profile preferences.
58  */
59 public class SipSettings extends PreferenceActivity {
60     private static final String PREFIX = "[SipSettings] ";
61     private static final boolean VERBOSE = false; /* STOP SHIP if true */
62 
63     public static final String SIP_SHARED_PREFERENCES = "SIP_PREFERENCES";
64 
65     private static final int MENU_ADD_ACCOUNT = Menu.FIRST;
66 
67     static final String KEY_SIP_PROFILE = "sip_profile";
68 
69     private static final String PREF_SIP_LIST = "sip_account_list";
70 
71     private static final int REQUEST_ADD_OR_EDIT_SIP_PROFILE = 1;
72 
73     private PackageManager mPackageManager;
74     private SipManager mSipManager;
75     private SipProfileDb mProfileDb;
76 
77     private SipProfile mProfile; // profile that's being edited
78 
79     private PreferenceCategory mSipListContainer;
80     private Map<String, SipPreference> mSipPreferenceMap;
81     private List<SipProfile> mSipProfileList;
82     private SipSharedPreferences mSipSharedPreferences;
83     private int mUid = Process.myUid();
84 
85     private class SipPreference extends Preference {
86         SipProfile mProfile;
SipPreference(Context c, SipProfile p)87         SipPreference(Context c, SipProfile p) {
88             super(c);
89             setProfile(p);
90         }
91 
getProfile()92         SipProfile getProfile() {
93             return mProfile;
94         }
95 
setProfile(SipProfile p)96         void setProfile(SipProfile p) {
97             mProfile = p;
98             setTitle(getProfileName(p));
99             updateSummary(mSipSharedPreferences.isReceivingCallsEnabled()
100                     ? getString(R.string.registration_status_checking_status)
101                     : getString(R.string.registration_status_not_receiving));
102         }
103 
updateSummary(String registrationStatus)104         void updateSummary(String registrationStatus) {
105             int profileUid = mProfile.getCallingUid();
106             if (VERBOSE) {
107                 log("SipPreference.updateSummary, profile uid: " + profileUid +
108                         " registration: " + registrationStatus +
109                         " status: " + registrationStatus);
110             }
111             String summary = "";
112             if ((profileUid > 0) && (profileUid != mUid)) {
113                 // from third party apps
114                 summary = getString(R.string.third_party_account_summary,
115                         getPackageNameFromUid(profileUid));
116             } else {
117                 summary = registrationStatus;
118             }
119             setSummary(summary);
120         }
121     }
122 
getPackageNameFromUid(int uid)123     private String getPackageNameFromUid(int uid) {
124         try {
125             String[] pkgs = mPackageManager.getPackagesForUid(uid);
126             ApplicationInfo ai = mPackageManager.getApplicationInfo(pkgs[0], 0);
127             return ai.loadLabel(mPackageManager).toString();
128         } catch (PackageManager.NameNotFoundException e) {
129             log("getPackageNameFromUid, cannot find name of uid: " + uid + ", exception: " + e);
130         }
131         return "uid:" + uid;
132     }
133 
134     @Override
onCreate(Bundle savedInstanceState)135     public void onCreate(Bundle savedInstanceState) {
136         super.onCreate(savedInstanceState);
137 
138         mSipManager = SipManager.newInstance(this);
139         mSipSharedPreferences = new SipSharedPreferences(this);
140         mProfileDb = new SipProfileDb(this);
141 
142         mPackageManager = getPackageManager();
143         setContentView(R.layout.sip_settings_ui);
144         addPreferencesFromResource(R.xml.sip_setting);
145         mSipListContainer = (PreferenceCategory) findPreference(PREF_SIP_LIST);
146 
147         updateProfilesStatus();
148 
149         ActionBar actionBar = getActionBar();
150         if (actionBar != null) {
151             actionBar.setDisplayHomeAsUpEnabled(true);
152         }
153     }
154 
155     @Override
onResume()156     public void onResume() {
157         super.onResume();
158     }
159 
160     @Override
onDestroy()161     protected void onDestroy() {
162         super.onDestroy();
163         unregisterForContextMenu(getListView());
164     }
165 
166     @Override
onActivityResult(final int requestCode, final int resultCode, final Intent intent)167     protected void onActivityResult(final int requestCode, final int resultCode,
168             final Intent intent) {
169         if (resultCode != RESULT_OK && resultCode != RESULT_FIRST_USER) return;
170         new Thread() {
171             @Override
172             public void run() {
173                 try {
174                     if (mProfile != null) {
175                         if (VERBOSE) log("onActivityResult, remove: " + mProfile.getProfileName());
176                         deleteProfile(mProfile);
177                     }
178 
179                     SipProfile profile = intent.getParcelableExtra(KEY_SIP_PROFILE);
180                     if (resultCode == RESULT_OK) {
181                         if (VERBOSE) log("onActivityResult, new: " + profile.getProfileName());
182                         addProfile(profile);
183                     }
184                     updateProfilesStatus();
185                 } catch (IOException e) {
186                     log("onActivityResult, can not handle the profile:  " + e);
187                 }
188             }
189         }.start();
190     }
191 
updateProfilesStatus()192     private void updateProfilesStatus() {
193         new Thread(new Runnable() {
194             @Override
195             public void run() {
196                 try {
197                     retrieveSipLists();
198                 } catch (Exception e) {
199                     log("updateProfilesStatus, exception: " + e);
200                 }
201             }
202         }).start();
203     }
204 
getProfileName(SipProfile profile)205     private String getProfileName(SipProfile profile) {
206         String profileName = profile.getProfileName();
207         if (TextUtils.isEmpty(profileName)) {
208             profileName = profile.getUserName() + "@" + profile.getSipDomain();
209         }
210         return profileName;
211     }
212 
retrieveSipLists()213     private void retrieveSipLists() {
214         mSipPreferenceMap = new LinkedHashMap<String, SipPreference>();
215         mSipProfileList = mProfileDb.retrieveSipProfileList();
216         processActiveProfilesFromSipService();
217         Collections.sort(mSipProfileList, new Comparator<SipProfile>() {
218             @Override
219             public int compare(SipProfile p1, SipProfile p2) {
220                 return getProfileName(p1).compareTo(getProfileName(p2));
221             }
222 
223             public boolean equals(SipProfile p) {
224                 // not used
225                 return false;
226             }
227         });
228         mSipListContainer.removeAll();
229         if (mSipProfileList.isEmpty()) {
230             getPreferenceScreen().removePreference(mSipListContainer);
231         } else {
232             getPreferenceScreen().addPreference(mSipListContainer);
233             for (SipProfile p : mSipProfileList) {
234                 addPreferenceFor(p);
235             }
236         }
237 
238         if (!mSipSharedPreferences.isReceivingCallsEnabled()) return;
239         for (SipProfile p : mSipProfileList) {
240             if (mUid == p.getCallingUid()) {
241                 try {
242                     mSipManager.setRegistrationListener(
243                             p.getUriString(), createRegistrationListener());
244                 } catch (SipException e) {
245                     log("retrieveSipLists, cannot set registration listener: " + e);
246                 }
247             }
248         }
249     }
250 
processActiveProfilesFromSipService()251     private void processActiveProfilesFromSipService() {
252         SipProfile[] activeList = mSipManager.getListOfProfiles();
253         for (SipProfile activeProfile : activeList) {
254             SipProfile profile = getProfileFromList(activeProfile);
255             if (profile == null) {
256                 mSipProfileList.add(activeProfile);
257             } else {
258                 profile.setCallingUid(activeProfile.getCallingUid());
259             }
260         }
261     }
262 
getProfileFromList(SipProfile activeProfile)263     private SipProfile getProfileFromList(SipProfile activeProfile) {
264         for (SipProfile p : mSipProfileList) {
265             if (p.getUriString().equals(activeProfile.getUriString())) {
266                 return p;
267             }
268         }
269         return null;
270     }
271 
addPreferenceFor(SipProfile p)272     private void addPreferenceFor(SipProfile p) {
273         String status;
274         if (VERBOSE) log("addPreferenceFor, profile uri: " + p.getUri());
275         SipPreference pref = new SipPreference(this, p);
276         mSipPreferenceMap.put(p.getUriString(), pref);
277         mSipListContainer.addPreference(pref);
278 
279         pref.setOnPreferenceClickListener(
280                 new Preference.OnPreferenceClickListener() {
281                     @Override
282                     public boolean onPreferenceClick(Preference pref) {
283                         handleProfileClick(((SipPreference) pref).mProfile);
284                         return true;
285                     }
286                 });
287     }
288 
handleProfileClick(final SipProfile profile)289     private void handleProfileClick(final SipProfile profile) {
290         int uid = profile.getCallingUid();
291         if (uid == mUid || uid == 0) {
292             startSipEditor(profile);
293             return;
294         }
295         new AlertDialog.Builder(this)
296                 .setTitle(R.string.alert_dialog_close)
297                 .setIconAttribute(android.R.attr.alertDialogIcon)
298                 .setPositiveButton(R.string.close_profile,
299                         new DialogInterface.OnClickListener() {
300                             @Override
301                             public void onClick(DialogInterface dialog, int w) {
302                                 deleteProfile(profile);
303                                 unregisterProfile(profile);
304                             }
305                         })
306                 .setNegativeButton(android.R.string.cancel, null)
307                 .show();
308     }
309 
unregisterProfile(final SipProfile p)310     private void unregisterProfile(final SipProfile p) {
311         // run it on background thread for better UI response
312         new Thread(new Runnable() {
313             @Override
314             public void run() {
315                 try {
316                     mSipManager.close(p.getUriString());
317                 } catch (Exception e) {
318                     log("unregisterProfile, unregister failed, SipService died? Exception: " + e);
319                 }
320             }
321         }, "unregisterProfile").start();
322     }
323 
deleteProfile(SipProfile p)324     void deleteProfile(SipProfile p) {
325         mSipProfileList.remove(p);
326         SipPreference pref = mSipPreferenceMap.remove(p.getUriString());
327         mSipListContainer.removePreference(pref);
328     }
329 
addProfile(SipProfile p)330     private void addProfile(SipProfile p) throws IOException {
331         try {
332             mSipManager.setRegistrationListener(p.getUriString(),
333                     createRegistrationListener());
334         } catch (Exception e) {
335             log("addProfile, cannot set registration listener: " + e);
336         }
337         mSipProfileList.add(p);
338         addPreferenceFor(p);
339     }
340 
startSipEditor(final SipProfile profile)341     private void startSipEditor(final SipProfile profile) {
342         mProfile = profile;
343         Intent intent = new Intent(this, SipEditor.class);
344         intent.putExtra(KEY_SIP_PROFILE, (Parcelable) profile);
345         startActivityForResult(intent, REQUEST_ADD_OR_EDIT_SIP_PROFILE);
346     }
347 
showRegistrationMessage(final String profileUri, final String message)348     private void showRegistrationMessage(final String profileUri,
349             final String message) {
350         runOnUiThread(new Runnable() {
351             @Override
352             public void run() {
353                 SipPreference pref = mSipPreferenceMap.get(profileUri);
354                 if (pref != null) {
355                     pref.updateSummary(message);
356                 }
357             }
358         });
359     }
360 
createRegistrationListener()361     private SipRegistrationListener createRegistrationListener() {
362         return new SipRegistrationListener() {
363             @Override
364             public void onRegistrationDone(String profileUri, long expiryTime) {
365                 showRegistrationMessage(profileUri, getString(
366                         R.string.registration_status_done));
367             }
368 
369             @Override
370             public void onRegistering(String profileUri) {
371                 showRegistrationMessage(profileUri, getString(
372                         R.string.registration_status_registering));
373             }
374 
375             @Override
376             public void onRegistrationFailed(String profileUri, int errorCode,
377                     String message) {
378                 switch (errorCode) {
379                     case SipErrorCode.IN_PROGRESS:
380                         showRegistrationMessage(profileUri, getString(
381                                 R.string.registration_status_still_trying));
382                         break;
383                     case SipErrorCode.INVALID_CREDENTIALS:
384                         showRegistrationMessage(profileUri, getString(
385                                 R.string.registration_status_invalid_credentials));
386                         break;
387                     case SipErrorCode.SERVER_UNREACHABLE:
388                         showRegistrationMessage(profileUri, getString(
389                                 R.string.registration_status_server_unreachable));
390                         break;
391                     case SipErrorCode.DATA_CONNECTION_LOST:
392                         if (SipManager.isSipWifiOnly(getApplicationContext())){
393                             showRegistrationMessage(profileUri, getString(
394                                     R.string.registration_status_no_wifi_data));
395                         } else {
396                             showRegistrationMessage(profileUri, getString(
397                                     R.string.registration_status_no_data));
398                         }
399                         break;
400                     case SipErrorCode.CLIENT_ERROR:
401                         showRegistrationMessage(profileUri, getString(
402                                 R.string.registration_status_not_running));
403                         break;
404                     default:
405                         showRegistrationMessage(profileUri, getString(
406                                 R.string.registration_status_failed_try_later,
407                                 message));
408                 }
409             }
410         };
411     }
412 
413     @Override
414     public boolean onCreateOptionsMenu(Menu menu) {
415         super.onCreateOptionsMenu(menu);
416         menu.add(0, MENU_ADD_ACCOUNT, 0, R.string.add_sip_account)
417                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
418         return true;
419     }
420 
421     @Override
422     public boolean onPrepareOptionsMenu(Menu menu) {
423         menu.findItem(MENU_ADD_ACCOUNT).setEnabled(SipUtil.isPhoneIdle(this));
424         return super.onPrepareOptionsMenu(menu);
425     }
426 
427     @Override
428     public boolean onOptionsItemSelected(MenuItem item) {
429         final int itemId = item.getItemId();
430         switch (itemId) {
431             case MENU_ADD_ACCOUNT: {
432                 startSipEditor(null);
433                 return true;
434             }
435             case android.R.id.home: {
436                 onBackPressed();
437                 return true;
438             }
439         }
440         return super.onOptionsItemSelected(item);
441     }
442 
443     private static void log(String msg) {
444         Log.d(SipUtil.LOG_TAG, PREFIX + msg);
445     }
446 }
447