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.phone.R;
22import com.android.phone.SipUtil;
23
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.SipException;
31import android.net.sip.SipErrorCode;
32import android.net.sip.SipProfile;
33import android.net.sip.SipManager;
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.preference.PreferenceScreen;
44import android.text.TextUtils;
45import android.util.Log;
46import android.view.MenuItem;
47import android.view.View;
48import android.view.ContextMenu;
49import android.view.ContextMenu.ContextMenuInfo;
50import android.widget.AdapterView.AdapterContextMenuInfo;
51import android.widget.Button;
52
53import java.io.IOException;
54import java.util.Collections;
55import java.util.Comparator;
56import java.util.LinkedHashMap;
57import java.util.List;
58import java.util.Map;
59
60/**
61 * The PreferenceActivity class for managing sip profile preferences.
62 */
63public class SipSettings extends PreferenceActivity {
64    public static final String SIP_SHARED_PREFERENCES = "SIP_PREFERENCES";
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 Button mButtonAddSipAccount;
83    private CheckBoxPreference mButtonSipReceiveCalls;
84    private PreferenceCategory mSipListContainer;
85    private Map<String, SipPreference> mSipPreferenceMap;
86    private List<SipProfile> mSipProfileList;
87    private SipSharedPreferences mSipSharedPreferences;
88    private int mUid = Process.myUid();
89
90    private class SipPreference extends Preference {
91        SipProfile mProfile;
92        SipPreference(Context c, SipProfile p) {
93            super(c);
94            setProfile(p);
95        }
96
97        SipProfile getProfile() {
98            return mProfile;
99        }
100
101        void setProfile(SipProfile p) {
102            mProfile = p;
103            setTitle(getProfileName(p));
104            updateSummary(mSipSharedPreferences.isReceivingCallsEnabled()
105                    ? getString(R.string.registration_status_checking_status)
106                    : getString(R.string.registration_status_not_receiving));
107        }
108
109        void updateSummary(String registrationStatus) {
110            int profileUid = mProfile.getCallingUid();
111            boolean isPrimary = mProfile.getUriString().equals(
112                    mSipSharedPreferences.getPrimaryAccount());
113            Log.v(TAG, "profile uid is " + profileUid + " isPrimary:"
114                    + isPrimary + " registration:" + registrationStatus
115                    + " Primary:" + mSipSharedPreferences.getPrimaryAccount()
116                    + " status:" + registrationStatus);
117            String summary = "";
118            if ((profileUid > 0) && (profileUid != mUid)) {
119                // from third party apps
120                summary = getString(R.string.third_party_account_summary,
121                        getPackageNameFromUid(profileUid));
122            } else if (isPrimary) {
123                summary = getString(R.string.primary_account_summary_with,
124                        registrationStatus);
125            } else {
126                summary = registrationStatus;
127            }
128            setSummary(summary);
129        }
130    }
131
132    private String getPackageNameFromUid(int uid) {
133        try {
134            String[] pkgs = mPackageManager.getPackagesForUid(uid);
135            ApplicationInfo ai =
136                    mPackageManager.getApplicationInfo(pkgs[0], 0);
137            return ai.loadLabel(mPackageManager).toString();
138        } catch (PackageManager.NameNotFoundException e) {
139            Log.e(TAG, "cannot find name of uid " + uid, 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        registerForAddSipListener();
157        registerForReceiveCallsCheckBox();
158        mCallManager = CallManager.getInstance();
159
160        updateProfilesStatus();
161    }
162
163    @Override
164    public void onResume() {
165        super.onResume();
166
167        if (mCallManager.getState() != Phone.State.IDLE) {
168            mButtonAddSipAccount.setEnabled(false);
169            mButtonSipReceiveCalls.setEnabled(false);
170        } else {
171            mButtonAddSipAccount.setEnabled(true);
172            mButtonSipReceiveCalls.setEnabled(true);
173        }
174    }
175
176    @Override
177    protected void onDestroy() {
178        super.onDestroy();
179        unregisterForContextMenu(getListView());
180    }
181
182    @Override
183    protected void onActivityResult(final int requestCode, final int resultCode,
184            final Intent intent) {
185        if (resultCode != RESULT_OK && resultCode != RESULT_FIRST_USER) return;
186        new Thread() {
187            public void run() {
188            try {
189                if (mProfile != null) {
190                    Log.v(TAG, "Removed Profile:" + mProfile.getProfileName());
191                    deleteProfile(mProfile);
192                }
193
194                SipProfile profile = intent.getParcelableExtra(KEY_SIP_PROFILE);
195                if (resultCode == RESULT_OK) {
196                    Log.v(TAG, "New Profile Name:" + profile.getProfileName());
197                    addProfile(profile);
198                }
199                updateProfilesStatus();
200            } catch (IOException e) {
201                Log.v(TAG, "Can not handle the profile : " + e.getMessage());
202            }
203        }}.start();
204    }
205
206    private void registerForAddSipListener() {
207        mButtonAddSipAccount =
208                (Button) findViewById(R.id.add_remove_account_button);
209        mButtonAddSipAccount.setOnClickListener(
210                new android.view.View.OnClickListener() {
211                    public void onClick(View v) {
212                        startSipEditor(null);
213                    }
214                });
215    }
216
217    private void registerForReceiveCallsCheckBox() {
218        mButtonSipReceiveCalls = (CheckBoxPreference) findPreference
219                (BUTTON_SIP_RECEIVE_CALLS);
220        mButtonSipReceiveCalls.setChecked(
221                mSipSharedPreferences.isReceivingCallsEnabled());
222        mButtonSipReceiveCalls.setOnPreferenceClickListener(
223                new OnPreferenceClickListener() {
224                    public boolean onPreferenceClick(Preference preference) {
225                        final boolean enabled =
226                                ((CheckBoxPreference) preference).isChecked();
227                        new Thread(new Runnable() {
228                                public void run() {
229                                    handleSipReceiveCallsOption(enabled);
230                                }
231                        }).start();
232                        return true;
233                    }
234                });
235    }
236
237    private synchronized void handleSipReceiveCallsOption(boolean enabled) {
238        mSipSharedPreferences.setReceivingCallsEnabled(enabled);
239        List<SipProfile> sipProfileList = mProfileDb.retrieveSipProfileList();
240        for (SipProfile p : sipProfileList) {
241            String sipUri = p.getUriString();
242            p = updateAutoRegistrationFlag(p, enabled);
243            try {
244                if (enabled) {
245                    mSipManager.open(p,
246                            SipUtil.createIncomingCallPendingIntent(), null);
247                } else {
248                    mSipManager.close(sipUri);
249                    if (mSipSharedPreferences.isPrimaryAccount(sipUri)) {
250                        // re-open in order to make calls
251                        mSipManager.open(p);
252                    }
253                }
254            } catch (Exception e) {
255                Log.e(TAG, "register failed", e);
256            }
257        }
258        updateProfilesStatus();
259    }
260
261    private SipProfile updateAutoRegistrationFlag(
262            SipProfile p, boolean enabled) {
263        SipProfile newProfile = new SipProfile.Builder(p)
264                .setAutoRegistration(enabled)
265                .build();
266        try {
267            mProfileDb.deleteProfile(p);
268            mProfileDb.saveProfile(newProfile);
269        } catch (Exception e) {
270            Log.e(TAG, "updateAutoRegistrationFlag error", e);
271        }
272        return newProfile;
273    }
274
275    private void updateProfilesStatus() {
276        new Thread(new Runnable() {
277            public void run() {
278                try {
279                    retrieveSipLists();
280                } catch (Exception e) {
281                    Log.e(TAG, "isRegistered", e);
282                }
283            }
284        }).start();
285    }
286
287    private String getProfileName(SipProfile profile) {
288        String profileName = profile.getProfileName();
289        if (TextUtils.isEmpty(profileName)) {
290            profileName = profile.getUserName() + "@" + profile.getSipDomain();
291        }
292        return profileName;
293    }
294
295    private void retrieveSipLists() {
296        mSipPreferenceMap = new LinkedHashMap<String, SipPreference>();
297        mSipProfileList = mProfileDb.retrieveSipProfileList();
298        processActiveProfilesFromSipService();
299        Collections.sort(mSipProfileList, new Comparator<SipProfile>() {
300            public int compare(SipProfile p1, SipProfile p2) {
301                return getProfileName(p1).compareTo(getProfileName(p2));
302            }
303
304            public boolean equals(SipProfile p) {
305                // not used
306                return false;
307            }
308        });
309        mSipListContainer.removeAll();
310        for (SipProfile p : mSipProfileList) {
311            addPreferenceFor(p);
312        }
313
314        if (!mSipSharedPreferences.isReceivingCallsEnabled()) return;
315        for (SipProfile p : mSipProfileList) {
316            if (mUid == p.getCallingUid()) {
317                try {
318                    mSipManager.setRegistrationListener(
319                            p.getUriString(), createRegistrationListener());
320                } catch (SipException e) {
321                    Log.e(TAG, "cannot set registration listener", e);
322                }
323            }
324        }
325    }
326
327    private void processActiveProfilesFromSipService() {
328        SipProfile[] activeList = mSipManager.getListOfProfiles();
329        for (SipProfile activeProfile : activeList) {
330            SipProfile profile = getProfileFromList(activeProfile);
331            if (profile == null) {
332                mSipProfileList.add(activeProfile);
333            } else {
334                profile.setCallingUid(activeProfile.getCallingUid());
335            }
336        }
337    }
338
339    private SipProfile getProfileFromList(SipProfile activeProfile) {
340        for (SipProfile p : mSipProfileList) {
341            if (p.getUriString().equals(activeProfile.getUriString())) {
342                return p;
343            }
344        }
345        return null;
346    }
347
348    private void addPreferenceFor(SipProfile p) {
349        String status;
350        Log.v(TAG, "addPreferenceFor profile uri" + p.getUri());
351        SipPreference pref = new SipPreference(this, p);
352        mSipPreferenceMap.put(p.getUriString(), pref);
353        mSipListContainer.addPreference(pref);
354
355        pref.setOnPreferenceClickListener(
356                new Preference.OnPreferenceClickListener() {
357                    public boolean onPreferenceClick(Preference pref) {
358                        handleProfileClick(((SipPreference) pref).mProfile);
359                        return true;
360                    }
361                });
362    }
363
364    private void handleProfileClick(final SipProfile profile) {
365        int uid = profile.getCallingUid();
366        if (uid == mUid || uid == 0) {
367            startSipEditor(profile);
368            return;
369        }
370        new AlertDialog.Builder(this)
371                .setTitle(R.string.alert_dialog_close)
372                .setIcon(android.R.drawable.ic_dialog_alert)
373                .setPositiveButton(R.string.close_profile,
374                        new DialogInterface.OnClickListener() {
375                            public void onClick(DialogInterface dialog, int w) {
376                                deleteProfile(profile);
377                                unregisterProfile(profile);
378                            }
379                        })
380                .setNegativeButton(android.R.string.cancel, null)
381                .show();
382    }
383
384    private void unregisterProfile(final SipProfile p) {
385        // run it on background thread for better UI response
386        new Thread(new Runnable() {
387            public void run() {
388                try {
389                    mSipManager.close(p.getUriString());
390                } catch (Exception e) {
391                    Log.e(TAG, "unregister failed, SipService died?", e);
392                }
393            }
394        }, "unregisterProfile").start();
395    }
396
397    void deleteProfile(SipProfile p) {
398        mSipProfileList.remove(p);
399        SipPreference pref = mSipPreferenceMap.remove(p.getUriString());
400        mSipListContainer.removePreference(pref);
401    }
402
403    private void addProfile(SipProfile p) throws IOException {
404        try {
405            mSipManager.setRegistrationListener(p.getUriString(),
406                    createRegistrationListener());
407        } catch (Exception e) {
408            Log.e(TAG, "cannot set registration listener", e);
409        }
410        mSipProfileList.add(p);
411        addPreferenceFor(p);
412    }
413
414    private void startSipEditor(final SipProfile profile) {
415        mProfile = profile;
416        Intent intent = new Intent(this, SipEditor.class);
417        intent.putExtra(KEY_SIP_PROFILE, (Parcelable) profile);
418        startActivityForResult(intent, REQUEST_ADD_OR_EDIT_SIP_PROFILE);
419    }
420
421    private void showRegistrationMessage(final String profileUri,
422            final String message) {
423        runOnUiThread(new Runnable() {
424            public void run() {
425                SipPreference pref = mSipPreferenceMap.get(profileUri);
426                if (pref != null) {
427                    pref.updateSummary(message);
428                }
429            }
430        });
431    }
432
433    private SipRegistrationListener createRegistrationListener() {
434        return new SipRegistrationListener() {
435            public void onRegistrationDone(String profileUri, long expiryTime) {
436                showRegistrationMessage(profileUri, getString(
437                        R.string.registration_status_done));
438            }
439
440            public void onRegistering(String profileUri) {
441                showRegistrationMessage(profileUri, getString(
442                        R.string.registration_status_registering));
443            }
444
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