Home | History | Annotate | Download | only in phone
      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  */
     16 package com.android.phone;
     17 
     18 import android.app.ActionBar;
     19 import android.app.Activity;
     20 import android.content.ComponentName;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.ServiceConnection;
     24 import android.os.AsyncResult;
     25 import android.os.AsyncTask;
     26 import android.os.Bundle;
     27 import android.os.Handler;
     28 import android.os.IBinder;
     29 import android.os.Message;
     30 import android.os.RemoteException;
     31 import android.preference.Preference;
     32 import android.preference.PreferenceCategory;
     33 import android.preference.PreferenceFragment;
     34 import android.preference.PreferenceScreen;
     35 import android.telephony.AccessNetworkConstants;
     36 import android.telephony.CellIdentity;
     37 import android.telephony.CellInfo;
     38 import android.telephony.NetworkRegistrationState;
     39 import android.telephony.ServiceState;
     40 import android.telephony.SubscriptionManager;
     41 import android.telephony.TelephonyManager;
     42 import android.util.Log;
     43 import android.view.LayoutInflater;
     44 import android.view.View;
     45 import android.view.ViewGroup;
     46 
     47 import com.android.internal.logging.MetricsLogger;
     48 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     49 import com.android.internal.telephony.OperatorInfo;
     50 import com.android.internal.telephony.Phone;
     51 import com.android.internal.telephony.PhoneFactory;
     52 
     53 import java.util.ArrayList;
     54 import java.util.Arrays;
     55 import java.util.HashMap;
     56 import java.util.List;
     57 import java.util.Map;
     58 
     59 /**
     60  * "Choose network" settings UI for the Phone app.
     61  */
     62 public 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