Here is a solution that uses UiAutomator to enable or disable "Aeroplane mode" from the drop-down status bar, which turns off all networking if enabled. It works on most Android OS versions, and it uses parts of the answer from user Aorlinn (12 October 2019).
app/build.gradle
dependencies {
androidTestImplementation "androidx.test.uiautomator:uiautomator:2.2.0"
}
AndroidManifest.xml
No extra permissions are needed, not even <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
, because UiAutomator is only clicking on a button in the status bar UI. Therefore the app does not need direct access to modify the device's Wifi or mobile data settings.
Kotlin
import android.os.Build
import android.provider.Settings
import androidx.test.espresso.matcher.ViewMatchers.assertThat
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObjectNotFoundException
import androidx.test.uiautomator.Until
import org.hamcrest.Matchers.`is`
import org.junit.Assert
import org.junit.Assume.assumeNoException
import org.junit.Assume.assumeThat
import java.io.IOException
private var airplaneModeOn = 0
private val OFF = 0
private val ON = 1
private var airplaneModeButtonPosition: Point? = null
/**
* Turn off the Internet connectivity by switching "Aeroplane mode" (flight mode) on. From:
* https://stackoverflow.com/questions/27620976/android-instrumentation-test-offline-cases#68956544
*/
@Test
fun checkAppWillWork_withNoInternetConnection() {
try {
airplaneModeOn = getCurrentAirplaneModeSetting()
} catch (e: Exception) {
e.printStackTrace()
assumeNoException("Cannot retrieve device setting of 'Airplane Mode'. Aborting the test.", e)
}
// Check that Airplane mode is off
assertThat(airplaneModeOn, `is`(OFF))
// Press the "Aeroplane mode" button on the 'Quick Settings' panel
toggleAeroplaneModeButton()
// Verify Airplane mode is on
assumeThat("Cannot change the 'Aeroplane Mode' setting. Test aborted.", airplaneModeOn, `is`(ON))
assertThat(airplaneModeOn, `is`(ON))
// Do some tests when the Internet is down
// Note: Switch the Internet back on in cleanup()
}
/**
* Turn the Internet connectivity on or off by pressing the "Aeroplane mode" button. It opens the
* status bar at the top, then drags it down to reveal the buttons on the 'Quick Settings' panel.
*/
@Throws(UiObjectNotFoundException::class)
private fun toggleAeroplaneModeButton() {
try {
airplaneModeOn = getCurrentAirplaneModeSetting()
} catch (e: SecurityException) {
e.printStackTrace()
Assert.fail()
} catch (e: IOException) {
e.printStackTrace()
Assert.fail()
}
// Open the status bar at the top; drag it down to reveal the buttons
val device = UiDevice.getInstance(getInstrumentation())
device.openQuickSettings()
// Wait for the button to be visible, because opening the Quick Settings is animated.
// You can use any string here; You only need a time delay wait here.
val description = By.desc("AeroplaneMode")
device.wait(Until.hasObject(description), 2000)
// Search for and click the button
var buttonClicked = clickObjectIfFound(device,
"Aeroplane mode", "Airplane mode", "機内モード", "Modo avión")
if (!buttonClicked) {
// Swipe the Quick Panel window to the LEFT, if possible
val screenWidth = device.displayWidth
val screenHeight = device.displayHeight
device.swipe((screenWidth * 0.80).toInt(), (screenHeight * 0.30).toInt(),
(screenWidth * 0.20).toInt(), (screenHeight * 0.30).toInt(), 50)
buttonClicked = clickObjectIfFound(device,
"Aeroplane mode", "Airplane mode", "機内モード", "Modo avión")
}
if (!buttonClicked) {
// Swipe the Quick Panel window to the RIGHT, if possible
val screenWidth = device.displayWidth
val screenHeight = device.displayHeight
device.swipe((screenWidth * 0.20).toInt(), (screenHeight * 0.30).toInt(),
(screenWidth * 0.80).toInt(), (screenHeight * 0.30).toInt(), 50)
clickObjectIfFound(device,
"Aeroplane mode", "Airplane mode", "機内モード", "Modo avión")
}
// Wait for the Internet to disconnect or re-connect
device.wait(Until.hasObject(description), 6000)
// Close the Quick Settings panel
getInstrumentation().context
.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
// Verify change in device settings
try {
airplaneModeOn = getCurrentAirplaneModeSetting()
} catch (e: SecurityException) {
e.printStackTrace()
Assert.fail()
} catch (e: IOException) {
e.printStackTrace()
Assert.fail()
}
}
/**
* On Android 8/9, use an 'adb shell' command to retrieve the "Airplane Mode" setting, like this:
*
*
* `adb shell settings list global | grep airplane_mode_on ==> "airplane_mode_on=0"`
*
*
* (But note that grep is not available on the Android shell. See guidance URLs below)
*
*
* * https://www.reddit.com/r/tasker/comments/fbi5ai/psa_you_can_use_adb_to_find_all_the_settings_that/
* * https://stackoverflow.com/questions/33970956/test-if-soft-keyboard-is-visible-using-espresso
*
*
* On all other Android OS versions, use `Settings.System.getInt()` to retrieve the "Airplane Mode" setting.
* It sets `airplaneModeOn = 1 (true)` or `airplaneModeOn = 0 (false)`
*
* @throws IOException if `executeShellCommand()` didn't work
* @throws SecurityException if `Settings.System.getInt()` didn't work
*/
@Throws(IOException::class, SecurityException::class)
private fun getCurrentAirplaneModeSetting(): Int {
if (Build.VERSION.SDK_INT in 26..28 /*Android 8-9*/) {
val shellResponse = UiDevice
.getInstance(getInstrumentation())
.executeShellCommand("settings list global")
airplaneModeOn = when {
shellResponse.contains("airplane_mode_on=1") -> 1
shellResponse.contains("airplane_mode_on=0") -> 0
else -> throw IOException("Unsuitable response from adb shell command 'settings list global'")
}
} else {
// Oddly this causes a SecurityException on Android 8,9 devices
airplaneModeOn = Settings.System.getInt(
getInstrumentation().context.contentResolver,
Settings.Global.AIRPLANE_MODE_ON,
0)
}
return airplaneModeOn
}
/**
* Make UiAutomator search for and click a single button, based on its text label.
*
* Sometimes multiple buttons will match the required text label. For example,
* when Airplane mode is switched on, "Mobile data Aeroplane mode" and "Aeroplane mode"
* are 2 separate buttons on the Quick Settings panel, on Android 10+.
* For Aeroplane mode, always click on the last matched item.
*
* @param textLabels You must supply the language variants of the button that you want to click,
* for example "Cancel" (English), "Cancelar" (Spanish), "취소" (Korean)
* @return True if a button was found and clicked, otherwise return false
*/
private fun clickObjectIfFound(device: UiDevice, vararg textLabels: String): Boolean {
for (languageVariant in textLabels) {
val availableButtons = device.findObjects(By.text(languageVariant))
if (availableButtons.size >= 1) {
if (airplaneModeButtonPosition == null) {
availableButtons[availableButtons.size - 1].click()
airplaneModeButtonPosition = availableButtons[availableButtons.size - 1].visibleCenter
return true
} else {
// Use the stored position to avoid clicking on the wrong button
for (button in availableButtons) {
if (button.visibleCenter == airplaneModeButtonPosition) {
button.click()
airplaneModeButtonPosition = null
return true
}
}
}
}
}
return false
}
@After
fun cleanup() {
// Switch the Internet connectivity back on, for other tests.
if (airplaneModeOn == ON) {
toggleAeroplaneModeButton()
assertThat(airplaneModeOn, `is`(OFF))
}
}
You will have to adjust the string "Aeroplane mode"
if your device is in a different language to English. For example, you could check for about 70 language translations here: https://github.com/aosp-mirror/platform_frameworks_base/search?q=global_actions_toggle_airplane_mode