1 /* 2 * Copyright (C) 2022 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.google.android.car.kitchensink.display; 18 19 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; 20 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC; 21 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH; 22 23 import android.annotation.Nullable; 24 import android.content.Context; 25 import android.content.res.TypedArray; 26 import android.hardware.display.DisplayManager; 27 import android.hardware.display.VirtualDisplay; 28 import android.hardware.input.InputManager; 29 import android.os.Handler; 30 import android.os.HandlerThread; 31 import android.util.AttributeSet; 32 import android.util.DisplayMetrics; 33 import android.util.Log; 34 import android.view.MotionEvent; 35 import android.view.Surface; 36 import android.view.SurfaceHolder; 37 import android.view.SurfaceView; 38 39 import com.google.android.car.kitchensink.R; 40 41 import java.io.PrintWriter; 42 import java.util.Objects; 43 import java.util.concurrent.CountDownLatch; 44 import java.util.concurrent.TimeUnit; 45 46 /** 47 * Custom view that hosts a virtual display. 48 */ 49 public final class VirtualDisplayView extends SurfaceView { 50 51 private static final String TAG = VirtualDisplayView.class.getSimpleName(); 52 53 private static final boolean REALLY_VERBOSE_IS_FINE = false; 54 55 private static final int WAIT_TIMEOUT_MS = 4_000; 56 private static final int NO_DISPLAY_ID = -42; 57 58 private final Context mContext; 59 private final InputManager mInputManager; 60 private String mName = "LAYOUT XML, Y U NO HAVE A NAME?"; 61 62 private final SurfaceHolder.Callback mSurfaceViewCallback = new SurfaceHolder.Callback() { 63 64 @Override 65 public void surfaceCreated(SurfaceHolder holder) { 66 Log.d(TAG, "surfaceCreated(): holder=" + holder); 67 } 68 69 @Override 70 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 71 Log.d(TAG, "surfaceChanged(): holder=" + holder + ", forma=:" + format 72 + ", width=" + width + ", height=" + height); 73 74 if (mSurface != null) { 75 Log.d(TAG, "Releasing old surface (" + mSurface + ")"); 76 mSurface.release(); 77 } 78 79 mSurface = holder.getSurface(); 80 } 81 82 @Override 83 public void surfaceDestroyed(SurfaceHolder holder) { 84 Log.d(TAG, "surfaceDestroyed, holder: " + holder + ", detaching surface from" 85 + " display, surface: " + holder.getSurface()); 86 // Detaching surface is similar to turning off the display 87 mSurface = null; 88 if (mVirtualDisplay != null) { 89 mVirtualDisplay.setSurface(null); 90 } 91 } 92 }; 93 94 @Nullable 95 private VirtualDisplay mVirtualDisplay; 96 97 private int mDisplayId = NO_DISPLAY_ID; 98 99 @Nullable 100 private Surface mSurface; 101 102 @Nullable 103 private Handler mHandler; 104 VirtualDisplayView(Context context)105 public VirtualDisplayView(Context context) { 106 this(context, null); 107 } 108 VirtualDisplayView(Context context, @Nullable AttributeSet attrs)109 public VirtualDisplayView(Context context, @Nullable AttributeSet attrs) { 110 super(context, attrs); 111 112 mContext = context; 113 String name = getName(context, attrs); 114 if (name != null) { 115 mName = name; 116 } 117 mInputManager = context.getSystemService(InputManager.class); 118 getHolder().addCallback(mSurfaceViewCallback); 119 } 120 121 // NOTE: it might be needed to handle focus and a11y events as well, but for now we'll assume 122 // it's not needed and keep it simpler 123 @Override onTouchEvent(MotionEvent event)124 public boolean onTouchEvent(MotionEvent event) { 125 if (REALLY_VERBOSE_IS_FINE) { 126 Log.v(TAG, "onTouchEvent(" + event + ")"); 127 } 128 129 if (mVirtualDisplay == null) { 130 return super.onTouchEvent(event); 131 } 132 133 if (mDisplayId == NO_DISPLAY_ID) { 134 // Shouldn't happen, but it doesn't hurt to check... 135 Log.w(TAG, "onTouchEvent(): display id not set, calling super instead"); 136 return super.onTouchEvent(event); 137 } 138 139 // NOTE: it might be need to re-calculate the coordinates to offset the diplay, but 140 // pparently it's working without it 141 event.setDisplayId(mDisplayId); 142 143 if (REALLY_VERBOSE_IS_FINE) { 144 Log.v(TAG, "re-dispatching event after changing display id to " + mDisplayId); 145 } 146 if (mInputManager.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC)) { 147 return true; 148 } 149 150 Log.w(TAG, "onTouchEvent(): not handled by display, calling super instead"); 151 return super.onTouchEvent(event); 152 } 153 154 /** 155 * Sets the name of the display 156 */ setName(String name)157 public void setName(String name) { 158 Objects.requireNonNull(name, "name cannot be null"); 159 Log.v(TAG, "Changing name from " + mName + " to " + name); 160 mName = name; 161 } 162 163 /** 164 * Gets the name of the display 165 */ getName()166 public String getName() { 167 return mName; 168 } 169 170 /** 171 * Creates the virtual display and return its id. 172 */ createVirtualDisplay()173 public int createVirtualDisplay() { 174 if (mVirtualDisplay != null) { 175 throw new IllegalStateException("Display already exist: " + mVirtualDisplay); 176 } 177 178 if (mSurface == null) { 179 throw new IllegalStateException("Surface not created yet (or released)"); 180 } 181 182 if (mHandler == null) { 183 HandlerThread handlerThread = new HandlerThread("VirtualDisplayHelperThread"); 184 Log.i(TAG, "Starting " + handlerThread); 185 handlerThread.start(); 186 mHandler = new Handler(handlerThread.getLooper()); 187 } 188 189 CountDownLatch latch = new CountDownLatch(1); 190 191 DisplayManager.DisplayListener listener = new DisplayManager.DisplayListener() { 192 @Override 193 public void onDisplayAdded(int displayId) { 194 Log.d(TAG, "onDisplayAdded(" + displayId + ")"); 195 latch.countDown(); 196 } 197 198 @Override 199 public void onDisplayRemoved(int displayId) { 200 Log.v(TAG, "onDisplayRemoved(" + displayId + ")"); 201 } 202 203 @Override 204 public void onDisplayChanged(int displayId) { 205 Log.v(TAG, "onDisplayChanged(" + displayId + ")"); 206 } 207 }; 208 209 DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); 210 DisplayMetrics metrics = new DisplayMetrics(); 211 displayManager.getDisplay(android.view.Display.DEFAULT_DISPLAY).getRealMetrics(metrics); 212 Log.v(TAG, "Physical display size: " + metrics.widthPixels + " x " + metrics.heightPixels); 213 Log.v(TAG, "View size: " + getWidth() + " x " + getHeight()); 214 215 Log.v(TAG, "Registering listener " + listener); 216 displayManager.registerDisplayListener(listener, mHandler); 217 218 int flags = VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY 219 | VIRTUAL_DISPLAY_FLAG_PUBLIC 220 | VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH; 221 222 Log.d(TAG, "Creating display named '" + mName + "'"); 223 mVirtualDisplay = displayManager.createVirtualDisplay(mName, 224 getWidth(), getHeight(), (int) metrics.xdpi, mSurface, flags); 225 int displayId = mVirtualDisplay.getDisplay().getDisplayId(); 226 Log.i(TAG, "Created display with id " + displayId); 227 boolean created = false; 228 try { 229 created = latch.await(WAIT_TIMEOUT_MS, TimeUnit.SECONDS); 230 231 } catch (InterruptedException e) { 232 Log.e(TAG, "Interruped waiting for display callback", e); 233 Thread.currentThread().interrupt(); 234 } finally { 235 Log.v(TAG, "Unregistering listener " + listener); 236 displayManager.unregisterDisplayListener(listener); 237 } 238 if (!created) { 239 throw new IllegalStateException("Timed out (up to " + WAIT_TIMEOUT_MS 240 + "ms waiting for callback"); 241 } 242 mDisplayId = displayId; 243 return displayId; 244 } 245 246 /** 247 * Deletes the virtual display. 248 */ deleteVirtualDisplay()249 public void deleteVirtualDisplay() { 250 if (mVirtualDisplay == null) { 251 throw new IllegalStateException("Display doesn't exist"); 252 } 253 releaseDisplay(); 254 } 255 256 /** 257 * Gets the virtual display. 258 */ 259 @Nullable getVirtualDisplay()260 public VirtualDisplay getVirtualDisplay() { 261 return mVirtualDisplay; 262 } 263 264 /** 265 * Releases the internal resources. 266 */ release()267 public void release() { 268 releaseDisplay(); 269 270 if (mSurface != null) { 271 Log.d(TAG, "Releasing surface"); 272 mSurface.release(); 273 mSurface = null; 274 } 275 } 276 277 /** 278 * Dumps its state. 279 */ dump(String prefix, PrintWriter writer)280 public void dump(String prefix, PrintWriter writer) { 281 writer.printf("%sName: %s\n", prefix, mName); 282 writer.printf("%sSurface: %s\n", prefix, mSurface); 283 writer.printf("%sVirtualDisplay: %s\n", prefix, mVirtualDisplay); 284 writer.printf("%sDisplayId: %d%s\n", prefix, mDisplayId, 285 (mDisplayId == NO_DISPLAY_ID ? " (not set)" : "")); 286 writer.printf("%sHandler: %s\n", prefix, mHandler); 287 writer.printf("%sWait timeout: %dms\n", prefix, WAIT_TIMEOUT_MS); 288 writer.printf("%sREALLY_VERBOSE_IS_FINE: %b\n", prefix, REALLY_VERBOSE_IS_FINE); 289 } 290 releaseDisplay()291 private void releaseDisplay() { 292 if (mVirtualDisplay != null) { 293 Log.i(TAG, "Releasing display id " + mDisplayId); 294 mVirtualDisplay.release(); 295 mVirtualDisplay = null; 296 mDisplayId = NO_DISPLAY_ID; 297 } 298 } 299 300 @Nullable getName(Context context, AttributeSet attrs)301 static String getName(Context context, AttributeSet attrs) { 302 if (attrs == null) { 303 return null; 304 } 305 TypedArray a = context.getTheme().obtainStyledAttributes(attrs, 306 R.styleable.VirtualDisplayView, /* defStyleAttr= */ 0, /* defStyleRes= */ 0); 307 try { 308 return a.getString(R.styleable.VirtualDisplayView_name); 309 } finally { 310 a.recycle(); 311 } 312 } 313 } 314