diff options
author | Roman Kiryanov <rkir@google.com> | 2021-04-02 21:25:57 -0700 |
---|---|---|
committer | Roman Kiryanov <rkir@google.com> | 2021-04-08 18:09:18 -0700 |
commit | 3f7d23eff0b24366c9ced62539b838ec96bd2f3b (patch) | |
tree | 5f1a2cb9b914bd2e49a5772b11cc404090b1bbad | |
parent | 3dd880eca50cf379926fc26e9a5c0d50cc50f953 (diff) | |
download | base-3f7d23eff0b24366c9ced62539b838ec96bd2f3b.tar.gz |
Separate the emulator specific part of ClipboardService.java
This way the Android Studio Emulator will be the owner
of the emulator specific code and the framework will not
see the implemetattion details.
Bug: 182436079
Test: check if emulator exchanges the clipboard with the host
Change-Id: Icdb25d51437ba0f8e98a2750b0b705c15755f106
Merged-In: Icdb25d51437ba0f8e98a2750b0b705c15755f106
3 files changed, 179 insertions, 154 deletions
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 72160c203595..f4e06d377a83 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -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); - } - } - }); - mHostMonitorThread = new Thread(mHostClipboardMonitor); - mHostMonitorThread.start(); + mEmulatorClipboardMonitor = new EmulatorClipboardMonitor((clip) -> { + synchronized (this) { + setPrimaryClipInternal(getClipboard(0), clip, android.os.Process.SYSTEM_UID); + } + }); + } 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); diff --git a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java new file mode 100644 index 000000000000..62b701aff398 --- /dev/null +++ b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java @@ -0,0 +1,168 @@ +/* + * 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) { + } + } +} diff --git a/services/core/java/com/android/server/clipboard/OWNERS b/services/core/java/com/android/server/clipboard/OWNERS new file mode 100644 index 000000000000..5449df908051 --- /dev/null +++ b/services/core/java/com/android/server/clipboard/OWNERS @@ -0,0 +1 @@ +per-file EmulatorClipboardMonitor.java = bohu@google.com,lfy@google.com,rkir@google.com |