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.Handler;
     40 import android.os.Message;
     41 import android.os.SystemProperties;
     42 import android.provider.Settings;
     43 import android.telephony.PhoneNumberUtils;
     44 import android.util.Log;
     45 import android.view.LayoutInflater;
     46 import android.view.View;
     47 import android.view.WindowManager;
     48 import android.widget.CheckBox;
     49 import android.widget.CompoundButton;
     50 import android.widget.TextView;
     51 
     52 import 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  */
     64 public 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 
     90     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 
    105     @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 (at) 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.
    196         if (!CallGatewayManager.hasPhoneProviderExtras(mIntent)) {
    197             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 
    228     /**
    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 
    236     @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                 }
    422                 startDelayedFinish();
    423             }
    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 }
    474