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.app;
18 
19 import android.annotation.MainThread;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.RequiresPermission;
23 import android.app.ActivityManager;
24 import android.app.ActivityOptions;
25 import android.app.PendingIntent;
26 import android.car.Car;
27 import android.car.builtin.util.Slogf;
28 import android.car.builtin.view.SurfaceControlHelper;
29 import android.car.builtin.view.TouchableInsetsProvider;
30 import android.car.builtin.view.ViewHelper;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.graphics.Rect;
34 import android.graphics.Region;
35 import android.os.DeadObjectException;
36 import android.os.RemoteException;
37 import android.util.Slog;
38 import android.view.SurfaceControl;
39 import android.view.SurfaceHolder;
40 import android.view.SurfaceView;
41 
42 import java.util.concurrent.atomic.AtomicBoolean;
43 
44 /**
45  * A {@link SurfaceView} that can embed a Task inside of it. The task management is done remotely
46  * in a process that has registered a TaskOrganizer with the system server.
47  * Usually this process is the Car System UI.
48  *
49  * @hide
50  */
51 public abstract class RemoteCarTaskView extends SurfaceView {
52     private static final String TAG = RemoteCarTaskView.class.getSimpleName();
53 
54     private final TouchableInsetsProvider mTouchableInsetsProvider;
55     private final SurfaceCallbackHandler mSurfaceCallbackHandler = new SurfaceCallbackHandler();
56     private final Rect mTmpRect = new Rect();
57     private final AtomicBoolean mReleased = new AtomicBoolean(false);
58     private boolean mInitialized = false;
59     boolean mSurfaceCreated = false;
60     private Region mObscuredTouchRegion;
61     private ICarTaskViewHost mICarTaskViewHost;
62 
RemoteCarTaskView(Context context)63     RemoteCarTaskView(Context context) {
64         super(context);
65         mTouchableInsetsProvider = new TouchableInsetsProvider(this);
66         getHolder().addCallback(mSurfaceCallbackHandler);
67     }
68 
69     /** Brings the embedded task to the front. Does nothing if there is no task. */
70     @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY)
71     @MainThread
showEmbeddedTask()72     public void showEmbeddedTask() {
73         try {
74             mICarTaskViewHost.showEmbeddedTask();
75         } catch (RemoteException e) {
76             Slogf.e(TAG, "exception in showEmbeddedTask", e);
77         }
78     }
79 
80     /**
81      * Sets the visibility of the embedded task.
82      *
83      * @hide
84      */
85     @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY)
86     @MainThread
setTaskVisibility(boolean visibility)87     public void setTaskVisibility(boolean visibility) {
88         try {
89             mICarTaskViewHost.setTaskVisibility(visibility);
90         } catch (RemoteException e) {
91             Slogf.e(TAG, "exception in setTaskVisibility", e);
92         }
93     }
94 
95     /**
96      * Reorders the embedded task.
97      *
98      * @hide
99      */
100     @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY)
101     @MainThread
reorderTask(boolean onTop)102     public void reorderTask(boolean onTop) {
103         try {
104             mICarTaskViewHost.reorderTask(onTop);
105         } catch (RemoteException e) {
106             Slogf.e(TAG, "exception in reorderTask for task", e);
107         }
108     }
109 
110     /**
111      * Updates the WM bounds for the underlying task as per the current view bounds. Does nothing
112      * if there is no task.
113      */
114     @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY)
115     @MainThread
updateWindowBounds()116     public void updateWindowBounds() {
117         ViewHelper.getBoundsOnScreen(RemoteCarTaskView.this, mTmpRect);
118         try {
119             mICarTaskViewHost.setWindowBounds(mTmpRect);
120         } catch (RemoteException e) {
121             Slogf.e(TAG, "exception in setWindowBounds", e);
122         }
123     }
124 
125     /**
126      * Sets the bounds of the window for the underlying Task.
127      *
128      * @param bounds the new bounds in screen coordinates.
129      *
130      * @hide
131      */
setWindowBounds(Rect bounds)132     public void setWindowBounds(Rect bounds) {
133         try {
134             mICarTaskViewHost.setWindowBounds(bounds);
135         } catch (RemoteException e) {
136             Slogf.e(TAG, "exception in setWindowBounds", e);
137         }
138     }
139 
140     /**
141      * Indicates a region of the view that is not touchable.
142      *
143      * @param obscuredRect the obscured region of the view.
144      */
145     @MainThread
setObscuredTouchRect(@onNull Rect obscuredRect)146     public void setObscuredTouchRect(@NonNull Rect obscuredRect) {
147         mObscuredTouchRegion = obscuredRect != null ? new Region(obscuredRect) : null;
148         mTouchableInsetsProvider.setObscuredTouchRegion(mObscuredTouchRegion);
149     }
150 
151     /**
152      * Indicates a region of the view that is not touchable.
153      *
154      * @param obscuredRegion the obscured region of the view.
155      */
156     @MainThread
setObscuredTouchRegion(@onNull Region obscuredRegion)157     public void setObscuredTouchRegion(@NonNull Region obscuredRegion) {
158         mObscuredTouchRegion = obscuredRegion;
159         mTouchableInsetsProvider.setObscuredTouchRegion(mObscuredTouchRegion);
160     }
161 
162     /**
163      * @return the {@link android.app.ActivityManager.RunningTaskInfo} of the task currently
164      * running in the TaskView.
165      */
166     @MainThread
getTaskInfo()167     @Nullable public abstract ActivityManager.RunningTaskInfo getTaskInfo();
168 
169     /**
170      * @return true, if the task view is initialized.
171      */
172     @MainThread
isInitialized()173     public boolean isInitialized() {
174         return mInitialized;
175     }
176 
177     /**
178      * @return true, if the task view is released.
179      *
180      * @hide
181      */
182     @MainThread
isReleased()183     public boolean isReleased() {
184         return mReleased.get();
185     }
186 
187     /**
188      * Adds the given insets on the Task.
189      *
190      * The given frame for the insets type are applied to the underlying task right away.
191      * If a rectangle for an insets type was added previously, it will be replaced with the
192      * new value.
193      * If a rectangle for a insets type was already added, but is not specified currently in
194      * {@code insets}, it will remain applied to the task. Clients should explicitly call
195      * {@link #removeInsets(int, int)} to remove the rectangle for that insets type from
196      * the underlying task.
197      *
198      * @param index An owner might add multiple insets sources with the same type.
199      *              This identifies them.
200      * @param type  The insets type of the insets source.
201      * @param frame The rectangle area of the insets source.
202      */
203     @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY)
204     @MainThread
addInsets(int index, int type, @NonNull Rect frame)205     public void addInsets(int index, int type, @NonNull Rect frame) {
206         try {
207             mICarTaskViewHost.addInsets(index, type, frame);
208         } catch (RemoteException e) {
209             Slog.e(TAG, "exception in addInsets", e);
210         }
211     }
212 
213     /**
214      * Removes the given insets from the Task.
215      *
216      * Note: This will only remove the insets that were added using
217      * {@link #addInsets(int, int, Rect)}
218      *
219      * @param index An owner might add multiple insets sources with the same type.
220      *              This identifies them.
221      * @param type  The insets type of the insets source. This doesn't accept the composite types.
222      */
223     @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY)
removeInsets(int index, int type)224     public void removeInsets(int index, int type) {
225         try {
226             mICarTaskViewHost.removeInsets(index, type);
227         } catch (RemoteException e) {
228             Slog.e(TAG, "exception in removeInsets", e);
229         }
230     }
231 
setRemoteHost(@onNull ICarTaskViewHost carTaskViewHost)232     void setRemoteHost(@NonNull ICarTaskViewHost carTaskViewHost) {
233         mICarTaskViewHost = carTaskViewHost;
234 
235         if (mSurfaceCreated) {
236             if (!mInitialized) {
237                 onInitialized();
238                 mInitialized = true;
239             }
240         }
241     }
242 
243     /**
244      * Starts the activity from the given {@code PendingIntent}
245      *
246      * @param pendingIntent Intent used to launch an activity.
247      * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
248      * @param options options for the activity.
249      * @param launchBounds the bounds (window size and position) that the activity should be
250      *                      launched in, in pixels and in screen coordinates.
251      */
startActivity( @onNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, @NonNull ActivityOptions options, @Nullable Rect launchBounds)252     void startActivity(
253             @NonNull PendingIntent pendingIntent,
254             @Nullable Intent fillInIntent,
255             @NonNull ActivityOptions options,
256             @Nullable Rect launchBounds) {
257         try {
258             mICarTaskViewHost.startActivity(
259                     pendingIntent, fillInIntent, options.toBundle(), launchBounds);
260         } catch (RemoteException exception) {
261             Slogf.e(TAG, "exception in startActivity", exception);
262         }
263     }
264 
createRootTask(int displayId)265     void createRootTask(int displayId) {
266         try {
267             mICarTaskViewHost.createRootTask(displayId);
268         } catch (RemoteException exception) {
269             Slogf.e(TAG, "exception in createRootTask", exception);
270         }
271     }
272 
createLaunchRootTask(int displayId, boolean embedHomeTask, boolean embedRecentsTask, boolean embedAssistantTask)273     void createLaunchRootTask(int displayId, boolean embedHomeTask, boolean embedRecentsTask,
274             boolean embedAssistantTask) {
275         try {
276             mICarTaskViewHost.createLaunchRootTask(displayId, embedHomeTask, embedRecentsTask,
277                     embedAssistantTask);
278         } catch (RemoteException exception) {
279             Slogf.e(TAG, "exception in createRootTask", exception);
280         }
281     }
282 
283     /** Release the resources associated with this task view. */
284     @MainThread
release()285     public void release() {
286         getHolder().removeCallback(mSurfaceCallbackHandler);
287         try {
288             mReleased.set(true);
289             mICarTaskViewHost.release();
290         } catch (DeadObjectException e) {
291             Slogf.w(TAG, "TaskView's host has already died", e);
292         } catch (RemoteException e) {
293             Slogf.e(TAG, "exception in release", e);
294         }
295         onReleased();
296     }
297 
298     /**
299      * Called when the task view is initialized. It is called only once for the lifetime of
300      * taskview.
301      */
onInitialized()302     abstract void onInitialized();
303 
304     /**
305      * Called when the task view is released. It is only called once for the lifetime of task view.
306      */
onReleased()307     abstract void onReleased();
308 
309     /**
310      * Called when the task has appeared in the taskview.
311      *
312      * @param taskInfo the taskInfo of the task that has appeared.
313      * @param leash the suface control for the task surface.
314      */
onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)315     void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
316     }
317 
318     /**
319      * Called when the task's info has changed.
320      *
321      * @param taskInfo the taskInfo of the task that has a change in info.
322      */
onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)323     void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
324     }
325 
326     /**
327      * Called when the task has vanished.
328      *
329      * @param taskInfo the taskInfo of the task that has vanished.
330      */
onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)331     void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
332     }
333 
334     @Override
onAttachedToWindow()335     public void onAttachedToWindow() {
336         super.onAttachedToWindow();
337         mTouchableInsetsProvider.addToViewTreeObserver();
338     }
339 
340     @Override
onDetachedFromWindow()341     public void onDetachedFromWindow() {
342         super.onDetachedFromWindow();
343         mTouchableInsetsProvider.removeFromViewTreeObserver();
344     }
345 
346     private class SurfaceCallbackHandler implements SurfaceHolder.Callback {
347         @Override
surfaceCreated(@onNull SurfaceHolder holder)348         public void surfaceCreated(@NonNull SurfaceHolder holder) {
349             if (mICarTaskViewHost != null) {
350                 if (!mInitialized) {
351                     onInitialized();
352                     mInitialized = true;
353                 }
354             }
355             mSurfaceCreated = true;
356             try {
357                 mICarTaskViewHost.notifySurfaceCreated(
358                         SurfaceControlHelper.copy(getSurfaceControl()));
359             } catch (RemoteException e) {
360                 Slogf.e(TAG, "exception in notifySurfaceCreated", e);
361             }
362         }
363 
364         @Override
surfaceChanged( @onNull SurfaceHolder holder, int format, int width, int height)365         public void surfaceChanged(
366                 @NonNull SurfaceHolder holder, int format, int width, int height) {
367             try {
368                 ViewHelper.getBoundsOnScreen(RemoteCarTaskView.this, mTmpRect);
369                 mICarTaskViewHost.setWindowBounds(mTmpRect);
370             } catch (RemoteException e) {
371                 Slogf.e(TAG, "exception in setWindowBounds", e);
372             }
373         }
374 
375         @Override
surfaceDestroyed(@onNull SurfaceHolder holder)376         public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
377             mSurfaceCreated = false;
378             try {
379                 mICarTaskViewHost.notifySurfaceDestroyed();
380             } catch (RemoteException e) {
381                 Slogf.e(TAG, "exception in notifySurfaceDestroyed", e);
382             }
383         }
384     }
385 }
386