Home | History | Annotate | Download | only in phone
      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.Uri;
     35 import android.net.sip.SipException;
     36 import android.net.sip.SipManager;
     37 import android.net.sip.SipProfile;
     38 import android.os.Bundle;
     39 import android.os.SystemProperties;
     40 import android.provider.Settings;
     41 import android.telephony.PhoneNumberUtils;
     42 import android.util.Log;
     43 import android.view.LayoutInflater;
     44 import android.view.View;
     45 import android.view.WindowManager;
     46 import android.widget.CheckBox;
     47 import android.widget.CompoundButton;
     48 import android.widget.TextView;
     49 
     50 import 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  */
     62 public 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 (at) 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.
    179         if (!PhoneUtils.hasPhoneProviderExtras(mIntent)) {
    180             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 }
    449