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