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