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 a possibility to retrieve the clipboard content on Android 10 #473

Merged
merged 2 commits into from
Nov 15, 2019
Merged
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
75 changes: 67 additions & 8 deletions lib/tools/adb-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@ const LOCALE_SETTING_ACTION = `${SETTINGS_HELPER_ID}.locale`;
const LOCATION_SERVICE = `${SETTINGS_HELPER_ID}/.LocationService`;
const LOCATION_RECEIVER = `${SETTINGS_HELPER_ID}/.receivers.LocationInfoReceiver`;
const LOCATION_RETRIEVAL_ACTION = `${SETTINGS_HELPER_ID}.location`;
const CLIPBOARD_RECEIVER = `${SETTINGS_HELPER_ID}/.receivers.ClipboardReceiver`;
const CLIPBOARD_RETRIEVAL_ACTION = `${SETTINGS_HELPER_ID}.clipboard.get`;
const APPIUM_IME = `${SETTINGS_HELPER_ID}/.AppiumIME`;
const MAX_SHELL_BUFFER_LENGTH = 1000;
const NOT_CHANGEABLE_PERM_ERROR = 'not a changeable permission type';


let methods = {};

/**
Expand Down Expand Up @@ -1727,6 +1730,30 @@ methods.screenrecord = function screenrecord (destination, options = {}) {
return new SubProcess(this.executable.path, fullCmd);
};

/**
* Executes the given function with the given input method context
* and then restores the IME to the original value
*
* @param {string} ime - Valid IME identifier
* @param {Function} fn - Function to execute
* @returns {*} The result of the given function
*/
methods.runInImeContext = async function runInImeContext (ime, fn) {
const originalIme = await this.defaultIME();
if (originalIme === ime) {
log.debug(`The original IME is the same as '${ime}'. There is no need to reset it`);
} else {
await this.setIME(ime);
}
try {
return await fn();
} finally {
if (originalIme !== ime) {
await this.setIME(originalIme);
}
}
};

/**
* Performs the given editor action on the focused input field.
* This method requires Appium Settings helper to be installed on the device.
Expand All @@ -1739,14 +1766,8 @@ methods.screenrecord = function screenrecord (destination, options = {}) {
*/
methods.performEditorAction = async function performEditorAction (action) {
log.debug(`Performing editor action: ${action}`);
const defaultIME = await this.defaultIME();
await this.enableIME(APPIUM_IME);
try {
await this.setIME(APPIUM_IME);
await this.shell(['input', 'text', `/${action}/`]);
} finally {
await this.setIME(defaultIME);
}
await this.runInImeContext(APPIUM_IME,
async () => await this.shell(['input', 'text', `/${action}/`]));
};

/**
Expand All @@ -1765,4 +1786,42 @@ methods.getTimeZone = async function getTimeZone () {
}
};

/**
* Retrieves the text content of the device's clipboard.
* The method works for Android below and above 29.
* It temorarily enforces the IME setting in order to workaround
* security limitations if needed.
* This method only works if Appium Settings v. 2.15+ is installed
* on the device under test
*
* @returns {string} The actual content of the main clipboard
* or an empty string if the clipboard is empty
* @throws {Error} If there was a problem while getting the
* clipboard contant
*/
methods.getClipboard = async function getClipboard () {
log.debug('Getting the clipboard content');
const retrieveClipboard = async () => await this.shell([
'am', 'broadcast',
'-n', CLIPBOARD_RECEIVER,
'-a', CLIPBOARD_RETRIEVAL_ACTION,
]);
let output;
try {
output = (await this.getApiLevel() >= 29)
? (await this.runInImeContext(APPIUM_IME, retrieveClipboard))
: (await retrieveClipboard());
} catch (err) {
throw new Error(`Cannot retrieve the current clipboard content from the device. ` +
`Make sure the Appium Settings application is up to date. ` +
`Original error: ${err.message}`);
}

const match = /data="([^"]*)"/.exec(output);
if (!match) {
throw new Error(`Cannot parse the actual cliboard content from the command output: ${output}`);
}
return Buffer.from(_.trim(match[1]), 'base64').toString('utf8');
Copy link
Member

@KazuCocoa KazuCocoa Nov 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed here should have been passed without decoding since the endpoint expected Base 64 encoded string.

# Current
[17] pry(#<AppiumLibCoreTest::Android::DeviceTest>)> c = @driver.get_clipboard
=> "\x85\xAAi\xCA\xD7\xAC\xB6)\xE0"

# Here returns `_.trim(match[1]`
[18] pry(#<AppiumLibCoreTest::Android::DeviceTest>)> @driver = @@core.start_driver
=> #<Appium::Core::Base::Driver:0x..fee76647dbb527b8e browser="unknown">
[19] pry(#<AppiumLibCoreTest::Android::DeviceTest>)> c = @driver.get_clipboard
=> "happy testing"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true, will fix

thanks for checking it

};

export default methods;