Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set clipboard text via adb shell as of API level 11

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?

like image 837
Konrad Reiche Avatar asked Jan 09 '13 17:01

Konrad Reiche


2 Answers

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.

like image 100
j__m Avatar answered Nov 19 '22 15:11

j__m


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())
like image 42
enigmaticPhysicist Avatar answered Nov 19 '22 14:11

enigmaticPhysicist