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 android.annotation.MainThread; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.RequiresPermission; 24 import android.annotation.SystemApi; 25 import android.app.ActivityManager; 26 import android.app.ActivityOptions; 27 import android.app.PendingIntent; 28 import android.car.Car; 29 import android.car.builtin.util.Slogf; 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.Binder; 36 import android.os.UserManager; 37 import android.util.Slog; 38 import android.view.Display; 39 import android.view.SurfaceControl; 40 41 import java.util.concurrent.Executor; 42 43 /** 44 * A {@link ControlledRemoteCarTaskView} should be used when the launch intent of the task is known 45 * before hand. 46 * 47 * The underlying task will be restarted if it is crashed depending on the 48 * {@link ControlledRemoteCarTaskViewConfig#shouldAutoRestartOnCrash()}. 49 * 50 * <p>It should be preferred when: 51 * <ul> 52 * <li>The underlying task is meant to be started by the host and be there forever.</li> 53 * </ul> 54 * 55 * @hide 56 */ 57 @SystemApi 58 public final class ControlledRemoteCarTaskView extends RemoteCarTaskView { 59 private static final String TAG = ControlledRemoteCarTaskView.class.getSimpleName(); 60 61 private final Executor mCallbackExecutor; 62 private final ControlledRemoteCarTaskViewCallback mCallback; 63 private final UserManager mUserManager; 64 private final CarTaskViewController mCarTaskViewController; 65 private final Context mContext; 66 private final ControlledRemoteCarTaskViewConfig mConfig; 67 private final Rect mTmpRect = new Rect(); 68 69 private ActivityManager.RunningTaskInfo mTaskInfo; 70 @Nullable private RunnerWithBackoff mStartActivityWithBackoff; 71 72 final ICarTaskViewClient mICarTaskViewClient = new ICarTaskViewClient.Stub() { 73 @Override 74 public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { 75 long identity = Binder.clearCallingIdentity(); 76 try { 77 mTaskInfo = taskInfo; 78 updateWindowBounds(); 79 if (taskInfo.taskDescription != null) { 80 ViewHelper.seResizeBackgroundColor(ControlledRemoteCarTaskView.this, 81 taskInfo.taskDescription.getBackgroundColor()); 82 } 83 ControlledRemoteCarTaskView.this.onTaskAppeared(taskInfo, leash); 84 } finally { 85 Binder.restoreCallingIdentity(identity); 86 } 87 } 88 89 @Override 90 public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { 91 long identity = Binder.clearCallingIdentity(); 92 try { 93 if (taskInfo.taskDescription != null) { 94 ViewHelper.seResizeBackgroundColor(ControlledRemoteCarTaskView.this, 95 taskInfo.taskDescription.getBackgroundColor()); 96 } 97 ControlledRemoteCarTaskView.this.onTaskInfoChanged(taskInfo); 98 } finally { 99 Binder.restoreCallingIdentity(identity); 100 } 101 } 102 103 @Override 104 public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { 105 long identity = Binder.clearCallingIdentity(); 106 try { 107 mTaskInfo = null; 108 ControlledRemoteCarTaskView.this.onTaskVanished(taskInfo); 109 } finally { 110 Binder.restoreCallingIdentity(identity); 111 } 112 } 113 114 @Override 115 public void setResizeBackgroundColor(SurfaceControl.Transaction t, int color) { 116 ViewHelper.seResizeBackgroundColor(ControlledRemoteCarTaskView.this, color); 117 } 118 119 @Override 120 public Rect getCurrentBoundsOnScreen() { 121 ViewHelper.getBoundsOnScreen(ControlledRemoteCarTaskView.this, mTmpRect); 122 return mTmpRect; 123 } 124 }; 125 ControlledRemoteCarTaskView( @onNull Context context, ControlledRemoteCarTaskViewConfig config, @NonNull Executor callbackExecutor, @NonNull ControlledRemoteCarTaskViewCallback callback, CarTaskViewController carTaskViewController, @NonNull UserManager userManager)126 ControlledRemoteCarTaskView( 127 @NonNull Context context, 128 ControlledRemoteCarTaskViewConfig config, 129 @NonNull Executor callbackExecutor, 130 @NonNull ControlledRemoteCarTaskViewCallback callback, 131 CarTaskViewController carTaskViewController, 132 @NonNull UserManager userManager) { 133 super(context); 134 mContext = context; 135 mConfig = config; 136 mCallbackExecutor = callbackExecutor; 137 mCallback = callback; 138 mCarTaskViewController = carTaskViewController; 139 mUserManager = userManager; 140 141 mCallbackExecutor.execute(() -> mCallback.onTaskViewCreated(this)); 142 if (mConfig.mShouldAutoRestartOnTaskRemoval) { 143 mStartActivityWithBackoff = new RunnerWithBackoff(this::startActivityInternal); 144 } 145 } 146 147 /** 148 * Starts the underlying activity, specified as part of 149 * {@link CarTaskViewController#createControlledRemoteCarTaskView(Executor, Intent, boolean, 150 * ControlledRemoteCarTaskViewCallback)}. 151 */ 152 @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY) 153 @MainThread startActivity()154 public void startActivity() { 155 if (mStartActivityWithBackoff == null) { 156 startActivityInternal(); 157 return; 158 } 159 mStartActivityWithBackoff.stop(); 160 mStartActivityWithBackoff.start(); 161 } 162 stopTheStartActivityBackoffIfExists()163 private void stopTheStartActivityBackoffIfExists() { 164 if (mStartActivityWithBackoff == null) { 165 if (CarTaskViewController.DBG) { 166 Slog.d(TAG, "mStartActivityWithBackoff is not present."); 167 } 168 return; 169 } 170 mStartActivityWithBackoff.stop(); 171 } 172 startActivityInternal()173 private void startActivityInternal() { 174 if (!mUserManager.isUserUnlocked()) { 175 if (CarTaskViewController.DBG) { 176 Slogf.d(TAG, "Can't start activity due to user is isn't unlocked"); 177 } 178 return; 179 } 180 181 // Use mContext.getDisplay() instead of View#getDisplay() since 182 // ControlledRemoteCarTaskViews can be created using window context. Using 183 // View#getDisplay() would return null. 184 Display display = mContext.getDisplay(); 185 186 // Don't start activity when the display is off. This can happen when the taskview is not 187 // attached to a window. 188 if (display == null) { 189 Slogf.w(TAG, "Can't start activity because display is not available in " 190 + "taskview yet."); 191 return; 192 } 193 // Don't start activity when the display is off for ActivityVisibilityTests. 194 if (display.getState() != Display.STATE_ON) { 195 Slogf.w(TAG, "Can't start activity due to the display is off"); 196 return; 197 } 198 199 ActivityOptions options = ActivityOptions 200 .makeCustomAnimation(mContext, /* enterResId= */ 0, /* exitResId= */ 0) 201 .setPendingIntentBackgroundActivityStartMode( 202 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); 203 Rect launchBounds = new Rect(); 204 ViewHelper.getBoundsOnScreen(this, launchBounds); 205 launchBounds.set(launchBounds); 206 if (CarTaskViewController.DBG) { 207 Slogf.d(TAG, "Starting (" + mConfig.mActivityIntent.getComponent() + ") on " 208 + launchBounds); 209 } 210 Intent fillInIntent = null; 211 if ((mConfig.mActivityIntent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0) { 212 fillInIntent = new Intent().addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 213 } 214 startActivity( 215 PendingIntent.getActivity(mContext, /* requestCode= */ 0, 216 mConfig.mActivityIntent, 217 PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT), 218 fillInIntent, options, launchBounds); 219 } 220 221 @Override onInitialized()222 void onInitialized() { 223 mContext.getMainExecutor().execute(() -> { 224 // Check for isReleased() because the car task view might have already been 225 // released but this code path is executed later because the executor was busy. 226 if (isReleased()) { 227 Slogf.w(TAG, "car task view has already been released"); 228 return; 229 } 230 startActivity(); 231 }); 232 mCallbackExecutor.execute(() -> mCallback.onTaskViewInitialized()); 233 } 234 235 @Override onReleased()236 void onReleased() { 237 mTaskInfo = null; 238 mCallbackExecutor.execute(() -> mCallback.onTaskViewReleased()); 239 mCarTaskViewController.onRemoteCarTaskViewReleased(this); 240 } 241 242 @Override onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)243 void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { 244 super.onTaskAppeared(taskInfo, leash); 245 // Stop the start activity backoff because a task has already appeared. 246 stopTheStartActivityBackoffIfExists(); 247 mCallbackExecutor.execute(() -> { 248 if (isReleased()) { 249 Slogf.w(TAG, "car task view has already been released"); 250 return; 251 } 252 mCallback.onTaskAppeared(taskInfo); 253 }); 254 } 255 256 @Override onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)257 void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { 258 super.onTaskInfoChanged(taskInfo); 259 mCallbackExecutor.execute(() -> { 260 if (isReleased()) { 261 Slogf.w(TAG, "car task view has already been released"); 262 return; 263 } 264 mCallback.onTaskInfoChanged(taskInfo); 265 }); 266 } 267 268 @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY) 269 @Override 270 @MainThread showEmbeddedTask()271 public void showEmbeddedTask() { 272 super.showEmbeddedTask(); 273 if (getTaskInfo() == null) { 274 if (CarTaskViewController.DBG) { 275 Slogf.d(TAG, "Embedded task not available, starting it now."); 276 } 277 startActivity(); 278 } 279 } 280 281 @Override onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)282 void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { 283 super.onTaskVanished(taskInfo); 284 if (mConfig.mShouldAutoRestartOnTaskRemoval && mCarTaskViewController.isHostVisible()) { 285 // onTaskVanished can be called when the host is in the background. In this case 286 // embedded activity should not be started. 287 Slogf.i(TAG, "Restarting task " + taskInfo.baseActivity 288 + " in ControlledRemoteCarTaskView"); 289 startActivity(); 290 } 291 mCallbackExecutor.execute(() -> { 292 if (isReleased()) { 293 Slogf.w(TAG, "car task view has already been released"); 294 return; 295 } 296 mCallback.onTaskVanished(taskInfo); 297 }); 298 } 299 300 /** 301 * @return the {@link android.app.ActivityManager.RunningTaskInfo} of the task currently 302 * running in the TaskView. 303 */ 304 @MainThread 305 @Override 306 @Nullable getTaskInfo()307 public ActivityManager.RunningTaskInfo getTaskInfo() { 308 return mTaskInfo; 309 } 310 getConfig()311 ControlledRemoteCarTaskViewConfig getConfig() { 312 return mConfig; 313 } 314 315 @Override toString()316 public String toString() { 317 return toString(/* withBounds= */ false); 318 } 319 toString(boolean withBounds)320 String toString(boolean withBounds) { 321 if (withBounds) { 322 ViewHelper.getBoundsOnScreen(this, mTmpRect); 323 } 324 return TAG + " {\n" 325 + " config=" + mConfig + "\n" 326 + " taskId=" + (getTaskInfo() == null ? "null" : getTaskInfo().taskId) + "\n" 327 + (withBounds ? (" boundsOnScreen=" + mTmpRect) : "") 328 + "}\n"; 329 } 330 331 // Since SurfaceView is public, these methods need to be overridden. Details in b/296680464. 332 @Override 333 @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY) 334 @MainThread addInsets(int index, int type, @NonNull Rect frame)335 public void addInsets(int index, int type, @NonNull Rect frame) { 336 super.addInsets(index, type, frame); 337 } 338 339 @Override 340 @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY) removeInsets(int index, int type)341 public void removeInsets(int index, int type) { 342 super.removeInsets(index, type); 343 } 344 345 @Override 346 @MainThread release()347 public void release() { 348 super.release(); 349 stopTheStartActivityBackoffIfExists(); 350 } 351 352 @Override onAttachedToWindow()353 public void onAttachedToWindow() { 354 super.onAttachedToWindow(); 355 } 356 357 358 @Override onDetachedFromWindow()359 public void onDetachedFromWindow() { 360 super.onDetachedFromWindow(); 361 } 362 363 @Override 364 @MainThread isInitialized()365 public boolean isInitialized() { 366 return super.isInitialized(); 367 } 368 369 @Override 370 @MainThread setObscuredTouchRegion(@onNull Region obscuredRegion)371 public void setObscuredTouchRegion(@NonNull Region obscuredRegion) { 372 super.setObscuredTouchRegion(obscuredRegion); 373 } 374 375 @Override 376 @MainThread setObscuredTouchRect(@onNull Rect obscuredRect)377 public void setObscuredTouchRect(@NonNull Rect obscuredRect) { 378 super.setObscuredTouchRect(obscuredRect); 379 } 380 381 @Override 382 @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY) 383 @MainThread updateWindowBounds()384 public void updateWindowBounds() { 385 super.updateWindowBounds(); 386 } 387 } 388