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