Merge "[ManualNetworkSelect] Improve error handling"
diff --git a/src/com/android/settings/network/telephony/NetworkScanHelper.java b/src/com/android/settings/network/telephony/NetworkScanHelper.java
index c4839b6..0ec61e7 100644
--- a/src/com/android/settings/network/telephony/NetworkScanHelper.java
+++ b/src/com/android/settings/network/telephony/NetworkScanHelper.java
@@ -37,6 +37,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
+import java.util.concurrent.CancellationException;
 import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
@@ -177,6 +178,9 @@
 
                 @Override
                 public void onFailure(Throwable t) {
+                    if (t instanceof CancellationException) {
+                        return;
+                    }
                     int errCode = Integer.parseInt(t.getMessage());
                     onError(errCode);
                 }
@@ -184,10 +188,16 @@
             mExecutor.execute(new NetworkScanSyncTask(
                     mTelephonyManager, (SettableFuture) mNetworkScanFuture));
         } else if (type == NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS) {
+            if (mNetworkScanRequester != null) {
+                return;
+            }
             mNetworkScanRequester = mTelephonyManager.requestNetworkScan(
                     NETWORK_SCAN_REQUEST,
                     mExecutor,
                     mInternalNetworkScanCallback);
+            if (mNetworkScanRequester == null) {
+                onError(NetworkScan.ERROR_RADIO_INTERFACE_ERROR);
+            }
         }
     }
 
