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