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