1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.emulator.multidisplay; 18 19 import android.app.Service; 20 import android.hardware.display.DisplayManager; 21 import android.hardware.display.VirtualDisplay; 22 import android.content.Intent; 23 import android.os.Handler; 24 import android.os.IBinder; 25 import android.os.Messenger; 26 import android.os.Parcel; 27 import android.os.RemoteException; 28 import android.os.ServiceManager; 29 import android.util.DebugUtils; 30 import android.util.Log; 31 import android.view.Surface; 32 33 import java.lang.Thread; 34 import java.io.FileDescriptor; 35 import java.io.PrintWriter; 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.List; 39 import java.util.stream.Collectors; 40 41 public final class MultiDisplayService extends Service { 42 private static final String TAG = "MultiDisplayService"; 43 private static final String DISPLAY_NAME = "Emulator 2D Display"; 44 private static final String[] UNIQUE_DISPLAY_ID = new String[]{"notUsed", "1234562", 45 "1234563", "1234564", 46 "1234565", "1234566", 47 "1234567", "1234568", 48 "1234569", "1234570", 49 "1234571"}; 50 private static final int MAX_DISPLAYS = 10; 51 private static final int ADD = 1; 52 private static final int DEL = 2; 53 // the following is used by resizabel to set display 54 // intentionally shifted 4 bits to avoid conflicting 55 // with existing multidisplay functions 56 private static final int SET_DISPLAY = 0x10; 57 58 private static final int FLAGS = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | 59 DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | 60 DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT | 61 DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED | 62 1 << 6 |//DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH 63 1 << 9; //DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; 64 65 private static final String SURFACE_COMPOSER_INTERFACE_KEY = "android.ui.ISurfaceComposer"; 66 private IBinder mSurfaceFlinger; 67 private DisplayManager mDisplayManager; 68 private VirtualDisplay mVirtualDisplay[]; 69 private Surface mSurface[]; 70 private Messenger mMessenger; 71 private ListenerThread mListener; 72 73 private final Handler mHandler = new Handler(); 74 75 final class MultiDisplay { 76 public int width; 77 public int height; 78 public int dpi; 79 public int flag; 80 public VirtualDisplay virtualDisplay; 81 public Surface surface; 82 public boolean enabled; 83 MultiDisplay()84 MultiDisplay() { 85 clear(); 86 } 87 clear()88 public void clear() { 89 width = 0; 90 height = 0; 91 dpi = 0; 92 flag = 0; 93 virtualDisplay = null; 94 surface = null; 95 enabled = false; 96 } 97 set(int w, int h, int d, int f)98 public void set(int w, int h, int d, int f) { 99 width = w; 100 height = h; 101 dpi = d; 102 flag = f; 103 enabled = true; 104 } 105 match(int w, int h, int d, int f)106 public boolean match(int w, int h, int d, int f) { 107 return (w == width && h == height && d == dpi && f == flag); 108 } 109 dump(PrintWriter writer, String prefix, boolean printHeader)110 private void dump(PrintWriter writer, String prefix, boolean printHeader) { 111 if (!enabled) { 112 if (printHeader) { 113 writer.println("disabled"); 114 } 115 return; 116 } 117 if (printHeader) { 118 writer.println("enabled"); 119 } 120 writer.printf("%sDimensions: %dx%d\n", prefix, width, height); 121 writer.printf("%sDPI: %d\n", prefix, dpi); 122 writer.printf("%sflags: %s\n", prefix, flagsToString(flag)); 123 writer.printf("%svirtualDisplay: %s\n", prefix, virtualDisplay); 124 writer.printf("%ssurface: %s\n", prefix, surface); 125 } 126 127 @Override toString()128 public String toString() { 129 return "MultiDisplay[dimensions=" + width + "x" + height 130 + ", dpi=" + dpi 131 + ", enabled=" + enabled 132 + ", flags=" + flagsToString(flag) 133 + ", virtualDisplay=" + virtualDisplay 134 + ", surface=" + surface 135 + "]"; 136 } 137 } 138 private MultiDisplay mMultiDisplay[]; 139 140 @Override onCreate()141 public void onCreate() { 142 Log.i(TAG, "Creating service"); 143 144 super.onCreate(); 145 146 try { 147 System.loadLibrary("emulator_multidisplay_jni"); 148 } catch (Exception e) { 149 Log.e(TAG, "Failed to loadLibrary: " + e); 150 } 151 152 mListener = new ListenerThread(); 153 mListener.start(); 154 155 mDisplayManager = getSystemService(DisplayManager.class); 156 mMultiDisplay = new MultiDisplay[MAX_DISPLAYS + 1]; 157 for (int i = 0; i < MAX_DISPLAYS + 1; i++) { 158 Log.d(TAG, "Creating display " + i); 159 mMultiDisplay[i] = new MultiDisplay(); 160 } 161 } 162 163 @Override onBind(Intent intent)164 public IBinder onBind(Intent intent) { 165 if(mMessenger == null) 166 mMessenger = new Messenger(mHandler); 167 return mMessenger.getBinder(); 168 } 169 170 @Override onStartCommand(Intent intent, int flags, int startId)171 public int onStartCommand(Intent intent, int flags, int startId) { 172 // keep it alive. 173 return START_STICKY; 174 } 175 176 @Override dump(FileDescriptor fd, PrintWriter writer, String[] args)177 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 178 if (args == null || args.length == 0) { 179 dump(writer); 180 } else { 181 runCmd(writer, args); 182 } 183 }; 184 dump(PrintWriter writer)185 private void dump(PrintWriter writer) { 186 writer.printf("Max displays: %d\n", MAX_DISPLAYS); 187 writer.printf("Unique display ids: %s\n", Arrays.toString(UNIQUE_DISPLAY_ID)); 188 writer.printf("Default flags: %s\n", flagsToString(FLAGS)); 189 dumpArray(writer, mVirtualDisplay, "virtual display"); 190 dumpArray(writer, mSurface, "surface"); 191 192 if (mMultiDisplay != null) { 193 int size = mMultiDisplay.length; 194 writer.printf("# of multi displays: %d\n", size); 195 for (int i = 0; i < size; i++) { 196 writer.printf(" MultiDisplay #%d: ", i); 197 mMultiDisplay[i].dump(writer, " ", /* printHeader= */ true); 198 } 199 } else { 200 writer.println("No multi display"); 201 } 202 } 203 dumpArray(PrintWriter writer, Object[] array, String name)204 private void dumpArray(PrintWriter writer, Object[] array, String name) { 205 if (array != null) { 206 int size = array.length; 207 writer.printf("# of %ss: %d\n", name, size); 208 for (int i = 0; i < size; i++) { 209 writer.printf(" %d: %s\n", i, array[i]); 210 } 211 } else { 212 writer.printf("No %s\n", name); 213 } 214 } 215 runCmd(PrintWriter writer, String[] args)216 private void runCmd(PrintWriter writer, String[] args) { 217 String cmd = args[0]; 218 219 switch (cmd) { 220 case "add": 221 runCmdAdd(writer, args); 222 break; 223 case "del": 224 runCmdDel(writer, args); 225 break; 226 case "list": 227 runCmdList(writer); 228 break; 229 default: 230 writer.printf("Invalid command: %s. Valid options are: \n", cmd); 231 case "help": 232 runCmdHelp(writer); 233 } 234 } 235 runCmdHelp(PrintWriter writer)236 private void runCmdHelp(PrintWriter writer) { 237 writer.println(" help - shows this help"); 238 writer.println(" list - list all virtual displays created by this tool"); 239 writer.println(" add <display_id> <width> <height> <dpi> <flags> - add a new virtual " 240 + "display with the given properties"); 241 writer.println(" del <display_id> - delete the given virtual display"); 242 } 243 runCmdList(PrintWriter writer)244 private void runCmdList(PrintWriter writer) { 245 if (mMultiDisplay == null) { 246 writer.println("No multi display"); 247 return; 248 } 249 250 List<MultiDisplay> enabledDisplays = Arrays.stream(mMultiDisplay).filter(d -> d.enabled) 251 .collect(Collectors.toList()); 252 253 if (enabledDisplays.isEmpty()) { 254 writer.println("No multi display added by the tool"); 255 return; 256 } 257 258 int size = enabledDisplays.size(); 259 writer.printf("%d display%s\n", size, (size == 1? "" : "s")); 260 for (int i = 0; i < size; i++) { 261 writer.printf("Display %d:\n", i); 262 enabledDisplays.get(i).dump(writer, " ", /* printHeader= */ false); 263 } 264 } 265 runCmdAdd(PrintWriter writer, String[] args)266 private void runCmdAdd(PrintWriter writer, String[] args) { 267 if (!hasExactlyArgs(writer, args, 6)) return; 268 269 int displayId = getIntArg(writer, args, 1); 270 int width = getIntArg(writer, args, 2); 271 int height = getIntArg(writer, args, 3); 272 int dpi = getIntArg(writer, args, 4); 273 int flags = getIntArg(writer, args, 5); 274 275 addVirtualDisplay(displayId, width, height, dpi, flags); 276 277 writer.printf("Display %d added \n", displayId); 278 } 279 runCmdDel(PrintWriter writer, String[] args)280 private void runCmdDel(PrintWriter writer, String[] args) { 281 if (!hasExactlyArgs(writer, args, 2)) return; 282 283 int displayId = getIntArg(writer, args, 1); 284 285 deleteVirtualDisplay(displayId); 286 287 writer.printf("Display %d deleted\n", displayId); 288 } 289 hasExactlyArgs(PrintWriter writer, String[] args, int expectedSize)290 private boolean hasExactlyArgs(PrintWriter writer, String[] args, int expectedSize) { 291 if (args.length != expectedSize) { 292 writer.printf("invalid number of arguments (%d) for command %s (expected %d).\n" 293 + "Valid command:\n", 294 args.length, args[0], expectedSize); 295 runCmdHelp(writer); 296 return false; 297 } 298 return true; 299 } 300 getIntArg(PrintWriter writer, String[] args, int index)301 private int getIntArg(PrintWriter writer, String[] args, int index) { 302 String value = "TBD"; 303 try { 304 value = args[index]; 305 return Integer.parseInt(value); 306 } catch (Exception e) { 307 throw new IllegalArgumentException("invalid integer at index " + index + ": " + value); 308 } 309 } 310 deleteVirtualDisplay(int displayId)311 private void deleteVirtualDisplay(int displayId) { 312 Log.d(TAG, "deleteVirtualDisplay(" + displayId + ")"); 313 if (!mMultiDisplay[displayId].enabled) { 314 return; 315 } 316 if (mMultiDisplay[displayId].virtualDisplay != null) { 317 mMultiDisplay[displayId].virtualDisplay.release(); 318 } 319 if (mMultiDisplay[displayId].surface != null) { 320 mMultiDisplay[displayId].surface.release(); 321 } 322 mMultiDisplay[displayId].clear(); 323 nativeReleaseListener(displayId); 324 } 325 createVirtualDisplay(int displayId, int w, int h, int dpi, int flag)326 private void createVirtualDisplay(int displayId, int w, int h, int dpi, int flag) { 327 mMultiDisplay[displayId].surface = nativeCreateSurface(displayId, w, h); 328 mMultiDisplay[displayId].virtualDisplay = mDisplayManager.createVirtualDisplay( 329 null /* projection */, 330 DISPLAY_NAME, w, h, dpi, 331 mMultiDisplay[displayId].surface, flag, 332 null /* callback */, 333 null /* handler */, 334 UNIQUE_DISPLAY_ID[displayId]); 335 mMultiDisplay[displayId].set(w, h, dpi, flag); 336 } 337 addVirtualDisplay(int displayId, int w, int h, int dpi, int flag)338 private void addVirtualDisplay(int displayId, int w, int h, int dpi, int flag) { 339 Log.d(TAG, "addVirtualDisplay(id=" + displayId + ", w=" + w + ", h=" + h 340 + ", dpi=" + dpi + ", flags=" + flagsToString(flag) + ")"); 341 if (mMultiDisplay[displayId].match(w, h, dpi, flag)) { 342 return; 343 } 344 if (mMultiDisplay[displayId].virtualDisplay == null) { 345 createVirtualDisplay(displayId, w, h, dpi, flag); 346 return; 347 } 348 if (mMultiDisplay[displayId].flag != flag) { 349 deleteVirtualDisplay(displayId); 350 createVirtualDisplay(displayId, w, h, dpi, flag); 351 return; 352 } 353 if (mMultiDisplay[displayId].width != w || mMultiDisplay[displayId].height != h) { 354 nativeResizeListener(displayId, w, h); 355 } 356 // only dpi changes 357 mMultiDisplay[displayId].virtualDisplay.resize(w, h, dpi); 358 mMultiDisplay[displayId].set(w, h, dpi, flag); 359 } 360 361 class ListenerThread extends Thread { ListenerThread()362 ListenerThread() { 363 super(TAG); 364 } 365 366 @Override run()367 public void run() { 368 while(nativeOpen() <= 0) { 369 Log.e(TAG, "failed to open multiDisplay pipe, retry"); 370 } 371 Log.d(TAG, "success open multiDisplay pipe"); 372 while(true) { 373 Log.d(TAG, "waiting to read pipe"); 374 int[] array = {0, 0, 0, 0, 0, 0}; 375 if (!nativeReadPipe(array)) { 376 Log.e(TAG, "failed and try again"); 377 continue; 378 } 379 Log.d(TAG, "have read something from pipe"); 380 Log.d(TAG, "run(): array= " + Arrays.toString(array)); 381 switch (array[0]) { 382 case ADD: { 383 for (int j = 0; j < 6; j++) { 384 Log.d(TAG, "received " + array[j]); 385 } 386 int i = array[1]; 387 int width = array[2]; 388 int height = array[3]; 389 int dpi = array[4]; 390 int flag = (array[5] != 0) ? array[5] : FLAGS; 391 if (i < 1 || i > MAX_DISPLAYS || width <=0 || height <=0 || dpi <=0 392 || flag < 0) { 393 Log.e(TAG, "invalid parameters for add/modify display"); 394 break; 395 } 396 addVirtualDisplay(i, width, height, dpi, flag); 397 break; 398 } 399 case DEL: { 400 int i = array[1]; 401 if (i < 1 || i > MAX_DISPLAYS) { 402 Log.e(TAG, "invalid parameters for delete display"); 403 break; 404 } 405 deleteVirtualDisplay(i); 406 break; 407 } 408 case SET_DISPLAY: { 409 for (int j = 0; j < 6; j++) { 410 Log.d(TAG, "SET_DISPLAY received " + array[j]); 411 } 412 if (mSurfaceFlinger == null) { 413 Log.d(TAG, "obtain surfaceflinger " ); 414 mSurfaceFlinger = ServiceManager.getService("SurfaceFlinger"); 415 } 416 if (mSurfaceFlinger != null) { 417 int i = array[1]; 418 Parcel data = Parcel.obtain(); 419 data.writeInterfaceToken(SURFACE_COMPOSER_INTERFACE_KEY); 420 data.writeInt(i); 421 try { 422 if (i >=0) { 423 mSurfaceFlinger.transact(1035, data, null, 0 /* flags */); 424 Log.d(TAG, "setting display to " + i); 425 } else { 426 Log.e(TAG, "invalid display id " + i); 427 } 428 } catch (RemoteException e) { 429 Log.e(TAG, "Could not set display:" + e.toString()); 430 } 431 } else { 432 Log.e(TAG, "cannot get SurfaceFlinger service"); 433 } 434 break; 435 } 436 // TODO(b/231763427): implement LIST 437 } 438 } 439 } 440 } 441 flagsToString(int flags)442 private static String flagsToString(int flags) { 443 return DebugUtils.flagsToString(DisplayManager.class, "VIRTUAL_DISPLAY_FLAG_", flags); 444 } 445 nativeOpen()446 private native int nativeOpen(); nativeCreateSurface(int displayId, int width, int height)447 private native Surface nativeCreateSurface(int displayId, int width, int height); nativeReadPipe(int[] arr)448 private native boolean nativeReadPipe(int[] arr); nativeReleaseListener(int displayId)449 private native boolean nativeReleaseListener(int displayId); nativeResizeListener(int displayId, int with, int height)450 private native boolean nativeResizeListener(int displayId, int with, int height); 451 } 452