From 1b64578f3c567ae95cc8d86f2ed37244107c8058 Mon Sep 17 00:00:00 2001 From: Bonian Chen Date: Mon, 23 Sep 2019 11:29:54 +0800 Subject: [PATCH] [ManualNetworkSelect] Improve error handling 1. [batch mode] Support graceful app stop behavior Avoid thread crash when UI abort the request. 2. [incremental mode] Stop search UI earlier when error during API access Generate error instead of keeping UI in progressing when fail to access Telephony API Bug: 139398483 Bug: 140448617 Test: atest NetworkScanHelperTest Change-Id: I429f3aa2ef692c27100514faa413b16dbd2459d7 --- .../network/telephony/NetworkScanHelper.java | 12 +- .../telephony/NetworkScanHelperTest.java | 237 ++++++++++++++++++ 2 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 tests/robotests/src/com/android/settings/network/telephony/NetworkScanHelperTest.java diff --git a/src/com/android/settings/network/telephony/NetworkScanHelper.java b/src/com/android/settings/network/telephony/NetworkScanHelper.java index c4839b61415..0ec61e77976 100644 --- a/src/com/android/settings/network/telephony/NetworkScanHelper.java +++ b/src/com/android/settings/network/telephony/NetworkScanHelper.java @@ -37,6 +37,7 @@ import com.google.common.util.concurrent.SettableFuture; 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 @@ public class NetworkScanHelper { @Override public void onFailure(Throwable t) { + if (t instanceof CancellationException) { + return; + } int errCode = Integer.parseInt(t.getMessage()); onError(errCode); } @@ -184,10 +188,16 @@ public class NetworkScanHelper { 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 class NetworkScanHelper { 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 00000000000..1f8c16d88c1 --- /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 expectedResult = new ArrayList(); + expectedResult.add(mOperatorInfo); + + when(mTelephonyManager.getAvailableNetworks()).thenReturn(mCellNetworkScanResult); + when(mCellNetworkScanResult.getStatus()).thenReturn( + CellNetworkScanResult.STATUS_SUCCESS); + when(mCellNetworkScanResult.getOperators()).thenReturn(expectedResult); + + ArgumentCaptor> argument = ArgumentCaptor.forClass(List.class); + + startNetworkScan_waitForAll(true); + + verify(mNetworkScanCallback, times(1)).onResults(argument.capture()); + List 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 expectedResult = new ArrayList(); + 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> argument = ArgumentCaptor.forClass(List.class); + + startNetworkScan_incremental(true); + + verify(mNetworkScanCallback, times(1)).onResults(argument.capture()); + List 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(); + } + } + +}