Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6ac56f73 authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

Merge "Separate the emulator specific part of ClipboardService.java" am: ab750baa am: 3ab180a9

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1663847

Change-Id: I981ec0a7542528ea00136a7e2ba18c2aa8a6d581
parents 5e543d37 3ab180a9
Loading
Loading
Loading
Loading
+10 −154
Original line number Diff line number Diff line
@@ -52,10 +52,6 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.system.VmSocketAddress;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
@@ -67,128 +63,9 @@ import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.wm.WindowManagerInternal;

import java.io.FileDescriptor;
import java.io.InterruptedIOException;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

// The following class is Android Emulator specific. It is used to read and
// write contents of the host system's clipboard.
class HostClipboardMonitor implements Runnable {
    public interface HostClipboardCallback {
        void onHostClipboardUpdated(String contents);
    }

    private FileDescriptor mPipe = null;
    private HostClipboardCallback mHostClipboardCallback;
    private static final String PIPE_NAME = "pipe:clipboard";
    private static final int HOST_PORT = 5000;

    private static byte[] createOpenHandshake() {
        // String.getBytes doesn't include the null terminator,
        // but the QEMU pipe device requires the pipe service name
        // to be null-terminated.

        final byte[] bits = Arrays.copyOf(PIPE_NAME.getBytes(), PIPE_NAME.length() + 1);
        bits[PIPE_NAME.length()] = 0;
        return bits;
    }

    private boolean openPipe() {
        try {
            final FileDescriptor fd = Os.socket(OsConstants.AF_VSOCK, OsConstants.SOCK_STREAM, 0);

            try {
                Os.connect(fd, new VmSocketAddress(HOST_PORT, OsConstants.VMADDR_CID_HOST));

                final byte[] handshake = createOpenHandshake();
                Os.write(fd, handshake, 0, handshake.length);
                mPipe = fd;
                return true;
            } catch (ErrnoException | SocketException | InterruptedIOException e) {
                Os.close(fd);
            }
        } catch (ErrnoException e) {
        }

        return false;
    }

    private void closePipe() {
        try {
            final FileDescriptor fd = mPipe;
            mPipe = null;
            if (fd != null) {
                Os.close(fd);
            }
        } catch (ErrnoException ignore) {
        }
    }

    private byte[] receiveMessage() throws ErrnoException, InterruptedIOException {
        final byte[] lengthBits = new byte[4];
        Os.read(mPipe, lengthBits, 0, lengthBits.length);

        final ByteBuffer bb = ByteBuffer.wrap(lengthBits);
        bb.order(ByteOrder.LITTLE_ENDIAN);
        final int msgLen = bb.getInt();

        final byte[] msg = new byte[msgLen];
        Os.read(mPipe, msg, 0, msg.length);

        return msg;
    }

    private void sendMessage(byte[] msg) throws ErrnoException, InterruptedIOException {
        final byte[] lengthBits = new byte[4];
        final ByteBuffer bb = ByteBuffer.wrap(lengthBits);
        bb.order(ByteOrder.LITTLE_ENDIAN);
        bb.putInt(msg.length);

        Os.write(mPipe, lengthBits, 0, lengthBits.length);
        Os.write(mPipe, msg, 0, msg.length);
    }

    public HostClipboardMonitor(HostClipboardCallback cb) {
        mHostClipboardCallback = cb;
    }

    @Override
    public void run() {
        while (!Thread.interrupted()) {
            try {
                // There's no guarantee that QEMU pipes will be ready at the moment
                // this method is invoked. We simply try to get the pipe open and
                // retry on failure indefinitely.
                while ((mPipe == null) && !openPipe()) {
                    Thread.sleep(100);
                }

                final byte[] receivedData = receiveMessage();
                mHostClipboardCallback.onHostClipboardUpdated(
                    new String(receivedData));
            } catch (ErrnoException | InterruptedIOException e) {
                closePipe();
            } catch (InterruptedException e) {
            }
        }
    }

    public void setHostClipboard(String content) {
        try {
            if (mPipe != null) {
                sendMessage(content.getBytes());
            }
        } catch (ErrnoException | InterruptedIOException e) {
            Slog.e("HostClipboardMonitor",
                   "Failed to set host clipboard " + e.getMessage());
        }
    }
}
import java.util.function.Consumer;

