Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add networking control commands supported on Android 11+ #641

Merged
merged 2 commits into from
Apr 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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