• 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.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.car.Car;
25 import android.car.builtin.app.TaskInfoHelper;
26 import android.car.builtin.util.Slogf;
27 import android.car.builtin.view.ViewHelper;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.graphics.Rect;
31 import android.os.Binder;
32 import android.os.IBinder;
33 import android.os.RemoteException;
34 import android.os.ServiceSpecificException;
35 import android.view.SurfaceControl;
36 
37 import com.android.internal.annotations.GuardedBy;
38 
39 import java.util.ArrayList;
40 import java.util.List;
41 import java.util.concurrent.Executor;
42 
43 /**
44  * A {@link RemoteCarRootTaskView} should be used when a given list of activities are required to
45  * appear inside the bounds of a the given {@link android.view.View} in the form of a task stack.
46  * This {@link RemoteCarTaskView} creates a root task and all the given activities will launch
47  * inside that root task.
48  *
49  * <p>It serves these use-cases:
50  * <ul>
51  *     <li>Should be used when the apps that are meant to be in it can be started from anywhere
52  *     in the system. i.e. when the host app has no control over their launching.</li>
53  *     <li>Suitable for apps like Assistant or Setup-Wizard.</li>
54  * </ul>
55  *
56  * @hide
57  */
58 public final class RemoteCarRootTaskView extends RemoteCarTaskView {
59     private static final String TAG = RemoteCarRootTaskView.class.getSimpleName();
60 
61     private final Executor mCallbackExecutor;
62     private final RemoteCarRootTaskViewCallback mCallback;
63     private final ICarActivityService mCarActivityService;
64     private final CarTaskViewController mCarTaskViewController;
65     private final Rect mTmpRect = new Rect();
66     private final Object mLock = new Object();
67     private final int mDisplayId;
68     @GuardedBy("mLock")
69     private final RootTaskStackManager mRootTaskStackManager = new RootTaskStackManager();
70     /**
71      * List of activities that appear in this {@link RemoteCarRootTaskView}. It's initialized
72      * with the value from {@link RemoteCarRootTaskViewConfig#getAllowListedActivities()} and
73      * can be updated by {@link #updateAllowListedActivities(List)}.
74      */
75     @GuardedBy("mLock")
76     private final ArrayList<ComponentName> mAllowListedActivities;
77 
78     @GuardedBy("mLock")
79     private ActivityManager.RunningTaskInfo mRootTask;
80 
81     final ICarTaskViewClient mICarTaskViewClient = new ICarTaskViewClient.Stub() {
82         @Override
83         public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
84             synchronized (mLock) {
85                 if (mRootTask == null) {
86                     mRootTask = taskInfo;
87                     setPersistentActivitiesOnRootTask(mAllowListedActivities,
88                             TaskInfoHelper.getToken(taskInfo));
89                     // If onTaskAppeared() is called, it implicitly means that
90                     // super.isInitialized() is true, as the root task is created only after
91                     // initialization.
92                     final long identity = Binder.clearCallingIdentity();
93                     try {
94                         mCallbackExecutor.execute(() -> {
95                             // Check for isReleased() because the car task view might have
96                             // already been released but this code path is executed later because
97                             // the executor was busy.
98                             if (isReleased()) {
99                                 Slogf.w(TAG, "car task view has already been released");
100                                 return;
101                             }
102                             mCallback.onTaskViewInitialized();
103                         });
104                     } finally {
105                         Binder.restoreCallingIdentity(identity);
106                     }
107 
108                     if (taskInfo.taskDescription != null) {
109                         ViewHelper.seResizeBackgroundColor(
110                                 RemoteCarRootTaskView.this,
111                                 taskInfo.taskDescription.getBackgroundColor());
112                     }
113                     updateWindowBounds();
114                 }
115                 mRootTaskStackManager.taskAppeared(taskInfo, leash);
116             }
117         }
118 
119         @Override
120         public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
121             synchronized (mLock) {
122                 if (mRootTask == null) {
123                     return;
124                 }
125                 if (mRootTask.taskId == taskInfo.taskId && taskInfo.taskDescription != null) {
126                     ViewHelper.seResizeBackgroundColor(
127                             RemoteCarRootTaskView.this,
128                             taskInfo.taskDescription.getBackgroundColor());
129                 }
130                 mRootTaskStackManager.taskInfoChanged(taskInfo);
131             }
132         }
133 
134         @Override
135         public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
136             synchronized (mLock) {
137                 if (mRootTask == null) {
138                     return;
139                 }
140                 if (mRootTask.taskId == taskInfo.taskId) {
141                     mRootTask = null;
142                 }
143                 mRootTaskStackManager.taskVanished(taskInfo);
144             }
145         }
146 
147         @Override
148         public void setResizeBackgroundColor(SurfaceControl.Transaction t, int color) {
149             ViewHelper.seResizeBackgroundColor(RemoteCarRootTaskView.this, color);
150         }
151 
152         @Override
153         public Rect getCurrentBoundsOnScreen() {
154             ViewHelper.getBoundsOnScreen(RemoteCarRootTaskView.this, mTmpRect);
155             return mTmpRect;
156         }
157     };
158 
RemoteCarRootTaskView( @onNull Context context, RemoteCarRootTaskViewConfig config, @NonNull Executor callbackExecutor, @NonNull RemoteCarRootTaskViewCallback callback, CarTaskViewController carTaskViewController, @NonNull ICarActivityService carActivityService)159     RemoteCarRootTaskView(
160             @NonNull Context context,
161             RemoteCarRootTaskViewConfig config,
162             @NonNull Executor callbackExecutor,
163             @NonNull RemoteCarRootTaskViewCallback callback,
164             CarTaskViewController carTaskViewController,
165             @NonNull ICarActivityService carActivityService) {
166         super(context);
167         mCallbackExecutor = callbackExecutor;
168         mCallback = callback;
169         mCarTaskViewController = carTaskViewController;
170         mCarActivityService = carActivityService;
171         synchronized (mLock) {
172             mAllowListedActivities = new ArrayList<>(config.getAllowListedActivities());
173         }
174         mDisplayId = config.getDisplayId();
175 
176         mCallbackExecutor.execute(() -> mCallback.onTaskViewCreated(this));
177     }
178 
179     /**
180      * Returns the task info of the top task running in the root task embedded in this task view.
181      *
182      * @return task info object of the top task.
183      */
184     @Nullable
getTopTaskInfo()185     public ActivityManager.RunningTaskInfo getTopTaskInfo() {
186         synchronized (mLock) {
187             return mRootTaskStackManager.getTopTask();
188         }
189     }
190 
191     @Override
onInitialized()192     void onInitialized() {
193         // A signal when the surface and host-side are ready. This task view initialization
194         // completes after root task has been created.
195         createRootTask(mDisplayId);
196     }
197 
198     @Override
isInitialized()199     public boolean isInitialized() {
200         synchronized (mLock) {
201             return super.isInitialized() && mRootTask != null;
202         }
203     }
204 
205     @Override
onReleased()206     void onReleased() {
207         mCallbackExecutor.execute(() -> mCallback.onTaskViewReleased());
208         mCarTaskViewController.onRemoteCarTaskViewReleased(this);
209     }
210 
211     @Nullable
212     @Override
getTaskInfo()213     public ActivityManager.RunningTaskInfo getTaskInfo() {
214         synchronized (mLock) {
215             return mRootTask;
216         }
217     }
218 
219     @Override
toString()220     public String toString() {
221         return toString(/* withBounds= */ false);
222     }
223 
224     /**
225      * Updates the list of activities that appear inside this {@link RemoteCarRootTaskView}.
226      *
227      * <p>Note:
228      * If an activity is already associated with another {@link RemoteCarRootTaskView}, its
229      * designation will be overridden.
230      *
231      * @param list list of {@link ComponentName} of activities to be designated to this
232      *                   {@link RemoteCarRootTaskView}
233      */
234     @RequiresPermission(Car.PERMISSION_CONTROL_CAR_APP_LAUNCH)
235     @MainThread
updateAllowListedActivities(List<ComponentName> list)236     public void updateAllowListedActivities(List<ComponentName> list) {
237         synchronized (mLock) {
238             if (mRootTask != null) {
239                 List<ComponentName> activitiesToRemove = findDifferences(mAllowListedActivities,
240                         list);
241                 List<ComponentName> activitiesToAdd = findDifferences(list, mAllowListedActivities);
242                 setPersistentActivitiesOnRootTask(activitiesToRemove, /* launchCookie= */ null);
243                 setPersistentActivitiesOnRootTask(activitiesToAdd,
244                         TaskInfoHelper.getToken(mRootTask));
245             }
246 
247             mAllowListedActivities.clear();
248             mAllowListedActivities.addAll(list);
249         }
250     }
251 
252     /**
253      * Returns all the {@link ComponentName}s in {@code firstList} which are not in
254      * {@code secondList}.
255      */
findDifferences(List<ComponentName> firstList, List<ComponentName> secondList)256     private List<ComponentName> findDifferences(List<ComponentName> firstList,
257             List<ComponentName> secondList) {
258         ArrayList<ComponentName> result = new ArrayList<>(firstList);
259         result.removeAll(secondList);
260         return result;
261     }
262 
setPersistentActivitiesOnRootTask(List<ComponentName> activities, IBinder launchCookie)263     private void setPersistentActivitiesOnRootTask(List<ComponentName> activities,
264             IBinder launchCookie) {
265         try {
266             mCarActivityService.setPersistentActivitiesOnRootTask(activities, launchCookie);
267         } catch (IllegalArgumentException | IllegalStateException | SecurityException e) {
268             throw e;
269         } catch (ServiceSpecificException e) {
270             throw new IllegalStateException(
271                 "Car service looks crashed on ServiceSpecificException " + e);
272         } catch (RemoteException | RuntimeException e) {
273             throw new IllegalStateException("Car service looks crashed on RemoteException " + e);
274         }
275     }
276 
toString(boolean withBounds)277     String toString(boolean withBounds) {
278         if (withBounds) {
279             ViewHelper.getBoundsOnScreen(this, mTmpRect);
280         }
281 
282         StringBuilder b = new StringBuilder(TAG).append(" {\n");
283 
284         b.append("  mDisplayId=").append(mDisplayId);
285         b.append("\n");
286 
287         b.append("  taskId=").append((getTaskInfo() == null ? "null" : getTaskInfo().taskId));
288         b.append("\n");
289 
290         if (withBounds) {
291             b.append("  boundsOnScreen=").append(mTmpRect);
292             b.append("\n");
293         }
294 
295         b.append("  mAllowListedActivities= [");
296         synchronized (mLock) {
297             for (ComponentName componentName : mAllowListedActivities) {
298                 b.append("\n    ").append(componentName);
299             }
300         }
301         b.append("  ]}\n");
302 
303         return b.toString();
304     }
305 }
306