I am trying to write a task in build.gradle that executes shell commands on all connected devices. However, when I run my task, I get the notorious 'multiple devices connected' error.
task(myTask, type: Exec) {
doFirst {
println 'myTask'
commandLine 'adb', 'shell', 'my command'
}
}
This is understandable, because I did not specify which device to run on with -s. However, I noticed that the installDebug task will execute its commands on all connected devices (install debug .apk on all devices).
Is there an API in the android plugin that returns a collection of devices IDs that I can iterate over?
Yes.You can check the Android Gradle Plugin source here, where you will find the following:
import com.android.ddmlib.AndroidDebugBridge
import com.android.ddmlib.IDevice
// ...
AndroidDebugBridge.initIfNeeded(false /*clientSupport*/)
AndroidDebugBridge bridge = AndroidDebugBridge.createBridge(android.getAdbExe().absolutePath,
false /*forceNewBridge*/)
long timeOut = 30000 // 30 sec
int sleepTime = 1000
while (!bridge.hasInitialDeviceList() && timeOut > 0) {
sleep(sleepTime)
timeOut -= sleepTime
}
if (timeOut <= 0 && !bridge.hasInitialDeviceList()) {
throw new RuntimeException("Timeout getting device list.", null)
}
IDevice[] devices = bridge.devices
if (devices.length == 0) {
throw new RuntimeException("No connected devices!", null)
}
File destination = project.file("$project.buildDir/outputs/screenshots")
delete destination
for (IDevice device : devices) {
// iterate over your devices here ;)
}
Also you will notice that there is a getter for adb as well that you can use in the loop from above:
project.exec {
executable = android.getAdbExe()
args '-s'
args "$device.serialNumber"
}
Old thread, but maybe it helps someone at some point.
As David Medenjak already mentioned the android.ddmlib is the solution.
You can use it like the following:
In yourscript.gradle:
import com.android.ddmlib.AndroidDebugBridge
import com.android.ddmlib.IDevice
import com.android.ddmlib.NullOutputReceiver
task pressPower {
description = "Press the power button of a device using the adb."
AndroidDebugBridge.initIfNeeded(false)
def bridge = AndroidDebugBridge.createBridge(android.adbExecutable.path, false)
doLast {
bridge.devices.each {
it.executeShellCommand("input keyevent 26", NullOutputReceiver.receiver)
}
}
}
In which "input keyevent 26" corresponds to the shell command ./adb shell input keyevent 26.
If you want to work with the output of the shell you can use the CollectingOutputReceiver like below:
import com.android.ddmlib.AndroidDebugBridge
import com.android.ddmlib.IDevice
import com.android.ddmlib.CollectingOutputReceiver
task getAnimationValue {
description = "Get the Value for the window animation scale."
AndroidDebugBridge.initIfNeeded(false)
def bridge = AndroidDebugBridge.createBridge(android.adbExecutable.path, false)
def receiver = CollectingOutputReceiver.newInstance()
doLast{
bridge.devices.each {
it.executeShellCommand("settings get global window_animation_scale", receiver)
println "Value: ${receiver.getOutput()}"
}
}
}
The task prints the value for the window animation scale gathered by receiver.getOutput().
Related
I need ADB command history similar to bash history.
I need a history file to be created in the Android phone.
Is there any such functionality?
If not, can any one point me to the code in ADBD where it receives the commands form the desktop?
I can implement the same.
I tried enabling shell history on Android, but it does not work for the commands invoked by ADB.
I changed the code in ADBD to implement the functionality.
Modified file: system/core/adb/shell_service.cpp
bool Subprocess::ForkAndExec(std::string* error) {
-----------
/* Writing the command to history file just before it is executed. */
addToHistory(command_.c_str());
execle(_PATH_BSHELL, _PATH_BSHELL, "-c", command_.c_str(), nullptr, cenv.data());
-----------
}
void addToHistory(const char * cmd)
{
FILE *fp = fopen("/data/adb_history.txt", "a");
if(NULL == fp)
{
printf("ERROR\n");
return;
}
fwrite(cmd, strlen(cmd), 1, fp);
fwrite("\n", 1, 1, fp);
fclose(fp);
return;
}
For now, it is working in superuser mode only.
Android Studio 2.0 Preview 2, Gradle Wrapper 2.8, Mac OS X
-MainProjectWorkspace
-|-build.gradle
-|-settings.gradle
-|-gradle.properties
-|-gradle
-|-MyLibraryDependencies
-|-MyMainModule
--|-build.gradle
--|-build
--|-src
---|-androidTest
---|-main
----|-assets
----|-jniLibs
----|-libs
----|-java
-----|-com
----|-res
----|-AndroidManifest.xml
MyMainModule/build.gradle
//Not a single SourceSets configurations.
productFlavors {
flavor1 {
}
flavor2 {
}
}
buildTypes {
release {
}
debug {
}
}
A genius developer left System.out.println statements, instead of Log statements in several hundreds of Java source-files in 'src/main/java'. Ideally, we do not want Sysout statements getting bundled with either of the applicationVariants, specially flavor1Release and flavor2Release.
Before we make amends to those hundreds of Java source-files and eventually switch the Sysout statements to Log statements, we would need to turn them off urgently.
Two possible solutions -
Execute a simple script that will remove all the Sysout statements in the Java source-files in 'src/main/java'. But about that, variants flavor1Debug and flavor2Debug need the Loggers displaying what's going on in the App.
Execute a simple Gradle task at build-time, copy Java source-files from 'src/main/java' to 'src/release/java', ensuring Sysout statements are omitted.
Solution 2 appears to be quickest, easiest, elegant. Particularly when the Gradle Custom-task is executed independently. Except for that, Gradle-sync in Android-Studio seems to be copying everything from 'src/main' to 'src/release' including assets, jniLibs, libs, res and even the AndroidManifest.xml. Fortunately, the Java source-files are ripped-off the Sysout statements in 'src/release', which is the expected result, and ideally, only 'src/release/java' should remain without any other folders.
task prepareRelease(type: Task) {
Pattern sysoutRegex = Pattern.compile(<Sysout-Pattern>)
try {
File releaseFolder = file("src/release")
File mainFolder = file("src/main")
ByteArrayOutputStream output = new ByteArrayOutputStream()
exec {
commandLine = "sh"
args = ["-c", "find ${mainFolder.canonicalPath} -name '*' -type f -print ",
"| xargs egrep -l '${sysoutRegex.pattern()}'"]
standardOutput = output
}
def fileList = output.toString().split(System.getProperty("line.separator"))
fileList.each { line ->
File srcFile = file(line)
File destFile = file(srcFile.canonicalPath.replace("main", "release"))
def content = srcFile.readLines()
if (!destFile.exists()) {
destFile.parentFile.mkdirs()
destFile.createNewFile()
destFile.writable = true
}
destFile.withWriter { out ->
content.eachWithIndex { contentLine, index ->
if (!sysoutRegex.matcher(contentLine).find()) {
out.println contentLine
}
}
}
} catch (Exception fail) {
throw fail
}
}
There is nothing in the custom Gradle-task that may cause this error of making "src/release" an exact copy of "src/main", which was not intended to be.
Any pointers to prevent this default copy of "src/main" to "src/release" will be greatly appreciated.
Based off RaGe's comment - "How about using *.java as your find pattern instead of * ?"
Kudos. I was not supposed to break the "find | xargs egrep " before the '|' into two separate args in the args[] in the exec task. Indeed, a Genius!!!
I'm working on an android app where I need to communicate with a bluetooth LE device and in the middle of the communication I receive a callback:
onCharacteristicWrite()
...which is expected. But the status of the operation is 134 instead of 0 (=success).
This GATT status constant is not defined in the official API but here is a translation in one of many unofficial lists:
public static final int GATT_CMD_STARTED = 134;
See: https://code.google.com/r/naranjomanuel-opensource-broadcom-ble/source/browse/framework/java/src/com/broadcom/bt/service/gatt/GattConstants.java?r=983950f9b35407446bf082633d70c7655c206d22
The consequence, that I can see, in my app is that I do not get an expected callback to:
onCharacteristicChanged()
Does anybody know what GATT_CMD_STARTED means? Is it an error?
The description of the following function taken from the bludroid sources hint that something is not working correctly in your GATT server.
Commands seem to "queue up" there, as there must be pending requests or value confirmations as hinted in the comment before the if(...) clause.
It might be worth checking what exactly is going on before you do the writeCharacteristic(...) as it seems to not finish correctly or create hiccups in your server.
/*******************************************************************************
**
** Function attp_cl_send_cmd
**
** Description Send a ATT command or enqueue it.
**
** Returns GATT_SUCCESS if command sent
** GATT_CONGESTED if command sent but channel congested
** GATT_CMD_STARTED if command queue up in GATT
** GATT_ERROR if command sending failure
**
*******************************************************************************/
tGATT_STATUS attp_cl_send_cmd(tGATT_TCB *p_tcb, UINT16 clcb_idx, UINT8 cmd_code, BT_HDR *p_cmd)
{
tGATT_STATUS att_ret = GATT_SUCCESS;
if (p_tcb != NULL)
{
cmd_code &= ~GATT_AUTH_SIGN_MASK;
/* no pending request or value confirmation */
if (p_tcb->pending_cl_req == p_tcb->next_slot_inq ||
cmd_code == GATT_HANDLE_VALUE_CONF)
{
att_ret = attp_send_msg_to_l2cap(p_tcb, p_cmd);
if (att_ret == GATT_CONGESTED || att_ret == GATT_SUCCESS)
{
/* do not enq cmd if handle value confirmation or set request */
if (cmd_code != GATT_HANDLE_VALUE_CONF && cmd_code != GATT_CMD_WRITE)
{
gatt_start_rsp_timer (clcb_idx);
gatt_cmd_enq(p_tcb, clcb_idx, FALSE, cmd_code, NULL);
}
}
else
att_ret = GATT_INTERNAL_ERROR;
}
else
{
att_ret = GATT_CMD_STARTED;
gatt_cmd_enq(p_tcb, clcb_idx, TRUE, cmd_code, p_cmd);
}
}
else
att_ret = GATT_ERROR;
return att_ret;
}
Starts at line 469 in android sources.
The native GATT error and statuscodes can be found here.
Is it possible to play a sound after my app was compiled and deployed on smartphone in Android Studio / IntelliJ
My workaround is to play a sound inside of onStart() method of my StartActivity, but I have to implement (copy/paste) this pattern for every new project. Not realy nice solution.
In Android Studio, go into Preferences > Appearance & Behavior > Notifications, go down to Gradle Build (Logging) and check the Read aloud box.
This will speak Gradle build finished in x minutes and x seconds when your build is complete.
A blend of https://stackoverflow.com/a/66226491/8636969 and https://stackoverflow.com/a/63632498/8636969 that's very simple for mac:
gradle.buildFinished { BuildResult buildResult ->
try {
"afplay /System/Library/Sounds/Submarine.aiff".execute()
} catch (Throwable ignored) {}
}
in your build.gradle. This just plays the Submarine sounds everytime a build finishes.
Refer to this we need to call the task inside afterEvaluate.
And since I can't comment, I will put my working code here. This code works for windows.
You can add this to your app/.gradle file inside android{ } tag :
afterEvaluate {
gradle.buildFinished{ BuildResult buildResult ->
if (buildResult.failure) {
['powershell', """(New-Object Media.SoundPlayer "C:\\failed_notif.wav").PlaySync();"""].execute()
println("failed doing task")
} else {
['powershell', """(New-Object Media.SoundPlayer "C:\\succeed_notif.wav").PlaySync();"""].execute()
println("build finished")
}
}
}
Please note that this method can only run with .wav file. If you want to use an .mp3 you can try this.
On Windows you can do it like this (in build.gradle):
gradle.buildFinished { BuildResult buildResult ->
// Beep on finish
try {
String filename = buildResult.getFailure() ? 'Alarm10.wav' : 'Alarm02.wav'
['powershell', '-c', """(New-Object Media.SoundPlayer "C:\\Windows\\Media\\${filename}").PlaySync();"""].execute()
} catch (Throwable ignored) {}
}
On mac based on this gist and this answer to find the folder do:
Create file speak.gradle with below content inside ~/.gradle/init.d folder (if you can't find init.d folder, you can create it)
speak.gradle
// When runnning a Gradle build in the background, it is convenient to be notified immediately
// via voice once the build has finished - without having to actively switch windows to find out -
// and being told the actual exception in case of a build failure.
// Put this file into the folder ~/.gradle/init.d to enable the acoustic notifications for all builds
gradle.addBuildListener(new BuildAdapter() {
#Override
void buildFinished(BuildResult result) {
def projectName = gradle.rootProject.name
if (result.failure) {
playSound('Submarine.aiff')
def e = getExceptionParts(result)
"say '$projectName build failed: ${e.first} ${e.second}.'".execute()
} else {
if (projectName != "buildSrc") {
playSound('Glass.aiff')
"say '$projectName build successful.'".execute()
}
}
}
private Tuple2<String, String> getExceptionParts(BuildResult result) {
def exception = result.failure
if (exception.cause != null) {
exception = exception.cause
}
def className = exception.class.simpleName
def of = className.indexOf('Exception')
new Tuple2<String, String>(className.substring(0, of), className.substring(of))
}
private void playSound(def sound) {
"afplay /System/Library/Sounds/$sound".execute()
sleep(100)
}
})
you can simplify more the sound to done and fail
I have the following setup in my build.gradle file:
// Task designed to bump version numbers. This should be the first task run
// after a new release branch is created.
task bumpVersion(description: 'Bumps the version number of the current Android release. Should be used as a standalone task, and should only be the first task called after creating a release branch.', group: 'Management') << {
Properties props = new Properties();
File propsFile = new File('gradle.properties');
props.load(propsFile.newDataInputStream());
def currentVersionCode = props.getProperty("CORE_VERSION_CODE") as int;
def currentVersionName = props.getProperty("CORE_VERSION_NAME") as String;
def intPortionsOfVersionName = currentVersionName.tokenize('.').toArray();
def leastSignificantPortion = intPortionsOfVersionName[intPortionsOfVersionName.length - 1] as int;
def newVersionCode = currentVersionCode + 1;
def newVersionName = "";
if (!project.hasProperty('newVersion')) {
leastSignificantPortion = leastSignificantPortion + 1;
intPortionsOfVersionName[intPortionsOfVersionName.length - 1] = leastSignificantPortion;
newVersionName = intPortionsOfVersionName.collect{ it }.join(".");
} else {
newVersionName = project.getProperty('newVersion');
}
props.setProperty("CORE_VERSION_NAME", newVersionName as String);
props.setProperty("CORE_VERSION_CODE", newVersionCode as String);
props.store(propsFile.newWriter(), null);
}
Under the line newVersionName = project.getProperty('newVersion') I try to acquire the property called "newVersion", if it exists, and bump the least significant digit if it's not available.
This works fine, but what I want to do is add a way to specify this option in the documentation (i.e. gradle help --task bumpVersion). For instance, if I run gradle help --task help, it gives me:
:help
Detailed task information for help
Path
:help
Type
Help (org.gradle.configuration.Help)
Options
--task The task, detailed help is requested for.
Description
Displays a help message
Notice how '--task' is under the Options section. I'm wondering how to do this with my own code.
This can be done using the #Option annotation.
#Option(option = "version", description = "Version number to use")
public void setVersion(String version) { ... }
Note: This is an internal API so it may change.
Edit: May have forgotten to mention you will have to implement your task as a custom task class to leverage this capability.