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
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.
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