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.phone; 18 19 import com.android.internal.telephony.CallManager; 20 import com.android.internal.telephony.Phone; 21 import com.android.internal.telephony.PhoneFactory; 22 import com.android.phone.sip.SipProfileDb; 23 import com.android.phone.sip.SipSettings; 24 import com.android.phone.sip.SipSharedPreferences; 25 26 import android.app.Activity; 27 import android.app.AlertDialog; 28 import android.app.Dialog; 29 import android.content.Context; 30 import android.content.DialogInterface; 31 import android.content.Intent; 32 import android.net.ConnectivityManager; 33 import android.net.NetworkInfo; 34 import android.net.Uri; 35 import android.net.sip.SipException; 36 import android.net.sip.SipManager; 37 import android.net.sip.SipProfile; 38 import android.os.Bundle; 39 import android.os.Handler; 40 import android.os.Message; 41 import android.os.SystemProperties; 42 import android.provider.Settings; 43 import android.telephony.PhoneNumberUtils; 44 import android.util.Log; 45 import android.view.LayoutInflater; 46 import android.view.View; 47 import android.view.WindowManager; 48 import android.widget.CheckBox; 49 import android.widget.CompoundButton; 50 import android.widget.TextView; 51 52 import java.util.List; 53 54 /** 55 * Activity that selects the proper phone type for an outgoing call. 56 * 57 * This activity determines which Phone type (SIP or PSTN) should be used 58 * for an outgoing phone call, depending on the outgoing "number" (which 59 * may be either a PSTN number or a SIP address) as well as the user's SIP 60 * preferences. In some cases this activity has no interaction with the 61 * user, but in other cases it may (by bringing up a dialog if the user's 62 * preference is "Ask for each call".) 63 */ 64 public class SipCallOptionHandler extends Activity implements 65 DialogInterface.OnClickListener, DialogInterface.OnCancelListener, 66 CompoundButton.OnCheckedChangeListener { 67 static final String TAG = "SipCallOptionHandler"; 68 private static final boolean DBG = 69 (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1); 70 71 static final int DIALOG_SELECT_PHONE_TYPE = 0; 72 static final int DIALOG_SELECT_OUTGOING_SIP_PHONE = 1; 73 static final int DIALOG_START_SIP_SETTINGS = 2; 74 static final int DIALOG_NO_INTERNET_ERROR = 3; 75 static final int DIALOG_NO_VOIP = 4; 76 static final int DIALOG_SIZE = 5; 77 78 private Intent mIntent; 79 private List<SipProfile> mProfileList; 80 private String mCallOption; 81 private String mNumber; 82 private SipSharedPreferences mSipSharedPreferences; 83 private SipProfileDb mSipProfileDb; 84 private Dialog[] mDialogs = new Dialog[DIALOG_SIZE]; 85 private SipProfile mOutgoingSipProfile; 86 private TextView mUnsetPriamryHint; 87 private boolean mUseSipPhone = false; 88 private boolean mMakePrimary = false; 89 90 private static final int EVENT_DELAYED_FINISH = 1; 91 92 private static final int DELAYED_FINISH_TIME = 2000; // msec 93 94 private final Handler mHandler = new Handler() { 95 @Override 96 public void handleMessage(Message msg) { 97 if (msg.what == EVENT_DELAYED_FINISH) { 98 finish(); 99 } else { 100 Log.wtf(TAG, "Unknown message id: " + msg.what); 101 } 102 } 103 }; 104 105 @Override 106 public void onCreate(Bundle savedInstanceState) { 107 super.onCreate(savedInstanceState); 108 109 Intent intent = getIntent(); 110 String action = intent.getAction(); 111 112 // This activity is only ever launched with the 113 // ACTION_SIP_SELECT_PHONE action. 114 if (!OutgoingCallBroadcaster.ACTION_SIP_SELECT_PHONE.equals(action)) { 115 Log.wtf(TAG, "onCreate: got intent action '" + action + "', expected " 116 + OutgoingCallBroadcaster.ACTION_SIP_SELECT_PHONE); 117 finish(); 118 return; 119 } 120 121 // mIntent is a copy of the original CALL intent that started the 122 // whole outgoing-call sequence. This intent will ultimately be 123 // passed to CallController.placeCall() after displaying the SIP 124 // call options dialog (if necessary). 125 mIntent = (Intent) intent.getParcelableExtra(OutgoingCallBroadcaster.EXTRA_NEW_CALL_INTENT); 126 if (mIntent == null) { 127 finish(); 128 return; 129 } 130 131 // Allow this activity to be visible in front of the keyguard. 132 // (This is only necessary for obscure scenarios like the user 133 // initiating a call and then immediately pressing the Power 134 // button.) 135 getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); 136 137 // If we're trying to make a SIP call, return a SipPhone if one is 138 // available. 139 // 140 // - If it's a sip: URI, this is definitely a SIP call, regardless 141 // of whether the data is a SIP address or a regular phone 142 // number. 143 // 144 // - If this is a tel: URI but the data contains an "@" character 145 // (see PhoneNumberUtils.isUriNumber()) we consider that to be a 146 // SIP number too. 147 // 148 // TODO: Eventually we may want to disallow that latter case 149 // (e.g. "tel:foo (at) example.com"). 150 // 151 // TODO: We should also consider moving this logic into the 152 // CallManager, where it could be made more generic. 153 // (For example, each "telephony provider" could be allowed 154 // to register the URI scheme(s) that it can handle, and the 155 // CallManager would then find the best match for every 156 // outgoing call.) 157 158 boolean voipSupported = PhoneUtils.isVoipSupported(); 159 if (DBG) Log.v(TAG, "voipSupported: " + voipSupported); 160 mSipProfileDb = new SipProfileDb(this); 161 mSipSharedPreferences = new SipSharedPreferences(this); 162 mCallOption = mSipSharedPreferences.getSipCallOption(); 163 if (DBG) Log.v(TAG, "Call option: " + mCallOption); 164 Uri uri = mIntent.getData(); 165 String scheme = uri.getScheme(); 166 mNumber = PhoneNumberUtils.getNumberFromIntent(mIntent, this); 167 boolean isInCellNetwork = PhoneGlobals.getInstance().phoneMgr.isRadioOn(); 168 boolean isKnownCallScheme = Constants.SCHEME_TEL.equals(scheme) 169 || Constants.SCHEME_SIP.equals(scheme); 170 boolean isRegularCall = Constants.SCHEME_TEL.equals(scheme) 171 && !PhoneNumberUtils.isUriNumber(mNumber); 172 173 // Bypass the handler if the call scheme is not sip or tel. 174 if (!isKnownCallScheme) { 175 setResultAndFinish(); 176 return; 177 } 178 179 // Check if VoIP feature is supported. 180 if (!voipSupported) { 181 if (!isRegularCall) { 182 showDialog(DIALOG_NO_VOIP); 183 } else { 184 setResultAndFinish(); 185 } 186 return; 187 } 188 189 // Since we are not sure if anyone has touched the number during 190 // the NEW_OUTGOING_CALL broadcast, we just check if the provider 191 // put their gateway information in the intent. If so, it means 192 // someone has changed the destination number. We then make the 193 // call via the default pstn network. However, if one just alters 194 // the destination directly, then we still let it go through the 195 // Internet call option process. 196 if (!CallGatewayManager.hasPhoneProviderExtras(mIntent)) { 197 if (!isNetworkConnected()) { 198 if (!isRegularCall) { 199 showDialog(DIALOG_NO_INTERNET_ERROR); 200 return; 201 } 202 } else { 203 if (mCallOption.equals(Settings.System.SIP_ASK_ME_EACH_TIME) 204 && isRegularCall && isInCellNetwork) { 205 showDialog(DIALOG_SELECT_PHONE_TYPE); 206 return; 207 } 208 if (!mCallOption.equals(Settings.System.SIP_ADDRESS_ONLY) 209 || !isRegularCall) { 210 mUseSipPhone = true; 211 } 212 } 213 } 214 215 if (mUseSipPhone) { 216 // If there is no sip profile and it is a regular call, then we 217 // should use pstn network instead. 218 if ((mSipProfileDb.getProfilesCount() > 0) || !isRegularCall) { 219 startGetPrimarySipPhoneThread(); 220 return; 221 } else { 222 mUseSipPhone = false; 223 } 224 } 225 setResultAndFinish(); 226 } 227 228 /** 229 * Starts a delayed finish() in order to give the UI 230 * some time to start up. 231 */ 232 private void startDelayedFinish() { 233 mHandler.sendEmptyMessageDelayed(EVENT_DELAYED_FINISH, DELAYED_FINISH_TIME); 234 } 235 236 @Override 237 public void onPause() { 238 super.onPause(); 239 if (isFinishing()) return; 240 for (Dialog dialog : mDialogs) { 241 if (dialog != null) dialog.dismiss(); 242 } 243 finish(); 244 } 245 246 protected Dialog onCreateDialog(int id) { 247 Dialog dialog; 248 switch(id) { 249 case DIALOG_SELECT_PHONE_TYPE: 250 dialog = new AlertDialog.Builder(this) 251 .setTitle(R.string.pick_outgoing_call_phone_type) 252 .setIconAttribute(android.R.attr.alertDialogIcon) 253 .setSingleChoiceItems(R.array.phone_type_values, -1, this) 254 .setNegativeButton(android.R.string.cancel, this) 255 .setOnCancelListener(this) 256 .create(); 257 break; 258 case DIALOG_SELECT_OUTGOING_SIP_PHONE: 259 dialog = new AlertDialog.Builder(this) 260 .setTitle(R.string.pick_outgoing_sip_phone) 261 .setIconAttribute(android.R.attr.alertDialogIcon) 262 .setSingleChoiceItems(getProfileNameArray(), -1, this) 263 .setNegativeButton(android.R.string.cancel, this) 264 .setOnCancelListener(this) 265 .create(); 266 addMakeDefaultCheckBox(dialog); 267 break; 268 case DIALOG_START_SIP_SETTINGS: 269 dialog = new AlertDialog.Builder(this) 270 .setTitle(R.string.no_sip_account_found_title) 271 .setMessage(R.string.no_sip_account_found) 272 .setIconAttribute(android.R.attr.alertDialogIcon) 273 .setPositiveButton(R.string.sip_menu_add, this) 274 .setNegativeButton(android.R.string.cancel, this) 275 .setOnCancelListener(this) 276 .create(); 277 break; 278 case DIALOG_NO_INTERNET_ERROR: 279 boolean wifiOnly = SipManager.isSipWifiOnly(this); 280 dialog = new AlertDialog.Builder(this) 281 .setTitle(wifiOnly ? R.string.no_wifi_available_title 282 : R.string.no_internet_available_title) 283 .setMessage(wifiOnly ? R.string.no_wifi_available 284 : R.string.no_internet_available) 285 .setIconAttribute(android.R.attr.alertDialogIcon) 286 .setPositiveButton(android.R.string.ok, this) 287 .setOnCancelListener(this) 288 .create(); 289 break; 290 case DIALOG_NO_VOIP: 291 dialog = new AlertDialog.Builder(this) 292 .setTitle(R.string.no_voip) 293 .setIconAttribute(android.R.attr.alertDialogIcon) 294 .setPositiveButton(android.R.string.ok, this) 295 .setOnCancelListener(this) 296 .create(); 297 break; 298 default: 299 dialog = null; 300 } 301 if (dialog != null) { 302 mDialogs[id] = dialog; 303 } 304 return dialog; 305 } 306 307 private void addMakeDefaultCheckBox(Dialog dialog) { 308 LayoutInflater inflater = (LayoutInflater) getSystemService( 309 Context.LAYOUT_INFLATER_SERVICE); 310 View view = inflater.inflate( 311 com.android.internal.R.layout.always_use_checkbox, null); 312 CheckBox makePrimaryCheckBox = 313 (CheckBox)view.findViewById(com.android.internal.R.id.alwaysUse); 314 makePrimaryCheckBox.setText(R.string.remember_my_choice); 315 makePrimaryCheckBox.setOnCheckedChangeListener(this); 316 mUnsetPriamryHint = (TextView)view.findViewById( 317 com.android.internal.R.id.clearDefaultHint); 318 mUnsetPriamryHint.setText(R.string.reset_my_choice_hint); 319 mUnsetPriamryHint.setVisibility(View.GONE); 320 ((AlertDialog)dialog).setView(view); 321 } 322 323 private CharSequence[] getProfileNameArray() { 324 CharSequence[] entries = new CharSequence[mProfileList.size()]; 325 int i = 0; 326 for (SipProfile p : mProfileList) { 327 entries[i++] = p.getProfileName(); 328 } 329 return entries; 330 } 331 332 public void onClick(DialogInterface dialog, int id) { 333 if (id == DialogInterface.BUTTON_NEGATIVE) { 334 // button negative is cancel 335 finish(); 336 return; 337 } else if(dialog == mDialogs[DIALOG_SELECT_PHONE_TYPE]) { 338 String selection = getResources().getStringArray( 339 R.array.phone_type_values)[id]; 340 if (DBG) Log.v(TAG, "User pick phone " + selection); 341 if (selection.equals(getString(R.string.internet_phone))) { 342 mUseSipPhone = true; 343 startGetPrimarySipPhoneThread(); 344 return; 345 } 346 } else if (dialog == mDialogs[DIALOG_SELECT_OUTGOING_SIP_PHONE]) { 347 mOutgoingSipProfile = mProfileList.get(id); 348 } else if ((dialog == mDialogs[DIALOG_NO_INTERNET_ERROR]) 349 || (dialog == mDialogs[DIALOG_NO_VOIP])) { 350 finish(); 351 return; 352 } else { 353 if (id == DialogInterface.BUTTON_POSITIVE) { 354 // Redirect to sip settings and drop the call. 355 Intent newIntent = new Intent(this, SipSettings.class); 356 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 357 startActivity(newIntent); 358 } 359 finish(); 360 return; 361 } 362 setResultAndFinish(); 363 } 364 365 public void onCancel(DialogInterface dialog) { 366 finish(); 367 } 368 369 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 370 mMakePrimary = isChecked; 371 if (isChecked) { 372 mUnsetPriamryHint.setVisibility(View.VISIBLE); 373 } else { 374 mUnsetPriamryHint.setVisibility(View.INVISIBLE); 375 } 376 } 377 378 private void createSipPhoneIfNeeded(SipProfile p) { 379 CallManager cm = PhoneGlobals.getInstance().mCM; 380 if (PhoneUtils.getSipPhoneFromUri(cm, p.getUriString()) != null) return; 381 382 // Create the phone since we can not find it in CallManager 383 try { 384 SipManager.newInstance(this).open(p); 385 Phone phone = PhoneFactory.makeSipPhone(p.getUriString()); 386 if (phone != null) { 387 cm.registerPhone(phone); 388 } else { 389 Log.e(TAG, "cannot make sipphone profile" + p); 390 } 391 } catch (SipException e) { 392 Log.e(TAG, "cannot open sip profile" + p, e); 393 } 394 } 395 396 private void setResultAndFinish() { 397 runOnUiThread(new Runnable() { 398 public void run() { 399 if (mOutgoingSipProfile != null) { 400 if (!isNetworkConnected()) { 401 showDialog(DIALOG_NO_INTERNET_ERROR); 402 return; 403 } 404 if (DBG) Log.v(TAG, "primary SIP URI is " + 405 mOutgoingSipProfile.getUriString()); 406 createSipPhoneIfNeeded(mOutgoingSipProfile); 407 mIntent.putExtra(OutgoingCallBroadcaster.EXTRA_SIP_PHONE_URI, 408 mOutgoingSipProfile.getUriString()); 409 if (mMakePrimary) { 410 mSipSharedPreferences.setPrimaryAccount( 411 mOutgoingSipProfile.getUriString()); 412 } 413 } 414 415 if (mUseSipPhone && mOutgoingSipProfile == null) { 416 showDialog(DIALOG_START_SIP_SETTINGS); 417 return; 418 } else { 419 // Woo hoo -- it's finally OK to initiate the outgoing call! 420 PhoneGlobals.getInstance().callController.placeCall(mIntent); 421 } 422 startDelayedFinish(); 423 } 424 }); 425 } 426 427 private boolean isNetworkConnected() { 428 ConnectivityManager cm = (ConnectivityManager) getSystemService( 429 Context.CONNECTIVITY_SERVICE); 430 if (cm != null) { 431 NetworkInfo ni = cm.getActiveNetworkInfo(); 432 if ((ni == null) || !ni.isConnected()) return false; 433 434 return ((ni.getType() == ConnectivityManager.TYPE_WIFI) 435 || !SipManager.isSipWifiOnly(this)); 436 } 437 return false; 438 } 439 440 private void startGetPrimarySipPhoneThread() { 441 new Thread(new Runnable() { 442 public void run() { 443 getPrimarySipPhone(); 444 } 445 }).start(); 446 } 447 448 private void getPrimarySipPhone() { 449 String primarySipUri = mSipSharedPreferences.getPrimaryAccount(); 450 451 mOutgoingSipProfile = getPrimaryFromExistingProfiles(primarySipUri); 452 if (mOutgoingSipProfile == null) { 453 if ((mProfileList != null) && (mProfileList.size() > 0)) { 454 runOnUiThread(new Runnable() { 455 public void run() { 456 showDialog(DIALOG_SELECT_OUTGOING_SIP_PHONE); 457 } 458 }); 459 return; 460 } 461 } 462 setResultAndFinish(); 463 } 464 465 private SipProfile getPrimaryFromExistingProfiles(String primarySipUri) { 466 mProfileList = mSipProfileDb.retrieveSipProfileList(); 467 if (mProfileList == null) return null; 468 for (SipProfile p : mProfileList) { 469 if (p.getUriString().equals(primarySipUri)) return p; 470 } 471 return null; 472 } 473 } 474