Android Studio: sound notification on build success - android

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

Related

How to generate java files from swig?Is this done by gradle or do it with command line?

This is my Build.gradle file:-
project.ext {
// * SWIG options *
// This and the SWIG "task" at the bottom are loosely based on:
//
// http://androiddeveloper.co.il/using-swig/
swigModuleFiles = ['yourfy.i']
swigIncludeDirs = ['src/main/cpp/yourfy/src', 'src/main/cpp/yourfy/src/nxcommon/src/libnxcommon']
swigJavaOutputDir = file("src/main/java/com/yourfy/yourfy/swig").absolutePath
swigCxxOutputDir = file("src/main/cpp/swig").absolutePath
swigModuleFilesAbs = []
swigIncludeDirOpts = []
swigCxxModuleFilesAbs = []
swigModuleFiles.each { moduleFile ->
swigModuleFilesAbs.add(file("src/main/cpp/yourfy/src/yourfy/" + moduleFile).absolutePath)
swigCxxModuleFilesAbs.add(swigCxxOutputDir + "/" + moduleFile + ".cxx")
}
swigIncludeDirs.each { incDir ->
swigIncludeDirOpts.add("-I" + file(incDir).absolutePath)
}
}
// * Generate SWIG wrappers *
// Generate .java and .cxx files for the SWIG bindings.
//
// It has to be done in Gradle (as opposed to the native library's CMakeLists.txt), because
// Gradle calls the Java implementationr BEFORE running the native build, so the .java SWIG files will
// not have been generated yet when the implementationr runs. We have to ensure that they are generated
// before the Java implementationr runs.
//
// Thanks, Gradle!
//
// I would love to do this as a task, but unfortunately, it does not work. For some reason,
// when building from Android Studio (NOT when running Gradle from command line), CMake is
// actually invoked first, before any other tasks, which means that the Gradle-generated SWIG
// CXX source files might still be missing, which CMake then complains about and aborts. For
// now, we will have to run SWIG at the top level all the time to get it working. Right now,
// it's reasonably fast to do so, let's see how long this holds.
// More info:
def swigExec = '/usr/local/bin/swig'
// TODO: Add some auto-detection
if (project.hasProperty('swig.executable')) {
swigExec = project.property('swig.executable')
}
if (swigExec != null && file(swigExec).isFile()) {
file(project.swigJavaOutputDir).mkdirs()
file(project.swigCxxOutputDir).mkdirs()
// Delete previous output files (.cxx, .h and *.java in respective directories)
(file(project.swigJavaOutputDir).listFiles() + file(project.swigCxxOutputDir).listFiles()).each { file ->
if (file.name.toLowerCase().endsWith(".cxx")
|| file.name.toLowerCase().endsWith(".h")
|| file.name.toLowerCase().endsWith(".java")
) {
file.delete()
}
}
[project.swigModuleFilesAbs, project.swigCxxModuleFilesAbs].transpose().each { moduleFile, cxxFile ->
exec {
commandLine(
swigExec,
'-java',
'-c++',
'-package', 'com.yourfy.yourfy.swig',
*project.swigIncludeDirOpts,
'-outdir', project.swigJavaOutputDir,
'-o', cxxFile,
moduleFile
)
}
}
} else {
logger.error('Property swig.executable not set or invalid! You should set it in ' +
'the gradle.properties file of your gradle user home directory, pointing to ' +
'a SWIG > 3.0 executable')
}
How to generate swig wrapper files in android?There are some links added to here but still its not figure out.
Also tried following links also mentioned in the comment:-
Stackoverflow question link
Github link
I was trying both the links but didn't understand but still seems it difficult.

Android Studio Gradle sourceSets copy

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!!!

How to query all connected devices in build.gradle?

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().

New Relic in Android Studio - newrelic.properties - variants

