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