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