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