• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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             (PhoneApp.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
onCreate(Bundle savedInstanceState)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 = PhoneApp.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
onPause()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 
onCreateDialog(int id)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 
addMakeDefaultCheckBox(Dialog dialog)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 
getProfileNameArray()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 
onClick(DialogInterface dialog, int id)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 
onCancel(DialogInterface dialog)340     public void onCancel(DialogInterface dialog) {
341         finish();
342     }
343 
onCheckedChanged(CompoundButton buttonView, boolean isChecked)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 
createSipPhoneIfNeeded(SipProfile p)353     private void createSipPhoneIfNeeded(SipProfile p) {
354         CallManager cm = PhoneApp.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 
setResultAndFinish()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                     PhoneApp.getInstance().callController.placeCall(mIntent);
396                 }
397                 finish();
398             }
399         });
400     }
401 
isNetworkConnected()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 
startGetPrimarySipPhoneThread()415     private void startGetPrimarySipPhoneThread() {
416         new Thread(new Runnable() {
417             public void run() {
418                 getPrimarySipPhone();
419             }
420         }).start();
421     }
422 
getPrimarySipPhone()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 
getPrimaryFromExistingProfiles(String primarySipUri)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