blob: 5a7e91f269d1e8d4890332198fcff57ca51b1cc5 [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;
22import com.android.phone.sip.SipProfileDb;
23import com.android.phone.sip.SipSettings;
24import com.android.phone.sip.SipSharedPreferences;
25
26import android.app.Activity;
27import android.app.AlertDialog;
28import android.app.Dialog;
29import android.content.Context;
30import android.content.DialogInterface;
31import android.content.Intent;
32import android.net.ConnectivityManager;
33import android.net.NetworkInfo;
34import android.net.Uri;
35import android.net.sip.SipException;
36import android.net.sip.SipManager;
37import android.net.sip.SipProfile;
38import android.os.Bundle;
Yorke Leea76228f2013-09-16 09:05:58 -070039import android.os.Handler;
40import android.os.Message;
Santos Cordon7d4ddf62013-07-10 11:58:08 -070041import android.os.SystemProperties;
42import android.provider.Settings;
43import android.telephony.PhoneNumberUtils;
44import android.util.Log;
45import android.view.LayoutInflater;
46import android.view.View;
47import android.view.WindowManager;
48import android.widget.CheckBox;
49import android.widget.CompoundButton;
50import android.widget.TextView;
51
52import 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 */
64public 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
Yorke Leea76228f2013-09-16 09:05:58 -070090 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
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700105 @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@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.
Santos Cordon69a69192013-08-22 14:25:42 -0700196 if (!CallGatewayManager.hasPhoneProviderExtras(mIntent)) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700197 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
Yorke Leea76228f2013-09-16 09:05:58 -0700228 /**
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
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700236 @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 }
Yorke Leea76228f2013-09-16 09:05:58 -0700422 startDelayedFinish();
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700423 }
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}