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