blob: 500f322da68f296064980842b9794c6cd3d289b0 [file] [log] [blame]
/**
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.phone;
import com.android.internal.telephony.CallManager;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import com.android.phone.sip.SipProfileDb;
import com.android.phone.sip.SipSettings;
import com.android.phone.sip.SipSharedPreferences;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.sip.SipException;
import android.net.sip.SipManager;
import android.net.sip.SipProfile;
import android.os.Bundle;
import android.os.SystemProperties;
import android.provider.Settings;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import java.util.List;
/**
* Activity that selects the proper phone type for an outgoing call.
*
* This activity determines which Phone type (SIP or PSTN) should be used
* for an outgoing phone call, depending on the outgoing "number" (which
* may be either a PSTN number or a SIP address) as well as the user's SIP
* preferences. In some cases this activity has no interaction with the
* user, but in other cases it may (by bringing up a dialog if the user's
* preference is "Ask for each call".)
*/
public class SipCallOptionHandler extends Activity implements
DialogInterface.OnClickListener, DialogInterface.OnCancelListener,
CompoundButton.OnCheckedChangeListener {
static final String TAG = "SipCallOptionHandler";
private static final boolean DBG =
(PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
static final int DIALOG_SELECT_PHONE_TYPE = 0;
static final int DIALOG_SELECT_OUTGOING_SIP_PHONE = 1;
static final int DIALOG_START_SIP_SETTINGS = 2;
static final int DIALOG_NO_INTERNET_ERROR = 3;
static final int DIALOG_NO_VOIP = 4;
static final int DIALOG_SIZE = 5;
private Intent mIntent;
private List<SipProfile> mProfileList;
private String mCallOption;
private String mNumber;
private SipSharedPreferences mSipSharedPreferences;
private SipProfileDb mSipProfileDb;
private Dialog[] mDialogs = new Dialog[DIALOG_SIZE];
private SipProfile mOutgoingSipProfile;
private TextView mUnsetPriamryHint;
private boolean mUseSipPhone = false;
private boolean mMakePrimary = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
String action = intent.getAction();
// This activity is only ever launched with the
// ACTION_SIP_SELECT_PHONE action.
if (!OutgoingCallBroadcaster.ACTION_SIP_SELECT_PHONE.equals(action)) {
Log.wtf(TAG, "onCreate: got intent action '" + action + "', expected "
+ OutgoingCallBroadcaster.ACTION_SIP_SELECT_PHONE);
finish();
return;
}
// mIntent is a copy of the original CALL intent that started the
// whole outgoing-call sequence. This intent will ultimately be
// passed to CallController.placeCall() after displaying the SIP
// call options dialog (if necessary).
mIntent = (Intent) intent.getParcelableExtra(OutgoingCallBroadcaster.EXTRA_NEW_CALL_INTENT);
if (mIntent == null) {
finish();
return;
}
// Allow this activity to be visible in front of the keyguard.
// (This is only necessary for obscure scenarios like the user
// initiating a call and then immediately pressing the Power
// button.)
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
// If we're trying to make a SIP call, return a SipPhone if one is
// available.
//
// - If it's a sip: URI, this is definitely a SIP call, regardless
// of whether the data is a SIP address or a regular phone
// number.
//
// - If this is a tel: URI but the data contains an "@" character
// (see PhoneNumberUtils.isUriNumber()) we consider that to be a
// SIP number too.
//
// TODO: Eventually we may want to disallow that latter case
// (e.g. "tel:foo@example.com").
//
// TODO: We should also consider moving this logic into the
// CallManager, where it could be made more generic.
// (For example, each "telephony provider" could be allowed
// to register the URI scheme(s) that it can handle, and the
// CallManager would then find the best match for every
// outgoing call.)
boolean voipSupported = PhoneUtils.isVoipSupported();
if (DBG) Log.v(TAG, "voipSupported: " + voipSupported);
mSipProfileDb = new SipProfileDb(this);
mSipSharedPreferences = new SipSharedPreferences(this);
mCallOption = mSipSharedPreferences.getSipCallOption();
if (DBG) Log.v(TAG, "Call option: " + mCallOption);
Uri uri = mIntent.getData();
String scheme = uri.getScheme();
mNumber = PhoneNumberUtils.getNumberFromIntent(mIntent, this);
boolean isInCellNetwork = PhoneGlobals.getInstance().phoneMgr.isRadioOn();
boolean isKnownCallScheme = Constants.SCHEME_TEL.equals(scheme)
|| Constants.SCHEME_SIP.equals(scheme);
boolean isRegularCall = Constants.SCHEME_TEL.equals(scheme)
&& !PhoneNumberUtils.isUriNumber(mNumber);
// Bypass the handler if the call scheme is not sip or tel.
if (!isKnownCallScheme) {
setResultAndFinish();
return;
}
// Check if VoIP feature is supported.
if (!voipSupported) {
if (!isRegularCall) {
showDialog(DIALOG_NO_VOIP);
} else {
setResultAndFinish();
}
return;
}
// Since we are not sure if anyone has touched the number during
// the NEW_OUTGOING_CALL broadcast, we just check if the provider
// put their gateway information in the intent. If so, it means
// someone has changed the destination number. We then make the
// call via the default pstn network. However, if one just alters
// the destination directly, then we still let it go through the
// Internet call option process.
if (!PhoneUtils.hasPhoneProviderExtras(mIntent)) {
if (!isNetworkConnected()) {
if (!isRegularCall) {
showDialog(DIALOG_NO_INTERNET_ERROR);
return;
}
} else {
if (mCallOption.equals(Settings.System.SIP_ASK_ME_EACH_TIME)
&& isRegularCall && isInCellNetwork) {
showDialog(DIALOG_SELECT_PHONE_TYPE);
return;
}
if (!mCallOption.equals(Settings.System.SIP_ADDRESS_ONLY)
|| !isRegularCall) {
mUseSipPhone = true;
}
}
}
if (mUseSipPhone) {
// If there is no sip profile and it is a regular call, then we
// should use pstn network instead.
if ((mSipProfileDb.getProfilesCount() > 0) || !isRegularCall) {
startGetPrimarySipPhoneThread();
return;
} else {
mUseSipPhone = false;
}
}
setResultAndFinish();
}
@Override
public void onPause() {
super.onPause();
if (isFinishing()) return;
for (Dialog dialog : mDialogs) {
if (dialog != null) dialog.dismiss();
}
finish();
}
protected Dialog onCreateDialog(int id) {
Dialog dialog;
switch(id) {
case DIALOG_SELECT_PHONE_TYPE:
dialog = new AlertDialog.Builder(this)
.setTitle(R.string.pick_outgoing_call_phone_type)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setSingleChoiceItems(R.array.phone_type_values, -1, this)
.setNegativeButton(android.R.string.cancel, this)
.setOnCancelListener(this)
.create();
break;
case DIALOG_SELECT_OUTGOING_SIP_PHONE:
dialog = new AlertDialog.Builder(this)
.setTitle(R.string.pick_outgoing_sip_phone)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setSingleChoiceItems(getProfileNameArray(), -1, this)
.setNegativeButton(android.R.string.cancel, this)
.setOnCancelListener(this)
.create();
addMakeDefaultCheckBox(dialog);
break;
case DIALOG_START_SIP_SETTINGS:
dialog = new AlertDialog.Builder(this)
.setTitle(R.string.no_sip_account_found_title)
.setMessage(R.string.no_sip_account_found)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setPositiveButton(R.string.sip_menu_add, this)
.setNegativeButton(android.R.string.cancel, this)
.setOnCancelListener(this)
.create();
break;
case DIALOG_NO_INTERNET_ERROR:
boolean wifiOnly = SipManager.isSipWifiOnly(this);
dialog = new AlertDialog.Builder(this)
.setTitle(wifiOnly ? R.string.no_wifi_available_title
: R.string.no_internet_available_title)
.setMessage(wifiOnly ? R.string.no_wifi_available
: R.string.no_internet_available)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setPositiveButton(android.R.string.ok, this)
.setOnCancelListener(this)
.create();
break;
case DIALOG_NO_VOIP:
dialog = new AlertDialog.Builder(this)
.setTitle(R.string.no_voip)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setPositiveButton(android.R.string.ok, this)
.setOnCancelListener(this)
.create();
break;
default:
dialog = null;
}
if (dialog != null) {
mDialogs[id] = dialog;
}
return dialog;
}
private void addMakeDefaultCheckBox(Dialog dialog) {
LayoutInflater inflater = (LayoutInflater) getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(
com.android.internal.R.layout.always_use_checkbox, null);
CheckBox makePrimaryCheckBox =
(CheckBox)view.findViewById(com.android.internal.R.id.alwaysUse);
makePrimaryCheckBox.setText(R.string.remember_my_choice);
makePrimaryCheckBox.setOnCheckedChangeListener(this);
mUnsetPriamryHint = (TextView)view.findViewById(
com.android.internal.R.id.clearDefaultHint);
mUnsetPriamryHint.setText(R.string.reset_my_choice_hint);
mUnsetPriamryHint.setVisibility(View.GONE);
((AlertDialog)dialog).setView(view);
}
private CharSequence[] getProfileNameArray() {
CharSequence[] entries = new CharSequence[mProfileList.size()];
int i = 0;
for (SipProfile p : mProfileList) {
entries[i++] = p.getProfileName();
}
return entries;
}
public void onClick(DialogInterface dialog, int id) {
if (id == DialogInterface.BUTTON_NEGATIVE) {
// button negative is cancel
finish();
return;
} else if(dialog == mDialogs[DIALOG_SELECT_PHONE_TYPE]) {
String selection = getResources().getStringArray(
R.array.phone_type_values)[id];
if (DBG) Log.v(TAG, "User pick phone " + selection);
if (selection.equals(getString(R.string.internet_phone))) {
mUseSipPhone = true;
startGetPrimarySipPhoneThread();
return;
}
} else if (dialog == mDialogs[DIALOG_SELECT_OUTGOING_SIP_PHONE]) {
mOutgoingSipProfile = mProfileList.get(id);
} else if ((dialog == mDialogs[DIALOG_NO_INTERNET_ERROR])
|| (dialog == mDialogs[DIALOG_NO_VOIP])) {
finish();
return;
} else {
if (id == DialogInterface.BUTTON_POSITIVE) {
// Redirect to sip settings and drop the call.
Intent newIntent = new Intent(this, SipSettings.class);
newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(newIntent);
}
finish();
return;
}
setResultAndFinish();
}
public void onCancel(DialogInterface dialog) {
finish();
}
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
mMakePrimary = isChecked;
if (isChecked) {
mUnsetPriamryHint.setVisibility(View.VISIBLE);
} else {
mUnsetPriamryHint.setVisibility(View.INVISIBLE);
}
}
private void createSipPhoneIfNeeded(SipProfile p) {
CallManager cm = PhoneGlobals.getInstance().mCM;
if (PhoneUtils.getSipPhoneFromUri(cm, p.getUriString()) != null) return;
// Create the phone since we can not find it in CallManager
try {
SipManager.newInstance(this).open(p);
Phone phone = PhoneFactory.makeSipPhone(p.getUriString());
if (phone != null) {
cm.registerPhone(phone);
} else {
Log.e(TAG, "cannot make sipphone profile" + p);
}
} catch (SipException e) {
Log.e(TAG, "cannot open sip profile" + p, e);
}
}
private void setResultAndFinish() {
runOnUiThread(new Runnable() {
public void run() {
if (mOutgoingSipProfile != null) {
if (!isNetworkConnected()) {
showDialog(DIALOG_NO_INTERNET_ERROR);
return;
}
if (DBG) Log.v(TAG, "primary SIP URI is " +
mOutgoingSipProfile.getUriString());
createSipPhoneIfNeeded(mOutgoingSipProfile);
mIntent.putExtra(OutgoingCallBroadcaster.EXTRA_SIP_PHONE_URI,
mOutgoingSipProfile.getUriString());
if (mMakePrimary) {
mSipSharedPreferences.setPrimaryAccount(
mOutgoingSipProfile.getUriString());
}
}
if (mUseSipPhone && mOutgoingSipProfile == null) {
showDialog(DIALOG_START_SIP_SETTINGS);
return;
} else {
// Woo hoo -- it's finally OK to initiate the outgoing call!
PhoneGlobals.getInstance().callController.placeCall(mIntent);
}
finish();
}
});
}
private boolean isNetworkConnected() {
ConnectivityManager cm = (ConnectivityManager) getSystemService(
Context.CONNECTIVITY_SERVICE);
if (cm != null) {
NetworkInfo ni = cm.getActiveNetworkInfo();
if ((ni == null) || !ni.isConnected()) return false;
return ((ni.getType() == ConnectivityManager.TYPE_WIFI)
|| !SipManager.isSipWifiOnly(this));
}
return false;
}
private void startGetPrimarySipPhoneThread() {
new Thread(new Runnable() {
public void run() {
getPrimarySipPhone();
}
}).start();
}
private void getPrimarySipPhone() {
String primarySipUri = mSipSharedPreferences.getPrimaryAccount();
mOutgoingSipProfile = getPrimaryFromExistingProfiles(primarySipUri);
if (mOutgoingSipProfile == null) {
if ((mProfileList != null) && (mProfileList.size() > 0)) {
runOnUiThread(new Runnable() {
public void run() {
showDialog(DIALOG_SELECT_OUTGOING_SIP_PHONE);
}
});
return;
}
}
setResultAndFinish();
}
private SipProfile getPrimaryFromExistingProfiles(String primarySipUri) {
mProfileList = mSipProfileDb.retrieveSipProfileList();
if (mProfileList == null) return null;
for (SipProfile p : mProfileList) {
if (p.getUriString().equals(primarySipUri)) return p;
}
return null;
}
}