diff --git a/lib/tools/adb-commands.js b/lib/tools/adb-commands.js index 43951c76..39321bd8 100644 --- a/lib/tools/adb-commands.js +++ b/lib/tools/adb-commands.js @@ -755,8 +755,10 @@ methods.sendTelnetCommand = async function sendTelnetCommand (command) { * @return {boolean} True if Airplane mode is enabled. */ methods.isAirplaneModeOn = async function isAirplaneModeOn () { - let stdout = await this.getSetting('global', 'airplane_mode_on'); + const stdout = await this.getSetting('global', 'airplane_mode_on'); return parseInt(stdout, 10) !== 0; + // Alternatively for Android 11+: + // return (await this.shell(['cmd', 'connectivity', 'airplane-mode'])).stdout.trim() === 'enabled'; }; /** @@ -765,13 +767,22 @@ methods.isAirplaneModeOn = async function isAirplaneModeOn () { * @param {boolean} on - True to enable the Airplane mode in Settings and false to disable it. */ methods.setAirplaneMode = async function setAirplaneMode (on) { - await this.setSetting('global', 'airplane_mode_on', on ? 1 : 0); + if (await this.getApiLevel() < 30) { + // This requires to call broadcastAirplaneMode afterwards to apply + await this.setSetting('global', 'airplane_mode_on', on ? 1 : 0); + return; + } + + await this.shell(['cmd', 'connectivity', 'airplane-mode', on ? 'enable' : 'disable']); }; /** * Broadcast the state of Airplane mode on the device under test. * This method should be called after {@link #setAirplaneMode}, otherwise * the mode change is not going to be applied for the device. + * ! This API requires root since Android API 24. Since API 30 + * there is a dedicated adb command to change airplane mode state, which + * does not require to call this one afterwards. * * @param {boolean} on - True to broadcast enable and false to broadcast disable. */ @@ -801,8 +812,10 @@ methods.broadcastAirplaneMode = async function broadcastAirplaneMode (on) { * @return {boolean} True if WiFi is enabled. */ methods.isWifiOn = async function isWifiOn () { - let stdout = await this.getSetting('global', 'wifi_on'); + const stdout = await this.getSetting('global', 'wifi_on'); return (parseInt(stdout, 10) !== 0); + // Alternative for Android 11+: + // return (await this.shell(['cmd', 'wifi', 'status']).stdout.includes('Wifi is enabled')); }; /** @@ -811,7 +824,7 @@ methods.isWifiOn = async function isWifiOn () { * @return {boolean} True if Data transfer is enabled. */ methods.isDataOn = async function isDataOn () { - let stdout = await this.getSetting('global', 'mobile_data'); + const stdout = await this.getSetting('global', 'mobile_data'); return (parseInt(stdout, 10) !== 0); }; diff --git a/lib/tools/settings-client-commands.js b/lib/tools/settings-client-commands.js index c602183c..a4719e94 100644 --- a/lib/tools/settings-client-commands.js +++ b/lib/tools/settings-client-commands.js @@ -114,7 +114,13 @@ commands.setWifiState = async function setWifiState (on, isEmulator = false) { await this.shell(['svc', 'wifi', on ? 'enable' : 'disable'], { privileged: await this.getApiLevel() < 26, }); - } else { + return; + } + + if (await this.getApiLevel() < 30) { + // Android below API 30 does not have a dedicated adb command + // to manipulate wifi connection state, so try to do it via Settings app + // as a workaround await this.requireRunningSettingsApp({shouldRestoreCurrentApp: true}); await this.shell([ @@ -123,7 +129,10 @@ commands.setWifiState = async function setWifiState (on, isEmulator = false) { '-n', WIFI_CONNECTION_SETTING_RECEIVER, '--es', 'setstatus', on ? 'enable' : 'disable' ]); + return; } + + await this.shell(['cmd', '-w', 'wifi', 'set-wifi-enabled', on ? 'enabled' : 'disabled']); }; /** @@ -139,7 +148,10 @@ commands.setDataState = async function setDataState (on, isEmulator = false) { await this.shell(['svc', 'data', on ? 'enable' : 'disable'], { privileged: await this.getApiLevel() < 26, }); - } else { + return; + } + + if (await this.getApiLevel() < 30) { await this.requireRunningSettingsApp({shouldRestoreCurrentApp: true}); await this.shell([ @@ -148,7 +160,10 @@ commands.setDataState = async function setDataState (on, isEmulator = false) { '-n', DATA_CONNECTION_SETTING_RECEIVER, '--es', 'setstatus', on ? 'enable' : 'disable' ]); + return; } + + await this.shell(['cmd', 'phone', 'data', on ? 'enable' : 'disable']); }; /** diff --git a/test/unit/adb-commands-specs.js b/test/unit/adb-commands-specs.js index 4e90a16c..e41c1a0c 100644 --- a/test/unit/adb-commands-specs.js +++ b/test/unit/adb-commands-specs.js @@ -344,12 +344,20 @@ describe('adb commands', withMocks({adb, logcat, teen_process, net}, function (m }); }); describe('setAirplaneMode', function () { - it('should call shell with correct args', async function () { + it('should call shell with correct args API 29', async function () { + mocks.adb.expects('getApiLevel').once().returns(29); mocks.adb.expects('setSetting') .once().withExactArgs('global', 'airplane_mode_on', 1) .returns(''); await adb.setAirplaneMode(1); }); + it('should call shell with correct args API 30', async function () { + mocks.adb.expects('getApiLevel').once().returns(30); + mocks.adb.expects('shell') + .once().withExactArgs(['cmd', 'connectivity', 'airplane-mode', 'enable']) + .returns(''); + await adb.setAirplaneMode(1); + }); }); describe('broadcastAirplaneMode', function () { it('should call shell with correct args', async function () { @@ -374,8 +382,9 @@ describe('adb commands', withMocks({adb, logcat, teen_process, net}, function (m }); }); describe('setWifiState', function () { - it('should call shell with correct args for real device', async function () { + it('should call shell with correct args for real device API 29', async function () { mocks.adb.expects('requireRunningSettingsApp').once(); + mocks.adb.expects('getApiLevel').once().returns(29); mocks.adb.expects('shell') .once().withExactArgs(['am', 'broadcast', '-a', 'io.appium.settings.wifi', '-n', 'io.appium.settings/.receivers.WiFiConnectionSettingReceiver', @@ -383,6 +392,13 @@ describe('adb commands', withMocks({adb, logcat, teen_process, net}, function (m .returns(''); await adb.setWifiState(true); }); + it('should call shell with correct args for real device API 30', async function () { + mocks.adb.expects('getApiLevel').once().returns(30); + mocks.adb.expects('shell') + .once().withExactArgs(['cmd', '-w', 'wifi', 'set-wifi-enabled', 'enabled']) + .returns(''); + await adb.setWifiState(true); + }); it('should call shell with correct args for emulator', async function () { mocks.adb.expects('getApiLevel') .once().returns(25); @@ -409,7 +425,8 @@ describe('adb commands', withMocks({adb, logcat, teen_process, net}, function (m }); }); describe('setDataState', function () { - it('should call shell with correct args for real device', async function () { + it('should call shell with correct args for real device API 29', async function () { + mocks.adb.expects('getApiLevel').once().returns(29); mocks.adb.expects('requireRunningSettingsApp').once(); mocks.adb.expects('shell') .once().withExactArgs(['am', 'broadcast', '-a', 'io.appium.settings.data_connection', @@ -418,6 +435,13 @@ describe('adb commands', withMocks({adb, logcat, teen_process, net}, function (m .returns(''); await adb.setDataState(false); }); + it('should call shell with correct args for real device API 30', async function () { + mocks.adb.expects('getApiLevel').once().returns(30); + mocks.adb.expects('shell') + .once().withExactArgs(['cmd', 'phone', 'data', 'disable']) + .returns(''); + await adb.setDataState(false); + }); it('should call shell with correct args for emulator', async function () { mocks.adb.expects('getApiLevel') .once().returns(26); @@ -431,6 +455,7 @@ describe('adb commands', withMocks({adb, logcat, teen_process, net}, function (m }); describe('setWifiAndData', function () { it('should call shell with correct args when turning only wifi on for real device', async function () { + mocks.adb.expects('getApiLevel').atLeast(1).returns(29); mocks.adb.expects('requireRunningSettingsApp').once(); mocks.adb.expects('shell') .once().withExactArgs(['am', 'broadcast', '-a', 'io.appium.settings.wifi', @@ -441,7 +466,7 @@ describe('adb commands', withMocks({adb, logcat, teen_process, net}, function (m }); it('should call shell with correct args when turning only wifi off for emulator', async function () { mocks.adb.expects('getApiLevel') - .once().returns(25); + .atLeast(1).returns(25); mocks.adb.expects('shell') .once().withExactArgs(['svc', 'wifi', 'disable'], { privileged: true @@ -451,7 +476,7 @@ describe('adb commands', withMocks({adb, logcat, teen_process, net}, function (m }); it('should call shell with correct args when turning only data on for emulator', async function () { mocks.adb.expects('getApiLevel') - .once().returns(25); + .atLeast(1).returns(25); mocks.adb.expects('shell') .once().withExactArgs(['svc', 'data', 'enable'], { privileged: true @@ -460,23 +485,21 @@ describe('adb commands', withMocks({adb, logcat, teen_process, net}, function (m await adb.setWifiAndData({data: true}, true); }); it('should call shell with correct args when turning only data off for real device', async function () { - mocks.adb.expects('requireRunningSettingsApp').once(); + mocks.adb.expects('getApiLevel').atLeast(1).returns(30); mocks.adb.expects('shell') - .once().withExactArgs(['am', 'broadcast', '-a', 'io.appium.settings.data_connection', - '-n', 'io.appium.settings/.receivers.DataConnectionSettingReceiver', - '--es', 'setstatus', 'disable']) + .once().withExactArgs(['cmd', 'phone', 'data', 'disable']) .returns(''); await adb.setWifiAndData({data: false}); }); it('should call shell with correct args when turning both wifi and data on for real device', async function () { + mocks.adb.expects('getApiLevel').atLeast(1).returns(29); mocks.adb.expects('requireRunningSettingsApp').atLeast(1); - mocks.adb.expects('shell').twice().returns(''); + mocks.adb.expects('shell').atLeast(1).returns(''); await adb.setWifiAndData({wifi: true, data: true}); }); it('should call shell with correct args when turning both wifi and data off for emulator', async function () { - mocks.adb.expects('getApiLevel') - .atLeast(1).returns(25); - mocks.adb.expects('shell').twice().returns(''); + mocks.adb.expects('getApiLevel').atLeast(1).returns(25); + mocks.adb.expects('shell').atLeast(1).returns(''); await adb.setWifiAndData({wifi: false, data: false}, true); }); });