20

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?

Konrad Reiche
  • 27,743
  • 15
  • 106
  • 143

2 Answers2

11

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.

j__m
  • 9,392
  • 1
  • 32
  • 56
  • It *is* possible to retrieve the clipboard contents in an non-obvious way. First determine the user ID (by looking in `/data/data/` or `adb shell ps | grep moz`) and then use `su` to change user: `adb shell su u0_a99 service call clipboard 2 s16 org.mozilla.firefox_beta`. This returns an ugly Parcel (with traces of UTF-16?). I have not tried to set something yet for the reasons you mentioned. – Lekensteyn Sep 09 '13 at 15:31
  • that's a good point - if you have root access on the device, you _can_ get around the uid issue using `su`. but yeah, there's still no way to construct a `ClipData` using the `service call` command. anyone interested in this is better off installing their own service and invoking that instead. it's trivial, as evidenced by the fact that i wrote one while answering the question and included the source. – j__m Sep 10 '13 at 02:16
  • 1
    I've created such a service and since it's working well I've put it on GitHub to be available for others: https://github.com/micer/clipboard-service. – Micer Apr 26 '18 at 15:24
2

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())
enigmaticPhysicist
  • 1,518
  • 16
  • 21
  • Works nicely, the only problem I have with it is the fact that the intent won't auto-close again. – Martin Braun Jul 03 '21 at 00:02
  • auto-close? You mean sharetoclipboard is still running after it fills the clipboard? How do you know, because I couldn't see it anywhere. It just sent a notification, then disappeared. – enigmaticPhysicist Jul 04 '21 at 16:19
  • Yes, but oddly enough, it only happens on my Pixel 3A. I also test on a POS device and there I only get a notification. On the Pixel 3A the sharetoclipboard app also opens. Not big of a deal, though. – Martin Braun Jul 04 '21 at 19:51