1 /* 2 * Copyright (C) 2020 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.input; 18 19 import static android.car.CarOccupantZoneManager.DisplayTypeEnum; 20 21 import android.annotation.CallbackExecutor; 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.RequiresPermission; 25 import android.annotation.SystemApi; 26 import android.car.Car; 27 import android.car.CarManagerBase; 28 import android.car.CarOccupantZoneManager; 29 import android.car.annotation.AddedInOrBefore; 30 import android.car.annotation.ApiRequirements; 31 import android.car.builtin.util.Slogf; 32 import android.os.Handler; 33 import android.os.IBinder; 34 import android.os.RemoteException; 35 import android.util.SparseArray; 36 import android.view.KeyEvent; 37 38 import com.android.internal.annotations.GuardedBy; 39 40 import java.lang.annotation.ElementType; 41 import java.lang.annotation.Retention; 42 import java.lang.annotation.RetentionPolicy; 43 import java.lang.annotation.Target; 44 import java.lang.ref.WeakReference; 45 import java.util.List; 46 import java.util.Objects; 47 import java.util.concurrent.Executor; 48 49 /** 50 * This API allows capturing selected input events. 51 */ 52 public final class CarInputManager extends CarManagerBase { 53 54 private static final String TAG = CarInputManager.class.getSimpleName(); 55 56 private static final boolean DEBUG = false; 57 58 private static final String PERMISSION_FRAMEWORK_MONITOR_INPUT = 59 "android.permission.MONITOR_INPUT"; 60 61 /** 62 * Callback for capturing input events. 63 * <p> 64 * Events (key, rotary and custom input events) are associated with display types. 65 * Display types are defined in {@link android.car.CarOccupantZoneManager}. This manager only 66 * accepts the driver display types ({@link CarOccupantZoneManager#DISPLAY_TYPE_MAIN} and 67 * {@link CarOccupantZoneManager#DISPLAY_TYPE_INSTRUMENT_CLUSTER}). 68 * 69 * @hide 70 */ 71 @SystemApi 72 public interface CarInputCaptureCallback { 73 /** 74 * Key events were captured. 75 * 76 * @param targetDisplayType the display type associated with the events passed as parameter 77 * @param keyEvents the key events to process 78 */ 79 @AddedInOrBefore(majorVersion = 33) onKeyEvents(@isplayTypeEnum int targetDisplayType, @NonNull List<KeyEvent> keyEvents)80 default void onKeyEvents(@DisplayTypeEnum int targetDisplayType, 81 @NonNull List<KeyEvent> keyEvents) {} 82 83 /** 84 * Rotary events were captured. 85 * 86 * @param targetDisplayType the display type associated with the events passed as parameter 87 * @param events the rotary events to process 88 */ 89 @AddedInOrBefore(majorVersion = 33) onRotaryEvents(@isplayTypeEnum int targetDisplayType, @NonNull List<RotaryEvent> events)90 default void onRotaryEvents(@DisplayTypeEnum int targetDisplayType, 91 @NonNull List<RotaryEvent> events) {} 92 93 /** 94 * Capture state for the display has changed due to other client making requests or 95 * releasing capture. Client should check {@code activeInputTypes} for which input types 96 * are currently captured. 97 * 98 * @param targetDisplayType the display type associated with the events passed as parameter 99 * @param activeInputTypes the input types to watch 100 */ 101 @AddedInOrBefore(majorVersion = 33) onCaptureStateChanged(@isplayTypeEnum int targetDisplayType, @NonNull @InputTypeEnum int[] activeInputTypes)102 default void onCaptureStateChanged(@DisplayTypeEnum int targetDisplayType, 103 @NonNull @InputTypeEnum int[] activeInputTypes) {} 104 105 /** 106 * Custom input events were captured. 107 * 108 * @param targetDisplayType the display type associated with the events passed as parameter 109 * @param events the custom input events to process 110 */ 111 @AddedInOrBefore(majorVersion = 33) onCustomInputEvents(@isplayTypeEnum int targetDisplayType, @NonNull List<CustomInputEvent> events)112 default void onCustomInputEvents(@DisplayTypeEnum int targetDisplayType, 113 @NonNull List<CustomInputEvent> events) {} 114 } 115 116 /** 117 * Client will wait for grant if the request is failing due to higher priority client. 118 * 119 * @hide 120 */ 121 @SystemApi 122 @AddedInOrBefore(majorVersion = 33) 123 public static final int CAPTURE_REQ_FLAGS_ALLOW_DELAYED_GRANT = 0x1; 124 125 /** 126 * Client wants to capture the keys for the whole display. This is only allowed to system 127 * process. 128 * 129 * @hide 130 */ 131 @SystemApi 132 @AddedInOrBefore(majorVersion = 33) 133 public static final int CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY = 0x2; 134 135 /** @hide */ 136 @IntDef(flag = true, prefix = {"CAPTURE_REQ_FLAGS_"}, value = { 137 CAPTURE_REQ_FLAGS_ALLOW_DELAYED_GRANT, 138 CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY, 139 }) 140 @Retention(RetentionPolicy.SOURCE) 141 public @interface CaptureRequestFlags {} 142 143 /** 144 * This is special type to cover all INPUT_TYPE_*. This is used for clients using 145 * {@link #CAPTURE_REQ_FLAGS_TAKE_ALL_EVENTS_FOR_DISPLAY} flag. 146 * 147 * @hide 148 */ 149 @SystemApi 150 @AddedInOrBefore(majorVersion = 33) 151 public static final int INPUT_TYPE_ALL_INPUTS = 1; 152 153 /** 154 * This covers rotary input device for navigation. 155 */ 156 @AddedInOrBefore(majorVersion = 33) 157 public static final int INPUT_TYPE_ROTARY_NAVIGATION = 10; 158 159 /** 160 * Volume knob. 161 */ 162 @AddedInOrBefore(majorVersion = 33) 163 public static final int INPUT_TYPE_ROTARY_VOLUME = 11; 164 165 /** 166 * This is the group of keys for DPAD. 167 * Included key events are: {@link KeyEvent#KEYCODE_DPAD_UP}, 168 * {@link KeyEvent#KEYCODE_DPAD_DOWN}, {@link KeyEvent#KEYCODE_DPAD_LEFT}, 169 * {@link KeyEvent#KEYCODE_DPAD_RIGHT}, {@link KeyEvent#KEYCODE_DPAD_CENTER}, 170 * {@link KeyEvent#KEYCODE_DPAD_DOWN_LEFT}, {@link KeyEvent#KEYCODE_DPAD_DOWN_RIGHT}, 171 * {@link KeyEvent#KEYCODE_DPAD_UP_LEFT}, {@link KeyEvent#KEYCODE_DPAD_UP_RIGHT} 172 */ 173 @AddedInOrBefore(majorVersion = 33) 174 public static final int INPUT_TYPE_DPAD_KEYS = 100; 175 176 /** 177 * This is for all {@code KeyEvent#KEYCODE_NAVIGATE_*} keys and {@link KeyEvent#KEYCODE_BACK}. 178 */ 179 @AddedInOrBefore(majorVersion = 33) 180 public static final int INPUT_TYPE_NAVIGATE_KEYS = 101; 181 182 /** 183 * This is for all {@code KeyEvent#KEYCODE_SYSTEM_NAVIGATE_*} keys. 184 */ 185 @AddedInOrBefore(majorVersion = 33) 186 public static final int INPUT_TYPE_SYSTEM_NAVIGATE_KEYS = 102; 187 188 /** 189 * This is for {@code HW_CUSTOM_INPUT} events. 190 */ 191 @AddedInOrBefore(majorVersion = 33) 192 public static final int INPUT_TYPE_CUSTOM_INPUT_EVENT = 200; 193 194 /** 195 * This is for touch mode input type. 196 */ 197 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 198 minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0) 199 public static final int INPUT_TYPE_TOUCH_SCREEN = 210; 200 201 /** 202 * This is for displays that don't support any input type 203 */ 204 @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.UPSIDE_DOWN_CAKE_0, 205 minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0) 206 public static final int INPUT_TYPE_NONE = -1; 207 208 /** @hide */ 209 @Retention(RetentionPolicy.SOURCE) 210 @IntDef(prefix = "INPUT_TYPE_", value = { 211 INPUT_TYPE_ALL_INPUTS, 212 INPUT_TYPE_ROTARY_NAVIGATION, 213 INPUT_TYPE_ROTARY_VOLUME, 214 INPUT_TYPE_DPAD_KEYS, 215 INPUT_TYPE_NAVIGATE_KEYS, 216 INPUT_TYPE_SYSTEM_NAVIGATE_KEYS, 217 INPUT_TYPE_CUSTOM_INPUT_EVENT, 218 INPUT_TYPE_TOUCH_SCREEN, 219 INPUT_TYPE_NONE, 220 }) 221 @Target({ElementType.TYPE_USE}) 222 public @interface InputTypeEnum {} 223 224 /** 225 * The client's request has succeeded and capture will start. 226 * 227 * @hide 228 */ 229 @SystemApi 230 @AddedInOrBefore(majorVersion = 33) 231 public static final int INPUT_CAPTURE_RESPONSE_SUCCEEDED = 0; 232 233 /** 234 * The client's request has failed due to higher priority client already capturing. If priority 235 * for the clients are the same, last client making request will be allowed to capture. 236 * 237 * @hide 238 */ 239 @SystemApi 240 @AddedInOrBefore(majorVersion = 33) 241 public static final int INPUT_CAPTURE_RESPONSE_FAILED = 1; 242 243 /** 244 * This is used when client has set {@link #CAPTURE_REQ_FLAGS_ALLOW_DELAYED_GRANT} in 245 * {@code requestFlags} and capturing is blocked due to existing higher priority client. 246 * When the higher priority client stops capturing, this client can capture events after 247 * getting @link CarInputCaptureCallback#onCaptureStateChanged(int, int[])} call. 248 * 249 * @hide 250 */ 251 @SystemApi 252 @AddedInOrBefore(majorVersion = 33) 253 public static final int INPUT_CAPTURE_RESPONSE_DELAYED = 2; 254 255 /** @hide */ 256 @Retention(RetentionPolicy.SOURCE) 257 @IntDef(prefix = "INPUT_CAPTURE_RESPONSE_", value = { 258 INPUT_CAPTURE_RESPONSE_SUCCEEDED, 259 INPUT_CAPTURE_RESPONSE_FAILED, 260 INPUT_CAPTURE_RESPONSE_DELAYED 261 }) 262 @Target({ElementType.TYPE_USE}) 263 public @interface InputCaptureResponseEnum {} 264 265 private final ICarInput mService; 266 private final ICarInputCallback mServiceCallback = new ICarInputCallbackImpl(this); 267 268 private final Object mLock = new Object(); 269 270 @GuardedBy("mLock") 271 private final SparseArray<CallbackHolder> mCarInputCaptureCallbacks = new SparseArray<>(1); 272 273 /** 274 * @hide 275 */ CarInputManager(Car car, IBinder service)276 public CarInputManager(Car car, IBinder service) { 277 super(car); 278 mService = ICarInput.Stub.asInterface(service); 279 } 280 281 /** 282 * Requests capturing of input event for the specified display for all requested input types. 283 * 284 * <p>The request can fail if a high priority client is holding it. The client can set 285 * {@link #CAPTURE_REQ_FLAGS_ALLOW_DELAYED_GRANT} in {@code requestFlags} to wait for the 286 * current high priority client to release it. 287 * 288 * <p>If only some of the input types specified are available, the request will either: 289 * <ul> 290 * <li>fail, returning {@link #INPUT_CAPTURE_RESPONSE_FAILED}, or 291 * <li>be deferred, returning {@link #INPUT_CAPTURE_RESPONSE_DELAYED}, if the 292 * {@link #CAPTURE_REQ_FLAGS_ALLOW_DELAYED_GRANT} flag is used. 293 * </ul> 294 * 295 * <p> After {@link #INPUT_CAPTURE_RESPONSE_DELAYED} is returned, no input types are captured 296 * until the client receives a {@link CarInputCaptureCallback#onCaptureStateChanged(int, int[])} 297 * call with valid input types. 298 * 299 * <p> The targetDisplayType parameter must only contain driver display types (which are 300 * {@link CarOccupantZoneManager#DISPLAY_TYPE_MAIN} and 301 * {@link CarOccupantZoneManager#DISPLAY_TYPE_INSTRUMENT_CLUSTER}. 302 * 303 * <p>Callbacks are grouped and stacked per display types. Only the most recently 304 * registered callback will receive the incoming events for the associated display and input 305 * types. For instance, if two callbacks are registered against the same display type, on the 306 * same {@link CarInputManager} instance, then only the last registered callback will receive 307 * events, even if they were registered for different input event types. 308 * 309 * @throws SecurityException if caller doesn't have one of the following permissions granted: 310 * {@code android.car.permission.CAR_MONITOR_INPUT} nor 311 * {@code android.Manifest.permission.MONITOR_INPUT} 312 * @throws IllegalArgumentException if targetDisplayType parameter correspond to a non supported 313 * display type 314 * @throws IllegalArgumentException if inputTypes parameter contains invalid or non supported 315 * values 316 * @param targetDisplayType the display type to register callback for 317 * @param inputTypes the input type to register callback for 318 * @param requestFlags the capture request flag 319 * @param callback the callback to receive the input events 320 * @return the input capture response indicating if registration succeed, failed or delayed 321 * 322 * @hide 323 */ 324 @SystemApi 325 @RequiresPermission(anyOf = {PERMISSION_FRAMEWORK_MONITOR_INPUT, 326 Car.PERMISSION_CAR_MONITOR_INPUT}) 327 @InputCaptureResponseEnum 328 @AddedInOrBefore(majorVersion = 33) requestInputEventCapture(@isplayTypeEnum int targetDisplayType, @NonNull @InputTypeEnum int[] inputTypes, @CaptureRequestFlags int requestFlags, @NonNull CarInputCaptureCallback callback)329 public int requestInputEventCapture(@DisplayTypeEnum int targetDisplayType, 330 @NonNull @InputTypeEnum int[] inputTypes, 331 @CaptureRequestFlags int requestFlags, 332 @NonNull CarInputCaptureCallback callback) { 333 Handler handler = getEventHandler(); 334 return requestInputEventCapture(targetDisplayType, inputTypes, requestFlags, handler::post, 335 callback); 336 } 337 338 /** 339 * Works just like {@link CarInputManager#requestInputEventCapture(int, int[], int, 340 * CarInputCaptureCallback)} except that callbacks are invoked using 341 * the executor passed as parameter. 342 * 343 * @throws SecurityException if caller doesn't have one of the following permissions granted: 344 * {@code android.car.permission.CAR_MONITOR_INPUT} nor 345 * {@code android.Manifest.permission.MONITOR_INPUT} 346 * @param targetDisplayType the display type to register callback against 347 * @param inputTypes the input type to register callback against 348 * @param requestFlags the capture request flag 349 * @param executor {@link Executor} to handle the callbacks 350 * @param callback the callback to receive the input events 351 * @return the input capture response indicating if registration succeed, failed or delayed 352 * @see CarInputManager#requestInputEventCapture(int, int[], int, CarInputCaptureCallback) 353 * 354 * @hide 355 */ 356 @SystemApi 357 @RequiresPermission(anyOf = {PERMISSION_FRAMEWORK_MONITOR_INPUT, 358 Car.PERMISSION_CAR_MONITOR_INPUT}) 359 @InputCaptureResponseEnum 360 @AddedInOrBefore(majorVersion = 33) requestInputEventCapture(@isplayTypeEnum int targetDisplayType, @NonNull @InputTypeEnum int[] inputTypes, @CaptureRequestFlags int requestFlags, @NonNull @CallbackExecutor Executor executor, @NonNull CarInputCaptureCallback callback)361 public int requestInputEventCapture(@DisplayTypeEnum int targetDisplayType, 362 @NonNull @InputTypeEnum int[] inputTypes, 363 @CaptureRequestFlags int requestFlags, 364 @NonNull @CallbackExecutor Executor executor, 365 @NonNull CarInputCaptureCallback callback) { 366 Objects.requireNonNull(executor); 367 Objects.requireNonNull(callback); 368 369 synchronized (mLock) { 370 mCarInputCaptureCallbacks.put(targetDisplayType, 371 new CallbackHolder(callback, executor)); 372 } 373 try { 374 return mService.requestInputEventCapture(mServiceCallback, targetDisplayType, 375 inputTypes, requestFlags); 376 } catch (RemoteException e) { 377 return handleRemoteExceptionFromCarService(e, INPUT_CAPTURE_RESPONSE_FAILED); 378 } 379 } 380 381 /** 382 * Stops capturing of given display. 383 * 384 * @hide 385 */ 386 @SystemApi 387 @AddedInOrBefore(majorVersion = 33) releaseInputEventCapture(@isplayTypeEnum int targetDisplayType)388 public void releaseInputEventCapture(@DisplayTypeEnum int targetDisplayType) { 389 CallbackHolder callbackHolder; 390 synchronized (mLock) { 391 callbackHolder = mCarInputCaptureCallbacks.get(targetDisplayType); 392 mCarInputCaptureCallbacks.delete(targetDisplayType); 393 } 394 if (callbackHolder == null) { 395 return; 396 } 397 try { 398 mService.releaseInputEventCapture(mServiceCallback, targetDisplayType); 399 } catch (RemoteException e) { 400 // ignore 401 } 402 } 403 404 /** 405 * Injects the {@link KeyEvent} passed as parameter against Car Input API. 406 * <p> 407 * The event parameter display id will be overridden accordingly to the display type also passed 408 * as parameter. 409 * 410 * @param event the key event to inject 411 * @param targetDisplayType the display type associated with the key event 412 * @throws RemoteException in case of failure when invoking car input service 413 * 414 * @hide 415 */ 416 @SystemApi 417 @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) 418 @AddedInOrBefore(majorVersion = 33) injectKeyEvent(@onNull KeyEvent event, @DisplayTypeEnum int targetDisplayType)419 public void injectKeyEvent(@NonNull KeyEvent event, @DisplayTypeEnum int targetDisplayType) { 420 try { 421 mService.injectKeyEvent(event, targetDisplayType); 422 } catch (RemoteException e) { 423 e.rethrowFromSystemServer(); 424 } 425 } 426 427 /** @hide */ 428 @Override 429 @AddedInOrBefore(majorVersion = 33) onCarDisconnected()430 protected void onCarDisconnected() { 431 synchronized (mLock) { 432 mCarInputCaptureCallbacks.clear(); 433 } 434 } 435 getCallback(@isplayTypeEnum int targetDisplayType)436 private CallbackHolder getCallback(@DisplayTypeEnum int targetDisplayType) { 437 synchronized (mLock) { 438 return mCarInputCaptureCallbacks.get(targetDisplayType); 439 } 440 } 441 dispatchKeyEvents(@isplayTypeEnum int targetDisplayType, List<KeyEvent> keyEvents)442 private void dispatchKeyEvents(@DisplayTypeEnum int targetDisplayType, 443 List<KeyEvent> keyEvents) { 444 CallbackHolder callbackHolder = getCallback(targetDisplayType); 445 if (callbackHolder == null) { 446 return; 447 } 448 callbackHolder.mExecutor.execute(() -> { 449 callbackHolder.mCallback.onKeyEvents(targetDisplayType, keyEvents); 450 }); 451 } 452 dispatchRotaryEvents(@isplayTypeEnum int targetDisplayType, List<RotaryEvent> events)453 private void dispatchRotaryEvents(@DisplayTypeEnum int targetDisplayType, 454 List<RotaryEvent> events) { 455 CallbackHolder callbackHolder = getCallback(targetDisplayType); 456 if (callbackHolder == null) { 457 return; 458 } 459 callbackHolder.mExecutor.execute(() -> { 460 callbackHolder.mCallback.onRotaryEvents(targetDisplayType, events); 461 }); 462 } 463 dispatchOnCaptureStateChanged(@isplayTypeEnum int targetDisplayType, int[] activeInputTypes)464 private void dispatchOnCaptureStateChanged(@DisplayTypeEnum int targetDisplayType, 465 int[] activeInputTypes) { 466 CallbackHolder callbackHolder = getCallback(targetDisplayType); 467 if (callbackHolder == null) { 468 return; 469 } 470 callbackHolder.mExecutor.execute(() -> { 471 callbackHolder.mCallback.onCaptureStateChanged(targetDisplayType, activeInputTypes); 472 }); 473 } 474 dispatchCustomInputEvents(@isplayTypeEnum int targetDisplayType, List<CustomInputEvent> events)475 private void dispatchCustomInputEvents(@DisplayTypeEnum int targetDisplayType, 476 List<CustomInputEvent> events) { 477 CallbackHolder callbackHolder = getCallback(targetDisplayType); 478 if (callbackHolder == null) { 479 return; 480 } 481 callbackHolder.mExecutor.execute(() -> { 482 if (DEBUG) { 483 Slogf.d(TAG, "Firing events " + events + " on callback " 484 + callbackHolder.mCallback); 485 } 486 callbackHolder.mCallback.onCustomInputEvents(targetDisplayType, events); 487 }); 488 } 489 490 private static final class ICarInputCallbackImpl extends ICarInputCallback.Stub { 491 492 private final WeakReference<CarInputManager> mManager; 493 ICarInputCallbackImpl(CarInputManager manager)494 private ICarInputCallbackImpl(CarInputManager manager) { 495 mManager = new WeakReference<>(manager); 496 } 497 498 @Override onKeyEvents(@isplayTypeEnum int targetDisplayType, @NonNull List<KeyEvent> keyEvents)499 public void onKeyEvents(@DisplayTypeEnum int targetDisplayType, 500 @NonNull List<KeyEvent> keyEvents) { 501 CarInputManager manager = mManager.get(); 502 if (manager == null) { 503 return; 504 } 505 manager.dispatchKeyEvents(targetDisplayType, keyEvents); 506 } 507 508 @Override onRotaryEvents(@isplayTypeEnum int targetDisplayType, @NonNull List<RotaryEvent> events)509 public void onRotaryEvents(@DisplayTypeEnum int targetDisplayType, 510 @NonNull List<RotaryEvent> events) { 511 CarInputManager manager = mManager.get(); 512 if (manager == null) { 513 return; 514 } 515 manager.dispatchRotaryEvents(targetDisplayType, events); 516 } 517 518 @Override onCaptureStateChanged(@isplayTypeEnum int targetDisplayType, @NonNull @InputTypeEnum int[] activeInputTypes)519 public void onCaptureStateChanged(@DisplayTypeEnum int targetDisplayType, 520 @NonNull @InputTypeEnum int[] activeInputTypes) { 521 CarInputManager manager = mManager.get(); 522 if (manager == null) { 523 return; 524 } 525 manager.dispatchOnCaptureStateChanged(targetDisplayType, activeInputTypes); 526 } 527 528 @Override onCustomInputEvents(@isplayTypeEnum int targetDisplayType, @NonNull List<CustomInputEvent> events)529 public void onCustomInputEvents(@DisplayTypeEnum int targetDisplayType, 530 @NonNull List<CustomInputEvent> events) { 531 CarInputManager manager = mManager.get(); 532 if (manager == null) { 533 return; 534 } 535 manager.dispatchCustomInputEvents(targetDisplayType, events); 536 } 537 } 538 539 /** 540 * Class used to bind {@link CarInputCaptureCallback} and their associated {@link Executor}. 541 */ 542 private static final class CallbackHolder { 543 544 final CarInputCaptureCallback mCallback; 545 546 final Executor mExecutor; 547 CallbackHolder(CarInputCaptureCallback callback, Executor executor)548 CallbackHolder(CarInputCaptureCallback callback, Executor executor) { 549 mCallback = callback; 550 mExecutor = executor; 551 } 552 } 553 } 554