SipSettings.java revision 1344f67ea331f9a485f54c4b5e26d62a5cfad3fb
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.text.TextUtils;
43import android.util.Log;
44import android.view.Menu;
45import android.view.MenuItem;
46
47import java.io.IOException;
48import java.util.Collections;
49import java.util.Comparator;
50import java.util.LinkedHashMap;
51import java.util.List;
52import java.util.Map;
53
54/**
55 * The PreferenceActivity class for managing sip profile preferences.
56 */
57public class SipSettings extends PreferenceActivity {
58    private static final String PREFIX = "[SipSettings] ";
59    private static final boolean VERBOSE = true; /* STOP SHIP if true */
60
61    public static final String SIP_SHARED_PREFERENCES = "SIP_PREFERENCES";
62
63    private static final int MENU_ADD_ACCOUNT = Menu.FIRST;
64
65    static final String KEY_SIP_PROFILE = "sip_profile";
66
67    private static final String BUTTON_SIP_RECEIVE_CALLS =
68            "sip_receive_calls_key";
69    private static final String PREF_SIP_LIST = "sip_account_list";
70
71    private static final int REQUEST_ADD_OR_EDIT_SIP_PROFILE = 1;
72
73    private PackageManager mPackageManager;
74    private SipManager mSipManager;
75    private SipProfileDb mProfileDb;
76
77    private SipProfile mProfile; // profile that's being edited
78
79    private CheckBoxPreference mButtonSipReceiveCalls;
80    private PreferenceCategory mSipListContainer;
81    private Map<String, SipPreference> mSipPreferenceMap;
82    private List<SipProfile> mSipProfileList;
83    private SipSharedPreferences mSipSharedPreferences;
84    private int mUid = Process.myUid();
85
86    private class SipPreference extends Preference {
87        SipProfile mProfile;
88        SipPreference(Context c, SipProfile p) {
89            super(c);
90            setProfile(p);
91        }
92
93        SipProfile getProfile() {
94            return mProfile;
95        }
96
97        void setProfile(SipProfile p) {
98            mProfile = p;
99            setTitle(getProfileName(p));
100            updateSummary(mSipSharedPreferences.isReceivingCallsEnabled()
101                    ? getString(R.string.registration_status_checking_status)
102                    : getString(R.string.registration_status_not_receiving));
103        }
104
105        void updateSummary(String registrationStatus) {
106            int profileUid = mProfile.getCallingUid();
107            boolean isPrimary = mProfile.getUriString().equals(
108                    mSipSharedPreferences.getPrimaryAccount());
109            if (VERBOSE) {
110                log("SipPreference.updateSummary, profile uid: " + profileUid +
111                        " isPrimary: " + isPrimary +
112                        " registration: " + registrationStatus +
113                        " Primary: " + mSipSharedPreferences.getPrimaryAccount() +
114                        " status: " + registrationStatus);
115            }
116            String summary = "";
117            if ((profileUid > 0) && (profileUid != mUid)) {
118                // from third party apps
119                summary = getString(R.string.third_party_account_summary,
120                        getPackageNameFromUid(profileUid));
121            } else if (isPrimary) {
122                summary = getString(R.string.primary_account_summary_with,
123                        registrationStatus);
124            } else {
125                summary = registrationStatus;
126            }
127            setSummary(summary);
128        }
129    }
130
131    private String getPackageNameFromUid(int uid) {
132        try {
133            String[] pkgs = mPackageManager.getPackagesForUid(uid);
134            ApplicationInfo ai = mPackageManager.getApplicationInfo(pkgs[0], 0);
135            return ai.loadLabel(mPackageManager).toString();
136        } catch (PackageManager.NameNotFoundException e) {
137            log("getPackageNameFromUid, cannot find name of uid: " + uid + ", exception: " + e);
138        }
139        return "uid:" + uid;
140    }
141
142    @Override
143    public void onCreate(Bundle savedInstanceState) {
144        super.onCreate(savedInstanceState);
145
146        mSipManager = SipManager.newInstance(this);
147        mSipSharedPreferences = new SipSharedPreferences(this);
148        mProfileDb = new SipProfileDb(this);
149
150        mPackageManager = getPackageManager();
151        setContentView(R.layout.sip_settings_ui);
152        addPreferencesFromResource(R.xml.sip_setting);
153        mSipListContainer = (PreferenceCategory) findPreference(PREF_SIP_LIST);
154        registerForReceiveCallsCheckBox();
155
156        updateProfilesStatus();
157
158        ActionBar actionBar = getActionBar();
159        if (actionBar != null) {
160            actionBar.setDisplayHomeAsUpEnabled(true);
161        }
162    }
163
164    @Override
165    public void onResume() {
166        super.onResume();
167
168        mButtonSipReceiveCalls.setEnabled(SipUtil.isPhoneIdle(this));
169    }
170
171    @Override
172    protected void onDestroy() {
173        super.onDestroy();
174        unregisterForContextMenu(getListView());
175    }
176
177    @Override
178    protected void onActivityResult(final int requestCode, final int resultCode,
179            final Intent intent) {
180        if (resultCode != RESULT_OK && resultCode != RESULT_FIRST_USER) return;
181        new Thread() {
182            @Override
183            public void run() {
184                try {
185                    if (mProfile != null) {
186                        if (VERBOSE) log("onActivityResult, remove: " + mProfile.getProfileName());
187                        deleteProfile(mProfile);
188                    }
189
190                    SipProfile profile = intent.getParcelableExtra(KEY_SIP_PROFILE);
191                    if (resultCode == RESULT_OK) {
192                        if (VERBOSE) log("onActivityResult, new: " + profile.getProfileName());
193                        addProfile(profile);
194                    }
195                    updateProfilesStatus();
196                } catch (IOException e) {
197                    log("onActivityResult, can not handle the profile:  " + e);
198                }
199            }
200        }.start();
201    }
202
203    private void registerForReceiveCallsCheckBox() {
204        mButtonSipReceiveCalls = (CheckBoxPreference) findPreference
205                (BUTTON_SIP_RECEIVE_CALLS);
206        mButtonSipReceiveCalls.setChecked(
207                mSipSharedPreferences.isReceivingCallsEnabled());
208        mButtonSipReceiveCalls.setOnPreferenceClickListener(
209                new OnPreferenceClickListener() {
210                    public boolean onPreferenceClick(Preference preference) {
211                        final boolean enabled = ((CheckBoxPreference) preference).isChecked();
212                        new Thread(new Runnable() {
213                                public void run() {
214                                    handleSipReceiveCallsOption(enabled);
215                                }
216                        }).start();
217                        return true;
218                    }
219                });
220    }
221
222    private synchronized void handleSipReceiveCallsOption(boolean enabled) {
223        mSipSharedPreferences.setReceivingCallsEnabled(enabled);
224        List<SipProfile> sipProfileList = mProfileDb.retrieveSipProfileList();
225        for (SipProfile p : sipProfileList) {
226            String sipUri = p.getUriString();
227            p = updateAutoRegistrationFlag(p, enabled);
228            try {
229                if (enabled) {
230                    mSipManager.open(p, SipUtil.createIncomingCallPendingIntent(this), null);
231                } else {
232                    mSipManager.close(sipUri);
233                    if (mSipSharedPreferences.isPrimaryAccount(sipUri)) {
234                        // re-open in order to make calls
235                        mSipManager.open(p);
236                    }
237                }
238            } catch (Exception e) {
239                log("handleSipReceiveCallsOption, register failed: " + e);
240            }
241        }
242        updateProfilesStatus();
243    }
244
245    private SipProfile updateAutoRegistrationFlag(
246            SipProfile p, boolean enabled) {
247        SipProfile newProfile = new SipProfile.Builder(p)
248                .setAutoRegistration(enabled)
249                .build();
250        try {
251            mProfileDb.deleteProfile(p);
252            mProfileDb.saveProfile(newProfile);
253        } catch (Exception e) {
254            log("updateAutoRegistrationFlag, exception: " + e);
255        }
256        return newProfile;
257    }
258
259    private void updateProfilesStatus() {
260        new Thread(new Runnable() {
261            @Override
262            public void run() {
263                try {
264                    retrieveSipLists();
265                } catch (Exception e) {
266                    log("updateProfilesStatus, exception: " + e);
267                }
268            }
269        }).start();
270    }
271
272    private String getProfileName(SipProfile profile) {
273        String profileName = profile.getProfileName();
274        if (TextUtils.isEmpty(profileName)) {
275            profileName = profile.getUserName() + "@" + profile.getSipDomain();
276        }
277        return profileName;
278    }
279
280    private void retrieveSipLists() {
281        mSipPreferenceMap = new LinkedHashMap<String, SipPreference>();
282        mSipProfileList = mProfileDb.retrieveSipProfileList();
283        processActiveProfilesFromSipService();
284        Collections.sort(mSipProfileList, new Comparator<SipProfile>() {
285            @Override
286            public int compare(SipProfile p1, SipProfile p2) {
287                return getProfileName(p1).compareTo(getProfileName(p2));
288            }
289
290            public boolean equals(SipProfile p) {
291                // not used
292                return false;
293            }
294        });
295        mSipListContainer.removeAll();
296        for (SipProfile p : mSipProfileList) {
297            addPreferenceFor(p);
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