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