Use the new scan API for manual network search.

This implementation takes three situations into consideration:
1. Using old scan API via Phone (support pre-Pixel2 devices). The app
will first send network scan request via new API and then receive an
ERROR_UNSUPPORTED error since modem won't support it. It will then
fall back to the old API to start a network query again via Phone. Logic
flow as below:
https://docs.google.com/drawings/d/1_LCNe6XKsqk9rp55Ik8Kw_GaKl3OsdlkerPNb6oKEiI/edit?usp=sharing

2. Using new scan API via TelephonyManager before HAL1_2 ready. The app
will first send network scan via new API and then receive a list of cell
info. The results is invalid since they do not have operator info. So it
will then fall back to the old API to start a network query again via
Phone. Logic flow as below:
https://docs.google.com/drawings/d/1fhG6INoNW4tXUCuOrkMRmsPdBQ1wvH8-dfCmMz2-QbY/edit?usp=sharing

3. Using new scan API via TelephonyManager after HAL1_2 ready. The app
will first send network scan via new API and then receive a list of cell
info, which contains both signal strength and operator info. Logic flow as below:
https://docs.google.com/drawings/d/1D7Zj0iNs0g0Ur4cy3lbYxufH6Fu5nntPiRZDM3sYHg0/edit?usp=sharing

Bug: 63984327
Test: Basic telephony sanity
Change-Id: Ifebbac9965a40acaff0d50d32ca8603c72a6a77f
Merged-In: Ifebbac9965a40acaff0d50d32ca8603c72a6a77f
diff --git a/src/com/android/phone/INetworkQueryService.aidl b/src/com/android/phone/INetworkQueryService.aidl
index b0fe992..f65c971 100644
--- a/src/com/android/phone/INetworkQueryService.aidl
+++ b/src/com/android/phone/INetworkQueryService.aidl
@@ -31,8 +31,14 @@
      * object on query completion.  If there is an existing request,
      * then just add the callback to the list of notifications
      * that will be sent upon query completion.
+     *
+     * It will send the network query with the use of
+     * <code>TelephonyManager.requestNetworkScan()</code> if the
+     * isIncrementalResult is true. And if the isIncrementalResult
+     * is set as false, it will try to send network query through
+     * <code>Phone.getAvailableNetworks()</code>.
      */
-    void startNetworkQuery(in INetworkQueryServiceCallback cb, in int phoneId);
+    void startNetworkQuery(in INetworkQueryServiceCallback cb, in int phoneId, boolean isIncrementalResult);
 
     /**
      * Tells the service that the requested query is to be ignored.
@@ -40,7 +46,7 @@
      * underlying RIL, but it ensures that the callback is removed
      * from the list of notifications.
      */
-    void stopNetworkQuery(in INetworkQueryServiceCallback cb);
+    void stopNetworkQuery();
 
     /**
      * Tells the service to unregister the network query callback.
diff --git a/src/com/android/phone/INetworkQueryServiceCallback.aidl b/src/com/android/phone/INetworkQueryServiceCallback.aidl
index 4c32883..2299f5e 100644
--- a/src/com/android/phone/INetworkQueryServiceCallback.aidl
+++ b/src/com/android/phone/INetworkQueryServiceCallback.aidl
@@ -16,6 +16,7 @@
 
 package com.android.phone;
 
+import android.telephony.CellInfo;
 import com.android.internal.telephony.OperatorInfo;
 
 /**
@@ -26,14 +27,25 @@
 oneway interface INetworkQueryServiceCallback {
 
     /**
-     * Called upon query completion, handing a status value and an
-     * array of OperatorInfo objects.
-     *
-     * @param networkInfoArray is the list of OperatorInfo. Can be
-     * null, indicating no results were found, or an error.
-     * @param status the status indicating if there were any
-     * problems with the request.
+     * Returns the scan results to the user, this callback will be
+     * called at least one time.
      */
-    void onQueryComplete(in List<OperatorInfo> networkInfoArray, int status);
+    void onResults(in List<CellInfo> results);
+
+    /**
+     * Informs the user that the scan has stopped.
+     *
+     * This callback will be called when the scan is finished or cancelled by the user.
+     * The related NetworkScanRequest will be deleted after this callback.
+     */
+    void onComplete();
+
+    /**
+     * Informs the user that there is some error about the scan.
+     *
+     * This callback will be called whenever there is any error about the scan,
+     * and the scan will be terminated. onComplete() will NOT be called.
+     */
+    void onError(int error);
 
 }
diff --git a/src/com/android/phone/NetworkQueryService.java b/src/com/android/phone/NetworkQueryService.java
index 84fde87..86f4b11 100644
--- a/src/com/android/phone/NetworkQueryService.java
+++ b/src/com/android/phone/NetworkQueryService.java
@@ -19,7 +19,6 @@
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
-import com.android.internal.telephony.OperatorInfo;
 import android.os.AsyncResult;
 import android.os.Binder;
 import android.os.Handler;
@@ -27,12 +26,23 @@
 import android.os.Message;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
-import android.telephony.SubscriptionManager;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneFactory;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.CellIdentityGsm;
+import android.telephony.CellInfo;
+import android.telephony.CellInfoGsm;
+import android.telephony.NetworkScan;
+import android.telephony.NetworkScanRequest;
+import android.telephony.RadioAccessSpecifier;
+import android.telephony.TelephonyManager;
+import android.telephony.TelephonyScanManager;
 import android.util.Log;
 
+import com.android.internal.telephony.OperatorInfo;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Service code used to assist in querying the network for service
@@ -44,7 +54,10 @@
     private static final boolean DBG = true;
 
     // static events
