Multi display support in emulator
Add JNI interface to create surface in the native.
Inform host about new display through pipe.
BUG: 114842496 131702523 134178274
Test: Run the app, verify one more display window created on emulator
Change-Id: Ife00a778c0156853ca7e83f68cd57617cc22d0a2
diff --git a/MultiDisplayProvider/Android.mk b/MultiDisplayProvider/Android.mk
new file mode 100644
index 0000000..21918a8
--- /dev/null
+++ b/MultiDisplayProvider/Android.mk
@@ -0,0 +1,28 @@
+# Copyright (C) 2019 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+# Build the application.
+LOCAL_MODULE_TAGS := optional
+LOCAL_PRIVILEGED_MODULE := true
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_PACKAGE_NAME := MultiDisplayProvider
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_CERTIFICATE := platform
+LOCAL_RESOURCE_DIR = $(LOCAL_PATH)/res
+LOCAL_JNI_SHARED_LIBRARIES := libemulator_multidisplay_jni
+include $(BUILD_PACKAGE)
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/MultiDisplayProvider/AndroidManifest.xml b/MultiDisplayProvider/AndroidManifest.xml
new file mode 100644
index 0000000..591b189
--- /dev/null
+++ b/MultiDisplayProvider/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<!-- sharedUserId with system is required to allow putting activities
+ on virtual/secondary displays -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.emulator.multidisplay"
+ android:sharedUserId="android.uid.system" >
+
+ <uses-sdk android:minSdkVersion="19" />
+ <application android:label="@string/app_name"
+ android:persistent="true">
+ <receiver android:name=".MultiDisplayServiceReceiver" >
+ <intent-filter>
+ <action android:name="com.android.emulator.multidisplay.START" />
+ </intent-filter>
+ </receiver>
+
+ <service android:name=".MultiDisplayService"
+ android:label="@string/app_name"
+ android:exported="true">
+ </service>
+
+ </application>
+</manifest>
diff --git a/MultiDisplayProvider/jni/Android.mk b/MultiDisplayProvider/jni/Android.mk
new file mode 100644
index 0000000..7ab3c03
--- /dev/null
+++ b/MultiDisplayProvider/jni/Android.mk
@@ -0,0 +1,41 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ com_android_emulator_multidisplay.cpp
+
+LOCAL_C_INCLUDES += \
+ $(JNI_H_INCLUDE) \
+ system/core/base/include \
+
+LOCAL_SHARED_LIBRARIES := \
+ libandroid_runtime \
+ libnativehelper \
+ libcutils \
+ libutils \
+ liblog \
+ libui \
+ libgui \
+
+LOCAL_CFLAGS += -Wall -Wextra -Wno-unused-parameter
+
+LOCAL_MODULE := libemulator_multidisplay_jni
+LOCAL_MODULE_TAGS := optional
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/MultiDisplayProvider/jni/com_android_emulator_multidisplay.cpp b/MultiDisplayProvider/jni/com_android_emulator_multidisplay.cpp
new file mode 100644
index 0000000..350b471
--- /dev/null
+++ b/MultiDisplayProvider/jni/com_android_emulator_multidisplay.cpp
@@ -0,0 +1,176 @@
+/*
+**
+** Copyright 2017, 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.
+*/
+
+#define LOG_TAG "android_emulator_multidisplay_JNI"
+#include <gui/BufferQueue.h>
+#include <gui/BufferItemConsumer.h>
+#include <gui/Surface.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/SurfaceComposerClient.h>
+#include <ui/DisplayInfo.h>
+
+#include <sys/epoll.h>
+
+#include "utils/Log.h"
+#include "../../include/qemu_pipe.h"
+#include "nativehelper/JNIHelp.h"
+#include <nativehelper/ScopedLocalRef.h>
+#include "jni.h"
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/android_view_Surface.h"
+#include "../../../goldfish-opengl/shared/OpenglCodecCommon/gralloc_cb.h"
+
+#define MAX_DISPLAYS 10
+
+using namespace android;
+
+static int gFd = 0;
+
+struct FrameListener : public ConsumerBase::FrameAvailableListener {
+ sp<BufferItemConsumer> mConsumer;
+ uint32_t mId;
+ uint32_t mCb;
+public:
+ void onFrameAvailable(const BufferItem& item) override {
+ BufferItem bufferItem;
+ mConsumer->acquireBuffer(&bufferItem, 0);
+ ANativeWindowBuffer* b = bufferItem.mGraphicBuffer->getNativeBuffer();
+ if (b && b->handle) {
+ cb_handle_t* cb = (cb_handle_t*)(b->handle);
+ if (mCb != cb->hostHandle) {
+ ALOGI("sent cb %d", cb->hostHandle);
+ mCb = cb->hostHandle;
+ std::vector<uint32_t> data = {8, mId, mCb};
+ WriteFully(gFd, data.data(), data.size() * sizeof(uint32_t));
+ }
+ }
+ else {
+ ALOGE("cannot get native buffer handler");
+ }
+ mConsumer->releaseBuffer(bufferItem);
+ }
+ void setDefaultBufferSize(uint32_t w, uint32_t h) {
+ mConsumer->setDefaultBufferSize(w, h);
+ }
+ FrameListener(sp<BufferItemConsumer>& consumer, uint32_t id)
+ : mConsumer(consumer), mId(id), mCb(0) { }
+};
+
+sp<FrameListener> gFrameListener[MAX_DISPLAYS + 1];
+
+static jobject nativeCreateSurface(JNIEnv *env, jobject obj, jint id, jint width, jint height)
+{
+ ALOGI("create surface for %d", id);
+ // Create surface for this new display
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ sp<BufferItemConsumer> bufferItemConsumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+ bufferItemConsumer = new BufferItemConsumer(consumer, GRALLOC_USAGE_HW_RENDER);
+ gFrameListener[id] = new FrameListener(bufferItemConsumer, id);
+ gFrameListener[id]->setDefaultBufferSize(width, height);
+ bufferItemConsumer->setFrameAvailableListener(gFrameListener[id]);
+ return android_view_Surface_createFromIGraphicBufferProducer(env, producer);
+}
+
+static jint nativeOpen(JNIEnv* env, jobject obj) {
+ // Open pipe
+ gFd = qemu_pipe_open("multidisplay");
+ if (gFd < 0) {
+ ALOGE("Error opening multidisplay pipe %d", gFd);
+ } else {
+ ALOGI("multidisplay pipe connected!!!");
+ }
+ return gFd;
+}
+
+static bool nativeReadPipe(JNIEnv* env, jobject obj, jintArray arr) {
+ static const uint8_t ADD = 1;
+ static const uint8_t DEL = 2;
+ int* arrp = env->GetIntArrayElements(arr, 0);
+ uint32_t length;
+ ReadFully(gFd, &length, sizeof(length));
+ std::vector<uint8_t> args(length, 0);
+ ReadFully(gFd, args.data(), (size_t)length);
+ switch(args[0]) {
+ case ADD: {
+ ALOGV("received add event");
+ *arrp = ADD;
+ for (int i = 1; i < 6; i++) {
+ *(arrp + i) = *(uint32_t*)(&args[(i - 1) * 4 + 1]);
+ }
+ env->ReleaseIntArrayElements(arr, arrp, JNI_COMMIT);
+ break;
+ }
+ case DEL: {
+ ALOGV("received del event");
+ *arrp = DEL;
+ *(arrp + 1) = *(uint32_t*)(&args[1]);
+ env->ReleaseIntArrayElements(arr, arrp, JNI_COMMIT);
+ break;
+ }
+ default: {
+ ALOGE("unexpected event %d", args[0]);
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool nativeReleaseListener(JNIEnv* env, jobject obj, jint id) {
+ if (gFrameListener[id].get()) {
+ ALOGV("clear gFrameListener %d", id);
+ gFrameListener[id].clear();
+ gFrameListener[id] = nullptr;
+ }
+ return true;
+}
+
+static bool nativeResizeListener(JNIEnv* env, jobject obj, jint id, jint w, jint h) {
+ if (gFrameListener[id]) {
+ gFrameListener[id]->setDefaultBufferSize(w, h);
+ return true;
+ }
+ return false;
+}
+
+static JNINativeMethod sMethods[] = {
+ { "nativeOpen", "()I", (void*) nativeOpen },
+ { "nativeCreateSurface", "(III)Landroid/view/Surface;", (void*) nativeCreateSurface },
+ { "nativeReadPipe", "([I)Z", (void*) nativeReadPipe},
+ { "nativeReleaseListener", "(I)Z", (void*) nativeReleaseListener},
+ { "nativeResizeListener", "(III)Z", (void*) nativeResizeListener},
+};
+
+/*
+ * JNI Initialization
+ */
+jint JNI_OnLoad(JavaVM *jvm, void *reserved)
+{
+ JNIEnv *env;
+
+ // Check JNI version
+ if (jvm->GetEnv((void **)&env, JNI_VERSION_1_6)) {
+ ALOGE("JNI version mismatch error");
+ return JNI_ERR;
+ }
+
+ jniRegisterNativeMethods(env, "com/android/emulator/multidisplay/MultiDisplayService",
+ sMethods, NELEM(sMethods));
+
+ return JNI_VERSION_1_6;
+}
diff --git a/MultiDisplayProvider/res/values/strings.xml b/MultiDisplayProvider/res/values/strings.xml
new file mode 100644
index 0000000..dfe8d68
--- /dev/null
+++ b/MultiDisplayProvider/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="app_name">Emulator Multi Display Provider</string>
+</resources>
diff --git a/MultiDisplayProvider/src/com/android/emulator/multidisplay/MultiDisplayService.java b/MultiDisplayProvider/src/com/android/emulator/multidisplay/MultiDisplayService.java
new file mode 100644
index 0000000..608b590
--- /dev/null
+++ b/MultiDisplayProvider/src/com/android/emulator/multidisplay/MultiDisplayService.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2018 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.emulator.multidisplay;
+
+import android.content.Context;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.Display;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.view.Surface;
+import android.os.Messenger;
+import android.os.Message;
+
+
+public class MultiDisplayService extends Service {
+ private static final String TAG = "MultiDisplayService";
+ private static final String DISPLAY_NAME = "Emulator 2D Display";
+ private static final String[] UNIQUE_DISPLAY_ID = new String[]{"notUsed", "1234562",
+ "1234563", "1234564",
+ "1234565", "1234566",
+ "1234567", "1234568",
+ "1234569", "1234570",
+ "1234571"};
+ private static final int MAX_DISPLAYS = 10;
+ private static final int ADD = 1;
+ private static final int DEL = 2;
+ private static final int mFlags = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC |
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY |
+ 1 << 6 |//DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
+ 1 << 9; //DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
+ private DisplayManager mDisplayManager;
+ private VirtualDisplay mVirtualDisplay[];
+ private Surface mSurface[];
+ private Messenger mMessenger;
+ private ListenerThread mListner;
+
+ private final Handler mHandler = new Handler();
+
+ class MultiDisplay {
+ public int width;
+ public int height;
+ public int dpi;
+ public int flag;
+ public VirtualDisplay virtualDisplay;
+ public Surface surface;
+ public boolean enabled;
+ MultiDisplay() {
+ clear();
+ }
+ public void clear() {
+ width = 0;
+ height = 0;
+ dpi = 0;
+ flag = 0;
+ virtualDisplay = null;
+ surface = null;
+ enabled = false;
+ }
+ public void set(int w, int h, int d, int f) {
+ width = w;
+ height = h;
+ dpi = d;
+ flag = f;
+ enabled = true;
+ }
+ public boolean match(int w, int h, int d, int f) {
+ return (w == width && h == height && d == dpi && f == flag);
+ }
+ }
+ private MultiDisplay mMultiDisplay[];
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ try {
+ System.loadLibrary("emulator_multidisplay_jni");
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to loadLibrary: " + e);
+ }
+
+ mListner = new ListenerThread();
+ mListner.start();
+
+ mDisplayManager = (DisplayManager)getSystemService(Context.DISPLAY_SERVICE);
+ mMultiDisplay = new MultiDisplay[MAX_DISPLAYS + 1];
+ for (int i = 0; i < MAX_DISPLAYS + 1; i++) {
+ mMultiDisplay[i] = new MultiDisplay();
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if(mMessenger == null)
+ mMessenger = new Messenger(mHandler);
+ return mMessenger.getBinder();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ // keep it alive.
+ return START_STICKY;
+ }
+
+ class ListenerThread extends Thread {
+ ListenerThread() {
+ super(TAG);
+ }
+
+ private void deleteVirtualDisplay(int displayId) {
+ int i = displayId;
+ if (mMultiDisplay[i].enabled == false) {
+ return;
+ }
+ if (mMultiDisplay[i].virtualDisplay != null) {
+ mMultiDisplay[i].virtualDisplay.release();
+ }
+ if (mMultiDisplay[i].surface != null) {
+ mMultiDisplay[i].surface.release();
+ }
+ mMultiDisplay[i].clear();
+ nativeReleaseListener(i);
+ }
+
+ private void createVirtualDisplay(int displayId, int w, int h, int dpi, int flag) {
+ int i = displayId;
+ mMultiDisplay[i].surface = nativeCreateSurface(i, w, h);
+ mMultiDisplay[i].virtualDisplay = mDisplayManager.createVirtualDisplay(
+ null /* projection */,
+ DISPLAY_NAME, w, h, dpi,
+ mMultiDisplay[i].surface, flag,
+ null /* callback */,
+ null /* handler */,
+ UNIQUE_DISPLAY_ID[i]);
+ mMultiDisplay[i].set(w, h, dpi, flag);
+ }
+
+ private void addVirtualDisplay(int displayId, int w, int h, int dpi, int flag) {
+ int i = displayId;
+ if (mMultiDisplay[i].match(w, h, dpi, flag)) {
+ return;
+ }
+ if (mMultiDisplay[i].virtualDisplay == null) {
+ createVirtualDisplay(i, w, h, dpi, flag);
+ return;
+ }
+ if (mMultiDisplay[i].flag != flag) {
+ deleteVirtualDisplay(i);
+ createVirtualDisplay(i, w, h, dpi, flag);
+ return;
+ }
+ if (mMultiDisplay[i].width != w || mMultiDisplay[i].height != h) {
+ nativeResizeListener(i, w, h);
+ }
+ // only dpi changes
+ mMultiDisplay[i].virtualDisplay.resize(w, h, dpi);
+ mMultiDisplay[i].set(w, h, dpi, flag);
+ }
+
+ @Override
+ public void run() {
+ while(nativeOpen() <= 0) {
+ Log.e(TAG, "failed to open multiDisplay pipe, retry");
+ }
+ while(true) {
+ int[] array = {0, 0, 0, 0, 0, 0};
+ if (!nativeReadPipe(array)) {
+ continue;
+ }
+ switch (array[0]) {
+ case ADD: {
+ for (int j = 0; j < 6; j++) {
+ Log.d(TAG, "received " + array[j]);
+ }
+ int i = array[1];
+ int width = array[2];
+ int height = array[3];
+ int dpi = array[4];
+ int flag = (array[5] != 0) ? array[5] : mFlags;
+ if (i < 1 || i > MAX_DISPLAYS || width <=0 || height <=0 || dpi <=0
+ || flag < 0) {
+ Log.e(TAG, "invalid parameters for add/modify display");
+ break;
+ }
+ addVirtualDisplay(i, width, height, dpi, flag);
+ break;
+ }
+ case DEL: {
+ int i = array[1];
+ Log.d(TAG, "DEL " + i);
+ if (i < 1 || i > MAX_DISPLAYS) {
+ Log.e(TAG, "invalid parameters for delete display");
+ break;
+ }
+ deleteVirtualDisplay(i);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private native int nativeOpen();
+ private native Surface nativeCreateSurface(int displayId, int width, int height);
+ private native boolean nativeReadPipe(int[] arr);
+ private native boolean nativeReleaseListener(int displayId);
+ private native boolean nativeResizeListener(int displayId, int with, int height);
+}
diff --git a/MultiDisplayProvider/src/com/android/emulator/multidisplay/MultiDisplayServiceReceiver.java b/MultiDisplayProvider/src/com/android/emulator/multidisplay/MultiDisplayServiceReceiver.java
new file mode 100644
index 0000000..c84c43a
--- /dev/null
+++ b/MultiDisplayProvider/src/com/android/emulator/multidisplay/MultiDisplayServiceReceiver.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 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.emulator.multidisplay;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+public class MultiDisplayServiceReceiver extends BroadcastReceiver {
+ private static final String TAG = "MultiDisplayServiceReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (isMyServiceRunning(MultiDisplayService.class, context) == false) {
+ Log.v(TAG, "startService");
+ context.startService(new Intent(context, MultiDisplayService.class));
+ }
+ }
+
+ private boolean isMyServiceRunning(Class<?> serviceClass, Context context) {
+ ActivityManager manager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
+ for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
+ if (serviceClass.getName().equals(service.service.getClassName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/include/qemu_pipe.h b/include/qemu_pipe.h
index 1056bb3..986c3da 100644
--- a/include/qemu_pipe.h
+++ b/include/qemu_pipe.h
@@ -30,6 +30,10 @@
# define D(...) do{}while(0)
#endif
+typedef int QEMU_PIPE_HANDLE;
+
+#define QEMU_PIPE_INVALID_HANDLE -1
+
static bool ReadFully(int fd, void* data, size_t byte_count) {
uint8_t* p = (uint8_t*)(data);
size_t remaining = byte_count;
@@ -109,4 +113,8 @@
return fd;
}
+static __inline__ bool
+qemu_pipe_valid(QEMU_PIPE_HANDLE pipe) {
+ return pipe >= 0;
+}
#endif /* ANDROID_INCLUDE_HARDWARE_QEMUD_PIPE_H */