I'm integrating New Relic in my project (with Android Studio & Gradle) which has 2 variants. Each variant has its own generated token, which I store in each variant's string.xml file.
In the New Relic documentation, it states the following:
In your project’s root directory (projectname/app), add a newrelic.properties file with the following line:
com.newrelic.application_token=generated_token
The problem is, if I do this, how can make the correct token appear for the correct variant? If this file must appear in the project root, I can't create one per variant, and so I'm forced to use the same token for both variants, which doesn't work for my requirements.
Any insight would be appreciated.
Okay, so after contacting the support team at New Relic, there is apparently no direct solution for this as of today, although they said they've opened a feature request, and so this problem might be solved soon.
From what I managed to understand, the reason this file is needed is so that the New Relic system can display an un-obfuscated error log when an exception occurs on a production version which has been obfuscated with ProGuard.
The New Relic system, with the help of this file, will upload the ProGuard mapping.txt file to the New Relic servers and associate it with your app according to the specified token. With this, New Relic can un-obfuscate stack traces and display a descriptive stack trace with actual class & method names, rather a, b, c, etc.
As a workaround, I was told that I can forego this file all together, if I upload the mapping file manually.
The mapping file can be found at:
build/outputs/proguard/release/mapping.txt
In order to manually upload the file, perform the following via command line:
curl -v -F proguard=#"<path_to_mapping.txt>" -H "X-APP-LICENSE-KEY:<APPLICATION_TOKEN>" https://mobile-symbol-upload.newrelic.com/symbol
This must be done for each variant which is being obfuscated with ProGuard (classically, release builds).
Source
Hope this helps someone else.
I solved creating some Gradle tasks. Please, take a look at https://discuss.newrelic.com/t/how-to-set-up-newrelic-properties-file-for-project-with-multiple-build-variants/46176/5
I following a code that worked pretty well for me.
Add the New Relic application token on a string resource file. i.e.: api.xml.
Create a new Gradle file. i.e: newrelic-util.gradle.
Add the following content on the newly created Gradle file:
apply plugin: 'com.android.application'
android.applicationVariants.all { variant ->
//<editor-fold desc="Setup New Relic property file">
def variantName = variant.name.capitalize()
def newRelicTasksGroup = "newrelic"
def projectDirPath = project.getProjectDir().absolutePath
def newRelicPropertyFileName = "newrelic.properties"
def newRelicPropertyFilePath = "${projectDirPath}/${newRelicPropertyFileName}"
// Cleanup task for New Relic property file creation process.
def deleteNewRelicPropertyFile = "deleteNewRelicPropertyFile"
def taskDeleteNewRelicPropertyFile = project.tasks.findByName(deleteNewRelicPropertyFile)
if (!taskDeleteNewRelicPropertyFile) {
taskDeleteNewRelicPropertyFile = tasks.create(name: deleteNewRelicPropertyFile) {
group = newRelicTasksGroup
description = "Delete the newrelic.properties file on project dir."
doLast {
new File("${newRelicPropertyFilePath}").with {
if (exists()) {
logger.lifecycle("Deleting file ${absolutePath}.")
delete()
} else {
logger.lifecycle("Nothing to do. File ${absolutePath} not found.")
}
}
}
}
}
/*
* Fix for warning message reported by task "newRelicMapUploadVariantName"
* Message:
* [newrelic] newrelic.properties was not found! Mapping file for variant [variantName] not uploaded.
* New Relic discussion:
* https://discuss.newrelic.com/t/how-to-set-up-newrelic-properties-file-for-project-with-multiple-build-variants/46176
*/
def requiredTaskName = "assemble${variantName}"
def taskAssembleByVariant = project.tasks.findByName(requiredTaskName)
def createNewRelicPropertyFileVariantName = "createNewRelicPropertyFile${variantName}"
// 0. Searching task candidate to be dependent.
if (taskAssembleByVariant) {
logger.debug("Candidate task to be dependent found: ${taskAssembleByVariant.name}")
// 1. Task creation
def taskCreateNewRelicPropertyFile = tasks.create(name: createNewRelicPropertyFileVariantName) {
group = newRelicTasksGroup
description = "Generate the newrelic.properties file on project dir.\nA key/value propety " +
"will be written in file to be consumed by newRelicMapUploadVariantName task."
logger.debug("Creating task: ${name}")
doLast {
def newRelicPropertyKey = "com.newrelic.application_token"
def newRelicStringResourceKey = "new_relic_key"
def targetResourceFileName = "api.xml"
def variantXmlResourceFilePath = "${projectDirPath}/src/${variant.name}/res/values/${targetResourceFileName}"
def mainXmlResourceFilePath = "${projectDirPath}/src/main/res/values/${targetResourceFileName}"
def xmlResourceFilesPaths = [variantXmlResourceFilePath, mainXmlResourceFilePath]
xmlResourceFilesPaths.any { xmlResourceFilePath ->
// 1.1. Searching xml resource file.
def xmlResourceFile = new File(xmlResourceFilePath)
if (xmlResourceFile.exists()) {
logger.lifecycle("Reading property from xml resource file: ${xmlResourceFilePath}.")
// 1.2. Searching for string name new_relic_key api.xml resource file.
def nodeResources = new XmlParser().parse(xmlResourceFile)
def nodeString = nodeResources.find {
Node nodeString -> nodeString.'#name'.toString() == newRelicStringResourceKey
}
// 1.3. Checking if string name new_relic_key was found.
if (nodeString != null) {
def newRelicApplicationToken = "${nodeString.value()[0]}"
logger.lifecycle("name:${nodeString.'#name'.toString()};" +
"value:${newRelicApplicationToken}")
// 1.4 Checking the content of newRelicApplicationToken
if (newRelicApplicationToken == 'null' || newRelicApplicationToken.allWhitespace) {
logger.warn("Invalid value for key ${newRelicStringResourceKey}. " +
"Please, consider configuring a value for key ${newRelicStringResourceKey}" +
" on ${xmlResourceFile.name} resource file for buildVariant: ${variantName}. " +
"The ${newRelicPropertyFileName} will be not created.")
return true // break the loop
}
// 1.5. File creation.
File fileProperties = new File(newRelicPropertyFilePath)
fileProperties.createNewFile()
logger.lifecycle("File ${fileProperties.absolutePath} created.")
// 1.6. Writing content on properties file.
def fileComments = "File generated dynamically by gradle task ${createNewRelicPropertyFileVariantName}.\n" +
"Don't change it manually.\n" +
"Don't track it on VCS."
new Properties().with {
load(fileProperties.newDataInputStream())
setProperty(newRelicPropertyKey, newRelicApplicationToken.toString())
store(fileProperties.newWriter(), fileComments)
}
logger.lifecycle("Properties saved on file ${fileProperties.absolutePath}.")
return true // break the loop
} else {
logger.warn("The key ${newRelicStringResourceKey} was not found on ${xmlResourceFile.absolutePath}.\n" +
"Please, consider configuring a key/value on ${xmlResourceFile.name} resource file for buildVariant: ${variantName}.")
return // continue to next xmlResourceFilePath
}
} else {
logger.error("Resource file not found: ${xmlResourceFile.absolutePath}")
return // continue next xmlResourceFilePath
}
}
}
}
// 2. Task dependency setup
// To check the task dependencies, use:
// logger.lifecycle("Task ${name} now depends on tasks:")
// dependsOn.forEach { dep -> logger.lifecycle("\tTask: ${dep}") }
tasks['clean'].dependsOn taskDeleteNewRelicPropertyFile
taskCreateNewRelicPropertyFile.dependsOn taskDeleteNewRelicPropertyFile
taskAssembleByVariant.dependsOn taskCreateNewRelicPropertyFile
} else {
logger.error("Required task ${requiredTaskName} was not found. " +
"The task ${createNewRelicPropertyFileVariantName} will be not created.")
}
//</editor-fold>
}
On app/build.gradle file, apply the Gradle file.
apply from: './newrelic-util.gradle'
That’s it. I created a file named newrelic-util.gradle on project app dir. If you execute the task assembleAnyVariantName, the task createNewRelicPropertyFileAnyVarianteName will be performed first. Tip: don’t track the generated file newrelic.properties file. Ignore it on your VCS.
Additionally, the task deleteNewRelicPropertyFile will be performed right before the tasks ‘clean’ and ‘createNewRelicPropertyFileAnyVarianteName’ in order to avoid a file with a wrong New Relic application token.

