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