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