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