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