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