1 /* 2 * Copyright (C) 2018 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 com.google.android.startop.iorap; 18 // TODO: rename to com.android.server.startop.iorap 19 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.app.job.JobInfo; 23 import android.app.job.JobParameters; 24 import android.app.job.JobService; 25 import android.app.job.JobScheduler; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.pm.ActivityInfo; 30 import android.os.IBinder; 31 import android.os.IBinder.DeathRecipient; 32 import android.os.Handler; 33 import android.os.Parcel; 34 import android.os.RemoteException; 35 import android.os.ServiceManager; 36 import android.os.SystemProperties; 37 import android.provider.DeviceConfig; 38 import android.util.ArraySet; 39 import android.util.Log; 40 41 import com.android.internal.annotations.VisibleForTesting; 42 import com.android.server.IoThread; 43 import com.android.server.LocalServices; 44 import com.android.server.SystemService; 45 import com.android.server.pm.BackgroundDexOptService; 46 import com.android.server.pm.PackageManagerService; 47 import com.android.server.wm.ActivityMetricsLaunchObserver; 48 import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto; 49 import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature; 50 import com.android.server.wm.ActivityMetricsLaunchObserverRegistry; 51 import com.android.server.wm.ActivityTaskManagerInternal; 52 53 import java.time.Duration; 54 import java.util.ArrayList; 55 import java.util.concurrent.TimeUnit; 56 import java.util.concurrent.atomic.AtomicBoolean; 57 import java.util.function.BooleanSupplier; 58 import java.util.HashMap; 59 import java.util.List; 60 61 /** 62 * System-server-local proxy into the {@code IIorap} native service. 63 */ 64 public class IorapForwardingService extends SystemService { 65 66 public static final String TAG = "IorapForwardingService"; 67 /** $> adb shell 'setprop log.tag.IorapForwardingService VERBOSE' */ 68 public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 69 /** $> adb shell 'setprop ro.iorapd.enable true' */ 70 private static boolean IS_ENABLED = SystemProperties.getBoolean("ro.iorapd.enable", true); 71 /** $> adb shell 'setprop iorapd.forwarding_service.wtf_crash true' */ 72 private static boolean WTF_CRASH = SystemProperties.getBoolean( 73 "iorapd.forwarding_service.wtf_crash", false); 74 private static final Duration TIMEOUT = Duration.ofSeconds(600L); 75 76 // "Unique" job ID from the service name. Also equal to 283673059. 77 public static final int JOB_ID_IORAPD = encodeEnglishAlphabetStringIntoInt("iorapd"); 78 // Run every 24 hours. 79 public static final long JOB_INTERVAL_MS = TimeUnit.HOURS.toMillis(24); 80 81 private IIorap mIorapRemote; 82 private final Object mLock = new Object(); 83 /** Handle onBinderDeath by periodically trying to reconnect. */ 84 private final Handler mHandler = 85 new BinderConnectionHandler(IoThread.getHandler().getLooper()); 86 87 private volatile IorapdJobService mJobService; // Write-once (null -> non-null forever). 88 private volatile static IorapForwardingService sSelfService; // Write once (null -> non-null). 89 90 91 /** 92 * Atomics set to true if the JobScheduler requests an abort. 93 */ 94 private final AtomicBoolean mAbortIdleCompilation = new AtomicBoolean(false); 95 96 /** 97 * Initializes the system service. 98 * <p> 99 * Subclasses must define a single argument constructor that accepts the context 100 * and passes it to super. 101 * </p> 102 * 103 * @param context The system server context. 104 */ IorapForwardingService(Context context)105 public IorapForwardingService(Context context) { 106 super(context); 107 108 if (DEBUG) { 109 Log.v(TAG, "IorapForwardingService (Context=" + context.toString() + ")"); 110 } 111 112 if (sSelfService != null) { 113 throw new AssertionError("only one service instance allowed"); 114 } 115 sSelfService = this; 116 } 117 118 //<editor-fold desc="Providers"> 119 /* 120 * Providers for external dependencies: 121 * - These are marked as protected to allow tests to inject different values via mocks. 122 */ 123 124 @VisibleForTesting provideLaunchObserverRegistry()125 protected ActivityMetricsLaunchObserverRegistry provideLaunchObserverRegistry() { 126 ActivityTaskManagerInternal amtInternal = 127 LocalServices.getService(ActivityTaskManagerInternal.class); 128 ActivityMetricsLaunchObserverRegistry launchObserverRegistry = 129 amtInternal.getLaunchObserverRegistry(); 130 return launchObserverRegistry; 131 } 132 133 @VisibleForTesting provideIorapRemote()134 protected IIorap provideIorapRemote() { 135 IIorap iorap; 136 try { 137 iorap = IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd")); 138 } catch (ServiceManager.ServiceNotFoundException e) { 139 Log.w(TAG, e.getMessage()); 140 return null; 141 } 142 143 try { 144 iorap.asBinder().linkToDeath(provideDeathRecipient(), /*flags*/0); 145 } catch (RemoteException e) { 146 handleRemoteError(e); 147 return null; 148 } 149 150 return iorap; 151 } 152 153 @VisibleForTesting provideDeathRecipient()154 protected DeathRecipient provideDeathRecipient() { 155 return new DeathRecipient() { 156 @Override 157 public void binderDied() { 158 Log.w(TAG, "iorapd has died"); 159 retryConnectToRemoteAndConfigure(/*attempts*/0); 160 161 if (mJobService != null) { 162 mJobService.onIorapdDisconnected(); 163 } 164 } 165 }; 166 } 167 168 @VisibleForTesting 169 protected boolean isIorapEnabled() { 170 // These two mendel flags should match those in iorapd native process 171 // system/iorapd/src/common/property.h 172 boolean isTracingEnabled = 173 getMendelFlag("iorap_perfetto_enable", "iorapd.perfetto.enable", false); 174 boolean isReadAheadEnabled = 175 getMendelFlag("iorap_readahead_enable", "iorapd.readahead.enable", false); 176 // Same as the property in iorapd.rc -- disabling this will mean the 'iorapd' binder process 177 // never comes up, so all binder connections will fail indefinitely. 178 return IS_ENABLED && (isTracingEnabled || isReadAheadEnabled); 179 } 180 181 private boolean getMendelFlag(String mendelFlag, String sysProperty, boolean defaultValue) { 182 // TODO(yawanng) use DeviceConfig to get mendel property. 183 // DeviceConfig doesn't work and the reason is not clear. 184 // Provider service is already up before IORapForwardService. 185 String mendelProperty = "persist.device_config." 186 + DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT 187 + "." 188 + mendelFlag; 189 return SystemProperties.getBoolean(mendelProperty, 190 SystemProperties.getBoolean(sysProperty, defaultValue)); 191 } 192 193 //</editor-fold> 194 195 @Override 196 public void onStart() { 197 if (DEBUG) { 198 Log.v(TAG, "onStart"); 199 } 200 201 retryConnectToRemoteAndConfigure(/*attempts*/0); 202 } 203 204 @Override 205 public void onBootPhase(int phase) { 206 if (phase == PHASE_BOOT_COMPLETED) { 207 if (DEBUG) { 208 Log.v(TAG, "onBootPhase(PHASE_BOOT_COMPLETED)"); 209 } 210 211 if (isIorapEnabled()) { 212 // Set up a recurring background job. This has to be done in a later phase since it 213 // has a dependency the job scheduler. 214 // 215 // Doing this too early can result in a ServiceNotFoundException for 'jobservice' 216 // or a null reference for #getSystemService(JobScheduler.class) 217 mJobService = new IorapdJobService(getContext()); 218 } 219 } 220 } 221 222 private class BinderConnectionHandler extends Handler { 223 public BinderConnectionHandler(android.os.Looper looper) { 224 super(looper); 225 } 226 227 public static final int MESSAGE_BINDER_CONNECT = 0; 228 229 private int mAttempts = 0; 230 231 @Override 232 public void handleMessage(android.os.Message message) { 233 switch (message.what) { 234 case MESSAGE_BINDER_CONNECT: 235 if (!retryConnectToRemoteAndConfigure(mAttempts)) { 236 mAttempts++; 237 } else { 238 mAttempts = 0; 239 } 240 break; 241 default: 242 throw new AssertionError("Unknown message: " + message.toString()); 243 } 244 } 245 } 246 247 /** 248 * Handle iorapd shutdowns and crashes, by attempting to reconnect 249 * until the service is reached again. 250 * 251 * <p>The first connection attempt is synchronous, 252 * subsequent attempts are done by posting delayed tasks to the IoThread.</p> 253 * 254 * @return true if connection succeeded now, or false if it failed now [and needs to requeue]. 255 */ 256 private boolean retryConnectToRemoteAndConfigure(int attempts) { 257 final int sleepTime = 1000; // ms 258 259 if (DEBUG) { 260 Log.v(TAG, "retryConnectToRemoteAndConfigure - attempt #" + attempts); 261 } 262 263 if (connectToRemoteAndConfigure()) { 264 return true; 265 } 266 267 // Either 'iorapd' is stuck in a crash loop (ouch!!) or we manually 268 // called 'adb shell stop iorapd' , which means this would loop until it comes back 269 // up. 270 // 271 // TODO: it would be good to get nodified of 'adb shell stop iorapd' to avoid 272 // printing this warning. 273 if (DEBUG) { 274 Log.v(TAG, "Failed to connect to iorapd, is it down? Delay for " + sleepTime); 275 } 276 277 // Use a handler instead of Thread#sleep to avoid backing up the binder thread 278 // when this is called from the death recipient callback. 279 mHandler.sendMessageDelayed( 280 mHandler.obtainMessage(BinderConnectionHandler.MESSAGE_BINDER_CONNECT), 281 sleepTime); 282 283 return false; 284 285 // Log.e(TAG, "Can't connect to iorapd - giving up after " + attempts + " attempts"); 286 } 287 288 private boolean connectToRemoteAndConfigure() { 289 synchronized (mLock) { 290 // Synchronize against any concurrent calls to this via the DeathRecipient. 291 return connectToRemoteAndConfigureLocked(); 292 } 293 } 294 295 private boolean connectToRemoteAndConfigureLocked() { 296 if (!isIorapEnabled()) { 297 if (DEBUG) { 298 Log.v(TAG, "connectToRemoteAndConfigure - iorapd is disabled, skip rest of work"); 299 } 300 // When we see that iorapd is disabled (when system server comes up), 301 // it stays disabled permanently until the next system server reset. 302 303 // TODO: consider listening to property changes as a callback, then we can 304 // be more dynamic about handling enable/disable. 305 return true; 306 } 307 308 // Connect to the native binder service. 309 mIorapRemote = provideIorapRemote(); 310 if (mIorapRemote == null) { 311 if (DEBUG) { 312 Log.e(TAG, "connectToRemoteAndConfigure - null iorap remote. check for Log.wtf?"); 313 } 314 return false; 315 } 316 invokeRemote(mIorapRemote, 317 (IIorap remote) -> remote.setTaskListener(new RemoteTaskListener()) ); 318 registerInProcessListenersLocked(); 319 320 Log.i(TAG, "Connected to iorapd native service."); 321 322 return true; 323 } 324 325 private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver(); 326 private final EventSequenceValidator mEventSequenceValidator = new EventSequenceValidator(); 327 private final DexOptPackagesUpdated mDexOptPackagesUpdated = new DexOptPackagesUpdated(); 328 private boolean mRegisteredListeners = false; 329 330 private void registerInProcessListenersLocked() { 331 if (mRegisteredListeners) { 332 // Listeners are registered only once (idempotent operation). 333 // 334 // Today listeners are tolerant of the remote side going away 335 // by handling remote errors. 336 // 337 // We could try to 'unregister' the listener when we get a binder disconnect, 338 // but we'd still have to handle the case of encountering synchronous errors so 339 // it really wouldn't be a win (other than having less log spew). 340 return; 341 } 342 343 // Listen to App Launch Sequence events from ActivityTaskManager, 344 // and forward them to the native binder service. 345 ActivityMetricsLaunchObserverRegistry launchObserverRegistry = 346 provideLaunchObserverRegistry(); 347 launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver); 348 launchObserverRegistry.registerLaunchObserver(mEventSequenceValidator); 349 350 BackgroundDexOptService.addPackagesUpdatedListener(mDexOptPackagesUpdated); 351 352 353 mRegisteredListeners = true; 354 } 355 356 private class DexOptPackagesUpdated implements BackgroundDexOptService.PackagesUpdatedListener { 357 @Override 358 public void onPackagesUpdated(ArraySet<String> updatedPackages) { 359 String[] updated = updatedPackages.toArray(new String[0]); 360 for (String packageName : updated) { 361 Log.d(TAG, "onPackagesUpdated: " + packageName); 362 invokeRemote(mIorapRemote, 363 (IIorap remote) -> 364 remote.onDexOptEvent(RequestId.nextValueForSequence(), 365 DexOptEvent.createPackageUpdate(packageName)) 366 ); 367 } 368 } 369 } 370 371 private class AppLaunchObserver implements ActivityMetricsLaunchObserver { 372 // We add a synthetic sequence ID here to make it easier to differentiate new 373 // launch sequences on the native side. 374 private @AppLaunchEvent.SequenceId long mSequenceId = -1; 375 376 // All callbacks occur on the same background thread. Don't synchronize explicitly. 377 378 @Override 379 public void onIntentStarted(@NonNull Intent intent, long timestampNs) { 380 // #onIntentStarted [is the only transition that] initiates a new launch sequence. 381 ++mSequenceId; 382 383 if (DEBUG) { 384 Log.v(TAG, String.format("AppLaunchObserver#onIntentStarted(%d, %s, %d)", 385 mSequenceId, intent, timestampNs)); 386 } 387 388 invokeRemote(mIorapRemote, 389 (IIorap remote) -> 390 remote.onAppLaunchEvent(RequestId.nextValueForSequence(), 391 new AppLaunchEvent.IntentStarted(mSequenceId, intent, timestampNs)) 392 ); 393 } 394 395 @Override 396 public void onIntentFailed() { 397 if (DEBUG) { 398 Log.v(TAG, String.format("AppLaunchObserver#onIntentFailed(%d)", mSequenceId)); 399 } 400 401 invokeRemote(mIorapRemote, 402 (IIorap remote) -> 403 remote.onAppLaunchEvent(RequestId.nextValueForSequence(), 404 new AppLaunchEvent.IntentFailed(mSequenceId)) 405 ); 406 } 407 408 @Override 409 public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity, 410 @Temperature int temperature) { 411 if (DEBUG) { 412 Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunched(%d, %s, %d)", 413 mSequenceId, activity, temperature)); 414 } 415 416 invokeRemote(mIorapRemote, 417 (IIorap remote) -> 418 remote.onAppLaunchEvent(RequestId.nextValueForSequence(), 419 new AppLaunchEvent.ActivityLaunched(mSequenceId, activity, temperature)) 420 ); 421 } 422 423 @Override 424 public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) { 425 if (DEBUG) { 426 Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunchCancelled(%d, %s)", 427 mSequenceId, activity)); 428 } 429 430 invokeRemote(mIorapRemote, 431 (IIorap remote) -> 432 remote.onAppLaunchEvent(RequestId.nextValueForSequence(), 433 new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId, 434 activity))); 435 } 436 437 @Override 438 public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity, 439 long timestampNs) { 440 if (DEBUG) { 441 Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunchFinished(%d, %s, %d)", 442 mSequenceId, activity, timestampNs)); 443 } 444 445 invokeRemote(mIorapRemote, 446 (IIorap remote) -> 447 remote.onAppLaunchEvent(RequestId.nextValueForSequence(), 448 new AppLaunchEvent.ActivityLaunchFinished(mSequenceId, 449 activity, 450 timestampNs)) 451 ); 452 } 453 454 @Override 455 public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity, 456 long timestampNs) { 457 if (DEBUG) { 458 Log.v(TAG, String.format("AppLaunchObserver#onReportFullyDrawn(%d, %s, %d)", 459 mSequenceId, activity, timestampNs)); 460 } 461 462 invokeRemote(mIorapRemote, 463 (IIorap remote) -> 464 remote.onAppLaunchEvent(RequestId.nextValueForSequence(), 465 new AppLaunchEvent.ReportFullyDrawn(mSequenceId, activity, timestampNs)) 466 ); 467 } 468 } 469 470 /** 471 * Debugging: 472 * 473 * $> adb shell dumpsys jobscheduler 474 * 475 * Search for 'IorapdJobServiceProxy'. 476 * 477 * JOB #1000/283673059: 6e54ed android/com.google.android.startop.iorap.IorapForwardingService$IorapdJobServiceProxy 478 * ^ ^ ^ 479 * (uid, job id) ComponentName(package/class) 480 * 481 * Forcing the job to be run, ignoring constraints: 482 * 483 * $> adb shell cmd jobscheduler run -f android 283673059 484 * ^ ^ 485 * package job_id 486 * 487 * ------------------------------------------------------------ 488 * 489 * This class is instantiated newly by the JobService every time 490 * it wants to run a new job. 491 * 492 * We need to forward invocations to the current running instance of 493 * IorapForwardingService#IorapdJobService. 494 * 495 * Visibility: Must be accessible from android.app.AppComponentFactory 496 */ 497 public static class IorapdJobServiceProxy extends JobService { 498 499 public IorapdJobServiceProxy() { 500 getActualIorapdJobService().bindProxy(this); 501 } 502 503 504 @NonNull 505 private IorapdJobService getActualIorapdJobService() { 506 // Can't ever be null, because the guarantee is that the 507 // IorapForwardingService is always running. 508 // We are in the same process as Job Service. 509 return sSelfService.mJobService; 510 } 511 512 // Called by system to start the job. 513 @Override 514 public boolean onStartJob(JobParameters params) { 515 return getActualIorapdJobService().onStartJob(params); 516 } 517 518 // Called by system to prematurely stop the job. 519 @Override 520 public boolean onStopJob(JobParameters params) { 521 return getActualIorapdJobService().onStopJob(params); 522 } 523 } 524 525 private class IorapdJobService extends JobService { 526 private final ComponentName IORAPD_COMPONENT_NAME; 527 528 private final Object mLock = new Object(); 529 // Jobs currently running remotely on iorapd. 530 // They were started by the JobScheduler and need to be finished. 531 private final HashMap<RequestId, JobParameters> mRunningJobs = new HashMap<>(); 532 533 private final JobInfo IORAPD_JOB_INFO; 534 535 private volatile IorapdJobServiceProxy mProxy; 536 537 public void bindProxy(IorapdJobServiceProxy proxy) { 538 mProxy = proxy; 539 } 540 541 // Create a new job service which immediately schedules a 24-hour idle maintenance mode 542 // background job to execute. 543 public IorapdJobService(Context context) { 544 if (DEBUG) { 545 Log.v(TAG, "IorapdJobService (Context=" + context.toString() + ")"); 546 } 547 548 // Schedule the proxy class to be instantiated by the JobScheduler 549 // when it is time to invoke background jobs for IorapForwardingService. 550 551 552 // This also needs a BIND_JOB_SERVICE permission in 553 // frameworks/base/core/res/AndroidManifest.xml 554 IORAPD_COMPONENT_NAME = new ComponentName(context, IorapdJobServiceProxy.class); 555 556 JobInfo.Builder builder = new JobInfo.Builder(JOB_ID_IORAPD, IORAPD_COMPONENT_NAME); 557 builder.setPeriodic(JOB_INTERVAL_MS); 558 builder.setPrefetch(true); 559 560 builder.setRequiresCharging(true); 561 builder.setRequiresDeviceIdle(true); 562 563 builder.setRequiresStorageNotLow(true); 564 565 IORAPD_JOB_INFO = builder.build(); 566 567 JobScheduler js = context.getSystemService(JobScheduler.class); 568 js.schedule(IORAPD_JOB_INFO); 569 Log.d(TAG, 570 "BgJob Scheduled (jobId=" + JOB_ID_IORAPD 571 + ", interval: " + JOB_INTERVAL_MS + "ms)"); 572 } 573 574 // Called by system to start the job. 575 @Override 576 public boolean onStartJob(JobParameters params) { 577 // Tell iorapd to start a background job. 578 Log.d(TAG, "Starting background job: " + params.toString()); 579 580 mAbortIdleCompilation.set(false); 581 // PackageManagerService starts before IORap service. 582 PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package"); 583 List<String> pkgs = pm.getAllPackages(); 584 runIdleCompilationAsync(params, pkgs); 585 return true; 586 } 587 588 private void runIdleCompilationAsync(final JobParameters params, final List<String> pkgs) { 589 new Thread("IORap_IdleCompilation") { 590 @Override 591 public void run() { 592 for (int i = 0; i < pkgs.size(); i++) { 593 String pkg = pkgs.get(i); 594 if (mAbortIdleCompilation.get()) { 595 Log.i(TAG, "The idle compilation is aborted"); 596 return; 597 } 598 599 // We wait until that job's sequence ID returns to us with 'Completed', 600 RequestId request; 601 synchronized (mLock) { 602 // TODO: would be cleaner if we got the request from the 603 // 'invokeRemote' function. Better yet, consider 604 // a Pair<RequestId, Future<TaskResult>> or similar. 605 request = RequestId.nextValueForSequence(); 606 mRunningJobs.put(request, params); 607 } 608 609 Log.i(TAG, String.format("IORap compile package: %s, [%d/%d]", 610 pkg, i + 1, pkgs.size())); 611 boolean shouldUpdateVersions = (i == 0); 612 if (!invokeRemote(mIorapRemote, (IIorap remote) -> 613 remote.onJobScheduledEvent(request, 614 JobScheduledEvent.createIdleMaintenance( 615 JobScheduledEvent.TYPE_START_JOB, 616 params, 617 pkg, 618 shouldUpdateVersions)))) { 619 synchronized (mLock) { 620 mRunningJobs.remove(request); // Avoid memory leaks. 621 } 622 } 623 624 // Wait until the job is complete and removed from the running jobs. 625 retryWithTimeout(TIMEOUT, () -> { 626 synchronized (mLock) { 627 return !mRunningJobs.containsKey(request); 628 } 629 }); 630 } 631 632 // Finish the job after all packages are compiled. 633 if (mProxy != null) { 634 mProxy.jobFinished(params, /*reschedule*/false); 635 } 636 } 637 }.start(); 638 } 639 640 /** Retry until timeout. */ 641 private boolean retryWithTimeout(final Duration timeout, BooleanSupplier supplier) { 642 long totalSleepTimeMs = 0L; 643 long sleepIntervalMs = 10L; 644 while (true) { 645 if (supplier.getAsBoolean()) { 646 return true; 647 } 648 try { 649 TimeUnit.MILLISECONDS.sleep(sleepIntervalMs); 650 } catch (InterruptedException e) { 651 Log.e(TAG, e.getMessage()); 652 return false; 653 } 654 655 totalSleepTimeMs += sleepIntervalMs; 656 if (totalSleepTimeMs > timeout.toMillis()) { 657 return false; 658 } 659 } 660 } 661 662 // Called by system to prematurely stop the job. 663 @Override 664 public boolean onStopJob(JobParameters params) { 665 // As this is unexpected behavior, print a warning. 666 Log.w(TAG, "onStopJob(params=" + params.toString() + ")"); 667 mAbortIdleCompilation.set(true); 668 669 // Yes, retry the job at a later time no matter what. 670 return true; 671 } 672 673 // Listen to *all* task completes for all requests. 674 // The majority of these might be unrelated to background jobs. 675 public void onIorapdTaskCompleted(RequestId requestId) { 676 JobParameters jobParameters; 677 synchronized (mLock) { 678 jobParameters = mRunningJobs.remove(requestId); 679 } 680 681 // Typical case: This was a task callback unrelated to our jobs. 682 if (jobParameters == null) { 683 return; 684 } 685 686 if (DEBUG) { 687 Log.v(TAG, 688 String.format("IorapdJobService#onIorapdTaskCompleted(%s), found params=%s", 689 requestId, jobParameters)); 690 } 691 692 Log.d(TAG, "Finished background job: " + jobParameters.toString()); 693 } 694 695 public void onIorapdDisconnected() { 696 synchronized (mLock) { 697 mRunningJobs.clear(); 698 } 699 700 if (DEBUG) { 701 Log.v(TAG, String.format("IorapdJobService#onIorapdDisconnected")); 702 } 703 704 // TODO: should we try to resubmit all incomplete jobs after it's reconnected? 705 } 706 } 707 708 private class RemoteTaskListener extends ITaskListener.Stub { 709 @Override 710 public void onProgress(RequestId requestId, TaskResult result) throws RemoteException { 711 if (DEBUG) { 712 Log.v(TAG, 713 String.format("RemoteTaskListener#onProgress(%s, %s)", requestId, result)); 714 } 715 716 // TODO: implement rest. 717 } 718 719 @Override 720 public void onComplete(RequestId requestId, TaskResult result) throws RemoteException { 721 if (DEBUG) { 722 Log.v(TAG, 723 String.format("RemoteTaskListener#onComplete(%s, %s)", requestId, result)); 724 } 725 726 if (mJobService != null) { 727 mJobService.onIorapdTaskCompleted(requestId); 728 } 729 730 // TODO: implement rest. 731 } 732 } 733 734 /** Allow passing lambdas to #invokeRemote */ 735 private interface RemoteRunnable { 736 // TODO: run(RequestId) ? 737 void run(IIorap iorap) throws RemoteException; 738 } 739 740 // Always pass in the iorap directly here to avoid data race. 741 private static boolean invokeRemote(IIorap iorap, RemoteRunnable r) { 742 if (iorap == null) { 743 Log.w(TAG, "IIorap went to null in this thread, drop invokeRemote."); 744 return false; 745 } 746 try { 747 r.run(iorap); 748 return true; 749 } catch (RemoteException e) { 750 // This could be a logic error (remote side returning error), which we need to fix. 751 // 752 // This could also be a DeadObjectException in which case its probably just iorapd 753 // being manually restarted. 754 // 755 // Don't make any assumption, since DeadObjectException could also mean iorapd crashed 756 // unexpectedly. 757 // 758 // DeadObjectExceptions are recovered from using DeathRecipient and #linkToDeath. 759 handleRemoteError(e); 760 return false; 761 } 762 } 763 764 private static void handleRemoteError(Throwable t) { 765 if (WTF_CRASH) { 766 // In development modes, we just want to crash. 767 throw new AssertionError("unexpected remote error", t); 768 } else { 769 // Log to wtf which gets sent to dropbox, and in system_server this does not crash. 770 Log.wtf(TAG, t); 771 } 772 } 773 774 // Encode A-Z bitstring into bits. Every character is bits. 775 // Characters outside of the range [a,z] are considered out of range. 776 // 777 // The least significant bits hold the last character. 778 // First 2 bits are left as 0. 779 private static int encodeEnglishAlphabetStringIntoInt(String name) { 780 int value = 0; 781 782 final int CHARS_PER_INT = 6; 783 final int BITS_PER_CHAR = 5; 784 // Note: 2 top bits are unused, this also means our values are non-negative. 785 final char CHAR_LOWER = 'a'; 786 final char CHAR_UPPER = 'z'; 787 788 if (name.length() > CHARS_PER_INT) { 789 throw new IllegalArgumentException( 790 "String too long. Cannot encode more than 6 chars: " + name); 791 } 792 793 for (int i = 0; i < name.length(); ++i) { 794 char c = name.charAt(i); 795 796 if (c < CHAR_LOWER || c > CHAR_UPPER) { 797 throw new IllegalArgumentException("String has out-of-range [a-z] chars: " + name); 798 } 799 800 // Avoid sign extension during promotion. 801 int cur_value = (c & 0xFFFF) - (CHAR_LOWER & 0xFFFF); 802 if (cur_value >= (1 << BITS_PER_CHAR)) { 803 throw new AssertionError("wtf? i=" + i + ", name=" + name); 804 } 805 806 value = value << BITS_PER_CHAR; 807 value = value | cur_value; 808 } 809 810 return value; 811 } 812 } 813