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