Skip to content

Commit

Permalink
feat: Add networking control commands supported on Android 11+ (#641)
Browse files Browse the repository at this point in the history
  • Loading branch information
mykola-mokhnach authored Apr 3, 2023
1 parent bed3238 commit 3dc4885
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 19 deletions.
21 changes: 17 additions & 4 deletions lib/tools/adb-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
};

/**
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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'));
};

/**
Expand All @@ -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);
};

Expand Down
19 changes: 17 additions & 2 deletions lib/tools/settings-client-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand All @@ -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']);
};

/**
Expand All @@ -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([
Expand All @@ -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']);
};

/**
Expand Down
49 changes: 36 additions & 13 deletions test/unit/adb-commands-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand All @@ -374,15 +382,23 @@ 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',
'--es', 'setstatus', 'enable'])
.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);
Expand All @@ -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',
Expand All @@ -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);
Expand All @@ -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',
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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);
});
});
Expand Down

0 comments on commit 3dc4885

Please sign in to comment.