Documentation on Guardian project ffmpeg android

I got the Gaurdian Project FFMPEG android java from the following link
https://github.com/guardianproject/android-ffmpeg-java
Is there any good documents available to use the library for code. Its difficult to use without documentation. Plz help me.
I managed to get it work.
First, download the guardian project ffmpeg library project:
Then import it in eclipse. (no need to follow their Build Procedure, with the NDK just import their project in eclipse directly)
Then right click on your Main project (not the library project) -> Properties -> Android -> Library -> Add
Then, use it this way :
File fileTmp = context.getActivity().getCacheDir();
File fileAppRoot = new File(context.getActivity().getApplicationInfo().dataDir);
FfmpegController fc = new FfmpegController(fileTmp, fileAppRoot);
final Clip out = new Clip("compiled.mp4");
fc.concatAndTrimFilesMP4Stream(videos, out, true, false, new ShellUtils.ShellCallback() {
#Override
public void shellOut(String shellLine) {
System.out.println("MIX> " + shellLine);
}
#Override
public void processComplete(int exitValue) {
if (exitValue != 0) {
System.err.println("concat non-zero exit: " + exitValue);
Log.d("ffmpeg","Compilation error. FFmpeg failed");
} else {
if(new File(out.path).exists()) {
Log.d("ffmpeg","Success file:"+out.path);
}
}
}
});
With an ArrayList<Clip> of videos you want to concatenate.

Categories

Resources