-    private static final int EVENT_NETWORK_SCAN_COMPLETED = 100;
+    private static final int EVENT_NETWORK_SCAN_VIA_PHONE_COMPLETED = 100;
+    private static final int EVENT_NETWORK_SCAN_RESULTS = 200;
+    private static final int EVENT_NETWORK_SCAN_ERROR = 300;
+    private static final int EVENT_NETWORK_SCAN_COMPLETED = 400;
 
     // static states indicating the query status of the service
     private static final int QUERY_READY = -1;
@@ -55,10 +68,20 @@
     public static final int QUERY_EXCEPTION = 1;
 
     static final String ACTION_LOCAL_BINDER = "com.android.phone.intent.action.LOCAL_BINDER";
-    
+
     /** state of the query service */
     private int mState;
-    
+
+    private NetworkScan mNetworkScan;
+
+    // NetworkScanRequest parameters
+    private static final int SCAN_TYPE = NetworkScanRequest.SCAN_TYPE_ONE_SHOT;
+    private static final boolean INCREMENTAL_RESULTS = true;
+    // The parameters below are in seconds
+    private static final int SEARCH_PERIODICITY_SEC = 5;
+    private static final int MAX_SEARCH_TIME_SEC = 60;
+    private static final int INCREMENTAL_RESULTS_PERIODICITY_SEC = 3;
+
     /**
      * Class for clients to access.  Because we know this service always
      * runs in the same process as its clients, we don't need to deal with
@@ -81,57 +104,150 @@
             switch (msg.what) {
                 // if the scan is complete, broadcast the results.
                 // to all registerd callbacks.
+                case EVENT_NETWORK_SCAN_VIA_PHONE_COMPLETED:
+                    if (DBG) log("scan via Phone completed, broadcasting results");
+                    broadcastQueryResults(msg);
+                    break;
+
+                case EVENT_NETWORK_SCAN_RESULTS:
+                    if (DBG) log("get scan results, broadcasting results");
+                    broadcastQueryResults(msg);
+                    break;
+
+                case EVENT_NETWORK_SCAN_ERROR:
+                    if (DBG) log("get scan error, broadcasting error code");
+                    broadcastQueryResults(msg);
+                    break;
+
                 case EVENT_NETWORK_SCAN_COMPLETED:
-                    if (DBG) log("scan completed, broadcasting results");
-                    broadcastQueryResults((AsyncResult) msg.obj);
+                    if (DBG) log("network scan or stop network query completed");
+                    broadcastQueryResults(msg);
                     break;
             }
         }
     };
-    
-    /** 
+
+    /**
      * List of callback objects, also used to synchronize access to 
      * itself and to changes in state.
      */
     final RemoteCallbackList<INetworkQueryServiceCallback> mCallbacks =
