1/*
2 * Copyright (C) 2018 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 */
16package com.android.phone;
17
18import android.app.ActionBar;
19import android.app.Activity;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.ServiceConnection;
24import android.os.AsyncResult;
25import android.os.AsyncTask;
26import android.os.Bundle;
27import android.os.Handler;
28import android.os.IBinder;
29import android.os.Message;
30import android.os.RemoteException;
31import android.preference.Preference;
32import android.preference.PreferenceCategory;
33import android.preference.PreferenceFragment;
34import android.preference.PreferenceScreen;
35import android.telephony.AccessNetworkConstants;
36import android.telephony.CellIdentity;
37import android.telephony.CellInfo;
38import android.telephony.NetworkRegistrationState;
39import android.telephony.ServiceState;
40import android.telephony.SubscriptionManager;
41import android.telephony.TelephonyManager;
42import android.util.Log;
43import android.view.LayoutInflater;
44import android.view.View;
45import android.view.ViewGroup;
46
47import com.android.internal.logging.MetricsLogger;
48import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
49import com.android.internal.telephony.OperatorInfo;
50import com.android.internal.telephony.Phone;
51import com.android.internal.telephony.PhoneFactory;
52
53import java.util.ArrayList;
54import java.util.Arrays;
55import java.util.HashMap;
56import java.util.List;
57import java.util.Map;
58
59/**
60 * "Choose network" settings UI for the Phone app.
61 */
62public class NetworkSelectSetting extends PreferenceFragment {
63
64    private static final String TAG = "NetworkSelectSetting";
65    private static final boolean DBG = true;
66
67    private static final int EVENT_NETWORK_SELECTION_DONE = 1;
68    private static final int EVENT_NETWORK_SCAN_RESULTS = 2;
69    private static final int EVENT_NETWORK_SCAN_ERROR = 3;
70    private static final int EVENT_NETWORK_SCAN_COMPLETED = 4;
71
72    private static final String PREF_KEY_CONNECTED_NETWORK_OPERATOR =
73            "connected_network_operator_preference";
74    private static final String PREF_KEY_NETWORK_OPERATORS = "network_operators_preference";
75
76    // used to add/remove NetworkOperatorsPreference.
77    private PreferenceCategory mNetworkOperatorsPreferences;
78    // used to add/remove connected NetworkOperatorPreference.
79    private PreferenceCategory mConnectedNetworkOperatorsPreference;
80    // manage the progress bar on the top of the page.
81    private View mProgressHeader;
82    private Preference mStatusMessagePreference;
83    private List<CellInfo> mCellInfoList;
84    private int mPhoneId = SubscriptionManager.INVALID_PHONE_INDEX;
85    private ViewGroup mFrameLayout;
86    private NetworkOperatorPreference mSelectedNetworkOperatorPreference;
87    private TelephonyManager mTelephonyManager;
88    private NetworkOperators mNetworkOperators;
89    private List<String> mForbiddenPlmns;
90
91    private final Runnable mUpdateNetworkOperatorsRunnable = () -> {
92        updateNetworkOperatorsPreferenceCategory();
93    };
94
95    /**
96     * Create a new instance of this fragment.
97     */
98    public static NetworkSelectSetting newInstance(int phoneId) {
99        Bundle args = new Bundle();
100        args.putInt(NetworkSelectSettingActivity.KEY_PHONE_ID, phoneId);
101        NetworkSelectSetting fragment = new NetworkSelectSetting();
102        fragment.setArguments(args);
103
104        return fragment;
105    }
106
107    @Override
108    public void onCreate(Bundle icicle) {
109        if (DBG) logd("onCreate");
110        super.onCreate(icicle);
111
112        mPhoneId = getArguments().getInt(NetworkSelectSettingActivity.KEY_PHONE_ID);
113
114        addPreferencesFromResource(R.xml.choose_network);
115        mConnectedNetworkOperatorsPreference =
116                (PreferenceCategory) findPreference(PREF_KEY_CONNECTED_NETWORK_OPERATOR);
117        mNetworkOperatorsPreferences =
118                (PreferenceCategory) findPreference(PREF_KEY_NETWORK_OPERATORS);
119        mStatusMessagePreference = new Preference(getContext());
120        mSelectedNetworkOperatorPreference = null;
121        mTelephonyManager = (TelephonyManager)
122                getContext().getSystemService(Context.TELEPHONY_SERVICE);
123        mNetworkOperators = new NetworkOperators(getContext());
124        setRetainInstance(true);
125    }
126
127    @Override
128    public void onViewCreated(View view, Bundle savedInstanceState) {
129        if (DBG) logd("onViewCreated");
130        super.onViewCreated(view, savedInstanceState);
131
132        if (getListView() != null) {
133            getListView().setDivider(null);
134        }
135        // Inflate progress bar
136        final Activity activity = getActivity();
137        if (activity != null) {
138            ActionBar actionBar = activity.getActionBar();
139            if (actionBar != null) {
140                // android.R.id.home will be triggered in
141                // {@link NetworkSelectSettingAcitivity#onOptionsItemSelected()}
142                actionBar.setDisplayHomeAsUpEnabled(true);
143            }
144            mFrameLayout = activity.findViewById(R.id.choose_network_content);
145            final LayoutInflater inflater = activity.getLayoutInflater();
146            final View pinnedHeader =
147                    inflater.inflate(R.layout.choose_network_progress_header, mFrameLayout, false);
148            mFrameLayout.addView(pinnedHeader);
149            mFrameLayout.setVisibility(View.VISIBLE);
150            mProgressHeader = pinnedHeader.findViewById(R.id.progress_bar_animation);
151            setProgressBarVisible(false);
152        }
153        forceConfigConnectedNetworkOperatorsPreferenceCategory();
154    }
155
156    @Override
157    public void onStart() {
158        if (DBG) logd("onStart");
159        super.onStart();
160        new AsyncTask<Void, Void, List<String>>() {
161            @Override
162            protected List<String> doInBackground(Void... voids) {
163                return Arrays.asList(mTelephonyManager.getForbiddenPlmns());
164            }
165
166            @Override
167            protected void onPostExecute(List<String> result) {
168                mForbiddenPlmns = result;
169                bindNetworkQueryService();
170            }
171        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
172    }
173
174    /**
175     * Invoked on each preference click in this hierarchy, overrides
176     * PreferenceActivity's implementation.  Used to make sure we track the
177     * preference click events.
178     * Since the connected network operator is either faked (when no data connection) or already
179     * connected, we do not allow user to click the connected network operator.
180     */
181    @Override
182    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
183                                         Preference preference) {
184        if (DBG) logd("User clicked the screen");
185        stopNetworkQuery();
186        setProgressBarVisible(false);
187        if (preference instanceof  NetworkOperatorPreference) {
188            // Refresh the last selected item in case users reselect network.
189            if (mSelectedNetworkOperatorPreference != null) {
190                mSelectedNetworkOperatorPreference.setSummary("");
191            }
192
193            mSelectedNetworkOperatorPreference = (NetworkOperatorPreference) preference;
194            CellInfo cellInfo = mSelectedNetworkOperatorPreference.getCellInfo();
195            if (DBG) logd("User click a NetworkOperatorPreference: " + cellInfo.toString());
196
197            // Send metrics event
198            MetricsLogger.action(getContext(),
199                    MetricsEvent.ACTION_MOBILE_NETWORK_MANUAL_SELECT_NETWORK);
200
201            // Connect to the network
202            Message msg = mHandler.obtainMessage(EVENT_NETWORK_SELECTION_DONE);
203            Phone phone = PhoneFactory.getPhone(mPhoneId);
204            if (phone != null) {
205                if (DBG) {
206                    logd("Connect to the network: " + CellInfoUtil.getNetworkTitle(cellInfo));
207                }
208                // Set summary as "Connecting" to the selected network.
209                mSelectedNetworkOperatorPreference.setSummary(R.string.network_connecting);
210
211                // Set summary as "Disconnected" to the previously connected network
212                if (mConnectedNetworkOperatorsPreference.getPreferenceCount() > 0) {
213                    NetworkOperatorPreference connectedNetworkOperator = (NetworkOperatorPreference)
214                            (mConnectedNetworkOperatorsPreference.getPreference(0));
215                    if (!CellInfoUtil.getNetworkTitle(cellInfo).equals(
216                            CellInfoUtil.getNetworkTitle(connectedNetworkOperator.getCellInfo()))) {
217                        connectedNetworkOperator.setSummary(R.string.network_disconnected);
218                    }
219                }
220
221                // Select network manually via Phone
222                OperatorInfo operatorInfo = CellInfoUtil.getOperatorInfoFromCellInfo(cellInfo);
223                if (DBG) logd("manually selected network operator: " + operatorInfo.toString());
224                phone.selectNetworkManually(operatorInfo, true, msg);
225                setProgressBarVisible(true);
226                return true;
227            } else {
228                loge("Error selecting network. phone is null.");
229                mSelectedNetworkOperatorPreference = null;
230                return false;
231            }
232
233        } else {
234            preferenceScreen.setEnabled(false);
235            return false;
236        }
237    }
238
239    @Override
240    public void onAttach(Activity activity) {
241        super.onAttach(activity);
242        if (!(getActivity() instanceof NetworkSelectSettingActivity)) {
243            throw new IllegalStateException("Parent activity is not NetworkSelectSettingActivity");
244        }
245    }
246
247    @Override
248    public void onStop() {
249        super.onStop();
250        if (DBG) logd("onStop");
251        getView().removeCallbacks(mUpdateNetworkOperatorsRunnable);
252        stopNetworkQuery();
253        // Unbind the NetworkQueryService
254        unbindNetworkQueryService();
255    }
256
257    private final Handler mHandler = new Handler() {
258        @Override
259        public void handleMessage(Message msg) {
260            AsyncResult ar;
261            switch (msg.what) {
262                case EVENT_NETWORK_SELECTION_DONE:
263                    if (DBG) logd("network selection done: hide the progress header");
264                    setProgressBarVisible(false);
265
266                    ar = (AsyncResult) msg.obj;
267                    if (ar.exception != null) {
268                        if (DBG) logd("manual network selection: failed! ");
269                        updateNetworkSelection();
270                        // Set summary as "Couldn't connect" to the selected network.
271                        mSelectedNetworkOperatorPreference.setSummary(
272                                R.string.network_could_not_connect);
273                    } else {
274                        if (DBG) logd("manual network selection: succeeded! ");
275                        // Set summary as "Connected" to the selected network.
276                        mSelectedNetworkOperatorPreference.setSummary(R.string.network_connected);
277                    }
278                    break;
279
280                case EVENT_NETWORK_SCAN_RESULTS:
281                    List<CellInfo> results = aggregateCellInfoList((List<CellInfo>) msg.obj);
282                    mCellInfoList = new ArrayList<>(results);
283                    if (DBG) logd("after aggregate: " + mCellInfoList.toString());
284                    if (mCellInfoList != null && mCellInfoList.size() != 0) {
285                        updateNetworkOperators();
286                    } else {
287                        addMessagePreference(R.string.empty_networks_list);
288                    }
289
290                    break;
291
292                case EVENT_NETWORK_SCAN_ERROR:
293                    int error = msg.arg1;
294                    if (DBG) logd("error while querying available networks " + error);
295                    stopNetworkQuery();
296                    addMessagePreference(R.string.network_query_error);
297                    break;
298
299                case EVENT_NETWORK_SCAN_COMPLETED:
300                    stopNetworkQuery();
301                    if (DBG) logd("scan complete");
302                    if (mCellInfoList == null) {
303                        // In case the scan timeout before getting any results
304                        addMessagePreference(R.string.empty_networks_list);
305                    }
306                    break;
307            }
308            return;
309        }
310    };
311
312    private void loadNetworksList() {
313        if (DBG) logd("load networks list...");
314        setProgressBarVisible(true);
315        try {
316            if (mNetworkQueryService != null) {
317                if (DBG) logd("start network query");
318                mNetworkQueryService
319                        .startNetworkQuery(mCallback, mPhoneId, true /* is incremental result */);
320            } else {
321                if (DBG) logd("unable to start network query, mNetworkQueryService is null");
322                addMessagePreference(R.string.network_query_error);
323            }
324        } catch (RemoteException e) {
325            loge("loadNetworksList: exception from startNetworkQuery " + e);
326            addMessagePreference(R.string.network_query_error);
327        }
328    }
329
330    /**
331     * This implementation of INetworkQueryServiceCallback is used to receive
332     * callback notifications from the network query service.
333     */
334    private final INetworkQueryServiceCallback mCallback = new INetworkQueryServiceCallback.Stub() {
335
336        /** Returns the scan results to the user, this callback will be called at lease one time. */
337        public void onResults(List<CellInfo> results) {
338            if (DBG) logd("get scan results.");
339            Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_RESULTS, results);
340            msg.sendToTarget();
341        }
342
343        /**
344         * Informs the user that the scan has stopped.
345         *
346         * This callback will be called when the scan is finished or cancelled by the user.
347         * The related NetworkScanRequest will be deleted after this callback.
348         */
349        public void onComplete() {
350            if (DBG) logd("network scan completed.");
351            Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED);
352            msg.sendToTarget();
353        }
354
355        /**
356         * Informs the user that there is some error about the scan.
357         *
358         * This callback will be called whenever there is any error about the scan, and the scan
359         * will be terminated. onComplete() will NOT be called.
360         */
361        public void onError(int error) {
362            if (DBG) logd("get onError callback with error code: " + error);
363            Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_ERROR, error, 0 /* arg2 */);
364            msg.sendToTarget();
365        }
366    };
367
368    /**
369     * Updates network operators from {@link INetworkQueryServiceCallback#onResults()}.
370     */
371    private void updateNetworkOperators() {
372        if (DBG) logd("updateNetworkOperators");
373        if (getActivity() != null) {
374            final View view = getView();
375            final Handler handler = view.getHandler();
376            if (handler != null && handler.hasCallbacks(mUpdateNetworkOperatorsRunnable)) {
377                return;
378            }
379            view.post(mUpdateNetworkOperatorsRunnable);
380        }
381    }
382
383    /**
384     * Update the currently available network operators list, which only contains the unregistered
385     * network operators. So if the device has no data and the network operator in the connected
386     * network operator category shows "Disconnected", it will also exist in the available network
387     * operator category for user to select. On the other hand, if the device has data and the
388     * network operator in the connected network operator category shows "Connected", it will not
389     * exist in the available network category.
390     */
391    private void updateNetworkOperatorsPreferenceCategory() {
392        mNetworkOperatorsPreferences.removeAll();
393
394        configConnectedNetworkOperatorsPreferenceCategory();
395        for (int index = 0; index < mCellInfoList.size(); index++) {
396            if (!mCellInfoList.get(index).isRegistered()) {
397                NetworkOperatorPreference pref = new NetworkOperatorPreference(
398                        mCellInfoList.get(index), getContext(), mForbiddenPlmns);
399                pref.setKey(CellInfoUtil.getNetworkTitle(mCellInfoList.get(index)));
400                pref.setOrder(index);
401                mNetworkOperatorsPreferences.addPreference(pref);
402            }
403        }
404    }
405
406    /**
407     * Config the connected network operator preference when the page was created. When user get
408     * into this page, the device might or might not have data connection.
409     *   - If the device has data:
410     *     1. use {@code ServiceState#getNetworkRegistrationStates()} to get the currently
411     *        registered cellIdentity, wrap it into a CellInfo;
412     *     2. set the signal strength level as strong;
413     *     3. use {@link TelephonyManager#getNetworkOperatorName()} to get the title of the
414     *        previously connected network operator, since the CellIdentity got from step 1 only has
415     *        PLMN.
416     *   - If the device has no data, we will remove the connected network operators list from the
417     *     screen.
418     */
419    private void forceConfigConnectedNetworkOperatorsPreferenceCategory() {
420        if (DBG) logd("Force config ConnectedNetworkOperatorsPreferenceCategory");
421        if (mTelephonyManager.getDataState() == mTelephonyManager.DATA_CONNECTED) {
422            // Try to get the network registration states
423            ServiceState ss = mTelephonyManager.getServiceStateForSubscriber(mPhoneId);
424            List<NetworkRegistrationState> networkList =
425                    ss.getNetworkRegistrationStates(AccessNetworkConstants.TransportType.WWAN);
426            if (networkList == null || networkList.size() == 0) {
427                loge("getNetworkRegistrationStates return null");
428                // Remove the connected network operators category
429                removeConnectedNetworkOperatorPreference();
430                return;
431            }
432            CellIdentity cellIdentity = networkList.get(0).getCellIdentity();
433            CellInfo cellInfo = CellInfoUtil.wrapCellInfoWithCellIdentity(cellIdentity);
434            if (cellInfo != null) {
435                if (DBG) logd("Currently registered cell: " + cellInfo.toString());
436                NetworkOperatorPreference pref =
437                        new NetworkOperatorPreference(cellInfo, getContext(), mForbiddenPlmns);
438                pref.setTitle(mTelephonyManager.getNetworkOperatorName());
439                pref.setSummary(R.string.network_connected);
440                // Update the signal strength icon, since the default signalStrength value would be
441                // zero (it would be quite confusing why the connected network has no signal)
442                pref.setIcon(NetworkOperatorPreference.NUMBER_OF_LEVELS - 1);
443
444                mConnectedNetworkOperatorsPreference.addPreference(pref);
445            } else {
446                loge("Invalid CellIfno: " + cellInfo.toString());
447                // Remove the connected network operators category
448                removeConnectedNetworkOperatorPreference();
449            }
450        } else {
451            if (DBG) logd("No currently registered cell");
452            // Remove the connected network operators category
453            removeConnectedNetworkOperatorPreference();
454        }
455    }
456
457    /**
458     * Configure the ConnectedNetworkOperatorsPreferenceCategory. The category only need to be
459     * configured if the category is currently empty or the operator network title of the previous
460     * connected network is different from the new one.
461     */
462    private void configConnectedNetworkOperatorsPreferenceCategory() {
463        if (DBG) logd("config ConnectedNetworkOperatorsPreferenceCategory");
464        // Remove the category if the CellInfo list is empty or does not have registered cell.
465        if (mCellInfoList.size() == 0) {
466            if (DBG) logd("empty cellinfo list");
467            removeConnectedNetworkOperatorPreference();
468        }
469        CellInfo connectedNetworkOperator = null;
470        for (CellInfo cellInfo: mCellInfoList) {
471            if (cellInfo.isRegistered()) {
472                connectedNetworkOperator = cellInfo;
473                break;
474            }
475        }
476        if (connectedNetworkOperator == null) {
477            if (DBG) logd("no registered network");
478            removeConnectedNetworkOperatorPreference();
479            return;
480        }
481
482        // config the category if it is empty.
483        if (mConnectedNetworkOperatorsPreference.getPreferenceCount() == 0) {
484            if (DBG) logd("ConnectedNetworkSelectList is empty, add one");
485            addConnectedNetworkOperatorPreference(connectedNetworkOperator);
486            return;
487        }
488        NetworkOperatorPreference previousConnectedNetworkOperator = (NetworkOperatorPreference)
489                (mConnectedNetworkOperatorsPreference.getPreference(0));
490
491        // config the category if the network title of the previous connected network is different
492        // from the new one.
493        String cTitle = CellInfoUtil.getNetworkTitle(connectedNetworkOperator);
494        String pTitle = CellInfoUtil.getNetworkTitle(
495                previousConnectedNetworkOperator.getCellInfo());
496        if (!cTitle.equals(pTitle)) {
497            if (DBG) logd("reconfig the category: connected network changed");
498            addConnectedNetworkOperatorPreference(connectedNetworkOperator);
499            return;
500        }
501        if (DBG) logd("same network operator is connected, only refresh the connected network");
502        // Otherwise same network operator is connected, only refresh the connected network
503        // operator preference (first and the only one in this category).
504        ((NetworkOperatorPreference) mConnectedNetworkOperatorsPreference.getPreference(0))
505                .refresh();
506        return;
507    }
508
509    /**
510     * Creates a Preference for the given {@link CellInfo} and adds it to the
511     * {@link #mConnectedNetworkOperatorsPreference}.
512     */
513    private void addConnectedNetworkOperatorPreference(CellInfo cellInfo) {
514        if (DBG) logd("addConnectedNetworkOperatorPreference");
515        // Remove the current ConnectedNetworkOperatorsPreference
516        removeConnectedNetworkOperatorPreference();
517        final NetworkOperatorPreference pref =
518                new NetworkOperatorPreference(cellInfo, getContext(), mForbiddenPlmns);
519        pref.setSummary(R.string.network_connected);
520        mConnectedNetworkOperatorsPreference.addPreference(pref);
521        PreferenceScreen preferenceScreen = getPreferenceScreen();
522        preferenceScreen.addPreference(mConnectedNetworkOperatorsPreference);
523    }
524
525    /** Removes all preferences and hide the {@link #mConnectedNetworkOperatorsPreference}. */
526    private void removeConnectedNetworkOperatorPreference() {
527        mConnectedNetworkOperatorsPreference.removeAll();
528        PreferenceScreen preferenceScreen = getPreferenceScreen();
529        preferenceScreen.removePreference(mConnectedNetworkOperatorsPreference);
530    }
531
532    protected void setProgressBarVisible(boolean visible) {
533        if (mProgressHeader != null) {
534            mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE);
535        }
536    }
537
538    private void addMessagePreference(int messageId) {
539        if (DBG) logd("remove callback");
540        getView().removeCallbacks(mUpdateNetworkOperatorsRunnable);
541        setProgressBarVisible(false);
542        if (DBG) logd("addMessagePreference");
543        mStatusMessagePreference.setTitle(messageId);
544        removeConnectedNetworkOperatorPreference();
545        mNetworkOperatorsPreferences.removeAll();
546        mNetworkOperatorsPreferences.addPreference(mStatusMessagePreference);
547    }
548
549    /**
550     * The Scan results may contains several cell infos with different radio technologies and signal
551     * strength for one network operator. Aggregate the CellInfoList by retaining only the cell info
552     * with the strongest signal strength.
553     */
554    private List<CellInfo> aggregateCellInfoList(List<CellInfo> cellInfoList) {
555        if (DBG) logd("before aggregate: " + cellInfoList.toString());
556        Map<String, CellInfo> map = new HashMap<>();
557        for (CellInfo cellInfo: cellInfoList) {
558            String networkTitle = CellInfoUtil.getNetworkTitle(cellInfo);
559            if (cellInfo.isRegistered() || !map.containsKey(networkTitle)) {
560                map.put(networkTitle, cellInfo);
561            } else {
562                if (map.get(networkTitle).isRegistered()
563                        || CellInfoUtil.getLevel(map.get(networkTitle))
564                        > CellInfoUtil.getLevel(cellInfo)) {
565                    // Skip if the stored cellInfo is registered or has higher signal strength level
566                    continue;
567                }
568                // Otherwise replace it with the new CellInfo
569                map.put(networkTitle, cellInfo);
570            }
571        }
572        return new ArrayList<>(map.values());
573    }
574
575    /**
576     * Service connection code for the NetworkQueryService.
577     * Handles the work of binding to a local object so that we can make
578     * the appropriate service calls.
579     */
580
581    /** Local service interface */
582    private INetworkQueryService mNetworkQueryService = null;
583    /** Flag indicating whether we have called bind on the service. */
584    boolean mShouldUnbind;
585
586    /** Service connection */
587    private final ServiceConnection mNetworkQueryServiceConnection = new ServiceConnection() {
588
589        /** Handle the task of binding the local object to the service */
590        public void onServiceConnected(ComponentName className, IBinder service) {
591            if (DBG) logd("connection created, binding local service.");
592            mNetworkQueryService = ((NetworkQueryService.LocalBinder) service).getService();
593            // Load the network list only when the service is well connected.
594            loadNetworksList();
595        }
596
597        /** Handle the task of cleaning up the local binding */
598        public void onServiceDisconnected(ComponentName className) {
599            if (DBG) logd("connection disconnected, cleaning local binding.");
600            mNetworkQueryService = null;
601        }
602    };
603
604    private void bindNetworkQueryService() {
605        if (DBG) logd("bindNetworkQueryService");
606        getContext().bindService(new Intent(getContext(), NetworkQueryService.class).setAction(
607                NetworkQueryService.ACTION_LOCAL_BINDER),
608                mNetworkQueryServiceConnection, Context.BIND_AUTO_CREATE);
609        mShouldUnbind = true;
610    }
611
612    private void unbindNetworkQueryService() {
613        if (DBG) logd("unbindNetworkQueryService");
614        if (mShouldUnbind) {
615            if (DBG) logd("mShouldUnbind is true");
616            // unbind the service.
617            getContext().unbindService(mNetworkQueryServiceConnection);
618            mShouldUnbind = false;
619        }
620    }
621
622    /**
623     * Call {@link NotificationMgr#updateNetworkSelection(int, int)} to send notification about
624     * no service of user selected operator
625     */
626    private void updateNetworkSelection() {
627        if (DBG) logd("Update notification about no service of user selected operator");
628        final PhoneGlobals app = PhoneGlobals.getInstance();
629        Phone phone = PhoneFactory.getPhone(mPhoneId);
630        if (phone != null) {
631            ServiceState ss = mTelephonyManager.getServiceStateForSubscriber(phone.getSubId());
632            if (ss != null) {
633                app.notificationMgr.updateNetworkSelection(ss.getState(), phone.getSubId());
634            }
635        }
636    }
637
638    private void stopNetworkQuery() {
639        // Stop the network query process
640        try {
641            if (mNetworkQueryService != null) {
642                if (DBG) logd("Stop network query");
643                mNetworkQueryService.stopNetworkQuery();
644                mNetworkQueryService.unregisterCallback(mCallback);
645            }
646        } catch (RemoteException e) {
647            loge("Exception from stopNetworkQuery " + e);
648        }
649    }
650
651    private void logd(String msg) {
652        Log.d(TAG, msg);
653    }
654
655    private void loge(String msg) {
656        Log.e(TAG, msg);
657    }
658}
659