SipSettings.java revision 4d45d1cf58a2003378fd35912d6d73a00001bf06
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.services.telephony.sip;
18
19import com.android.internal.telephony.Phone;
20import com.android.internal.telephony.PhoneConstants;
21
22import android.app.ActionBar;
23import android.app.AlertDialog;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.Intent;
27import android.content.pm.ApplicationInfo;
28import android.content.pm.PackageManager;
29import android.net.sip.SipErrorCode;
30import android.net.sip.SipException;
31import android.net.sip.SipManager;
32import android.net.sip.SipProfile;
33import android.net.sip.SipRegistrationListener;
34import android.os.Bundle;
35import android.os.Parcelable;
36import android.os.Process;
37import android.preference.CheckBoxPreference;
38import android.preference.Preference;
39import android.preference.Preference.OnPreferenceClickListener;
40import android.preference.PreferenceActivity;
41import android.preference.PreferenceCategory;
42import android.telecom.PhoneAccount;
43import android.telecom.TelecomManager;
44import android.text.TextUtils;
45import android.util.Log;
46import android.view.Menu;
47import android.view.MenuItem;
48
49import java.io.IOException;
50import java.util.Collections;
51import java.util.Comparator;
52import java.util.LinkedHashMap;
53import java.util.List;
54import java.util.Map;
55
56/**
57 * The PreferenceActivity class for managing sip profile preferences.
58 */
59public class SipSettings extends PreferenceActivity {
60    private static final String PREFIX = "[SipSettings] ";
61    private static final boolean VERBOSE = false; /* STOP SHIP if true */
62
63    public static final String SIP_SHARED_PREFERENCES = "SIP_PREFERENCES";
64
65    private static final int MENU_ADD_ACCOUNT = Menu.FIRST;
66
67    static final String KEY_SIP_PROFILE = "sip_profile";
68
69    private static final String BUTTON_SIP_RECEIVE_CALLS =
70            "sip_receive_calls_key";
71    private static final String PREF_SIP_LIST = "sip_account_list";
72
73    private static final int REQUEST_ADD_OR_EDIT_SIP_PROFILE = 1;
74
75    private PackageManager mPackageManager;
76    private SipManager mSipManager;
77    private SipProfileDb mProfileDb;
78
79    private SipProfile mProfile; // profile that's being edited
80
81    private CheckBoxPreference mButtonSipReceiveCalls;
82    private PreferenceCategory mSipListContainer;
83    private Map<String, SipPreference> mSipPreferenceMap;
84    private List<SipProfile> mSipProfileList;
85    private SipSharedPreferences mSipSharedPreferences;
86    private int mUid = Process.myUid();
87
88    private class SipPreference extends Preference {
89        SipProfile mProfile;
90        SipPreference(Context c, SipProfile p) {
91            super(c);
92            setProfile(p);
93        }
94
95        SipProfile getProfile() {
96            return mProfile;
97        }
98
99        void setProfile(SipProfile p) {
100            mProfile = p;
101            setTitle(getProfileName(p));
102            boolean isEnabled = SipUtil.isPhoneAccountEnabled(getContext(), p);
103            updateSummary(isEnabled && mSipSharedPreferences.isReceivingCallsEnabled()
104                    ? getString(R.string.registration_status_checking_status)
105                    : getString(R.string.registration_status_not_receiving));
106        }
107
108        void updateSummary(String registrationStatus) {
109            int profileUid = mProfile.getCallingUid();
110            if (VERBOSE) {
111                log("SipPreference.updateSummary, profile uid: " + profileUid +
112                        " registration: " + registrationStatus +
113                        " status: " + registrationStatus);
114            }
115            String summary = "";
116            if ((profileUid > 0) && (profileUid != mUid)) {
117                // from third party apps
118                summary = getString(R.string.third_party_account_summary,
119                        getPackageNameFromUid(profileUid));
120            } else {
121                summary = registrationStatus;
122            }
123            setSummary(summary);
124        }
125    }
126
127    private String getPackageNameFromUid(int uid) {
128        try {
129            String[] pkgs = mPackageManager.getPackagesForUid(uid);
130            ApplicationInfo ai = mPackageManager.getApplicationInfo(pkgs[0], 0);
131            return ai.loadLabel(mPackageManager).toString();
132        } catch (PackageManager.NameNotFoundException e) {
133            log("getPackageNameFromUid, cannot find name of uid: " + uid + ", exception: " + e);
134        }
135        return "uid:" + uid;
136    }
137
138    @Override
139    public void onCreate(Bundle savedInstanceState) {
140        super.onCreate(savedInstanceState);
141
142        mSipManager = SipManager.newInstance(this);
143        mSipSharedPreferences = new SipSharedPreferences(this);
144        mProfileDb = new SipProfileDb(this);
145
146        mPackageManager = getPackageManager();
147        setContentView(R.layout.sip_settings_ui);
148        addPreferencesFromResource(R.xml.sip_setting);
149        mSipListContainer = (PreferenceCategory) findPreference(PREF_SIP_LIST);
150        registerForReceiveCallsCheckBox();
151
152        updateProfilesStatus();
153
154        ActionBar actionBar = getActionBar();
155        if (actionBar != null) {
156            actionBar.setDisplayHomeAsUpEnabled(true);
157        }
158    }
159
160    @Override
161    public void onResume() {
162        super.onResume();
163
164        mButtonSipReceiveCalls.setEnabled(SipUtil.isPhoneIdle(this));
165    }
166
167    @Override
168    protected void onDestroy() {
169        super.onDestroy();
170        unregisterForContextMenu(getListView());
171    }
172
173    @Override
174    protected void onActivityResult(final int requestCode, final int resultCode,
175            final Intent intent) {
176        if (resultCode != RESULT_OK && resultCode != RESULT_FIRST_USER) return;
177        new Thread() {
178            @Override
179            public void run() {
180                try {
181                    if (mProfile != null) {
182                        if (VERBOSE) log("onActivityResult, remove: " + mProfile.getProfileName());
183                        deleteProfile(mProfile);
184                    }
185
186                    SipProfile profile = intent.getParcelableExtra(KEY_SIP_PROFILE);
187                    if (resultCode == RESULT_OK) {
188                        if (VERBOSE) log("onActivityResult, new: " + profile.getProfileName());
189                        addProfile(profile);
190                    }
191                    updateProfilesStatus();
192                } catch (IOException e) {
193                    log("onActivityResult, can not handle the profile:  " + e);
194                }
195            }
196        }.start();
197    }
198
199    private void registerForReceiveCallsCheckBox() {
200        mButtonSipReceiveCalls = (CheckBoxPreference) findPreference
201                (BUTTON_SIP_RECEIVE_CALLS);
202        mButtonSipReceiveCalls.setChecked(
203                mSipSharedPreferences.isReceivingCallsEnabled());
204        mButtonSipReceiveCalls.setOnPreferenceClickListener(
205                new OnPreferenceClickListener() {
206                    public boolean onPreferenceClick(Preference preference) {
207                        final boolean enabled = ((CheckBoxPreference) preference).isChecked();
208                        new Thread(new Runnable() {
209                                public void run() {
210                                    handleSipReceiveCallsOption(enabled);
211                                }
212                        }).start();
213                        return true;
214                    }
215                });
216    }
217
218    /**
219     * Handles changes to the "receive calls" option.
220     *
221     * @param isReceivingCalls {@code True} if receiving incoming SIP calls.
222     */
223    private synchronized void handleSipReceiveCallsOption(boolean isReceivingCalls) {
224        mSipSharedPreferences.setReceivingCallsEnabled(isReceivingCalls);
225
226        // Mark all profiles as auto-register if we are now receiving calls.
227        List<SipProfile> sipProfileList = mProfileDb.retrieveSipProfileList();
228        for (SipProfile p : sipProfileList) {
229            p = updateAutoRegistrationFlag(p, isReceivingCalls);
230        }
231
232        // Restart all Sip services to ensure we reflect whether we are receiving calls.
233        SipAccountRegistry sipAccountRegistry = SipAccountRegistry.getInstance();
234        sipAccountRegistry.restartSipService(this);
235
236        updateProfilesStatus();
237    }
238
239    private SipProfile updateAutoRegistrationFlag(SipProfile p, boolean enabled) {
240        SipProfile newProfile = new SipProfile.Builder(p)
241                .setAutoRegistration(enabled)
242                .build();
243        try {
244            // Note: The profile is updated, but the associated PhoneAccount is left alone since
245            // the only thing that changed is the auto-registration flag, which is not part of the
246            // PhoneAccount.
247            mProfileDb.deleteProfile(p);
248            mProfileDb.saveProfile(newProfile);
249        } catch (Exception e) {
250            log("updateAutoRegistrationFlag, exception: " + e);
251        }
252        return newProfile;
253    }
254
255    private void updateProfilesStatus() {
256        new Thread(new Runnable() {
257            @Override
258            public void run() {
259                try {
260                    retrieveSipLists();
261                } catch (Exception e) {
262                    log("updateProfilesStatus, exception: " + e);
263                }
264            }
265        }).start();
266    }
267
268    private String getProfileName(SipProfile profile) {
269        String profileName = profile.getProfileName();
270        if (TextUtils.isEmpty(profileName)) {
271            profileName = profile.getUserName() + "@" + profile.getSipDomain();
272        }
273        return profileName;
274    }
275
276    private void retrieveSipLists() {
277        mSipPreferenceMap = new LinkedHashMap<String, SipPreference>();
278        mSipProfileList = mProfileDb.retrieveSipProfileList();
279        processActiveProfilesFromSipService();
280        Collections.sort(mSipProfileList, new Comparator<SipProfile>() {
281            @Override
282            public int compare(SipProfile p1, SipProfile p2) {
283                return getProfileName(p1).compareTo(getProfileName(p2));
284            }
285
286            public boolean equals(SipProfile p) {
287                // not used
288                return false;
289            }
290        });
291        mSipListContainer.removeAll();
292        if (mSipProfileList.isEmpty()) {
293            getPreferenceScreen().removePreference(mSipListContainer);
294        } else {
295            getPreferenceScreen().addPreference(mSipListContainer);
296            for (SipProfile p : mSipProfileList) {
297                addPreferenceFor(p);
298            }
299        }
300
301        if (!mSipSharedPreferences.isReceivingCallsEnabled()) return;
302        for (SipProfile p : mSipProfileList) {
303            if (mUid == p.getCallingUid() &&
304                    SipUtil.isPhoneAccountEnabled(getApplicationContext(), p)) {
305                try {
306                    mSipManager.setRegistrationListener(
307                            p.getUriString(), createRegistrationListener());
308                } catch (SipException e) {
309                    log("retrieveSipLists, cannot set registration listener: " + e);
310                }
311            }
312        }
313    }
314
315    private void processActiveProfilesFromSipService() {
316        SipProfile[] activeList = mSipManager.getListOfProfiles();
317        for (SipProfile activeProfile : activeList) {
318            SipProfile profile = getProfileFromList(activeProfile);
319            if (profile == null) {
320                mSipProfileList.add(activeProfile);
321            } else {
322                profile.setCallingUid(activeProfile.getCallingUid());
323            }
324        }
325    }
326
327    private SipProfile getProfileFromList(SipProfile activeProfile) {
328        for (SipProfile p : mSipProfileList) {
329            if (p.getUriString().equals(activeProfile.getUriString())) {
330                return p;
331            }
332        }
333        return null;
334    }
335
336    private void addPreferenceFor(SipProfile p) {
337        String status;
338        if (VERBOSE) log("addPreferenceFor, profile uri: " + p.getUri());
339        SipPreference pref = new SipPreference(this, p);
340        mSipPreferenceMap.put(p.getUriString(), pref);
341        mSipListContainer.addPreference(pref);
342
343        pref.setOnPreferenceClickListener(
344                new Preference.OnPreferenceClickListener() {
345                    @Override
346                    public boolean onPreferenceClick(Preference pref) {
347                        handleProfileClick(((SipPreference) pref).mProfile);
348                        return true;
349                    }
350                });
351    }
352
353    private void handleProfileClick(final SipProfile profile) {
354        int uid = profile.getCallingUid();
355        if (uid == mUid || uid == 0) {
356            startSipEditor(profile);
357            return;
358        }
359        new AlertDialog.Builder(this)
360                .setTitle(R.string.alert_dialog_close)
361                .setIconAttribute(android.R.attr.alertDialogIcon)
362                .setPositiveButton(R.string.close_profile,
363                        new DialogInterface.OnClickListener() {
364                            @Override
365                            public void onClick(DialogInterface dialog, int w) {
366                                deleteProfile(profile);
367                                unregisterProfile(profile);
368                            }
369                        })
370                .setNegativeButton(android.R.string.cancel, null)
371                .show();
372    }
373
374    private void unregisterProfile(final SipProfile p) {
375        // run it on background thread for better UI response
376        new Thread(new Runnable() {
377            @Override
378            public void run() {
379                try {
380                    mSipManager.close(p.getUriString());
381                } catch (Exception e) {
382                    log("unregisterProfile, unregister failed, SipService died? Exception: " + e);
383                }
384            }
385        }, "unregisterProfile").start();
386    }
387
388    void deleteProfile(SipProfile p) {
389        mSipProfileList.remove(p);
390        SipPreference pref = mSipPreferenceMap.remove(p.getUriString());
391        mSipListContainer.removePreference(pref);
392    }
393
394    private void addProfile(SipProfile p) throws IOException {
395        try {
396            mSipManager.setRegistrationListener(p.getUriString(),
397                    createRegistrationListener());
398        } catch (Exception e) {
399            log("addProfile, cannot set registration listener: " + e);
400        }
401        mSipProfileList.add(p);
402        addPreferenceFor(p);
403    }
404
405    private void startSipEditor(final SipProfile profile) {
406        mProfile = profile;
407        Intent intent = new Intent(this, SipEditor.class);
408        intent.putExtra(KEY_SIP_PROFILE, (Parcelable) profile);
409        startActivityForResult(intent, REQUEST_ADD_OR_EDIT_SIP_PROFILE);
410    }
411
412    private void showRegistrationMessage(final String profileUri,
413            final String message) {
414        runOnUiThread(new Runnable() {
415            @Override
416            public void run() {
417                SipPreference pref = mSipPreferenceMap.get(profileUri);
418                if (pref != null) {
419                    pref.updateSummary(message);
420                }
421            }
422        });
423    }
424
425    private SipRegistrationListener createRegistrationListener() {
426        return new SipRegistrationListener() {
427            @Override
428            public void onRegistrationDone(String profileUri, long expiryTime) {
429                showRegistrationMessage(profileUri, getString(
430                        R.string.registration_status_done));
431            }
432
433            @Override
434            public void onRegistering(String profileUri) {
435                showRegistrationMessage(profileUri, getString(
436                        R.string.registration_status_registering));
437            }
438
439            @Override
440            public void onRegistrationFailed(String profileUri, int errorCode,
441                    String message) {
442                switch (errorCode) {
443                    case SipErrorCode.IN_PROGRESS:
444                        showRegistrationMessage(profileUri, getString(
445                                R.string.registration_status_still_trying));
446                        break;
447                    case SipErrorCode.INVALID_CREDENTIALS:
448                        showRegistrationMessage(profileUri, getString(
449                                R.string.registration_status_invalid_credentials));
450                        break;
451                    case SipErrorCode.SERVER_UNREACHABLE:
452                        showRegistrationMessage(profileUri, getString(
453                                R.string.registration_status_server_unreachable));
454                        break;
455                    case SipErrorCode.DATA_CONNECTION_LOST:
456                        if (SipManager.isSipWifiOnly(getApplicationContext())){
457                            showRegistrationMessage(profileUri, getString(
458                                    R.string.registration_status_no_wifi_data));
459                        } else {
460                            showRegistrationMessage(profileUri, getString(
461                                    R.string.registration_status_no_data));
462                        }
463                        break;
464                    case SipErrorCode.CLIENT_ERROR:
465                        showRegistrationMessage(profileUri, getString(
466                                R.string.registration_status_not_running));
467                        break;
468                    default:
469                        showRegistrationMessage(profileUri, getString(
470                                R.string.registration_status_failed_try_later,
471                                message));
472                }
473            }
474        };
475    }
476
477    @Override
478    public boolean onCreateOptionsMenu(Menu menu) {
479        super.onCreateOptionsMenu(menu);
480        menu.add(0, MENU_ADD_ACCOUNT, 0, R.string.add_sip_account)
481                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
482        return true;
483    }
484
485    @Override
486    public boolean onPrepareOptionsMenu(Menu menu) {
487        menu.findItem(MENU_ADD_ACCOUNT).setEnabled(SipUtil.isPhoneIdle(this));
488        return super.onPrepareOptionsMenu(menu);
489    }
490
491    @Override
492    public boolean onOptionsItemSelected(MenuItem item) {
493        final int itemId = item.getItemId();
494        switch (itemId) {
495            case MENU_ADD_ACCOUNT: {
496                startSipEditor(null);
497                return true;
498            }
499        }
500        return super.onOptionsItemSelected(item);
501    }
502
503    private static void log(String msg) {
504        Log.d(SipUtil.LOG_TAG, PREFIX + msg);
505    }
506}
507