1 /* 2 * Copyright (C) 2023 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 android.car.view; 18 19 import android.annotation.MainThread; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.RequiresPermission; 23 import android.annotation.SystemApi; 24 import android.app.Activity; 25 import android.car.Car; 26 import android.car.app.CarActivityManager; 27 import android.car.builtin.util.Slogf; 28 import android.car.builtin.view.TouchableInsetsProvider; 29 import android.content.Context; 30 import android.graphics.Rect; 31 import android.graphics.Region; 32 import android.os.IBinder; 33 import android.util.AttributeSet; 34 import android.util.Dumpable; 35 import android.util.Log; 36 import android.util.Pair; 37 import android.util.Slog; 38 import android.view.Surface; 39 import android.view.SurfaceControl; 40 import android.view.SurfaceHolder; 41 import android.view.SurfaceView; 42 43 import com.android.internal.annotations.VisibleForTesting; 44 45 import java.io.PrintWriter; 46 47 /** 48 * {@link SurfaceView} which can render the {@link Surface} of mirrored Task. 49 * 50 * @hide 51 */ 52 @SystemApi 53 @SuppressWarnings("[NotCloseable]") // View object won't be used in try-with-resources statement. 54 public final class MirroredSurfaceView extends SurfaceView { 55 private static final String TAG = MirroredSurfaceView.class.getSimpleName(); 56 private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG); 57 58 private final SurfaceControl.Transaction mTransaction; 59 private final TouchableInsetsProvider mTouchableInsetsProvider; 60 61 private SurfaceControl mMirroredSurface; 62 private Rect mSourceBounds; 63 private CarActivityManager mCarAM; 64 MirroredSurfaceView(@onNull Context context)65 public MirroredSurfaceView(@NonNull Context context) { 66 this(context, /* attrs= */ null); 67 } 68 MirroredSurfaceView(@onNull Context context, @Nullable AttributeSet attrs)69 public MirroredSurfaceView(@NonNull Context context, @Nullable AttributeSet attrs) { 70 this(context, attrs, /* defStyle= */ 0); 71 } 72 MirroredSurfaceView(@onNull Context context, @Nullable AttributeSet attrs, int defStyle)73 public MirroredSurfaceView(@NonNull Context context, @Nullable AttributeSet attrs, 74 int defStyle) { 75 this(context, attrs, defStyle, /* defStyleRes= */ 0); 76 } 77 MirroredSurfaceView(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)78 public MirroredSurfaceView(@NonNull Context context, @Nullable AttributeSet attrs, 79 int defStyleAttr, int defStyleRes) { 80 this(context, attrs, defStyleAttr, defStyleRes, 81 new SurfaceControl.Transaction(), /* touchableInsetsProvider= */ null); 82 } 83 84 @VisibleForTesting MirroredSurfaceView(@onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes, SurfaceControl.Transaction transaction, TouchableInsetsProvider touchableInsetsProvider)85 MirroredSurfaceView(@NonNull Context context, @Nullable AttributeSet attrs, 86 int defStyleAttr, int defStyleRes, 87 SurfaceControl.Transaction transaction, 88 TouchableInsetsProvider touchableInsetsProvider) { 89 super(context, attrs, defStyleAttr, defStyleRes); 90 mTransaction = transaction; 91 mTouchableInsetsProvider = touchableInsetsProvider != null 92 ? touchableInsetsProvider : new TouchableInsetsProvider(this); 93 94 getHolder().addCallback(mSurfaceCallback); 95 if (context instanceof Activity) { 96 ((Activity) context).addDumpable(mDumper); 97 } 98 99 Car.createCar(/* context= */ context, /* handler= */ null, 100 Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, 101 (car, ready) -> { 102 if (!ready) { 103 Slog.w(TAG, "CarService looks crashed"); 104 mCarAM = null; 105 return; 106 } 107 mCarAM = car.getCarManager(CarActivityManager.class); 108 }); 109 } 110 111 /** 112 * Attaches the mirrored Surface which is represented by the given token to this View. 113 * <p> 114 * <b>Note:</b> MirroredSurfaceView will hold the Surface unless you call {@link #release()} 115 * explicitly. This is so that the host can keep the Surface when {@link Activity#onStop()} and 116 * {@link Activity#onStart()} are called again. 117 * 118 * @param token A token to access the Task Surface to mirror. 119 * @return true if the operation is successful. 120 */ 121 @RequiresPermission(Car.PERMISSION_ACCESS_MIRRORRED_SURFACE) 122 @MainThread mirrorSurface(@onNull IBinder token)123 public boolean mirrorSurface(@NonNull IBinder token) { 124 if (mCarAM == null) { 125 Slogf.e(TAG, "Failed to mirrorSurface because CarService isn't ready yet"); 126 return false; 127 } 128 if (mMirroredSurface != null) { 129 removeMirroredSurface(); 130 } 131 Pair<SurfaceControl, Rect> mirroredSurfaceInfo = mCarAM.getMirroredSurface(token); 132 if (mirroredSurfaceInfo == null) { 133 Slogf.e(TAG, "Failed to getMirroredSurface: token=%s", token); 134 return false; 135 } 136 mMirroredSurface = mirroredSurfaceInfo.first; 137 mSourceBounds = mirroredSurfaceInfo.second; 138 if (getHolder() == null) { 139 // reparentMirroredSurface() will happen when the SurfaceHolder is created. 140 if (DBG) Slog.d(TAG, "mirrorSurface: Surface is not ready"); 141 return true; 142 } 143 reparentMirroredSurface(); 144 return true; 145 } 146 147 /** 148 * Indicates a region of the view that is not touchable. 149 * 150 * @param obscuredRegion the obscured region of the view. 151 */ 152 @MainThread setObscuredTouchRegion(@ullable Region obscuredRegion)153 public void setObscuredTouchRegion(@Nullable Region obscuredRegion) { 154 mTouchableInsetsProvider.setObscuredTouchRegion(obscuredRegion); 155 } 156 157 /** 158 * Releases {@link MirroredSurfaceView} and associated {@link Surface}. 159 */ 160 @MainThread release()161 public void release() { 162 getHolder().removeCallback(mSurfaceCallback); 163 removeMirroredSurface(); 164 } 165 166 @Override finalize()167 protected void finalize() throws Throwable { 168 if (mMirroredSurface != null) { 169 removeMirroredSurface(); 170 } 171 super.finalize(); 172 } 173 reparentMirroredSurface()174 private void reparentMirroredSurface() { 175 if (DBG) Slog.d(TAG, "reparentMirroredSurface"); 176 calculateScale(); 177 mTransaction.setVisibility(mMirroredSurface, /* visible= */true) 178 .reparent(mMirroredSurface, getSurfaceControl()) 179 .apply(); 180 } 181 removeMirroredSurface()182 private void removeMirroredSurface() { 183 if (mMirroredSurface == null) { 184 Slog.w(TAG, "Skip removeMirroredSurface() on null Surface."); 185 return; 186 } 187 mTransaction.reparent(mMirroredSurface, null).apply(); 188 mMirroredSurface.release(); 189 mMirroredSurface = null; 190 } 191 calculateScale()192 private void calculateScale() { 193 if (mMirroredSurface == null) { 194 Slog.i(TAG, "Skip calculateScale() since MirroredSurface is not attached"); 195 return; 196 } 197 if (getWidth() == 0 || getHeight() == 0) { 198 Slog.i(TAG, "Skip calculateScale() since the View is not inflated."); 199 return; 200 } 201 // scale: > 1.0 Zoom out, < 1.0 Zoom in 202 float horizontalScale = (float) mSourceBounds.width() / getWidth(); 203 float verticalScale = (float) mSourceBounds.height() / getHeight(); 204 float mirroringScale = Math.max(horizontalScale, verticalScale); 205 206 int width = (int) Math.ceil(mSourceBounds.width() / mirroringScale); 207 int height = (int) Math.ceil(mSourceBounds.height() / mirroringScale); 208 Rect destBounds = new Rect(0, 0, width, height); 209 210 if (DBG) Slogf.d(TAG, "calculateScale: scale=%f", mirroringScale); 211 mTransaction.setGeometry(mMirroredSurface, mSourceBounds, destBounds, Surface.ROTATION_0) 212 .apply(); 213 } 214 215 private final SurfaceHolder.Callback mSurfaceCallback = new SurfaceHolder.Callback() { 216 @Override 217 public void surfaceCreated(@NonNull SurfaceHolder holder) { 218 if (mMirroredSurface == null) { 219 // reparentMirroredSurface() will happen when mirrorSurface() is called. 220 if (DBG) { 221 Slog.d(TAG, "surfaceCreated: skip reparenting" 222 + " because the mirrored Surface isn't ready."); 223 } 224 return; 225 } 226 reparentMirroredSurface(); 227 } 228 229 @Override 230 public void surfaceChanged(@NonNull SurfaceHolder holder, int format, 231 int width, int height) { 232 calculateScale(); 233 } 234 235 @Override 236 public void surfaceDestroyed(@NonNull SurfaceHolder holder) { 237 // Don't remove mMirroredSurface autonomously, because it may not get it again 238 // after some timeout. So the host Activity needs to keep it for the next onStart event. 239 } 240 }; 241 242 @Override onAttachedToWindow()243 protected void onAttachedToWindow() { 244 super.onAttachedToWindow(); 245 mTouchableInsetsProvider.addToViewTreeObserver(); 246 } 247 248 @Override onDetachedFromWindow()249 protected void onDetachedFromWindow() { 250 mTouchableInsetsProvider.removeFromViewTreeObserver(); 251 super.onDetachedFromWindow(); 252 } 253 254 private final Dumpable mDumper = new Dumpable() { 255 private static final String INDENTATION = " "; 256 @NonNull 257 @Override 258 public String getDumpableName() { 259 return TAG; 260 } 261 262 @Override 263 public void dump(@NonNull PrintWriter writer, @Nullable String[] args) { 264 writer.println(TAG + ": id=#" + Integer.toHexString(getId())); 265 writer.println(INDENTATION + "mirroredSurface=" + mMirroredSurface); 266 writer.println(INDENTATION + "sourceBound=" + mSourceBounds); 267 writer.println(INDENTATION + "touchableInsetsProvider=" + mTouchableInsetsProvider); 268 } 269 }; 270 } 271