/**
 * Implementation of the clipboard for copy and paste.
@@ -214,8 +91,7 @@ public class ClipboardService extends SystemService {
    private final ContentCaptureManagerInternal mContentCaptureInternal;
    private final AutofillManagerInternal mAutofillInternal;
    private final IBinder mPermissionOwner;
    private HostClipboardMonitor mHostClipboardMonitor = null;
    private Thread mHostMonitorThread = null;
    private final Consumer<ClipData> mEmulatorClipboardMonitor;

    private final SparseArray<PerUserClipboard> mClipboards = new SparseArray<>();

@@ -237,22 +113,13 @@ public class ClipboardService extends SystemService {
        final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard");
        mPermissionOwner = permOwner;
        if (IS_EMULATOR) {
            mHostClipboardMonitor = new HostClipboardMonitor(
                new HostClipboardMonitor.HostClipboardCallback() {
                    @Override
                    public void onHostClipboardUpdated(String contents){
                        ClipData clip =
                            new ClipData("host clipboard",
                                         new String[]{"text/plain"},
                                         new ClipData.Item(contents));
                        synchronized(mClipboards) {
                            setPrimaryClipInternal(getClipboard(0), clip,
                                    android.os.Process.SYSTEM_UID);
                        }
            mEmulatorClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
                synchronized (this) {
                    setPrimaryClipInternal(getClipboard(0), clip, android.os.Process.SYSTEM_UID);
                }
            });
            mHostMonitorThread = new Thread(mHostClipboardMonitor);
            mHostMonitorThread.start();
        } else {
            mEmulatorClipboardMonitor = (clip) -> {};
        }
    }

@@ -547,18 +414,7 @@ public class ClipboardService extends SystemService {
    }

    void setPrimaryClipInternal(@Nullable ClipData clip, int uid) {
        // Push clipboard to host, if any
        if (mHostClipboardMonitor != null) {
            if (clip == null) {
                // Someone really wants the clipboard cleared, so push empty
                mHostClipboardMonitor.setHostClipboard("");
            } else if (clip.getItemCount() > 0) {
                final CharSequence text = clip.getItemAt(0).getText();
                if (text != null) {
                    mHostClipboardMonitor.setHostClipboard(text.toString());
                }
            }
        }
        mEmulatorClipboardMonitor.accept(clip);

        // Update this user
        final int userId = UserHandle.getUserId(uid);
+168 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.clipboard;

import android.annotation.Nullable;
import android.content.ClipData;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.system.VmSocketAddress;
import android.util.Slog;

import java.io.FileDescriptor;
import java.io.InterruptedIOException;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.function.Consumer;

// The following class is Android Emulator specific. It is used to read and
// write contents of the host system's clipboard.
class EmulatorClipboardMonitor implements Consumer<ClipData> {
    private static final String TAG = "EmulatorClipboardMonitor";
    private static final String PIPE_NAME = "pipe:clipboard";
    private static final int HOST_PORT = 5000;
    private final Thread mHostMonitorThread;
    private FileDescriptor mPipe = null;

    private static byte[] createOpenHandshake() {
        // String.getBytes doesn't include the null terminator,
        // but the QEMU pipe device requires the pipe service name
        // to be null-terminated.

        final byte[] bits = Arrays.copyOf(PIPE_NAME.getBytes(), PIPE_NAME.length() + 1);
        bits[PIPE_NAME.length()] = 0;
        return bits;
    }

    private boolean isPipeOpened() {
        return mPipe != null;
    }

    private synchronized boolean openPipe() {
        if (mPipe != null) {
            return true;
        }

        try {
            final FileDescriptor fd = Os.socket(OsConstants.AF_VSOCK, OsConstants.SOCK_STREAM, 0);

            try {
                Os.connect(fd, new VmSocketAddress(HOST_PORT, OsConstants.VMADDR_CID_HOST));

                final byte[] handshake = createOpenHandshake();
                Os.write(fd, handshake, 0, handshake.length);
                mPipe = fd;
                return true;
            } catch (ErrnoException | SocketException | InterruptedIOException e) {
                Os.close(fd);
            }
        } catch (ErrnoException e) {
        }

        return false;
    }

    private synchronized void closePipe() {
        try {
            final FileDescriptor fd = mPipe;
            mPipe = null;
            if (fd != null) {
                Os.close(fd);
            }
        } catch (ErrnoException ignore) {
        }
    }

    private byte[] receiveMessage() throws ErrnoException, InterruptedIOException {
        final byte[] lengthBits = new byte[4];
        Os.read(mPipe, lengthBits, 0, lengthBits.length);

        final ByteBuffer bb = ByteBuffer.wrap(lengthBits);
        bb.order(ByteOrder.LITTLE_ENDIAN);
        final int msgLen = bb.getInt();

        final byte[] msg = new byte[msgLen];
        Os.read(mPipe, msg, 0, msg.length);

        return msg;
    }

    private void sendMessage(final byte[] msg) throws ErrnoException, InterruptedIOException {
        final byte[] lengthBits = new byte[4];
        final ByteBuffer bb = ByteBuffer.wrap(lengthBits);
        bb.order(ByteOrder.LITTLE_ENDIAN);
        bb.putInt(msg.length);

        Os.write(mPipe, lengthBits, 0, lengthBits.length);
        Os.write(mPipe, msg, 0, msg.length);
    }

    EmulatorClipboardMonitor(final Consumer<ClipData> setAndroidClipboard) {
        this.mHostMonitorThread = new Thread(() -> {
            while (!Thread.interrupted()) {
                try {
                    // There's no guarantee that QEMU pipes will be ready at the moment
                    // this method is invoked. We simply try to get the pipe open and
                    // retry on failure indefinitely.
                    while (!openPipe()) {
                        Thread.sleep(100);
                    }

                    final byte[] receivedData = receiveMessage();

                    final String str = new String(receivedData);
                    final ClipData clip = new ClipData("host clipboard",
                                                       new String[]{"text/plain"},
                                                       new ClipData.Item(str));

                    setAndroidClipboard.accept(clip);
                } catch (ErrnoException | InterruptedIOException e) {
                    closePipe();
                } catch (InterruptedException | IllegalArgumentException e) {
                }
            }
        });

        this.mHostMonitorThread.start();
    }

    @Override
    public void accept(final @Nullable ClipData clip) {
        if (clip == null) {
            setHostClipboardImpl("");
        } else if (clip.getItemCount() > 0) {
            final CharSequence text = clip.getItemAt(0).getText();
            if (text != null) {
                setHostClipboardImpl(text.toString());
            }
        }
    }

    private void setHostClipboardImpl(final String value) {
        try {
            if (isPipeOpened()) {
                sendMessage(value.getBytes());
            }
        } catch (ErrnoException | InterruptedIOException e) {
            Slog.e(TAG, "Failed to set host clipboard " + e.getMessage());
        } catch (IllegalArgumentException e) {
        }
    }
}
+1 −0
Original line number Diff line number Diff line
per-file EmulatorClipboardMonitor.java = bohu@google.com,lfy@google.com,rkir@google.com