1 /*
2  * Copyright (C) 2006 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.phone;
18 
19 import android.app.Dialog;
20 import android.app.ProgressDialog;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.Intent;
25 import android.content.ServiceConnection;
26 import android.os.AsyncResult;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.IBinder;
30 import android.os.Message;
31 import android.os.RemoteException;
32 import android.os.UserManager;
33 import android.preference.Preference;
34 import android.preference.PreferenceActivity;
35 import android.preference.PreferenceGroup;
36 import android.preference.PreferenceScreen;
37 import android.telephony.ServiceState;
38 import android.telephony.TelephonyManager;
39 import android.text.TextUtils;
40 import android.util.Log;
41 import android.telephony.SubscriptionManager;
42 
43 import com.android.internal.telephony.CommandException;
44 import com.android.internal.telephony.Phone;
45 import com.android.internal.telephony.PhoneFactory;
46 import com.android.internal.telephony.OperatorInfo;
47 
48 import java.util.HashMap;
49 import java.util.List;
50 import android.text.BidiFormatter;
51 import android.text.TextDirectionHeuristics;
52 
53 /**
54  * "Networks" settings UI for the Phone app.
55  */
56 public class NetworkSetting extends PreferenceActivity
57         implements DialogInterface.OnCancelListener {
58 
59     private static final String LOG_TAG = "phone";
60     private static final boolean DBG = true;
61 
62     private static final int EVENT_NETWORK_SCAN_COMPLETED = 100;
63     private static final int EVENT_NETWORK_SELECTION_DONE = 200;
64     private static final int EVENT_AUTO_SELECT_DONE = 300;
65 
66     //dialog ids
67     private static final int DIALOG_NETWORK_SELECTION = 100;
68     private static final int DIALOG_NETWORK_LIST_LOAD = 200;
69     private static final int DIALOG_NETWORK_AUTO_SELECT = 300;
70 
71     //String keys for preference lookup
72     private static final String LIST_NETWORKS_KEY = "list_networks_key";
73     private static final String BUTTON_SRCH_NETWRKS_KEY = "button_srch_netwrks_key";
74     private static final String BUTTON_AUTO_SELECT_KEY = "button_auto_select_key";
75 
76     //map of network controls to the network data.
77     private HashMap<Preference, OperatorInfo> mNetworkMap;
78 
79     int mPhoneId = SubscriptionManager.INVALID_PHONE_INDEX;
80     protected boolean mIsForeground = false;
81 
82     private UserManager mUm;
83     private boolean mUnavailable;
84 
85     /** message for network selection */
86     String mNetworkSelectMsg;
87 
88     //preference objects
89     private PreferenceGroup mNetworkList;
90     private Preference mSearchButton;
91     private Preference mAutoSelect;
92 
93     private final Handler mHandler = new Handler() {
94         @Override
95         public void handleMessage(Message msg) {
96             AsyncResult ar;
97             switch (msg.what) {
98                 case EVENT_NETWORK_SCAN_COMPLETED:
99                     networksListLoaded ((List<OperatorInfo>) msg.obj, msg.arg1);
100                     break;
101 
102                 case EVENT_NETWORK_SELECTION_DONE:
103                     if (DBG) log("hideProgressPanel");
104                     removeDialog(DIALOG_NETWORK_SELECTION);
105                     getPreferenceScreen().setEnabled(true);
106 
107                     ar = (AsyncResult) msg.obj;
108                     if (ar.exception != null) {
109                         if (DBG) log("manual network selection: failed!");
110                         displayNetworkSelectionFailed(ar.exception);
111                     } else {
112                         if (DBG) log("manual network selection: succeeded!");
113                         displayNetworkSelectionSucceeded();
114                     }
115 
116                     break;
117                 case EVENT_AUTO_SELECT_DONE:
118                     if (DBG) log("hideProgressPanel");
119 
120                     // Always try to dismiss the dialog because activity may
121                     // be moved to background after dialog is shown.
122                     try {
123                         dismissDialog(DIALOG_NETWORK_AUTO_SELECT);
124                     } catch (IllegalArgumentException e) {
125                         // "auto select" is always trigged in foreground, so "auto select" dialog
126                         //  should be shown when "auto select" is trigged. Should NOT get
127                         // this exception, and Log it.
128                         Log.w(LOG_TAG, "[NetworksList] Fail to dismiss auto select dialog ", e);
129                     }
130                     getPreferenceScreen().setEnabled(true);
131 
132                     ar = (AsyncResult) msg.obj;
133                     if (ar.exception != null) {
134                         if (DBG) log("automatic network selection: failed!");
135                         displayNetworkSelectionFailed(ar.exception);
136                     } else {
137                         if (DBG) log("automatic network selection: succeeded!");
138                         displayNetworkSelectionSucceeded();
139                     }
140 
141                     break;
142             }
143 
144             return;
145         }
146     };
147 
148     /**
149      * Service connection code for the NetworkQueryService.
150      * Handles the work of binding to a local object so that we can make
151      * the appropriate service calls.
152      */
153 
154     /** Local service interface */
155     private INetworkQueryService mNetworkQueryService = null;
156 
157     /** Service connection */
158     private final ServiceConnection mNetworkQueryServiceConnection = new ServiceConnection() {
159 
160         /** Handle the task of binding the local object to the service */
161         public void onServiceConnected(ComponentName className, IBinder service) {
162             if (DBG) log("connection created, binding local service.");
163             mNetworkQueryService = ((NetworkQueryService.LocalBinder) service).getService();
164             // as soon as it is bound, run a query.
165             loadNetworksList();
166         }
167 
168         /** Handle the task of cleaning up the local binding */
169         public void onServiceDisconnected(ComponentName className) {
170             if (DBG) log("connection disconnected, cleaning local binding.");
171             mNetworkQueryService = null;
172         }
173     };
174 
175     /**
176      * This implementation of INetworkQueryServiceCallback is used to receive
177      * callback notifications from the network query service.
178      */
179     private final INetworkQueryServiceCallback mCallback = new INetworkQueryServiceCallback.Stub() {
180 
181         /** place the message on the looper queue upon query completion. */
182         public void onQueryComplete(List<OperatorInfo> networkInfoArray, int status) {
183             if (DBG) log("notifying message loop of query completion.");
184             Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED,
185                     status, 0, networkInfoArray);
186             msg.sendToTarget();
187         }
188     };
189 
190     @Override
onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)191     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
192         boolean handled = false;
193 
194         if (preference == mSearchButton) {
195             loadNetworksList();
196             handled = true;
197         } else if (preference == mAutoSelect) {
198             selectNetworkAutomatic();
199             handled = true;
200         } else {
201             Preference selectedCarrier = preference;
202 
203             String networkStr = selectedCarrier.getTitle().toString();
204             if (DBG) log("selected network: " + networkStr);
205 
206             Message msg = mHandler.obtainMessage(EVENT_NETWORK_SELECTION_DONE);
207             Phone phone = PhoneFactory.getPhone(mPhoneId);
208             if (phone != null) {
209                 phone.selectNetworkManually(mNetworkMap.get(selectedCarrier), true, msg);
210                 displayNetworkSeletionInProgress(networkStr);
211                 handled = true;
212             } else {
213                 log("Error selecting network. phone is null.");
214             }
215 
216 
217         }
218 
219         return handled;
220     }
221 
222     //implemented for DialogInterface.OnCancelListener
onCancel(DialogInterface dialog)223     public void onCancel(DialogInterface dialog) {
224         // request that the service stop the query with this callback object.
225         try {
226             mNetworkQueryService.stopNetworkQuery(mCallback);
227         } catch (RemoteException e) {
228             log("onCancel: exception from stopNetworkQuery " + e);
229         }
230         finish();
231     }
232 
getNormalizedCarrierName(OperatorInfo ni)233     public String getNormalizedCarrierName(OperatorInfo ni) {
234         if (ni != null) {
235             return ni.getOperatorAlphaLong() + " (" + ni.getOperatorNumeric() + ")";
236         }
237         return null;
238     }
239 
240     @Override
onCreate(Bundle icicle)241     protected void onCreate(Bundle icicle) {
242         super.onCreate(icicle);
243 
244         mUm = (UserManager) getSystemService(Context.USER_SERVICE);
245 
246         if (mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
247             setContentView(R.layout.telephony_disallowed_preference_screen);
248             mUnavailable = true;
249             return;
250         }
251 
252         addPreferencesFromResource(R.xml.carrier_select);
253 
254         int subId;
255         Intent intent = getIntent();
256         if (intent != null && intent.getExtras() != null) {
257             subId = intent.getExtras().getInt(GsmUmtsOptions.EXTRA_SUB_ID);
258             if (SubscriptionManager.isValidSubscriptionId(subId)) {
259                 mPhoneId = SubscriptionManager.getPhoneId(subId);
260             }
261         }
262 
263         mNetworkList = (PreferenceGroup) getPreferenceScreen().findPreference(LIST_NETWORKS_KEY);
264         mNetworkMap = new HashMap<Preference, OperatorInfo>();
265 
266         mSearchButton = getPreferenceScreen().findPreference(BUTTON_SRCH_NETWRKS_KEY);
267         mAutoSelect = getPreferenceScreen().findPreference(BUTTON_AUTO_SELECT_KEY);
268 
269         // Start the Network Query service, and bind it.
270         // The OS knows to start he service only once and keep the instance around (so
271         // long as startService is called) until a stopservice request is made.  Since
272         // we want this service to just stay in the background until it is killed, we
273         // don't bother stopping it from our end.
274         startService (new Intent(this, NetworkQueryService.class));
275         bindService (new Intent(this, NetworkQueryService.class).setAction(
276                 NetworkQueryService.ACTION_LOCAL_BINDER),
277                 mNetworkQueryServiceConnection, Context.BIND_AUTO_CREATE);
278     }
279 
280     @Override
onResume()281     public void onResume() {
282         super.onResume();
283         mIsForeground = true;
284     }
285 
286     @Override
onPause()287     public void onPause() {
288         super.onPause();
289         mIsForeground = false;
290     }
291 
292     /**
293      * Override onDestroy() to unbind the query service, avoiding service
294      * leak exceptions.
295      */
296     @Override
onDestroy()297     protected void onDestroy() {
298         try {
299             // used to un-register callback
300             mNetworkQueryService.unregisterCallback(mCallback);
301         } catch (RemoteException e) {
302             log("onDestroy: exception from unregisterCallback " + e);
303         }
304 
305         if (!mUnavailable) {
306             // unbind the service.
307             unbindService(mNetworkQueryServiceConnection);
308         }
309         super.onDestroy();
310     }
311 
312     @Override
onCreateDialog(int id)313     protected Dialog onCreateDialog(int id) {
314 
315         if ((id == DIALOG_NETWORK_SELECTION) || (id == DIALOG_NETWORK_LIST_LOAD) ||
316                 (id == DIALOG_NETWORK_AUTO_SELECT)) {
317             ProgressDialog dialog = new ProgressDialog(this);
318             switch (id) {
319                 case DIALOG_NETWORK_SELECTION:
320                     // It would be more efficient to reuse this dialog by moving
321                     // this setMessage() into onPreparedDialog() and NOT use
322                     // removeDialog().  However, this is not possible since the
323                     // message is rendered only 2 times in the ProgressDialog -
324                     // after show() and before onCreate.
325                     dialog.setMessage(mNetworkSelectMsg);
326                     dialog.setCancelable(false);
327                     dialog.setIndeterminate(true);
328                     break;
329                 case DIALOG_NETWORK_AUTO_SELECT:
330                     dialog.setMessage(getResources().getString(R.string.register_automatically));
331                     dialog.setCancelable(false);
332                     dialog.setIndeterminate(true);
333                     break;
334                 case DIALOG_NETWORK_LIST_LOAD:
335                 default:
336                     // reinstate the cancelablity of the dialog.
337                     dialog.setMessage(getResources().getString(R.string.load_networks_progress));
338                     dialog.setCanceledOnTouchOutside(false);
339                     dialog.setOnCancelListener(this);
340                     break;
341             }
342             return dialog;
343         }
344         return null;
345     }
346 
347     @Override
onPrepareDialog(int id, Dialog dialog)348     protected void onPrepareDialog(int id, Dialog dialog) {
349         if ((id == DIALOG_NETWORK_SELECTION) || (id == DIALOG_NETWORK_LIST_LOAD) ||
350                 (id == DIALOG_NETWORK_AUTO_SELECT)) {
351             // when the dialogs come up, we'll need to indicate that
352             // we're in a busy state to dissallow further input.
353             getPreferenceScreen().setEnabled(false);
354         }
355     }
356 
displayEmptyNetworkList(boolean flag)357     private void displayEmptyNetworkList(boolean flag) {
358         mNetworkList.setTitle(flag ? R.string.empty_networks_list : R.string.label_available);
359     }
360 
displayNetworkSeletionInProgress(String networkStr)361     private void displayNetworkSeletionInProgress(String networkStr) {
362         // TODO: use notification manager?
363         mNetworkSelectMsg = getResources().getString(R.string.register_on_network, networkStr);
364 
365         if (mIsForeground) {
366             showDialog(DIALOG_NETWORK_SELECTION);
367         }
368     }
369 
displayNetworkQueryFailed(int error)370     private void displayNetworkQueryFailed(int error) {
371         String status = getResources().getString(R.string.network_query_error);
372 
373         final PhoneGlobals app = PhoneGlobals.getInstance();
374         app.notificationMgr.postTransientNotification(
375                 NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status);
376     }
377 
displayNetworkSelectionFailed(Throwable ex)378     private void displayNetworkSelectionFailed(Throwable ex) {
379         String status;
380 
381         if ((ex != null && ex instanceof CommandException) &&
382                 ((CommandException)ex).getCommandError()
383                   == CommandException.Error.ILLEGAL_SIM_OR_ME)
384         {
385             status = getResources().getString(R.string.not_allowed);
386         } else {
387             status = getResources().getString(R.string.connect_later);
388         }
389 
390         final PhoneGlobals app = PhoneGlobals.getInstance();
391         app.notificationMgr.postTransientNotification(
392                 NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status);
393 
394         TelephonyManager tm = (TelephonyManager) app.getSystemService(Context.TELEPHONY_SERVICE);
395         Phone phone = PhoneFactory.getPhone(mPhoneId);
396         if (phone != null) {
397             ServiceState ss = tm.getServiceStateForSubscriber(phone.getSubId());
398             if (ss != null) {
399                 app.notificationMgr.updateNetworkSelection(ss.getState());
400             }
401         }
402     }
403 
displayNetworkSelectionSucceeded()404     private void displayNetworkSelectionSucceeded() {
405         String status = getResources().getString(R.string.registration_done);
406 
407         final PhoneGlobals app = PhoneGlobals.getInstance();
408         app.notificationMgr.postTransientNotification(
409                 NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status);
410 
411         mHandler.postDelayed(new Runnable() {
412             public void run() {
413                 finish();
414             }
415         }, 3000);
416     }
417 
loadNetworksList()418     private void loadNetworksList() {
419         if (DBG) log("load networks list...");
420 
421         if (mIsForeground) {
422             showDialog(DIALOG_NETWORK_LIST_LOAD);
423         }
424 
425         // delegate query request to the service.
426         try {
427             mNetworkQueryService.startNetworkQuery(mCallback, mPhoneId);
428         } catch (RemoteException e) {
429             log("loadNetworksList: exception from startNetworkQuery " + e);
430             if (mIsForeground) {
431                 try {
432                     dismissDialog(DIALOG_NETWORK_LIST_LOAD);
433                 } catch (IllegalArgumentException e1) {
434                     // do nothing
435                 }
436             }
437         }
438 
439         displayEmptyNetworkList(false);
440     }
441 
442     /**
443      * networksListLoaded has been rewritten to take an array of
444      * OperatorInfo objects and a status field, instead of an
445      * AsyncResult.  Otherwise, the functionality which takes the
446      * OperatorInfo array and creates a list of preferences from it,
447      * remains unchanged.
448      */
networksListLoaded(List<OperatorInfo> result, int status)449     private void networksListLoaded(List<OperatorInfo> result, int status) {
450         if (DBG) log("networks list loaded");
451 
452         // used to un-register callback
453         try {
454             mNetworkQueryService.unregisterCallback(mCallback);
455         } catch (RemoteException e) {
456             log("networksListLoaded: exception from unregisterCallback " + e);
457         }
458 
459         // update the state of the preferences.
460         if (DBG) log("hideProgressPanel");
461 
462         // Always try to dismiss the dialog because activity may
463         // be moved to background after dialog is shown.
464         try {
465             dismissDialog(DIALOG_NETWORK_LIST_LOAD);
466         } catch (IllegalArgumentException e) {
467             // It's not a error in following scenario, we just ignore it.
468             // "Load list" dialog will not show, if NetworkQueryService is
469             // connected after this activity is moved to background.
470             if (DBG) log("Fail to dismiss network load list dialog " + e);
471         }
472 
473         getPreferenceScreen().setEnabled(true);
474         clearList();
475 
476         if (status != NetworkQueryService.QUERY_OK) {
477             if (DBG) log("error while querying available networks");
478             displayNetworkQueryFailed(status);
479             displayEmptyNetworkList(true);
480         } else {
481             if (result != null){
482                 displayEmptyNetworkList(false);
483 
484                 // create a preference for each item in the list.
485                 // just use the operator name instead of the mildly
486                 // confusing mcc/mnc.
487                 for (OperatorInfo ni : result) {
488                     Preference carrier = new Preference(this, null);
489                     carrier.setTitle(getNetworkTitle(ni));
490                     carrier.setPersistent(false);
491                     mNetworkList.addPreference(carrier);
492                     mNetworkMap.put(carrier, ni);
493 
494                     if (DBG) log("  " + ni);
495                 }
496             } else {
497                 displayEmptyNetworkList(true);
498             }
499         }
500     }
501 
502     /**
503      * Returns the title of the network obtained in the manual search.
504      *
505      * @param OperatorInfo contains the information of the network.
506      *
507      * @return Long Name if not null/empty, otherwise Short Name if not null/empty,
508      * else MCCMNC string.
509      */
510 
getNetworkTitle(OperatorInfo ni)511     private String getNetworkTitle(OperatorInfo ni) {
512         if (!TextUtils.isEmpty(ni.getOperatorAlphaLong())) {
513             return ni.getOperatorAlphaLong();
514         } else if (!TextUtils.isEmpty(ni.getOperatorAlphaShort())) {
515             return ni.getOperatorAlphaShort();
516         } else {
517             BidiFormatter bidiFormatter = BidiFormatter.getInstance();
518             return bidiFormatter.unicodeWrap(ni.getOperatorNumeric(), TextDirectionHeuristics.LTR);
519         }
520     }
521 
clearList()522     private void clearList() {
523         for (Preference p : mNetworkMap.keySet()) {
524             mNetworkList.removePreference(p);
525         }
526         mNetworkMap.clear();
527     }
528 
selectNetworkAutomatic()529     private void selectNetworkAutomatic() {
530         if (DBG) log("select network automatically...");
531         if (mIsForeground) {
532             showDialog(DIALOG_NETWORK_AUTO_SELECT);
533         }
534 
535         Message msg = mHandler.obtainMessage(EVENT_AUTO_SELECT_DONE);
536         Phone phone = PhoneFactory.getPhone(mPhoneId);
537         if (phone != null) {
538             phone.setNetworkSelectionModeAutomatic(msg);
539         }
540     }
541 
log(String msg)542     private void log(String msg) {
543         Log.d(LOG_TAG, "[NetworksList] " + msg);
544     }
545 }
546