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.assertPlatformVersionAtLeast; 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.app.ActivityManager; 27 import android.app.ActivityOptions; 28 import android.app.PendingIntent; 29 import android.car.Car; 30 import android.car.PlatformVersion; 31 import android.car.annotation.ApiRequirements; 32 import android.car.builtin.util.Slogf; 33 import android.car.builtin.view.SurfaceControlHelper; 34 import android.car.builtin.view.TouchableInsetsProvider; 35 import android.car.builtin.view.ViewHelper; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.graphics.Rect; 39 import android.graphics.Region; 40 import android.os.Build; 41 import android.os.DeadObjectException; 42 import android.os.RemoteException; 43 import android.util.Log; 44 import android.view.SurfaceControl; 45 import android.view.SurfaceHolder; 46 import android.view.SurfaceView; 47 48 /** 49 * A {@link SurfaceView} that can embed a Task inside of it. The task management is done remotely 50 * in a process that has registered a TaskOrganizer with the system server. 51 * Usually this process is the Car System UI. 52 * 53 * @hide 54 */ 55 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 56 public abstract class RemoteCarTaskView extends SurfaceView { 57 private static final String TAG = RemoteCarTaskView.class.getSimpleName(); 58 59 private final TouchableInsetsProvider mTouchableInsetsProvider; 60 private final SurfaceCallbackHandler mSurfaceCallbackHandler = new SurfaceCallbackHandler(); 61 private final Rect mTmpRect = new Rect(); 62 private boolean mInitialized = false; 63 boolean mSurfaceCreated = false; 64 private Region mObscuredTouchRegion; 65 private ICarTaskViewHost mICarTaskViewHost; 66 RemoteCarTaskView(Context context)67 RemoteCarTaskView(Context context) { 68 super(context); 69 assertPlatformVersionAtLeast(PlatformVersion.VERSION_CODES.UPSIDE_DOWN_CAKE_0); 70 mTouchableInsetsProvider = new TouchableInsetsProvider(this); 71 getHolder().addCallback(mSurfaceCallbackHandler); 72 } 73 74 /** Brings the embedded task to the front. Does nothing if there is no task. */ 75 @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY) 76 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 77 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) 78 @MainThread showEmbeddedTask()79 public void showEmbeddedTask() { 80 try { 81 mICarTaskViewHost.showEmbeddedTask(); 82 } catch (RemoteException e) { 83 Slogf.e(TAG, "exception in showEmbeddedTask", e); 84 } 85 } 86 87 /** 88 * Updates the WM bounds for the underlying task as per the current view bounds. Does nothing 89 * if there is no task. 90 */ 91 @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY) 92 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 93 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) 94 @MainThread updateWindowBounds()95 public void updateWindowBounds() { 96 ViewHelper.getBoundsOnScreen(RemoteCarTaskView.this, mTmpRect); 97 try { 98 mICarTaskViewHost.setWindowBounds(mTmpRect); 99 } catch (RemoteException e) { 100 Slogf.e(TAG, "exception in setWindowBounds", e); 101 } 102 } 103 104 /** 105 * Indicates a region of the view that is not touchable. 106 * 107 * @param obscuredRect the obscured region of the view. 108 */ 109 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 110 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) 111 @MainThread setObscuredTouchRect(@onNull Rect obscuredRect)112 public void setObscuredTouchRect(@NonNull Rect obscuredRect) { 113 mObscuredTouchRegion = obscuredRect != null ? new Region(obscuredRect) : null; 114 mTouchableInsetsProvider.setObscuredTouchRegion(mObscuredTouchRegion); 115 } 116 117 /** 118 * Indicates a region of the view that is not touchable. 119 * 120 * @param obscuredRegion the obscured region of the view. 121 */ 122 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 123 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) 124 @MainThread setObscuredTouchRegion(@onNull Region obscuredRegion)125 public void setObscuredTouchRegion(@NonNull Region obscuredRegion) { 126 mObscuredTouchRegion = obscuredRegion; 127 mTouchableInsetsProvider.setObscuredTouchRegion(mObscuredTouchRegion); 128 } 129 130 /** 131 * @return the {@link android.app.ActivityManager.RunningTaskInfo} of the task currently 132 * running in the TaskView. 133 */ 134 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 135 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) 136 @MainThread getTaskInfo()137 @Nullable public abstract ActivityManager.RunningTaskInfo getTaskInfo(); 138 139 /** 140 * @return true, if the task view is initialized. 141 */ 142 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 143 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) 144 @MainThread isInitialized()145 public boolean isInitialized() { 146 return mInitialized; 147 } 148 149 /** 150 * Adds the given insets on the Task. 151 * 152 * The given frame for the insets type are applied to the underlying task right away. 153 * If a rectangle for an insets type was added previously, it will be replaced with the 154 * new value. 155 * If a rectangle for a insets type was already added, but is not specified currently in 156 * {@code insets}, it will remain applied to the task. Clients should explicitly call 157 * {@link #removeInsets(int, int)} to remove the rectangle for that insets type from 158 * the underlying task. 159 * 160 * @param index An owner might add multiple insets sources with the same type. 161 * This identifies them. 162 * @param type The insets type of the insets source. 163 * @param frame The rectangle area of the insets source. 164 */ 165 @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY) 166 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 167 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) 168 @MainThread addInsets(int index, int type, @NonNull Rect frame)169 public void addInsets(int index, int type, @NonNull Rect frame) { 170 try { 171 mICarTaskViewHost.addInsets(index, type, frame); 172 } catch (RemoteException e) { 173 Log.e(TAG, "exception in addInsets", e); 174 } 175 } 176 177 /** 178 * Removes the given insets from the Task. 179 * 180 * Note: This will only remove the insets that were added using 181 * {@link #addInsets(int, int, Rect)} 182 * 183 * @param index An owner might add multiple insets sources with the same type. 184 * This identifies them. 185 * @param type The insets type of the insets source. This doesn't accept the composite types. 186 */ 187 @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY) 188 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 189 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) removeInsets(int index, int type)190 public void removeInsets(int index, int type) { 191 try { 192 mICarTaskViewHost.removeInsets(index, type); 193 } catch (RemoteException e) { 194 Log.e(TAG, "exception in removeInsets", e); 195 } 196 } 197 setRemoteHost(@onNull ICarTaskViewHost carTaskViewHost)198 void setRemoteHost(@NonNull ICarTaskViewHost carTaskViewHost) { 199 mICarTaskViewHost = carTaskViewHost; 200 201 if (mSurfaceCreated) { 202 if (!mInitialized) { 203 onInitialized(); 204 mInitialized = true; 205 } 206 } 207 } 208 209 /** 210 * Starts the activity from the given {@code PendingIntent} 211 * 212 * @param pendingIntent Intent used to launch an activity. 213 * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()} 214 * @param options options for the activity. 215 * @param launchBounds the bounds (window size and position) that the activity should be 216 * launched in, in pixels and in screen coordinates. 217 */ startActivity( @onNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, @NonNull ActivityOptions options, @Nullable Rect launchBounds)218 void startActivity( 219 @NonNull PendingIntent pendingIntent, 220 @Nullable Intent fillInIntent, 221 @NonNull ActivityOptions options, 222 @Nullable Rect launchBounds) { 223 try { 224 mICarTaskViewHost.startActivity( 225 pendingIntent, fillInIntent, options.toBundle(), launchBounds); 226 } catch (RemoteException exception) { 227 Slogf.e(TAG, "exception in startActivity", exception); 228 } 229 } 230 createRootTask(int displayId)231 void createRootTask(int displayId) { 232 try { 233 mICarTaskViewHost.createRootTask(displayId); 234 } catch (RemoteException exception) { 235 Slogf.e(TAG, "exception in createRootTask", exception); 236 } 237 } 238 createLaunchRootTask(int displayId, boolean embedHomeTask, boolean embedRecentsTask, boolean embedAssistantTask)239 void createLaunchRootTask(int displayId, boolean embedHomeTask, boolean embedRecentsTask, 240 boolean embedAssistantTask) { 241 try { 242 mICarTaskViewHost.createLaunchRootTask(displayId, embedHomeTask, embedRecentsTask, 243 embedAssistantTask); 244 } catch (RemoteException exception) { 245 Slogf.e(TAG, "exception in createRootTask", exception); 246 } 247 } 248 249 /** Release the resources associated with this task view. */ 250 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 251 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) 252 @MainThread release()253 public void release() { 254 getHolder().removeCallback(mSurfaceCallbackHandler); 255 try { 256 mICarTaskViewHost.release(); 257 } catch (DeadObjectException e) { 258 Slogf.w(TAG, "TaskView's host has already died", e); 259 } catch (RemoteException e) { 260 Slogf.e(TAG, "exception in release", e); 261 } 262 onReleased(); 263 } 264 265 /** 266 * Called when the task view is initialized. It is called only once for the lifetime of 267 * taskview. 268 */ onInitialized()269 abstract void onInitialized(); 270 271 /** 272 * Called when the task view is released. It is only called once for the lifetime of task view. 273 */ onReleased()274 abstract void onReleased(); 275 276 /** 277 * Called when the task has appeared in the taskview. 278 * 279 * @param taskInfo the taskInfo of the task that has appeared. 280 * @param leash the suface control for the task surface. 281 */ onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)282 void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { 283 } 284 285 /** 286 * Called when the task's info has changed. 287 * 288 * @param taskInfo the taskInfo of the task that has a change in info. 289 */ onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)290 void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { 291 } 292 293 /** 294 * Called when the task has vanished. 295 * 296 * @param taskInfo the taskInfo of the task that has vanished. 297 */ onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)298 void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { 299 } 300 301 @Override 302 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 303 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) onAttachedToWindow()304 public void onAttachedToWindow() { 305 super.onAttachedToWindow(); 306 mTouchableInsetsProvider.addToViewTreeObserver(); 307 } 308 309 @Override 310 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 311 minPlatformVersion = ApiRequirements.PlatformVersion.UPSIDE_DOWN_CAKE_0) onDetachedFromWindow()312 public void onDetachedFromWindow() { 313 super.onDetachedFromWindow(); 314 mTouchableInsetsProvider.removeFromViewTreeObserver(); 315 } 316 317 private class SurfaceCallbackHandler implements SurfaceHolder.Callback { 318 @Override surfaceCreated(@onNull SurfaceHolder holder)319 public void surfaceCreated(@NonNull SurfaceHolder holder) { 320 if (mICarTaskViewHost != null) { 321 if (!mInitialized) { 322 onInitialized(); 323 mInitialized = true; 324 } 325 } 326 mSurfaceCreated = true; 327 try { 328 mICarTaskViewHost.notifySurfaceCreated( 329 SurfaceControlHelper.copy(getSurfaceControl())); 330 } catch (RemoteException e) { 331 Slogf.e(TAG, "exception in notifySurfaceCreated", e); 332 } 333 } 334 335 @Override surfaceChanged( @onNull SurfaceHolder holder, int format, int width, int height)336 public void surfaceChanged( 337 @NonNull SurfaceHolder holder, int format, int width, int height) { 338 try { 339 ViewHelper.getBoundsOnScreen(RemoteCarTaskView.this, mTmpRect); 340 mICarTaskViewHost.setWindowBounds(mTmpRect); 341 } catch (RemoteException e) { 342 Slogf.e(TAG, "exception in setWindowBounds", e); 343 } 344 } 345 346 @Override surfaceDestroyed(@onNull SurfaceHolder holder)347 public void surfaceDestroyed(@NonNull SurfaceHolder holder) { 348 mSurfaceCreated = false; 349 try { 350 mICarTaskViewHost.notifySurfaceDestroyed(); 351 } catch (RemoteException e) { 352 Slogf.e(TAG, "exception in notifySurfaceDestroyed", e); 353 } 354 } 355 } 356 } 357