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