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