@@ -199,7 +209,7 @@
     public void stopNetworkQuery() {
         if (mNetworkScanRequester != null) {
             mNetworkScanRequester.stopScan();
-            mNetworkScanFuture = null;
+            mNetworkScanRequester = null;
         }
 
         if (mNetworkScanFuture != null) {
diff --git a/tests/robotests/src/com/android/settings/network/telephony/NetworkScanHelperTest.java b/tests/robotests/src/com/android/settings/network/telephony/NetworkScanHelperTest.java
new file mode 100644
index 0000000..1f8c16d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/network/telephony/NetworkScanHelperTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.network.telephony;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.telephony.CellInfo;
+import android.telephony.NetworkScan;
+import android.telephony.NetworkScanRequest;
+import android.telephony.TelephonyManager;
+import android.telephony.TelephonyScanManager;
+import com.android.internal.telephony.CellNetworkScanResult;
+import com.android.internal.telephony.OperatorInfo;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class NetworkScanHelperTest {
+
+    @Mock
+    private TelephonyManager mTelephonyManager;
+
+    @Mock
+    private CellNetworkScanResult mCellNetworkScanResult;
+
+    @Mock
+    private NetworkScanHelper.NetworkScanCallback mNetworkScanCallback;
+
+    private static final long THREAD_EXECUTION_TIMEOUT_MS = 3000L;
+
+    private ExecutorService mNetworkScanExecutor;
+    private NetworkScanHelper mNetworkScanHelper;
+
+    private static final int SCAN_ID = 1234;
+    private static final int SUB_ID = 1;
+
+    private NetworkScan mNetworkScan;
+    private OperatorInfo mOperatorInfo;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mNetworkScanExecutor = Executors.newFixedThreadPool(1);
+
+        mNetworkScanHelper = new NetworkScanHelper(mTelephonyManager,
+                mNetworkScanCallback, mNetworkScanExecutor);
+
+        mNetworkScan = spy(new NetworkScan(SCAN_ID, SUB_ID));
+        mOperatorInfo = new OperatorInfo("Testing", "Test", "12345", "unknown");
+    }
+
+    @Test
+    public void startNetworkScan_scanOnceAndSuccess_completionWithResult() {
+        ArrayList<OperatorInfo> expectedResult = new ArrayList<OperatorInfo>();
+        expectedResult.add(mOperatorInfo);
+
+        when(mTelephonyManager.getAvailableNetworks()).thenReturn(mCellNetworkScanResult);
+        when(mCellNetworkScanResult.getStatus()).thenReturn(
+                CellNetworkScanResult.STATUS_SUCCESS);
+        when(mCellNetworkScanResult.getOperators()).thenReturn(expectedResult);
+
+        ArgumentCaptor<List<CellInfo>> argument = ArgumentCaptor.forClass(List.class);
+
+        startNetworkScan_waitForAll(true);
+
+        verify(mNetworkScanCallback, times(1)).onResults(argument.capture());
+        List<CellInfo> actualResult = argument.getValue();
+        assertThat(actualResult.size()).isEqualTo(expectedResult.size());
+        verify(mNetworkScanCallback, times(1)).onComplete();
+    }
+
+    @Test
+    public void startNetworkScan_scanOnceAndFail_failureWithErrorCode() {
+        when(mTelephonyManager.getAvailableNetworks()).thenReturn(mCellNetworkScanResult);
+        when(mCellNetworkScanResult.getStatus()).thenReturn(
+                CellNetworkScanResult.STATUS_RADIO_GENERIC_FAILURE);
+
+        startNetworkScan_waitForAll(true);
+
+        verify(mNetworkScanCallback, times(1)).onError(anyInt());
+    }
+
+    @Test
+    public void startNetworkScan_scanOnceAndAbort_withoutCrash() {
+        when(mCellNetworkScanResult.getStatus()).thenReturn(
+                CellNetworkScanResult.STATUS_RADIO_GENERIC_FAILURE);
+
+        doAnswer(new Answer() {
+            @Override
+            public Object answer(InvocationOnMock invocation) throws Throwable {
+                Thread.sleep(THREAD_EXECUTION_TIMEOUT_MS);
+                return mCellNetworkScanResult;
+            }
+        }).when(mTelephonyManager).getAvailableNetworks();
+
+        startNetworkScan_waitForAll(false);
+
+        verify(mNetworkScanCallback, times(0)).onError(anyInt());
+    }
+
+    @Test
+    public void startNetworkScan_incrementalAndSuccess_completionWithResult() {
+        ArrayList<CellInfo> expectedResult = new ArrayList<CellInfo>();
+        expectedResult.add(CellInfoUtil.convertOperatorInfoToCellInfo(mOperatorInfo));
+
+        doAnswer(new Answer() {
+            @Override
+            public Object answer(InvocationOnMock invocation) throws Throwable {
+                TelephonyScanManager.NetworkScanCallback callback =
+                        (TelephonyScanManager.NetworkScanCallback)
+                        (invocation.getArguments()[2]);
+                callback.onResults(expectedResult);
+                callback.onComplete();
+                return mNetworkScan;
+            }
+        }).when(mTelephonyManager).requestNetworkScan(
+                any(NetworkScanRequest.class), any(Executor.class),
+                any(TelephonyScanManager.NetworkScanCallback.class));
+
+        ArgumentCaptor<List<CellInfo>> argument = ArgumentCaptor.forClass(List.class);
+
+        startNetworkScan_incremental(true);
+
+        verify(mNetworkScanCallback, times(1)).onResults(argument.capture());
+        List<CellInfo> actualResult = argument.getValue();
+        assertThat(actualResult.size()).isEqualTo(expectedResult.size());
+        verify(mNetworkScanCallback, times(1)).onComplete();
+    }
+
+    @Test
+    public void startNetworkScan_incrementalAndImmediateFailure_failureWithErrorCode() {
+        doReturn(null).when(mTelephonyManager).requestNetworkScan(
+                any(NetworkScanRequest.class), any(Executor.class),
+                any(TelephonyScanManager.NetworkScanCallback.class));
+
+        startNetworkScan_incremental(true);
+
+        verify(mNetworkScanCallback, times(1)).onError(anyInt());
+    }
+
+    @Test
+    public void startNetworkScan_incrementalAndFailure_failureWithErrorCode() {
+        doAnswer(new Answer() {
+            @Override
+            public Object answer(InvocationOnMock invocation) throws Throwable {
+                TelephonyScanManager.NetworkScanCallback callback =
+                        (TelephonyScanManager.NetworkScanCallback)
+                        (invocation.getArguments()[2]);
+                callback.onError(CellNetworkScanResult.STATUS_RADIO_GENERIC_FAILURE);
+                return mNetworkScan;
+            }
+        }).when(mTelephonyManager).requestNetworkScan(
+                any(NetworkScanRequest.class), any(Executor.class),
+                any(TelephonyScanManager.NetworkScanCallback.class));
+
+        startNetworkScan_incremental(true);
+
+        verify(mNetworkScanCallback, times(1)).onError(anyInt());
+    }
+
+    @Test
+    public void startNetworkScan_incrementalAndAbort_doStop() {
+        doReturn(mNetworkScan).when(mTelephonyManager).requestNetworkScan(
+                any(NetworkScanRequest.class), any(Executor.class),
+                any(TelephonyScanManager.NetworkScanCallback.class));
+
+        startNetworkScan_incremental(false);
+
+        verify(mNetworkScan, times(1)).stopScan();
+    }
+
+    private void startNetworkScan_waitForAll(boolean waitForCompletion) {
+        mNetworkScanHelper.startNetworkScan(
+                NetworkScanHelper.NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS);
+        if (!waitForCompletion) {
+            mNetworkScanHelper.stopNetworkQuery();
+        }
+
+        mNetworkScanExecutor.shutdown();
+
+        boolean executorTerminate = false;
+        try {
+            executorTerminate = mNetworkScanExecutor.awaitTermination(
+                    THREAD_EXECUTION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (Exception ex) {
+        }
+
+        assertThat(executorTerminate).isEqualTo(waitForCompletion);
+    }
+
+    private void startNetworkScan_incremental(boolean waitForCompletion) {
+        mNetworkScanHelper.startNetworkScan(
+                NetworkScanHelper.NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS);
+        if (!waitForCompletion) {
+            mNetworkScanHelper.stopNetworkQuery();
+        }
+    }
+
+}