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