-        new RemoteCallbackList<INetworkQueryServiceCallback> ();
-    
+            new RemoteCallbackList<INetworkQueryServiceCallback>();
+
+    /**
+     * This implementation of NetworkScanCallbackImpl is used to receive callback notifications from
+     * the Telephony Manager.
+     */
+    public class NetworkScanCallbackImpl extends TelephonyScanManager.NetworkScanCallback {
+
+        /** Returns the scan results to the user, this callback will be called at least one time. */
+        public void onResults(List<CellInfo> results) {
+            if (DBG) log("got network scan results: " + results.size());
+            Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_RESULTS, results);
+            msg.sendToTarget();
+        }
+
+        /**
+         * Informs the user that the scan has stopped.
+         *
+         * This callback will be called when the scan is finished or cancelled by the user.
+         * The related NetworkScanRequest will be deleted after this callback.
+         */
+        public void onComplete() {
+            if (DBG) log("network scan completed");
+            Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED);
+            msg.sendToTarget();
+        }
+
+        /**
+         * Informs the user that there is some error about the scan.
+         *
+         * This callback will be called whenever there is any error about the scan, and the scan
+         * will be terminated. onComplete() will NOT be called.
+         */
+        public void onError(int error) {
+            if (DBG) log("network scan got error: " + error);
+            Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_ERROR, error, 0 /* arg2 */);
+            msg.sendToTarget();
+        }
+    }
+
     /**
      * Implementation of the INetworkQueryService interface.
      */
     private final INetworkQueryService.Stub mBinder = new INetworkQueryService.Stub() {
-        
+
         /**
          * Starts a query with a INetworkQueryServiceCallback object if
          * one has not been started yet.  Ignore the new query request
          * if the query has been started already.  Either way, place the
-         * callback object in the queue to be notified upon request 
+         * callback object in the queue to be notified upon request
          * completion.
          */
-        public void startNetworkQuery(INetworkQueryServiceCallback cb, int phoneId) {
+        public void startNetworkQuery(
+                INetworkQueryServiceCallback cb, int phoneId, boolean isIncrementalResult) {
             if (cb != null) {
                 // register the callback to the list of callbacks.
                 synchronized (mCallbacks) {
                     mCallbacks.register(cb);
                     if (DBG) log("registering callback " + cb.getClass().toString());
-                    
+
                     switch (mState) {
                         case QUERY_READY:
-                            // TODO: we may want to install a timeout here in case we
-                            // do not get a timely response from the RIL.
-                            Phone phone = PhoneFactory.getPhone(phoneId);
-                            if (phone != null) {
-                                phone.getAvailableNetworks(
-                                        mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED));
+
+                            if (isIncrementalResult) {
+                                if (DBG) log("start network scan via TelephonManager");
+                                TelephonyManager tm = (TelephonyManager) getSystemService(
+                                        Context.TELEPHONY_SERVICE);
+                                // The Radio Access Specifiers below are meant to scan
+                                // all the bands for all the supported technologies.
+                                RadioAccessSpecifier gsm = new RadioAccessSpecifier(
+                                        AccessNetworkConstants.AccessNetworkType.GERAN,
+                                        null /* bands */,
+                                        null /* channels */);
+                                RadioAccessSpecifier lte = new RadioAccessSpecifier(
+                                        AccessNetworkConstants.AccessNetworkType.EUTRAN,
+                                        null /* bands */,
+                                        null /* channels */);
+                                RadioAccessSpecifier wcdma = new RadioAccessSpecifier(
+                                        AccessNetworkConstants.AccessNetworkType.UTRAN,
+                                        null /* bands */,
+                                        null /* channels */);
+                                RadioAccessSpecifier[] radioAccessSpecifier = {gsm, lte, wcdma};
+                                NetworkScanRequest networkScanRequest = new NetworkScanRequest(
+                                        SCAN_TYPE,
+                                        radioAccessSpecifier,
+                                        SEARCH_PERIODICITY_SEC,
+                                        MAX_SEARCH_TIME_SEC,
+                                        INCREMENTAL_RESULTS,
+                                        INCREMENTAL_RESULTS_PERIODICITY_SEC,
+                                        null /* List of PLMN ids (MCC-MNC) */);
+
+                                // Construct a NetworkScanCallback
+                                NetworkQueryService.NetworkScanCallbackImpl
+                                        networkScanCallback =
+                                        new NetworkQueryService.NetworkScanCallbackImpl();
+
+                                // Request network scan
+                                mNetworkScan = tm.requestNetworkScan(networkScanRequest,
+                                        networkScanCallback);
                                 mState = QUERY_IS_RUNNING;
-                                if (DBG) log("starting new query");
                             } else {
-                                if (DBG) {
-                                    log("phone is null");
+                                Phone phone = PhoneFactory.getPhone(phoneId);
+                                if (phone != null) {
+                                    phone.getAvailableNetworks(
+                                            mHandler.obtainMessage(
+                                                    EVENT_NETWORK_SCAN_VIA_PHONE_COMPLETED));
+                                    mState = QUERY_IS_RUNNING;
+                                    if (DBG) log("start network scan via Phone");
+                                } else {
+                                    if (DBG) {
+                                        log("phone is null");
+                                    }
                                 }
                             }
+
                             break;
-                            
                         // do nothing if we're currently busy.
                         case QUERY_IS_RUNNING:
                             if (DBG) log("query already in progress");
@@ -141,19 +257,24 @@
                 }
             }
         }
-        
+
         /**
          * Stops a query with a INetworkQueryServiceCallback object as
          * a token.
          */
-        public void stopNetworkQuery(INetworkQueryServiceCallback cb) {
-            // currently we just unregister the callback, since there is 
-            // no way to tell the RIL to terminate the query request.  
-            // This means that the RIL may still be busy after the stop 
-            // request was made, but the state tracking logic ensures
-            // that the delay will only last for 1 request even with
-            // repeated button presses in the NetworkSetting activity.
-            unregisterCallback(cb);
+        public void stopNetworkQuery() {
+            if (DBG) log("stop network query");
+            // Tells the RIL to terminate the query request.
+            if (mNetworkScan != null) {
+                try {
+                    mNetworkScan.stop();
+                    mState = QUERY_READY;
+                } catch (RemoteException e) {
+                    if (DBG) log("stop mNetworkScan failed");
+                } catch (IllegalArgumentException e) {
+                    // Do nothing, scan has already completed.
+                }
+            }
         }
 
         /**
@@ -171,7 +292,7 @@
 
     @Override
     public void onCreate() {
-        mState = QUERY_READY;        
+        mState = QUERY_READY;
     }
 
     /**
@@ -180,7 +301,7 @@
     @Override
     public void onStart(Intent intent, int startId) {
     }
-    
+
     /**
      * Handle the bind request.
      */
@@ -198,38 +319,85 @@
      * Broadcast the results from the query to all registered callback
      * objects. 
      */
-    private void broadcastQueryResults (AsyncResult ar) {
+    private void broadcastQueryResults(Message msg) {
         // reset the state.
         synchronized (mCallbacks) {
             mState = QUERY_READY;
-            
-            // see if we need to do any work.
-            if (ar == null) {
-                if (DBG) log("AsyncResult is null.");
-                return;
-            }
-    
-            // TODO: we may need greater accuracy here, but for now, just a
-            // simple status integer will suffice.
-            int exception = (ar.exception == null) ? QUERY_OK : QUERY_EXCEPTION;
-            if (DBG) log("AsyncResult has exception " + exception);
-            
+
             // Make the calls to all the registered callbacks.
             for (int i = (mCallbacks.beginBroadcast() - 1); i >= 0; i--) {
-                INetworkQueryServiceCallback cb = mCallbacks.getBroadcastItem(i); 
+                INetworkQueryServiceCallback cb = mCallbacks.getBroadcastItem(i);
                 if (DBG) log("broadcasting results to " + cb.getClass().toString());
                 try {
-                    cb.onQueryComplete((ArrayList<OperatorInfo>) ar.result, exception);
+                    switch (msg.what) {
+                        case EVENT_NETWORK_SCAN_VIA_PHONE_COMPLETED:
+                            AsyncResult ar = (AsyncResult) msg.obj;
+                            if (ar != null) {
+                                cb.onResults(getCellInfoList((List<OperatorInfo>) ar.result));
+                            } else {
+                                if (DBG) log("AsyncResult is null.");
+                            }
+                            // Send the onComplete() callback to indicate the one-time network
+                            // scan has completed.
+                            cb.onComplete();
+                            break;
+
+                        case EVENT_NETWORK_SCAN_RESULTS:
+                            cb.onResults((List<CellInfo>) msg.obj);
+                            break;
+
+                        case EVENT_NETWORK_SCAN_COMPLETED:
+                            cb.onComplete();
+                            break;
+
+                        case EVENT_NETWORK_SCAN_ERROR:
+                            cb.onError(msg.arg1);
+                            break;
+                    }
                 } catch (RemoteException e) {
                 }
             }
-            
+
             // finish up.
             mCallbacks.finishBroadcast();
         }
     }
-    
+
+    /**
+     * Wraps up a list of OperatorInfo object to a list of CellInfo object. GsmCellInfo is used here
+     * only because operatorInfo does not contain technology type while CellInfo is an abstract
+     * object that requires to specify technology type. It doesn't matter which CellInfo type to
+     * use here, since we only want to wrap the operator info and PLMN to a CellInfo object.
+     */
+    private List<CellInfo> getCellInfoList(List<OperatorInfo> operatorInfoList) {
+        List<CellInfo> cellInfoList = new ArrayList<>();
+        for (OperatorInfo oi: operatorInfoList) {
+            String operatorNumeric = oi.getOperatorNumeric();
+            String mcc = null;
+            String mnc = null;
+            log("operatorNumeric: " + operatorNumeric);
+            if (operatorNumeric != null && operatorNumeric.matches("^[0-9]{5,6}$")) {
+                mcc = operatorNumeric.substring(0, 3);
+                mnc = operatorNumeric.substring(3);
+            }
+            CellIdentityGsm cig = new CellIdentityGsm(
+                    Integer.MAX_VALUE /* lac */,
+                    Integer.MAX_VALUE /* cid */,
+                    Integer.MAX_VALUE /* arfcn */,
+                    Integer.MAX_VALUE /* bsic */,
+                    mcc,
+                    mnc,
+                    oi.getOperatorAlphaLong(),
+                    oi.getOperatorAlphaShort());
+
+            CellInfoGsm ci = new CellInfoGsm();
+            ci.setCellIdentity(cig);
+            cellInfoList.add(ci);
+        }
+        return cellInfoList;
+    }
+
     private static void log(String msg) {
         Log.d(LOG_TAG, msg);
-    }    
-}
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/phone/NetworkSelectListPreference.java b/src/com/android/phone/NetworkSelectListPreference.java
index 1af31b2..cc54b09 100644
--- a/src/com/android/phone/NetworkSelectListPreference.java
+++ b/src/com/android/phone/NetworkSelectListPreference.java
@@ -28,6 +28,16 @@
 import android.os.RemoteException;
 import android.preference.ListPreference;
 import android.preference.Preference;
+import android.telephony.CellInfo;
+import android.telephony.CellInfoCdma;
+import android.telephony.CellInfoGsm;
+import android.telephony.CellInfoLte;
+import android.telephony.CellInfoWcdma;
+import android.telephony.CellSignalStrengthCdma;
+import android.telephony.CellSignalStrengthGsm;
+import android.telephony.CellSignalStrengthLte;
+import android.telephony.CellSignalStrengthWcdma;
+import android.telephony.NetworkScan;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.BidiFormatter;
@@ -42,6 +52,7 @@
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 
+import java.util.ArrayList;
 import java.util.List;
 
 
@@ -57,19 +68,22 @@
     private static final String LOG_TAG = "networkSelect";
     private static final boolean DBG = true;
 
-    private static final int EVENT_NETWORK_SCAN_COMPLETED = 100;
-    private static final int EVENT_NETWORK_SELECTION_DONE = 200;
+    private static final int EVENT_NETWORK_SELECTION_DONE = 1;
+    private static final int EVENT_NETWORK_SCAN_RESULTS = 2;
+    private static final int EVENT_NETWORK_SCAN_ERROR = 3;
+    private static final int EVENT_NETWORK_SCAN_COMPLETED = 4;
 
     //dialog ids
     private static final int DIALOG_NETWORK_SELECTION = 100;
     private static final int DIALOG_NETWORK_LIST_LOAD = 200;
 
     private int mPhoneId = SubscriptionManager.INVALID_PHONE_INDEX;
-    private List<OperatorInfo> mOperatorInfoList;
-    private OperatorInfo mOperatorInfo;
+    private List<CellInfo> mCellInfoList;
+    private CellInfo mCellInfo;
 
     private int mSubId;
     private NetworkOperators mNetworkOperators;
+    private boolean mNeedScanAgain;
 
     private ProgressDialog mProgressDialog;
     public NetworkSelectListPreference(Context context, AttributeSet attrs) {
@@ -77,14 +91,15 @@
     }
 
     public NetworkSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
+                                       int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
     @Override
     protected void onClick() {
         sendMetricsEvent(null);
-        loadNetworksList();
+        // Scan the network with setting the isIncrementalResult as true via TelephonyManager first.
+        loadNetworksList(true);
     }
 
     private final Handler mHandler = new Handler() {
@@ -92,10 +107,6 @@
         public void handleMessage(Message msg) {
             AsyncResult ar;
             switch (msg.what) {
-                case EVENT_NETWORK_SCAN_COMPLETED:
-                    networksListLoaded((List<OperatorInfo>) msg.obj, msg.arg1);
-                    break;
-
                 case EVENT_NETWORK_SELECTION_DONE:
                     if (DBG) logd("hideProgressPanel");
                     try {
@@ -110,15 +121,98 @@
                         mNetworkOperators.displayNetworkSelectionFailed(ar.exception);
                     } else {
                         if (DBG) {
-                            logd("manual network selection: succeeded!"
-                                    + getNetworkTitle(mOperatorInfo));
+                            logd("manual network selection: succeeded! "
+                                    + getNetworkTitle(mCellInfo));
                         }
                         mNetworkOperators.displayNetworkSelectionSucceeded();
                     }
                     mNetworkOperators.getNetworkSelectionMode();
                     break;
-            }
 
+                case EVENT_NETWORK_SCAN_RESULTS:
+                    List<CellInfo> results = (List<CellInfo>) msg.obj;
+                    results.removeIf(cellInfo -> cellInfo == null);
+                    if (results.size() > 0) {
+                        boolean isInvalidCellInfoList = true;
+                        // Regard the list as invalid only if all the elements in the list are
+                        // invalid.
+                        for (CellInfo cellInfo : results) {
+                            if (!isInvalidCellInfo(cellInfo)) {
+                                isInvalidCellInfoList = false;
+                                break;
+                            }
+                        }
+                        if (isInvalidCellInfoList) {
+                            mNeedScanAgain = true;
+                            if (DBG) {
+                                logd("Invalid cell info. Stop current network scan "
+                                        + "and start a new one via old API");
+                            }
+                            // Stop current network scan flow. This behavior will result in a
+                            // onComplete() callback, after which we will start a new network query
+                            // via Phone.getAvailableNetworks(). This behavior might also result in
+                            // a onError() callback if the modem did not stop network query
+                            // successfully. In this case we will display network query failed
+                            // instead of resending a new request.
+                            try {
+                                if (mNetworkQueryService != null) {
+                                    mNetworkQueryService.stopNetworkQuery();
+                                }
+                            } catch (RemoteException e) {
+                                loge("exception from stopNetworkQuery " + e);
+                            }
+                        } else {
+                            // TODO(b/70530820): Display the scan results incrementally after
+                            // finalizing the UI desing on Mobile Network Setting page. For now,
+                            // just update the CellInfo list when received the onResult callback,
+                            // and display the scan result when received the onComplete callback
+                            // in the end.
+                            mCellInfoList = new ArrayList<>(results);
+                            if (DBG) logd("CALLBACK_SCAN_RESULTS" + mCellInfoList.toString());
+                        }
+                    }
+
+                    break;
+
+                case EVENT_NETWORK_SCAN_ERROR:
+                    int error = msg.arg1;
+                    if (DBG) logd("error while querying available networks " + error);
+                    if (error == NetworkScan.ERROR_UNSUPPORTED) {
+                        if (DBG) {
+                            logd("Modem does not support: try to scan network again via Phone");
+                        }
+                        loadNetworksList(false);
+                    } else {
+                        try {
+                            if (mNetworkQueryService != null) {
+                                mNetworkQueryService.unregisterCallback(mCallback);
+                            }
+                        } catch (RemoteException e) {
+                            loge("onError: exception from unregisterCallback " + e);
+                        }
+                        displayNetworkQueryFailed(error);
+                    }
+                    break;
+
+                case EVENT_NETWORK_SCAN_COMPLETED:
+                    if (mNeedScanAgain) {
+                        logd("CellInfo is invalid to display. Start a new scan via Phone. ");
+                        loadNetworksList(false);
+                        mNeedScanAgain = false;
+                    } else {
+                        try {
+                            if (mNetworkQueryService != null) {
+                                mNetworkQueryService.unregisterCallback(mCallback);
+                            }
+                        } catch (RemoteException e) {
+                            loge("onComplete: exception from unregisterCallback " + e);
+                        }
+                        if (DBG) logd("scan complete, load the cellInfosList");
+                        // Modify UI to indicate users that the scan has completed.
+                        networksListLoaded();
+                    }
+                    break;
+            }
             return;
         }
     };
@@ -130,11 +224,34 @@
      */
     private final INetworkQueryServiceCallback mCallback = new INetworkQueryServiceCallback.Stub() {
 
-        /** place the message on the looper queue upon query completion. */
-        public void onQueryComplete(List<OperatorInfo> networkInfoArray, int status) {
-            if (DBG) logd("notifying message loop of query completion.");
-            Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED,
-                    status, 0, networkInfoArray);
+        /** Returns the scan results to the user, this callback will be called at lease one time. */
+        public void onResults(List<CellInfo> results) {
+            if (DBG) logd("get scan results.");
+            Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_RESULTS, results);
+            msg.sendToTarget();
+        }
+
+        /**
+         * Informs the user that the scan has stopped.
+         *
+         * This callback will be called when the scan is finished or cancelled by the user.
+         * The related NetworkScanRequest will be deleted after this callback.
+         */
+        public void onComplete() {
+            if (DBG) logd("network scan completed.");
+            Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED);
+            msg.sendToTarget();
+        }
+
+        /**
+         * Informs the user that there is some error about the scan.
+         *
+         * This callback will be called whenever there is any error about the scan, and the scan
+         * will be terminated. onComplete() will NOT be called.
+         */
+        public void onError(int error) {
+            if (DBG) logd("get onError callback with error code: " + error);
+            Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_ERROR, error, 0 /* arg2 */);
             msg.sendToTarget();
         }
     };
@@ -142,10 +259,12 @@
     @Override
     //implemented for DialogInterface.OnCancelListener
     public void onCancel(DialogInterface dialog) {
+        if (DBG) logd("user manually close the dialog");
         // request that the service stop the query with this callback object.
         try {
             if (mNetworkQueryService != null) {
-                mNetworkQueryService.stopNetworkQuery(mCallback);
+                mNetworkQueryService.stopNetworkQuery();
+                mNetworkQueryService.unregisterCallback(mCallback);
             }
             // If cancelled, we query NetworkSelectMode and update states of AutoSelect button.
             mNetworkOperators.getNetworkSelectionMode();
@@ -164,18 +283,6 @@
         }
     }
 
-    /**
-     * Return normalized carrier name given network info.
-     *
-     * @param ni is network information in OperatorInfo type.
-     */
-    public String getNormalizedCarrierName(OperatorInfo ni) {
-        if (ni != null) {
-            return ni.getOperatorAlphaLong() + " (" + ni.getOperatorNumeric() + ")";
-        }
-        return null;
-    }
-
     // This method is provided besides initialize() because bind to network query service
     // may be binded after initialize(). In that case this method needs to be called explicitly
     // to set mNetworkQueryService. Otherwise mNetworkQueryService will remain null.
@@ -185,12 +292,13 @@
 
     // This initialize method needs to be called for this preference to work properly.
     protected void initialize(int subId, INetworkQueryService queryService,
-            NetworkOperators networkOperators, ProgressDialog progressDialog) {
+                              NetworkOperators networkOperators, ProgressDialog progressDialog) {
         mSubId = subId;
         mNetworkQueryService = queryService;
         mNetworkOperators = networkOperators;
         // This preference should share the same progressDialog with networkOperators category.
         mProgressDialog = progressDialog;
+        mNeedScanAgain = false;
 
         if (SubscriptionManager.isValidSubscriptionId(mSubId)) {
             mPhoneId = SubscriptionManager.getPhoneId(mSubId);
@@ -236,7 +344,7 @@
     }
 
     private void displayNetworkSelectionInProgress() {
-        showProgressBar(DIALOG_NETWORK_SELECTION);
+        showProgressDialog(DIALOG_NETWORK_SELECTION);
     }
 
     private void displayNetworkQueryFailed(int error) {
@@ -253,15 +361,17 @@
                 NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status);
     }
 
-    private void loadNetworksList() {
+    private void loadNetworksList(boolean isIncrementalResult) {
         if (DBG) logd("load networks list...");
 
-        showProgressBar(DIALOG_NETWORK_LIST_LOAD);
+        if (!mNeedScanAgain) {
+            // Avoid blinking while showing the dialog again.
+            showProgressDialog(DIALOG_NETWORK_LIST_LOAD);
+        }
 
-        // delegate query request to the service.
         try {
             if (mNetworkQueryService != null) {
-                mNetworkQueryService.startNetworkQuery(mCallback, mPhoneId);
+                mNetworkQueryService.startNetworkQuery(mCallback, mPhoneId, isIncrementalResult);
             } else {
                 displayNetworkQueryFailed(NetworkQueryService.QUERY_EXCEPTION);
             }
@@ -271,25 +381,9 @@
         }
     }
 
-    /**
-     * networksListLoaded has been rewritten to take an array of
-     * OperatorInfo objects and a status field, instead of an
-     * AsyncResult.  Otherwise, the functionality which takes the
-     * OperatorInfo array and creates a list of preferences from it,
-     * remains unchanged.
-     */
-    private void networksListLoaded(List<OperatorInfo> result, int status) {
+    private void networksListLoaded() {
         if (DBG) logd("networks list loaded");
 
-        // used to un-register callback
-        try {
-            if (mNetworkQueryService != null) {
-                mNetworkQueryService.unregisterCallback(mCallback);
-            }
-        } catch (RemoteException e) {
-            loge("networksListLoaded: exception from unregisterCallback " + e);
-        }
-
         // update the state of the preferences.
         if (DBG) logd("hideProgressPanel");
 
@@ -305,62 +399,28 @@
         }
 
         setEnabled(true);
-        clearList();
 
-        if (status != NetworkQueryService.QUERY_OK) {
-            if (DBG) logd("error while querying available networks");
-            displayNetworkQueryFailed(status);
-        } else {
-            if (result != null) {
-                // create a preference for each item in the list.
-                // just use the operator name instead of the mildly
-                // confusing mcc/mnc.
-                mOperatorInfoList = result;
-                CharSequence[] networkEntries = new CharSequence[result.size()];
-                CharSequence[] networkEntryValues = new CharSequence[result.size()];
-                for (int i = 0; i < mOperatorInfoList.size(); i++) {
-                    if (mOperatorInfoList.get(i).getState() == OperatorInfo.State.FORBIDDEN) {
-                        networkEntries[i] = getNetworkTitle(mOperatorInfoList.get(i))
-                            + " "
-                            + getContext().getResources().getString(R.string.forbidden_network);
-                    } else {
-                        networkEntries[i] = getNetworkTitle(mOperatorInfoList.get(i));
-                    }
-                    networkEntryValues[i] = Integer.toString(i + 2);
+        if (mCellInfoList != null) {
+            // create a preference for each item in the list.
+            // just use the operator name instead of the mildly
+            // confusing mcc/mnc.
+            List<CharSequence> networkEntriesList = new ArrayList<>();
+            List<CharSequence> networkEntryValuesList = new ArrayList<>();
+            for (CellInfo cellInfo: mCellInfoList) {
+                // Display each operator name only once.
+                String networkTitle = getNetworkTitle(cellInfo);
+                if (!networkEntriesList.contains(networkTitle)) {
+                    networkEntriesList.add(networkTitle);
+                    networkEntryValuesList.add(Integer.toString(networkEntriesList.size() + 1));
                 }
-
-                setEntries(networkEntries);
-                setEntryValues(networkEntryValues);
-
-                super.onClick();
-            } else {
-                displayEmptyNetworkList();
             }
-        }
-    }
+            setEntries(networkEntriesList.toArray(new CharSequence[networkEntriesList.size()]));
+            setEntryValues(networkEntryValuesList.toArray(
+                    new CharSequence[networkEntryValuesList.size()]));
 
-    /**
-     * Returns the title of the network obtained in the manual search.
-     *
-     * @param ni contains the information of the network.
-     *
-     * @return Long Name if not null/empty, otherwise Short Name if not null/empty,
-     * else MCCMNC string.
-     */
-    private String getNetworkTitle(OperatorInfo ni) {
-        if (!TextUtils.isEmpty(ni.getOperatorAlphaLong())) {
-            return ni.getOperatorAlphaLong();
-        } else if (!TextUtils.isEmpty(ni.getOperatorAlphaShort())) {
-            return ni.getOperatorAlphaShort();
+            super.onClick();
         } else {
-            BidiFormatter bidiFormatter = BidiFormatter.getInstance();
-            return bidiFormatter.unicodeWrap(ni.getOperatorNumeric(), TextDirectionHeuristics.LTR);
-        }
-    }
-
-    private void clearList() {
-        if (mOperatorInfoList != null) {
-            mOperatorInfoList.clear();
+            displayEmptyNetworkList();
         }
     }
 
@@ -370,7 +430,7 @@
         }
     }
 
-    private void showProgressBar(int id) {
+    private void showProgressDialog(int id) {
         if (mProgressDialog == null) {
             mProgressDialog = new ProgressDialog(getContext());
         } else {
@@ -378,29 +438,27 @@
             dismissProgressBar();
         }
 
-        if ((id == DIALOG_NETWORK_SELECTION) || (id == DIALOG_NETWORK_LIST_LOAD)) {
-            switch (id) {
-                case DIALOG_NETWORK_SELECTION:
-                    final String networkSelectMsg = getContext().getResources()
-                            .getString(R.string.register_on_network,
-                                    getNetworkTitle(mOperatorInfo));
-                    mProgressDialog.setMessage(networkSelectMsg);
-                    mProgressDialog.setCanceledOnTouchOutside(false);
-                    mProgressDialog.setCancelable(false);
-                    mProgressDialog.setIndeterminate(true);
-                    break;
-                case DIALOG_NETWORK_LIST_LOAD:
-                    mProgressDialog.setMessage(
-                            getContext().getResources().getString(R.string.load_networks_progress));
-                    mProgressDialog.setCanceledOnTouchOutside(false);
-                    mProgressDialog.setCancelable(true);
-                    mProgressDialog.setIndeterminate(false);
-                    mProgressDialog.setOnCancelListener(this);
-                    break;
-                default:
-            }
-            mProgressDialog.show();
+        switch (id) {
+            case DIALOG_NETWORK_SELECTION:
+                final String networkSelectMsg = getContext().getResources()
+                        .getString(R.string.register_on_network,
+                                getNetworkTitle(mCellInfo));
+                mProgressDialog.setMessage(networkSelectMsg);
+                mProgressDialog.setCanceledOnTouchOutside(false);
+                mProgressDialog.setCancelable(false);
+                mProgressDialog.setIndeterminate(true);
+                break;
+            case DIALOG_NETWORK_LIST_LOAD:
+                mProgressDialog.setMessage(
+                        getContext().getResources().getString(R.string.load_networks_progress));
+                mProgressDialog.setCanceledOnTouchOutside(false);
+                mProgressDialog.setCancelable(true);
+                mProgressDialog.setIndeterminate(false);
+                mProgressDialog.setOnCancelListener(this);
+                break;
+            default:
         }
+        mProgressDialog.show();
     }
 
     /**
@@ -413,24 +471,119 @@
     @Override
     public boolean onPreferenceChange(Preference preference, Object newValue) {
         int operatorIndex = findIndexOfValue((String) newValue);
-        mOperatorInfo = mOperatorInfoList.get(operatorIndex);
+        mCellInfo = mCellInfoList.get(operatorIndex);
+        if (DBG) logd("selected network: " + mCellInfo.toString());
 
-        if (DBG) logd("selected network: " + getNetworkTitle(mOperatorInfo));
-
-        sendMetricsEvent(getNetworkTitle(mOperatorInfo));
+        sendMetricsEvent(getNetworkTitle(mCellInfo));
 
         Message msg = mHandler.obtainMessage(EVENT_NETWORK_SELECTION_DONE);
         Phone phone = PhoneFactory.getPhone(mPhoneId);
         if (phone != null) {
-            phone.selectNetworkManually(mOperatorInfo, true, msg);
+            OperatorInfo operatorInfo = getOperatorInfoFromCellInfo(mCellInfo);
+            if (DBG) logd("manually selected network: " + operatorInfo.toString());
+            phone.selectNetworkManually(operatorInfo, true, msg);
             displayNetworkSelectionInProgress();
         } else {
             loge("Error selecting network. phone is null.");
         }
-
         return true;
     }
 
+    /**
+     * Returns the title of the network obtained in the manual search.
+     *
+     * @param cellInfo contains the information of the network.
+     * @return Long Name if not null/empty, otherwise Short Name if not null/empty,
+     * else MCCMNC string.
+     */
+    private String getNetworkTitle(CellInfo cellInfo) {
+        OperatorInfo ni = getOperatorInfoFromCellInfo(cellInfo);
+
+        if (!TextUtils.isEmpty(ni.getOperatorAlphaLong())) {
+            return ni.getOperatorAlphaLong();
+        } else if (!TextUtils.isEmpty(ni.getOperatorAlphaShort())) {
+            return ni.getOperatorAlphaShort();
+        } else {
+            BidiFormatter bidiFormatter = BidiFormatter.getInstance();
+            return bidiFormatter.unicodeWrap(ni.getOperatorNumeric(), TextDirectionHeuristics.LTR);
+        }
+    }
+
+    /**
+     * Wrap a cell info into an operator info.
+     */
+    private OperatorInfo getOperatorInfoFromCellInfo(CellInfo cellInfo) {
+        OperatorInfo oi;
+        if (cellInfo instanceof CellInfoLte) {
+            CellInfoLte lte = (CellInfoLte) cellInfo;
+            oi = new OperatorInfo(
+                    (String) lte.getCellIdentity().getOperatorAlphaLong(),
+                    (String) lte.getCellIdentity().getOperatorAlphaShort(),
+                    lte.getCellIdentity().getMobileNetworkOperator());
+        } else if (cellInfo instanceof CellInfoWcdma) {
+            CellInfoWcdma wcdma = (CellInfoWcdma) cellInfo;
+            oi = new OperatorInfo(
+                    (String) wcdma.getCellIdentity().getOperatorAlphaLong(),
+                    (String) wcdma.getCellIdentity().getOperatorAlphaShort(),
+                    wcdma.getCellIdentity().getMobileNetworkOperator());
+        } else if (cellInfo instanceof CellInfoGsm) {
+            CellInfoGsm gsm = (CellInfoGsm) cellInfo;
+            oi = new OperatorInfo(
+                    (String) gsm.getCellIdentity().getOperatorAlphaLong(),
+                    (String) gsm.getCellIdentity().getOperatorAlphaShort(),
+                    gsm.getCellIdentity().getMobileNetworkOperator());
+        } else if (cellInfo instanceof CellInfoCdma) {
+            CellInfoCdma cdma = (CellInfoCdma) cellInfo;
+            oi = new OperatorInfo(
+                    (String) cdma.getCellIdentity().getOperatorAlphaLong(),
+                    (String) cdma.getCellIdentity().getOperatorAlphaShort(),
+                    "" /* operator numeric */);
+        } else {
+            oi = new OperatorInfo("", "", "");
+        }
+        return oi;
+    }
+
+
+    /**
+     * Check if the CellInfo is valid to display. If a CellInfo has signal strength but does
+     * not have operator info, it is invalid to display.
+     */
+    private boolean isInvalidCellInfo(CellInfo cellInfo) {
+        if (DBG) logd("Check isInvalidCellInfo: " + cellInfo.toString());
+        CharSequence al = null;
+        CharSequence as = null;
+        boolean hasSignalStrength = false;
+        if (cellInfo instanceof CellInfoLte) {
+            CellInfoLte lte = (CellInfoLte) cellInfo;
+            al = lte.getCellIdentity().getOperatorAlphaLong();
+            as = lte.getCellIdentity().getOperatorAlphaShort();
+            hasSignalStrength = !lte.getCellSignalStrength().equals(new CellSignalStrengthLte());
+        } else if (cellInfo instanceof CellInfoWcdma) {
+            CellInfoWcdma wcdma = (CellInfoWcdma) cellInfo;
+            al = wcdma.getCellIdentity().getOperatorAlphaLong();
+            as = wcdma.getCellIdentity().getOperatorAlphaShort();
+            hasSignalStrength = !wcdma.getCellSignalStrength().equals(
+                    new CellSignalStrengthWcdma());
+        } else if (cellInfo instanceof CellInfoGsm) {
+            CellInfoGsm gsm = (CellInfoGsm) cellInfo;
+            al = gsm.getCellIdentity().getOperatorAlphaLong();
+            as = gsm.getCellIdentity().getOperatorAlphaShort();
+            hasSignalStrength = !gsm.getCellSignalStrength().equals(new CellSignalStrengthGsm());
+        } else if (cellInfo instanceof CellInfoCdma) {
+            CellInfoCdma cdma = (CellInfoCdma) cellInfo;
+            al = cdma.getCellIdentity().getOperatorAlphaLong();
+            as = cdma.getCellIdentity().getOperatorAlphaShort();
+            hasSignalStrength = !cdma.getCellSignalStrength().equals(new CellSignalStrengthCdma());
+        } else {
+            return true;
+        }
+        if (TextUtils.isEmpty(al) && TextUtils.isEmpty(as) && hasSignalStrength) {
+            return true;
+        }
+        return false;
+    }
+
     @Override
     protected Parcelable onSaveInstanceState() {
         final Parcelable superState = super.onSaveInstanceState();
@@ -442,7 +595,7 @@
         final SavedState myState = new SavedState(superState);
         myState.mDialogListEntries = getEntries();
         myState.mDialogListEntryValues = getEntryValues();
-        myState.mOperatorInfoList = mOperatorInfoList;
+        myState.mCellInfoList = mCellInfoList;
         return myState;
     }
 
@@ -462,8 +615,8 @@
         if (getEntryValues() == null && myState.mDialogListEntryValues != null) {
             setEntryValues(myState.mDialogListEntryValues);
         }
-        if (mOperatorInfoList == null && myState.mOperatorInfoList != null) {
-            mOperatorInfoList = myState.mOperatorInfoList;
+        if (mCellInfoList == null && myState.mCellInfoList != null) {
+            mCellInfoList = myState.mCellInfoList;
         }
 
         super.onRestoreInstanceState(myState.getSuperState());
@@ -478,14 +631,14 @@
     private static class SavedState extends BaseSavedState {
         CharSequence[] mDialogListEntries;
         CharSequence[] mDialogListEntryValues;
-        List<OperatorInfo> mOperatorInfoList;
+        List<CellInfo> mCellInfoList;
 
         SavedState(Parcel source) {
             super(source);
             final ClassLoader boot = Object.class.getClassLoader();
             mDialogListEntries = source.readCharSequenceArray();
             mDialogListEntryValues = source.readCharSequenceArray();
-            mOperatorInfoList = source.readParcelableList(mOperatorInfoList, boot);
+            mCellInfoList = source.readParcelableList(mCellInfoList, boot);
         }
 
         @Override
@@ -493,7 +646,7 @@
             super.writeToParcel(dest, flags);
             dest.writeCharSequenceArray(mDialogListEntries);
             dest.writeCharSequenceArray(mDialogListEntryValues);
-            dest.writeParcelableList(mOperatorInfoList, flags);
+            dest.writeParcelableList(mCellInfoList, flags);
         }
 
         SavedState(Parcelable superState) {
@@ -515,7 +668,7 @@
     private void sendMetricsEvent(String network) {
         final LogMaker logMaker =
                 new LogMaker(MetricsEvent.ACTION_MOBILE_NETWORK_MANUAL_SELECT_NETWORK)
-                .setType(MetricsEvent.TYPE_ACTION);
+                        .setType(MetricsEvent.TYPE_ACTION);
 
         if (network != null) {
             // Since operator list is loaded dynamically from modem, we cannot know which network
@@ -534,4 +687,4 @@
     private void loge(String msg) {
         Log.e(LOG_TAG, "[NetworksList] " + msg);
     }
-}
+}
\ No newline at end of file