• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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