blob: 295e88694d50e33f5b09a5fb46656892d8d02d3e [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;
39import android.os.SystemProperties;
40import android.provider.Settings;
41import android.telephony.PhoneNumberUtils;
42import android.util.Log;
43import android.view.LayoutInflater;
44import android.view.View;
45import android.view.WindowManager;
46import android.widget.CheckBox;
47import android.widget.CompoundButton;
48import android.widget.TextView;
49
50import 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 */
62public 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@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.
Santos Cordon69a69192013-08-22 14:25:42 -0700179 if (!CallGatewayManager.hasPhoneProviderExtras(mIntent)) {
Santos Cordon7d4ddf62013-07-10 11:58:08 -0700180 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}