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 
20 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
21 
22 
23 import android.Manifest;
24 import android.annotation.MainThread;
25 import android.annotation.NonNull;
26 import android.annotation.RequiresPermission;
27 import android.annotation.SystemApi;
28 import android.annotation.UiContext;
29 import android.app.Activity;
30 import android.car.Car;
31 import android.car.builtin.util.Slogf;
32 import android.content.Context;
33 import android.os.RemoteException;
34 import android.os.UserManager;
35 import android.util.Log;
36 import android.util.Slog;
37 
38 import java.util.ArrayList;
39 import java.util.Iterator;
40 import java.util.List;
41 import java.util.concurrent.Executor;
42 
43 /**
44  * This class is used for creating task views & is created on a per activity basis.
45  * @hide
46  */
47 @SystemApi
48 public final class CarTaskViewController {
49     private static final String TAG = CarTaskViewController.class.getSimpleName();
50     static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
51 
52     private final ICarSystemUIProxy mService;
53     private final Context mHostContext;
54     private final CarTaskViewControllerHostLifecycle mLifecycle;
55     private final List<RemoteCarTaskView> mRemoteCarTaskViews =
56             new ArrayList<>();
57     private final CarTaskViewInputInterceptor mTaskViewInputInterceptor;
58     private final ICarActivityService mCarActivityService;
59 
60     private boolean mReleased = false;
61 
62     /**
63      * @param service the binder interface to communicate with the car system UI.
64      * @hide
65      */
CarTaskViewController(@iContext Context hostContext, @NonNull CarTaskViewControllerHostLifecycle lifecycle, @NonNull ICarSystemUIProxy service, ICarActivityService carActivityService)66     CarTaskViewController(@UiContext Context hostContext,
67             @NonNull CarTaskViewControllerHostLifecycle lifecycle,
68             @NonNull ICarSystemUIProxy service,
69             ICarActivityService carActivityService) {
70         mHostContext = hostContext;
71         mService = service;
72         mLifecycle = lifecycle;
73         mCarActivityService = carActivityService;
74         mTaskViewInputInterceptor = new CarTaskViewInputInterceptor(hostContext, lifecycle, this);
75     }
76 
77     /**
78      * Creates a new {@link ControlledRemoteCarTaskView}.
79      *
80      * @param callbackExecutor the executor to get the {@link ControlledRemoteCarTaskViewCallback}
81      *                         on.
82      * @param controlledRemoteCarTaskViewCallback the callback to monitor the
83      *                                            {@link ControlledRemoteCarTaskView} related
84      *                                            events.
85      */
86     @RequiresPermission(allOf = {Manifest.permission.INJECT_EVENTS,
87             Manifest.permission.INTERNAL_SYSTEM_WINDOW}, conditional = true)
88     @MainThread
createControlledRemoteCarTaskView( @onNull ControlledRemoteCarTaskViewConfig controlledRemoteCarTaskViewConfig, @NonNull Executor callbackExecutor, @NonNull ControlledRemoteCarTaskViewCallback controlledRemoteCarTaskViewCallback)89     public void createControlledRemoteCarTaskView(
90             @NonNull ControlledRemoteCarTaskViewConfig controlledRemoteCarTaskViewConfig,
91             @NonNull Executor callbackExecutor,
92             @NonNull ControlledRemoteCarTaskViewCallback controlledRemoteCarTaskViewCallback) {
93         if (mReleased) {
94             throw new IllegalStateException("CarTaskViewController is already released");
95         }
96         ControlledRemoteCarTaskView taskViewClient =
97                 new ControlledRemoteCarTaskView(
98                         mHostContext,
99                         controlledRemoteCarTaskViewConfig,
100                         callbackExecutor,
101                         controlledRemoteCarTaskViewCallback,
102                         /* carTaskViewController= */ this,
103                         mHostContext.getSystemService(UserManager.class));
104 
105         try {
106             ICarTaskViewHost host = mService.createControlledCarTaskView(
107                     taskViewClient.mICarTaskViewClient);
108             taskViewClient.setRemoteHost(host);
109             mRemoteCarTaskViews.add(taskViewClient);
110 
111             if (controlledRemoteCarTaskViewConfig.mShouldCaptureGestures
112                     || controlledRemoteCarTaskViewConfig.mShouldCaptureLongPress) {
113                 assertPermission(Manifest.permission.INJECT_EVENTS);
114                 assertPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW);
115                 mTaskViewInputInterceptor.init();
116             }
117         } catch (RemoteException e) {
118             Slogf.e(TAG, "Unable to create task view.", e);
119         }
120     }
121 
122     /**
123      * Creates a new {@link RemoteCarRootTaskView}.
124      *
125      * @param callbackExecutor the executor to get the {@link RemoteCarRootTaskViewCallback} on.
126      * @param remoteCarRootTaskViewCallback the callback to monitor the
127      *                                      {@link RemoteCarRootTaskView} related events.
128      * @hide
129      */
130     @RequiresPermission(Car.PERMISSION_CONTROL_CAR_APP_LAUNCH)
131     @MainThread
createRemoteCarRootTaskView( @onNull RemoteCarRootTaskViewConfig remoteCarRootTaskViewConfig, @NonNull Executor callbackExecutor, @NonNull RemoteCarRootTaskViewCallback remoteCarRootTaskViewCallback)132     public void createRemoteCarRootTaskView(
133             @NonNull RemoteCarRootTaskViewConfig remoteCarRootTaskViewConfig,
134             @NonNull Executor callbackExecutor,
135             @NonNull RemoteCarRootTaskViewCallback remoteCarRootTaskViewCallback) {
136         assertPermission(Car.PERMISSION_CONTROL_CAR_APP_LAUNCH);
137         if (mReleased) {
138             throw new IllegalStateException("CarTaskViewController is already released");
139         }
140         RemoteCarRootTaskView taskViewClient =
141                 new RemoteCarRootTaskView(
142                         mHostContext,
143                         remoteCarRootTaskViewConfig,
144                         callbackExecutor,
145                         remoteCarRootTaskViewCallback,
146                         /* carTaskViewController= */ this,
147                         mCarActivityService
148                 );
149 
150         try {
151             ICarTaskViewHost host = mService.createCarTaskView(taskViewClient.mICarTaskViewClient);
152             taskViewClient.setRemoteHost(host);
153             mRemoteCarTaskViews.add(taskViewClient);
154         } catch (RemoteException e) {
155             Slogf.e(TAG, "Unable to create root task view.", e);
156         }
157     }
158 
159     /**
160      * Creates a new {@link RemoteCarDefaultRootTaskView}.
161      *
162      * @param callbackExecutor the executor to get the {@link RemoteCarDefaultRootTaskViewCallback}
163      *                         on.
164      * @param remoteCarDefaultRootTaskViewCallback the callback to monitor the
165      *                                             {@link RemoteCarDefaultRootTaskView} related
166      *                                             events.
167      * @hide
168      */
169     @MainThread
createRemoteCarDefaultRootTaskView( @onNull RemoteCarDefaultRootTaskViewConfig remoteCarDefaultRootTaskViewConfig, @NonNull Executor callbackExecutor, @NonNull RemoteCarDefaultRootTaskViewCallback remoteCarDefaultRootTaskViewCallback)170     public void createRemoteCarDefaultRootTaskView(
171             @NonNull RemoteCarDefaultRootTaskViewConfig remoteCarDefaultRootTaskViewConfig,
172             @NonNull Executor callbackExecutor,
173             @NonNull RemoteCarDefaultRootTaskViewCallback remoteCarDefaultRootTaskViewCallback) {
174         if (mReleased) {
175             throw new IllegalStateException("CarTaskViewController is already released");
176         }
177         RemoteCarDefaultRootTaskView taskViewClient =
178                 new RemoteCarDefaultRootTaskView(
179                         mHostContext,
180                         remoteCarDefaultRootTaskViewConfig,
181                         callbackExecutor,
182                         remoteCarDefaultRootTaskViewCallback,
183                         /* carTaskViewController= */ this
184                 );
185 
186         try {
187             ICarTaskViewHost host = mService.createCarTaskView(
188                     taskViewClient.mICarTaskViewClient);
189             taskViewClient.setRemoteHost(host);
190             mRemoteCarTaskViews.add(taskViewClient);
191         } catch (RemoteException e) {
192             Slogf.e(TAG, "Unable to create default root task view.", e);
193         }
194     }
195 
onRemoteCarTaskViewReleased(@onNull RemoteCarTaskView taskView)196     void onRemoteCarTaskViewReleased(@NonNull RemoteCarTaskView taskView) {
197         if (mReleased) {
198             Slog.w(TAG, "Failed to remove the taskView as the "
199                     + "CarTaskViewController is already released");
200             return;
201         }
202         if (!mRemoteCarTaskViews.contains(taskView)) {
203             Slog.w(TAG, "This taskView has already been removed");
204             return;
205         }
206         mRemoteCarTaskViews.remove(taskView);
207     }
208 
assertPermission(String permission)209     private void assertPermission(String permission) {
210         if (mHostContext.checkCallingOrSelfPermission(permission)
211                 != PERMISSION_GRANTED) {
212             throw new SecurityException("requires " + permission);
213         }
214     }
215 
216     /**
217      * Releases all the resources held by the taskviews associated with this controller.
218      *
219      * <p> Once {@link #release()} is called, the current instance of {@link CarTaskViewController}
220      * cannot be used further. A new instance should be requested using
221      * {@link CarActivityManager#getCarTaskViewController(Activity, Executor,
222      * CarTaskViewControllerCallback)}.
223      */
224     @MainThread
release()225     public void release() {
226         if (mReleased) {
227             Slogf.w(TAG, "CarTaskViewController is already released");
228             return;
229         }
230         releaseTaskViews();
231         mTaskViewInputInterceptor.release();
232         mReleased = true;
233     }
234 
235     @MainThread
releaseTaskViews()236     void releaseTaskViews() {
237         Iterator<RemoteCarTaskView> iterator = mRemoteCarTaskViews.iterator();
238         while (iterator.hasNext()) {
239             RemoteCarTaskView taskView = iterator.next();
240             // Remove the task view here itself because release triggers removal again which can
241             // result in concurrent modification exception.
242             iterator.remove();
243             taskView.release();
244         }
245     }
246 
247     /**
248      * Brings all the embedded tasks to the front.
249      */
250     @MainThread
showEmbeddedTasks()251     public void showEmbeddedTasks() {
252         if (mReleased) {
253             throw new IllegalStateException("CarTaskViewController is already released");
254         }
255         for (int i = 0, length = mRemoteCarTaskViews.size(); i < length; i++) {
256             RemoteCarTaskView remoteCarTaskView = mRemoteCarTaskViews.get(i);
257             // TODO(b/267314188): Add a new method in ICarSystemUI to call
258             // showEmbeddedTask in a single WCT for multiple tasks.
259             remoteCarTaskView.showEmbeddedTask();
260         }
261     }
262 
263     /**
264      * Brings all the embedded controlled tasks to the front.
265      */
266     @MainThread
showEmbeddedControlledTasks()267     void showEmbeddedControlledTasks() {
268         if (mReleased) {
269             throw new IllegalStateException("CarTaskViewController is already released");
270         }
271         for (int i = 0, length = mRemoteCarTaskViews.size(); i < length; i++) {
272             RemoteCarTaskView carTaskView = mRemoteCarTaskViews.get(i);
273             // TODO(b/267314188): Add a new method in ICarSystemUI to call
274             // showEmbeddedTask in a single WCT for multiple tasks.
275             if (carTaskView instanceof ControlledRemoteCarTaskView) {
276                 carTaskView.showEmbeddedTask();
277             }
278         }
279     }
280 
isHostVisible()281     boolean isHostVisible() {
282         return mLifecycle.isVisible();
283     }
284 
getRemoteCarTaskViews()285     List<RemoteCarTaskView> getRemoteCarTaskViews() {
286         return mRemoteCarTaskViews;
287     }
288 }
289