I would like to start a background SL4A script (on a remote device) from within a different SL4A script. I can launch a script from a terminal by running something like this:
$ am start -a \
com.googlecode.android_scripting.action.LAUNCH_BACKGROUND_SCRIPT -n \
com.googlecode.android_scripting/.activity.ScriptingLayerServiceLauncher -e \
com.googlecode.android_scripting.extra.SCRIPT_PATH /sdcard/sl4a/scripts/main.py
I can't translate this into a startActivity call in Python.
The answer to a different question on opening a Twitter client works nicely, but I don't know how to extend that code. For example, how would you add a script path, and where would you put the line com.googlecode.android_scripting/.activity.ScriptingLayerServiceLauncher?
This function will launch any SL4A script from inside another one.
The first argument should be a path to the script you want to launch. The script can be in any language you have an interpreter installed for.
The second argument is optional and should be a bool. It defaults to False. It controls whether the terminal will be visible, so you can see output and errors. It does not effect whether the script has a UI or not.
from android import Android
droid = Android()
def launch_script(path, visible=False):
visibilty = 'FORE' if visible else 'BACK'
activity = 'com.googlecode.android_scripting.action.LAUNCH_{0}GROUND_SCRIPT'.format(visibilty)
extras = {'com.googlecode.android_scripting.extra.SCRIPT_PATH': path}
packagename = 'com.googlecode.android_scripting'
classname = 'com.googlecode.android_scripting.activity.ScriptingLayerServiceLauncher'
intent = droid.makeIntent(activity, None, None, extras, None, packagename, classname).result
droid.startActivityIntent(intent)
There's a gist for this code here.
After many, many failed attempts, I now have this working in Ruby - I had an easier time generating the JSON extras this way than in Python.
Important!
In the command-line version, you call on "com.googlecode.android_scripting/.activity.ScriptingLayerServiceLauncher"
From within a script, this is called as "com.googlecode.android_scripting.activity.ScriptingLayerServiceLauncher", without the slash. Leaving in the slash crashes sl4a.
[code]
require 'android'
require 'json/pure'
d=Android.new
script = '/sdcard/sl4a/scripts/YOUR_SCRIPT'
data = {"com.googlecode.android_scripting.extra.SCRIPT_PATH"=>script}
extras = JSON.generate(data)
d.startActivity('com.googlecode.android_scripting.action.LAUNCH_BACKGROUND_SCRIPT','','',data,true,'com.googlecode.android_scripting','com.googlecode.android_scripting.activity.ScriptingLayerServiceLauncher')
[/code]
I hope this helps!
Related
I want to create an AVD (Android Virtual Device) through command line in python. For that, I need to pass a string n to the stdin. I have tried the following
emulator_create = str(subprocess.check_output([android,'create', 'avd', '-n', emulator_name, '-t', target_id, '-b', abi],stdin=PIPE))
emulator_create.communicate("n")
but it raises the following error
raise CalledProcessError(retcode, cmd, output=output)
subprocess.CalledProcessError: Command '['/home/fahim/Android/Sdk/tools/android', 'create', 'avd', '-n', 'samsung_1', '-t', '5', '-b', 'android-tv/x86']' returned non-zero exit status 1
Process finished with exit code 1
What can I do?
There's something not working with your example. subprocess.check_output() returns the output from the child process you want to execute, not a handle to this process. In other words you get a string object (or maybe a bytes object) which you cannot use to manipulate the child process.
Probably what happens is that your script, using subprocess.check_output(), will execute the child process and wait until it is finished before continuing. But since you are never able to communicate with it, it will finish with a non-zero return value which will raise the subprocess.CalledProcessError
Now, using grep as an example of a command that waits on the standard input to execute something (since I don't have an Android Virtual Device creator installed) you could do this:
#!/usr/bin/env python2.7
import subprocess
external_command = ['/bin/grep', 'StackOverflow']
input_to_send = '''Almost every body uses Facebook
You can also say that about Google
But you can find an answer on StackOverflow
Even if you're an old programmer
'''
child_process = subprocess.Popen(args=external_command,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
universal_newlines=True)
stdout_from_child, stderr_from_child = child_process.communicate(input_to_send)
print "Output from child process:", stdout_from_child
child_process.wait()
It will print "Output from child process: But you can find an answer on StackOverflow", which is the output from grep.
In this example, I have
Used the class subprocess.Popen to create an handle to the child process
Setting arguments stdin and stdout with the value subprocess.PIPE to enables us to communicate later on with this process.
Used its .communicate() method to send a string to its standard input. In the same step, I retrieved its standard output and standard error output.
Printed the standard output retrieved in the last step (just so to show that it is actually working)
Waited that this child process is finished
In Python 3.5, it's even simpler:
#!/usr/bin/env python3.5
import subprocess
external_command = ['/bin/grep', 'StackOverflow']
input_to_send = '''Almost every body uses Facebook
You can also say that about Google
But you can find an answer on StackOverflow
Even if you're an old programmer
'''
completed_process_result = subprocess.run(args=external_command,
input=input_to_send,
stdout=subprocess.PIPE,
universal_newlines=True)
print("Output from child process:", completed_process_result.stdout)
In this example, I have:
Used the module function subprocess.run() to execute the command.
The input argument is the string we send to the standard input of the child process
The return value is used later on to retreive the output of the child process
Now you have to adapt this code to your situation.
So I was trying to use rpush for the push notifications of my mobile app using this gem : https://github.com/rpush/rpush. I'm using the sinatra framework. But I keep getting this error, even though I wrote --> require 'rpush' on the top of my file. Can someone experienced in ruby help me? I'm new to ruby so please bear with me. Here's my code
require 'rpush'
Module Notifier
def rpush_client
app = Rpush::Gcm::App.new
app.name = "App-Name"
app.auth_key = "XXXXXXXXXXXXXXX"
app.connections = 1
app.save!
end
def notify(user_id,alert)
rpush_client
session = db_find_one('dbname.sessions',{user_id: user_id})
if session.present?
device = session['devices'].first
token = device['device_token']
n = Rpush::Gcm::Notification.new
n.app = Rpush::Gcm::App.find_by_name("App-Name")
n.registration_ids = ["token", token]
n.data = { message: alert }
n.save!
Rpush.push
end
end
end
I know its a dumb question but tired of looking for it on here.
Are you using bundler with Sinatra? If so, you shouldn't need to explicitly require Rpush. See here: http://bundler.io/sinatra.html
What I did wrong
In my case, I just installed rpush with
bundle add rpush
and faced the same error.
What I should've done
As clearly mentioned in README.md, you have to run the following command after add rpush to Gemfile:
$ bundle
$ bundle exec rpush init
What you'll get for correct installation
This will generate config/initializers/rpush.rb and (lots of) migration files.
You may want to run migration after that.
Tips
I recommend to use rpush-redis.
You don't need to run extra migrations with it. You can safely get rid of garbage migration files.
config.redis_options may resemble the following:
Rpush.configure do |config|
# Supported clients are :active_record and :redis
config.client = :redis
# Options passed to Redis.new
config.redis_options = {
url: ENV.fetch('REDIS_URL', 'redis://localhost:6379/0'),
password: ENV.fetch('REDIS_PASSWORD', '')
}
end
Making an app at the moment for my personal use (rooted) and it requires getting certain pixels colors from the screen. I was trying to accomplish this through the Runtime.
Process p = Runtime.getRuntime().exec("screencap");
p.waitFor();
InputStream is = p.getInputStream()
BitmapFactory.decodeStream(is);
and I get factory returned null.
but if I dump the process to my sd card through adb -d shell screencap /sdcard/ss.dump and access it from my app
BitmapFactory.decodeFile("/sdcard/ss.dump");
all goes well.
So it there anyway to dump the stream straight into BitmapFactory within my app?
Thanks SO and please excuse the generally laziness/shortcuts of the example code.
This might help if not too far off your intended path. (I think you are using node / javascript). I spawned the ADB.EXE command producing a stream (and being 'jailed' on Windows the program must transform the stream to account for linefeed ending differences. So with that, I have working the following:
exports.capture = function(filename) {
// you'll need to map your requirement (decodeStream) instead
// of streaming to a file
var strm = fs.createWriteStream(path);
var cv = new Convert();
cv.pipe(strm);
var capture = spawn(cmd, args);
capture.stdout.on('data', function(data) {
cv.write(data);
});
capture.stdout.on('exit', function(data) {
cv.end();
});
}
To explain the process, spawn is running the ADB command, on windows, CR-LF are inserted (being a PNG stream), and stream is chunked / piped through a fs-transformation. Others on the web have described the process as adb.exe shell screencap -p | sed 's/\r$//' > output.file. And it does work. To be clear the conversion is CR-CR-LF => LF for us window jailed birds. And if you don't want to implement a SED and not use javascript regular expressions converting binary->strings->binary, you may follow my path. There is probably a simpler way, I just don't know what it is...
So, Convert() is an object with methods that converts the byte stream on the fly.
See the codewind blog link is: http://codewinds.com/blog/2013-08-20-nodejs-transform-streams.html to build your convert.
When using screencap from inside an app you must use su, i.e. root. When you do this via adb it runs under a different user id, which has more permissions than a normal Android app.
There are several examples how to use screencap, e.g. here.
Before API level 11, it was possible to set the content of the clipboard by using the service program on the adb shell:
service call SERVICE CODE [i32 INT | s16 STR] ...
Options:
i32: Write the integer INT into the send parcel.
s16: Write the UTF-16 string STR into the send parcel.
There were three integer codes to define the methods:
1 TRANSACTION_getClipboardText
2 TRANSACTION_setClipboardText
3 TRANSACTION_hasClipboardText
For instance this command
$ adb shell service call clipboard 2 i32 1 i32 1 s16 "Hello Android!"
set the clipboard's content to "Hello Android!". As of API level 11 the listed methods are deprecated and the new ones take ClipData as an argument. How do you set the clipboard content now via adb shell?
You've asked two different questions here. The service calls are not related to the API functions.
Android is in general overly-aggressive about marking APIs as deprecated. In this case, it only means that there are new functions with more functionality. The functionality of getText(), hasText(), and setText() still exists and those functions will continue to work, but they are now implemented as trivial wrappers around the newer functions.
As far as the service calls go, those are an internal implementation detail and as you've noticed are not guaranteed to work across Android versions. If you peer into the Android source code, you'll find these transactions currently defined:
TRANSACTION_setPrimaryClip = 1
TRANSACTION_getPrimaryClip = 2
TRANSACTION_getPrimaryClipDescription = 3
TRANSACTION_hasPrimaryClip = 4
TRANSACTION_addPrimaryClipChangedListener = 5
TRANSACTION_removePrimaryClipChangedListener = 6
TRANSACTION_hasClipboardText = 7
The source code also indicates what parameters these transactions require. Unfortunately, TRANSACTION_setPrimaryClip requires a ClipData, which is not an i32 or an s16 and thus is not compatible with service call. We have bigger problems than that however; these transactions require the calling package name as a parameter, and the clipboard service validates that the specified package name matches the calling uid. When using the adb shell, the calling uid is either UID_ROOT or UID_SHELL, neither of which owns any packages, so there is no way to pass that check. Simply put, the new clipboard service cannot be used this way.
What can you do about all this? You can create your own service for manipulating the clipboard from the commandline and install it onto your device. I don't know if there's any way to extend service call, but you can use am startservice as a suitable replacement. If you've created and installed that custom clipboard service, then you could invoke it as:
am startservice -a MySetClipboard -e text "clipboard text"
The code to implement this service could look like this:
public MyService extends Service {
public int onStartCommand(Intent intent, int flags, int startId) {
String text = intent.getStringExtra("text");
ClipboardManager.setText(text);
stopSelf();
return START_NOT_STICKY;
}
}
The service should have an intent-filter that declares it capable of handling the MySetClipboard intent action.
I found a way to do this using com.tengu.sharetoclipboard. You install it with F-Droid, then you start it with am over adb with the following arguments:
adb shell am start-activity \
-a android.intent.action.SEND \
-e android.intent.extra.TEXT <sh-escaped-text> \
-t 'text/plain' \
com.tengu.sharetoclipboard
<sh-escaped-text> is the new contents of the android clipboard. Note that this text must be escaped so that it is not interpreted specially by sh on the remote end. In practice, that means surrounding it with single quotes and replacing all single quotes with '\''. For instance, this would work fine if the local shell is fish:
adb shell am start-activity \
-a android.intent.action.SEND \
-e android.intent.extra.TEXT '\'I\'\\\'\'m pretty sure $HOME is set.\'' \
-t 'text/plain' \
com.tengu.sharetoclipboard
After fish parses it, the argument is 'I'\''m pretty sure $HOME is set.'. After sh parses it, the argument is I'm pretty sure $HOME is set..
Here's a python script to simplify this process:
#!/usr/bin/env python
import sys
import os
def shsafe(s):
return "'" + s.replace("'", "'\\''") + "'"
def exec_adb(text):
os.execvp('adb', [
'adb', 'shell', 'am', 'start-activity',
'-a', 'android.intent.action.SEND',
'-e', 'android.intent.extra.TEXT', shsafe(text),
'-t', 'text/plain',
'com.tengu.sharetoclipboard',
])
if sys.stdin.isatty():
if len(sys.argv) >= 2:
exec_adb(' '.join(sys.argv[1:]))
else:
sys.stderr.write(
'''Send something to the android clipboard over ADB. Requires
com.tengu.sharetoclipboard.
acb <text>
<some command> | acb
acb <some_text_file.txt''')
exit(1)
else:
exec_adb(sys.stdin.read())
I wanto to inject mouse event into an running application ( like clicking and moving the mouse pointer). Is it possible with monkey tool ? If yes, can you give me a roadmap about how to use monkey tool in android application development platform ?
I am driving apps in emulators using Monkey.
I have a Python script that gets fed to the monkeyrunner tool. This is largely coming from the sample on the Android developer website.
It is possible to invoke activities and respond to them.
For instance here I invoke an activity, which in turn runs another one; at that point I enter 4 digits and 'click' the coordinates 100,400 on the screen corresponding to the 'ok' button.
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice
if __name__ == "__main__":
# Connects to the current device, returning a MonkeyDevice object
device = MonkeyRunner.waitForConnection()
# Installs the Android package. Notice that this method returns a boolean, so you can test
# to see if the installation worked.
# device.installPackage('myproject/bin/MyApplication.apk')
# sets a variable with the package's internal name
package = 'com.mypackage'
# sets a variable with the name of an Activity in the package
activity = 'com.mypackage.myactivity'
# sets the name of the component to start
runComponent = package + '/' + activity
# Runs the component
device.startActivity(component=runComponent)
if device.getProperty('am.current.comp.class') == 'com.mypackage.anotheractivity':
device.type('0000')
# Click the 'Enter' button on screen at coordinates 100,400
device.touch(100, 600, 'DOWN_AND_UP')
I hope that helps.