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