• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 package android.os;
17 
18 import static android.os.ProfilingTrigger.TriggerType;
19 
20 import android.annotation.FlaggedApi;
21 import android.annotation.IntDef;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.content.Context;
25 import android.os.profiling.Flags;
26 import android.util.Log;
27 
28 import com.android.internal.annotations.GuardedBy;
29 import com.android.internal.annotations.VisibleForTesting;
30 
31 import java.io.File;
32 import java.io.IOException;
33 import java.lang.annotation.Retention;
34 import java.lang.annotation.RetentionPolicy;
35 import java.nio.file.Files;
36 import java.nio.file.Path;
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.UUID;
40 import java.util.concurrent.Executor;
41 import java.util.function.Consumer;
42 
43 /**
44  * <p>
45  * This class allows the caller to:
46  * <ul>
47  * <li>Request profiling and listen for results. Profiling types supported are: system traces,
48  *     java heap dumps, heap profiles, and stack traces.</li>
49  * <li>Register triggers for the system to capture profiling on the apps behalf.</li>
50  * </ul>
51  * </p>
52  *
53  * <p>
54  * The {@link #requestProfiling} API can be used to begin profiling. Profiling may be ended manually
55  * using the CancellationSignal provided in the request, or as a result of a timeout. The timeout
56  * may be either the system default or caller defined in the parameter bundle for select types.
57  * </p>
58  *
59  * <p>
60  * The profiling results are delivered to the requesting app's data directory and a pointer to the
61  * file will be received using the app provided listeners.
62  * </p>
63  *
64  * <p>
65  * Apps can provide listeners in one or both of two ways:
66  * <ul>
67  * <li>A request-specific listener included with the request. This will trigger only with a result
68  *     from the request it was provided with.</li>
69  * <li>A global listener provided by {@link #registerForAllProfilingResults}. This will be triggered
70  *     for all results belonging to your app. This listener is the only way to receive results from
71  *     system triggered profiling instances set up with {@link #addProfilingTriggers}.</li>
72  * </ul>
73  * </p>
74  *
75  * <p>
76  * Requests are rate limited and not guaranteed to be filled. Rate limiting can be disabled for
77  * local testing of {@link #requestProfiling} using the shell command
78  * {@code device_config put profiling_testing rate_limiter.disabled true}
79  * </p>
80  *
81  * <p>
82  * For local testing, profiling results can be accessed more easily by enabling debug mode. This
83  * will retain output files in a temporary system directory. The locations of the retained files
84  * will be available in logcat. The behavior and command varies by version:
85  * <ul>
86  * <li>For Android versions 16 and above, debug mode will retain both unredacted (where applicable)
87  * and redacted results in the temporary directory. It can be enabled with the shell command
88  * {@code device_config put profiling_testing delete_temporary_results.disabled true} and disabled
89  * by setting that same value back to false. Retained results are accessible on all build types.
90  * </li>
91  * <li>For Android version 15, debug mode will retain only the unredacted result (where applicable)
92  * in the temporary directory. It can be enabled with the shell command
93  * {@code device_config put profiling_testing delete_unredacted_trace.disabled true} and disabled
94  * by setting that same value back to false. The retained unredacted file can only be accessed on
95  * builds with root access. To access the redacted output file on an unrooted device, apps can copy
96  * the file from {@code /pkg/files/profiling/file.type} to {@code /pkg/cache/file.type}.
97  * </li>
98  * </ul>
99  * </p>
100  *
101  * <p>
102  * In order to test profiling triggers, enable testing mode for your app with the shell command
103  * {@code device_config put profiling_testing system_triggered_profiling.testing_package_name
104  * com.your.app} which will:
105  * <ul>
106  * <li>Ensure that a background trace is running.</li>
107  * <li>Allow all triggers for the provided package name to pass the system level rate limiter.
108  *     This mode will continue until manually stopped with the shell command
109  *     {@code device_config delete profiling_testing
110  *     system_triggered_profiling.testing_package_name}.
111  *     </li>
112  * </ul>
113  * </p>
114  *
115  * <p>
116  * Results are redacted and contain specific information about the requesting process only.
117  * </p>
118  */
119 @FlaggedApi(Flags.FLAG_TELEMETRY_APIS)
120 public final class ProfilingManager {
121     private static final String TAG = ProfilingManager.class.getSimpleName();
122     private static final boolean DEBUG = false;
123 
124     /** Profiling type for {@link #requestProfiling} to request a java heap dump. */
125     public static final int PROFILING_TYPE_JAVA_HEAP_DUMP = 1;
126 
127     /** Profiling type for {@link #requestProfiling} to request a heap profile. */
128     public static final int PROFILING_TYPE_HEAP_PROFILE = 2;
129 
130     /** Profiling type for {@link #requestProfiling} to request a stack sample. */
131     public static final int PROFILING_TYPE_STACK_SAMPLING = 3;
132 
133     /** Profiling type for {@link #requestProfiling} to request a system trace. */
134     public static final int PROFILING_TYPE_SYSTEM_TRACE = 4;
135 
136     /* Begin public API defined keys. */
137     /* End public API defined keys. */
138 
139     /* Begin not-public API defined keys/values. */
140     /**
141      * Can only be used with profiling type heap profile, stack sampling, or system trace.
142      * Value of type int.
143      * @hide
144      */
145     public static final String KEY_DURATION_MS = "KEY_DURATION_MS";
146 
147     /**
148      * Can only be used with profiling type heap profile. Value of type long.
149      * @hide
150      */
151     public static final String KEY_SAMPLING_INTERVAL_BYTES = "KEY_SAMPLING_INTERVAL_BYTES";
152 
153     /**
154      * Can only be used with profiling type heap profile. Value of type boolean.
155      * @hide
156      */
157     public static final String KEY_TRACK_JAVA_ALLOCATIONS = "KEY_TRACK_JAVA_ALLOCATIONS";
158 
159     /**
160      * Can only be used with profiling type stack sampling. Value of type int.
161      * @hide
162      */
163     public static final String KEY_FREQUENCY_HZ = "KEY_FREQUENCY_HZ";
164 
165     /**
166      * Can be used with all profiling types. Value of type int.
167      * @hide
168      */
169     public static final String KEY_SIZE_KB = "KEY_SIZE_KB";
170 
171     /**
172      * Can be used with profiling type system trace.
173      * Value of type int must be one of:
174      * {@link VALUE_BUFFER_FILL_POLICY_DISCARD}
175      * {@link VALUE_BUFFER_FILL_POLICY_RING_BUFFER}
176      * @hide
177      */
178     public static final String KEY_BUFFER_FILL_POLICY = "KEY_BUFFER_FILL_POLICY";
179 
180     /** @hide */
181     public static final int VALUE_BUFFER_FILL_POLICY_DISCARD = 1;
182 
183     /** @hide */
184     public static final int VALUE_BUFFER_FILL_POLICY_RING_BUFFER = 2;
185     /* End not-public API defined keys/values. */
186 
187     /**
188      * @hide *
189      */
190     @IntDef(
191         prefix = {"PROFILING_TYPE_"},
192         value = {
193             PROFILING_TYPE_JAVA_HEAP_DUMP,
194             PROFILING_TYPE_HEAP_PROFILE,
195             PROFILING_TYPE_STACK_SAMPLING,
196             PROFILING_TYPE_SYSTEM_TRACE,
197         })
198     @Retention(RetentionPolicy.SOURCE)
199     public @interface ProfilingType {}
200 
201     private final Object mLock = new Object();
202     private final Context mContext;
203 
204     /** @hide */
205     @VisibleForTesting
206     @GuardedBy("mLock")
207     public final ArrayList<ProfilingRequestCallbackWrapper> mCallbacks = new ArrayList<>();
208 
209     /** @hide */
210     @VisibleForTesting
211     @GuardedBy("mLock")
212     public IProfilingService mProfilingService;
213 
214     /**
215      * Constructor for ProfilingManager.
216      *
217      * @hide
218      */
ProfilingManager(Context context)219     public ProfilingManager(Context context) {
220         mContext = context;
221     }
222 
223     /**
224      * Request system profiling.
225      *
226      * <p class="note">
227      *   Note: use of this API directly is not recommended for most use cases.
228      *   Consider using the
229      *   <a href="https://developer.android.com/reference/androidx/core/os/Profiling">higher level
230      *   wrappers provided by AndroidX</a> that will construct the request correctly, supporting
231      *   available options with simplified request parameters.
232      * </p>
233      *
234      * <p>
235      *   Both a listener and an executor must be set at the time of the request for the request to
236      *   be considered for fulfillment. Listener/executor pairs can be set in this method, with
237      *   {@link #registerForAllProfilingResults}, or both. The listener and executor must be set
238      *   together, in the same call. If no listener and executor combination is set, the request
239      *   will be discarded and no callback will be received.
240      * </p>
241      *
242      * <p>
243      *   Requests will be rate limited and are not guaranteed to be filled.
244      * </p>
245      *
246      * <p>
247      *   There might be a delay before profiling begins.
248      *   For continuous profiling types (system tracing, stack sampling, and heap profiling),
249      *   we recommend starting the collection early and stopping it with {@code cancellationSignal}
250      *   immediately after the area of interest to ensure that the section you want profiled is
251      *   captured.
252      *   For heap dumps, we recommend testing locally to ensure that the heap dump is collected at
253      *   the proper time.
254      * </p>
255      *
256      * @param profilingType Type of profiling to collect.
257      * @param parameters Bundle of request related parameters. If the bundle contains any
258      *                  unrecognized parameters, the request will be fail with
259      *                  {@link android.os.ProfilingResult#ERROR_FAILED_INVALID_REQUEST}. If the
260      *                  values for the parameters are out of supported range, the closest possible
261      *                  in range value will be chosen.
262      *                  Use of <a href=
263      *                  "https://developer.android.com/reference/androidx/core/os/Profiling">
264      *                  androidx wrappers</a> is recommended over generating this directly.
265      * @param tag Caller defined data to help identify the output.
266      *                  The first 20 alphanumeric characters, plus dashes, will be lowercased
267      *                  and included in the output filename.
268      * @param cancellationSignal for caller requested cancellation.
269      *                  Results will be returned if available.
270      *                  If this is null, the requesting app will not be able to stop the collection.
271      *                  The collection will stop after timing out with either the provided
272      *                  configurations or with system defaults
273      * @param executor  The executor to call back with.
274      *                  Will only be used for the listener provided in this method.
275      *                  If this is null, and no global executor and listener combinations are
276      *                  registered at the time of the request, the request will be dropped.
277      * @param listener  Listener to be triggered with result. Any global listeners registered via
278      *                  {@link #registerForAllProfilingResults} will also be triggered. If this is
279      *                  null, and no global listener and executor combinations are registered at
280      *                  the time of the request, the request will be dropped.
281      */
requestProfiling( @rofilingType int profilingType, @Nullable Bundle parameters, @Nullable String tag, @Nullable CancellationSignal cancellationSignal, @Nullable Executor executor, @Nullable Consumer<ProfilingResult> listener)282     public void requestProfiling(
283             @ProfilingType int profilingType,
284             @Nullable Bundle parameters,
285             @Nullable String tag,
286             @Nullable CancellationSignal cancellationSignal,
287             @Nullable Executor executor,
288             @Nullable Consumer<ProfilingResult> listener) {
289         synchronized (mLock) {
290             try {
291                 final UUID key = UUID.randomUUID();
292 
293                 if (executor != null && listener != null) {
294                     // Listeners are provided, store them.
295                     mCallbacks.add(new ProfilingRequestCallbackWrapper(executor, listener, key));
296                 } else if (mCallbacks.isEmpty()) {
297                     // No listeners have been registered by any path, toss the request.
298                     throw new IllegalArgumentException(
299                             "No listeners have been registered. Request has been discarded.");
300                 }
301                 // If neither case above was hit, app wide listeners were provided. Continue.
302 
303                 final IProfilingService service = getOrCreateIProfilingServiceLocked(false);
304                 if (service == null) {
305                     executor.execute(() -> listener.accept(
306                             new ProfilingResult(ProfilingResult.ERROR_UNKNOWN, null, tag,
307                                 "ProfilingService is not available",
308                                 Flags.systemTriggeredProfilingNew()
309                                         ? ProfilingTrigger.TRIGGER_TYPE_NONE : 0)));
310                     if (DEBUG) Log.d(TAG, "ProfilingService is not available");
311                     return;
312                 }
313 
314                 String packageName = mContext.getPackageName();
315                 if (packageName == null) {
316                     executor.execute(() -> listener.accept(
317                             new ProfilingResult(ProfilingResult.ERROR_UNKNOWN, null, tag,
318                                     "Failed to resolve package name",
319                                     Flags.systemTriggeredProfilingNew()
320                                             ? ProfilingTrigger.TRIGGER_TYPE_NONE : 0)));
321                     if (DEBUG) Log.d(TAG, "Failed to resolve package name.");
322                     return;
323                 }
324 
325                 // For key, use most and least significant bits so we can create an identical UUID
326                 // after passing over binder.
327                 service.requestProfiling(profilingType, parameters, tag,
328                         key.getMostSignificantBits(), key.getLeastSignificantBits(),
329                         packageName);
330                 if (cancellationSignal != null) {
331                     cancellationSignal.setOnCancelListener(
332                             () -> {
333                                 synchronized (mLock) {
334                                     try {
335                                         service.requestCancel(key.getMostSignificantBits(),
336                                                 key.getLeastSignificantBits());
337                                     } catch (RemoteException e) {
338                                         // Ignore, request in flight already and we can't stop it.
339                                     }
340                                 }
341                             }
342                     );
343                 }
344             } catch (RemoteException e) {
345                 if (DEBUG) Log.d(TAG, "Binder exception processing request", e);
346                 executor.execute(() -> listener.accept(
347                         new ProfilingResult(ProfilingResult.ERROR_UNKNOWN, null, tag,
348                                 "Binder exception processing request",
349                                 Flags.systemTriggeredProfilingNew()
350                                         ? ProfilingTrigger.TRIGGER_TYPE_NONE : 0)));
351                 throw new RuntimeException("Unable to request profiling.");
352             }
353         }
354     }
355 
356     /**
357      * Register a listener to be called for all profiling results for this uid. Listeners set here
358      * will be called in addition to any provided with the request.
359      *
360      * <p class="note"> Note: If a callback attempt fails (for example, because your app is killed
361      * while a trace is in progress) re-delivery may be attempted using a listener added via this
362      * method. </p>
363      *
364      * @param executor The executor to call back with.
365      * @param listener Listener to be triggered with result.
366      */
registerForAllProfilingResults( @onNull Executor executor, @NonNull Consumer<ProfilingResult> listener)367     public void registerForAllProfilingResults(
368             @NonNull Executor executor,
369             @NonNull Consumer<ProfilingResult> listener) {
370         synchronized (mLock) {
371             // Only notify {@link mProfilingService} of a general listener being added if it already
372             // exists as registering it also handles the notifying.
373             boolean shouldNotifyService = mProfilingService != null;
374 
375             if (getOrCreateIProfilingServiceLocked(true) == null) {
376                 // If the binder object was not successfully registered then this listener will
377                 // not ever be triggered.
378                 executor.execute(() -> listener.accept(new ProfilingResult(
379                         ProfilingResult.ERROR_UNKNOWN, null, null,
380                         "Binder exception processing request",
381                         Flags.systemTriggeredProfilingNew()
382                                 ? ProfilingTrigger.TRIGGER_TYPE_NONE : 0)));
383                 return;
384             }
385             mCallbacks.add(new ProfilingRequestCallbackWrapper(executor, listener, null));
386 
387             if (shouldNotifyService) {
388                 // Notify service that a general listener was added. General listeners are also used
389                 // for queued callbacks if any are waiting.
390                 try {
391                     mProfilingService.generalListenerAdded();
392                 } catch (RemoteException e) {
393                     // Do nothing. Binder callback is already registered, but service won't know
394                     // there is a general listener so queued callbacks won't occur.
395                     Log.d(TAG, "Exception notifying service of general callback,"
396                             + " queued callbacks will not occur.", e);
397                 }
398             }
399         }
400     }
401 
402     /**
403      * Unregister a listener that was to be called for all profiling results. If no listener is
404      * provided, all listeners for this process that were not submitted with a profiling request
405      * will be removed.
406      *
407      * @param listener Listener to unregister and no longer be triggered with the results.
408      *                 Null to remove all global listeners for this uid.
409      */
unregisterForAllProfilingResults( @ullable Consumer<ProfilingResult> listener)410     public void unregisterForAllProfilingResults(
411             @Nullable Consumer<ProfilingResult> listener) {
412         synchronized (mLock) {
413             if (mCallbacks.isEmpty()) {
414                 // No callbacks, nothing to remove.
415                 return;
416             }
417 
418             if (listener == null) {
419                 // Remove all global listeners.
420                 ArrayList<ProfilingRequestCallbackWrapper> listenersToRemove = new ArrayList<>();
421                 for (int i = 0; i < mCallbacks.size(); i++) {
422                     ProfilingRequestCallbackWrapper wrapper = mCallbacks.get(i);
423                     // Only remove global listeners which are not tied to a specific request. These
424                     // can be identified by checking that they do not have an associated key.
425                     if (wrapper.mKey == null) {
426                         listenersToRemove.add(wrapper);
427                     }
428                 }
429                 mCallbacks.removeAll(listenersToRemove);
430             } else {
431                 // Remove the provided listener only.
432                 for (int i = 0; i < mCallbacks.size(); i++) {
433                     ProfilingRequestCallbackWrapper wrapper = mCallbacks.get(i);
434                     if (listener.equals(wrapper.mListener)) {
435                         mCallbacks.remove(i);
436                         return;
437                     }
438                 }
439             }
440         }
441     }
442 
443     /**
444      * <p>
445      * Register the provided list of triggers for this process.
446      * </p>
447      *
448      * <p>
449      * Profiling triggers are system events that an app can register interest in, and then receive
450      * profiling data when any of the registered triggers occur. There is no guarantee that these
451      * triggers will be filled. Results, if available, will be delivered only to a global listener
452      * added using {@link #registerForAllProfilingResults}.
453      *</p>
454      *
455      * <p>
456      * Only one of each trigger type can be added at a time.
457      * <ul>
458      * <li>If the provided list contains a trigger type that is already registered then the new one
459      *     will replace the existing one.</li>
460      * <li>If the provided list contains more than one trigger object for a trigger type then only
461      *     one will be kept.</li>
462      * </ul>
463      * </p>
464      *
465      * <p>
466      * Apps can define their own per-trigger rate limiting to help ensure they receive results
467      * aligned with their needs. More details can be found at
468      * {@link ProfilingTrigger.Builder#setRateLimitingPeriodHours}.
469      * </p>
470      */
471     @FlaggedApi(Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW)
addProfilingTriggers(@onNull List<ProfilingTrigger> triggers)472     public void addProfilingTriggers(@NonNull List<ProfilingTrigger> triggers) {
473         synchronized (mLock) {
474             if (triggers.isEmpty()) {
475                 // No triggers are being added, nothing to do.
476                 if (DEBUG) Log.d(TAG, "Trying to add an empty list of triggers.");
477                 return;
478             }
479 
480             final IProfilingService service = getOrCreateIProfilingServiceLocked(false);
481             if (service == null) {
482                 // If we can't access service then we can't do anything. Return.
483                 if (DEBUG) Log.d(TAG, "ProfilingService is not available, triggers will be lost.");
484                 return;
485             }
486 
487             String packageName = mContext.getPackageName();
488             if (packageName == null) {
489                 if (DEBUG) Log.d(TAG, "Failed to resolve package name.");
490                 return;
491             }
492 
493             try {
494                 service.addProfilingTriggers(toValueParcelList(triggers), packageName);
495             } catch (RemoteException e) {
496                 if (DEBUG) Log.d(TAG, "Binder exception processing request", e);
497                 throw new RuntimeException("Unable to add profiling triggers.");
498             }
499         }
500     }
501 
502     @FlaggedApi(Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW)
toValueParcelList( List<ProfilingTrigger> triggerList)503     private List<ProfilingTriggerValueParcel> toValueParcelList(
504             List<ProfilingTrigger> triggerList) {
505         List<ProfilingTriggerValueParcel> triggerValueParcelList =
506                 new ArrayList<ProfilingTriggerValueParcel>();
507 
508         for (int i = 0; i < triggerList.size(); i++) {
509             triggerValueParcelList.add(triggerList.get(i).toValueParcel());
510         }
511 
512         return triggerValueParcelList;
513     }
514 
515     /** Remove triggers for this process with trigger types in the provided list. */
516     @FlaggedApi(Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW)
removeProfilingTriggersByType(@onNull @riggerType int[] triggers)517     public void removeProfilingTriggersByType(@NonNull @TriggerType int[] triggers) {
518         synchronized (mLock) {
519             if (triggers.length == 0) {
520                 // No triggers are being removed, nothing to do.
521                 if (DEBUG) Log.d(TAG, "Trying to remove an empty list of triggers.");
522                 return;
523             }
524 
525             final IProfilingService service = getOrCreateIProfilingServiceLocked(false);
526             if (service == null) {
527                 // If we can't access service then we can't do anything. Return.
528                 if (DEBUG) {
529                     Log.d(TAG, "ProfilingService is not available, triggers will not be removed.");
530                 }
531                 return;
532             }
533 
534             String packageName = mContext.getPackageName();
535             if (packageName == null) {
536                 if (DEBUG) Log.d(TAG, "Failed to resolve package name.");
537                 return;
538             }
539 
540             try {
541                 service.removeProfilingTriggers(triggers, packageName);
542             } catch (RemoteException e) {
543                 if (DEBUG) Log.d(TAG, "Binder exception processing request", e);
544                 throw new RuntimeException("Unable to remove profiling triggers.");
545             }
546         }
547     }
548 
549     /** Remove all triggers for this process. */
550     @FlaggedApi(Flags.FLAG_SYSTEM_TRIGGERED_PROFILING_NEW)
clearProfilingTriggers()551     public void clearProfilingTriggers() {
552         synchronized (mLock) {
553             final IProfilingService service = getOrCreateIProfilingServiceLocked(false);
554             if (service == null) {
555                 // If we can't access service then we can't do anything. Return.
556                 if (DEBUG) {
557                     Log.d(TAG, "ProfilingService is not available, triggers will not be removed.");
558                 }
559                 return;
560             }
561 
562             String packageName = mContext.getPackageName();
563             if (packageName == null) {
564                 if (DEBUG) Log.d(TAG, "Failed to resolve package name.");
565                 return;
566             }
567 
568             try {
569                 service.clearProfilingTriggers(packageName);
570             } catch (RemoteException e) {
571                 if (DEBUG) Log.d(TAG, "Binder exception processing request", e);
572                 throw new RuntimeException("Unable to clear profiling triggers.");
573             }
574         }
575     }
576 
577     /** @hide */
578     @VisibleForTesting
579     @GuardedBy("mLock")
getOrCreateIProfilingServiceLocked( boolean isGeneralListener)580     public @Nullable IProfilingService getOrCreateIProfilingServiceLocked(
581             boolean isGeneralListener) {
582         // We only register the callback with registerResultsCallback once per binder object, and we
583         // only create one binder object per ProfilingManager instance. If the object already exists
584         // then it was successfully created and registered previously so we can just return it.
585         if (mProfilingService != null) {
586             return mProfilingService;
587         }
588 
589         mProfilingService = IProfilingService.Stub.asInterface(
590                 ProfilingFrameworkInitializer.getProfilingServiceManager()
591                     .getProfilingServiceRegisterer().get());
592         if (mProfilingService == null) {
593             // Service is not accessible, all requests will fail.
594             return mProfilingService;
595         }
596         try {
597             mProfilingService.registerResultsCallback(isGeneralListener,
598                     new IProfilingResultCallback.Stub() {
599 
600                         /**
601                          * Called by {@link ProfilingService} when a result is ready,
602                          * both for success and failure.
603                          */
604                         @Override
605                         public void sendResult(@Nullable String resultFile, long keyMostSigBits,
606                                 long keyLeastSigBits, int status, @Nullable String tag,
607                                 @Nullable String error, int triggerType) {
608                             synchronized (mLock) {
609                                 if (mCallbacks.isEmpty()) {
610                                     // This shouldn't happen - no callbacks, nowhere to report this
611                                     // result.
612                                     if (DEBUG) Log.d(TAG, "No callbacks");
613                                     mProfilingService = null;
614                                     return;
615                                 }
616 
617                                 // This shouldn't be true, but if the file is null ensure the status
618                                 // represents a failure.
619                                 final boolean overrideStatusToError = resultFile == null
620                                         && status == ProfilingResult.ERROR_NONE;
621 
622                                 UUID key = new UUID(keyMostSigBits, keyLeastSigBits);
623                                 int removeListenerPos = -1;
624                                 for (int i = 0; i < mCallbacks.size(); i++) {
625                                     ProfilingRequestCallbackWrapper wrapper = mCallbacks.get(i);
626                                     if (key.equals(wrapper.mKey)) {
627                                         // At most 1 listener can have a key matching this result:
628                                         // the one registered with the request, remove that one
629                                         // only.
630                                         if (removeListenerPos == -1) {
631                                             removeListenerPos = i;
632                                         } else {
633                                             // This should never happen.
634                                             if (DEBUG) {
635                                                 Log.d(TAG,
636                                                         "More than 1 listener with the same key");
637                                             }
638                                         }
639                                     } else if (wrapper.mKey != null) {
640                                         // If the key is not null, and doesn't matched the result
641                                         // key, then this key belongs to another request and should
642                                         // not be triggered.
643                                         continue;
644                                     }
645 
646                                     // TODO: b/337017299 - check resultFile is valid before
647                                     // returning Now trigger the callback for any listener that
648                                     // doesn't belong to another request.
649                                     wrapper.mExecutor.execute(() -> wrapper.mListener.accept(
650                                             new ProfilingResult(overrideStatusToError
651                                                     ? ProfilingResult.ERROR_UNKNOWN : status,
652                                                     getAppFileDir() + resultFile, tag, error,
653                                                     triggerType)));
654                                 }
655 
656                                 // Remove the single listener that was tied to the request, if
657                                 // applicable.
658                                 if (removeListenerPos != -1) {
659                                     mCallbacks.remove(removeListenerPos);
660                                 }
661                             }
662                         }
663 
664                         /**
665                          * Called by {@link ProfilingService} when a trace is ready and needs to be
666                          * copied to callers internal storage.
667                          *
668                          * This method will open a new file and pass back the FileDescriptor for
669                          * ProfilingService to write to via a new binder call.
670                          *
671                          * Takes in key most/least significant bits which represent the key that
672                          * will be used to associate this back to a profiling session which will
673                          * write to the generated file.
674                          */
675                         @Override
676                         public void generateFile(String filePathRelative, String fileName,
677                                 long keyMostSigBits, long keyLeastSigBits) {
678                             synchronized (mLock) {
679                                 String filePathAbsolute = getAppFileDir() + filePathRelative;
680                                 try {
681                                     // Ensure the profiling directory exists. Create it if it
682                                     // doesn't.
683                                     final File profilingDir = new File(filePathAbsolute);
684                                     if (!profilingDir.exists()) {
685                                         profilingDir.mkdir();
686                                     }
687 
688                                     // Create the profiling file for the output to be written to.
689                                     final File profilingFile = new File(
690                                             filePathAbsolute + fileName);
691                                     profilingFile.createNewFile();
692                                     if (!profilingFile.exists()) {
693                                         // Failed to create output file. Result may be lost.
694                                         if (DEBUG) Log.d(TAG, "Output file couldn't be created");
695                                         return;
696                                     }
697 
698                                     // Wrap the new output file in a {@link ParcelFileDescriptor} to
699                                     // send back to {@link ProfilingService} to write to.
700                                     ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
701                                             profilingFile,
702                                             ParcelFileDescriptor.MODE_READ_WRITE);
703                                     IProfilingService service =
704                                             getOrCreateIProfilingServiceLocked(false);
705 
706                                     if (service == null) {
707                                         // Unable to send file descriptor because we have nowhere to
708                                         // send it to. Result may be lost. Close descriptor and
709                                         // delete file.
710                                         if (DEBUG) Log.d(TAG, "Unable to send file descriptor");
711                                         tryToCleanupGeneratedFile(pfd, profilingFile);
712                                         return;
713                                     }
714 
715                                     try {
716                                         // Send the file descriptor to service to write to.
717                                         service.receiveFileDescriptor(pfd, keyMostSigBits,
718                                                 keyLeastSigBits);
719                                     } catch (RemoteException e) {
720                                         // If we failed to send it, try to clean it up as it won't
721                                         // be used.
722                                         if (DEBUG) {
723                                             Log.d(TAG, "Failed sending file descriptor to service",
724                                                     e);
725                                         }
726                                         tryToCleanupGeneratedFile(pfd, profilingFile);
727                                     }
728                                 } catch (Exception e) {
729                                     // Failure prepping output file. Result may be lost.
730                                     if (DEBUG) Log.d(TAG, "Exception preparing file", e);
731                                     return;
732                                 }
733                             }
734                         }
735 
736                         /**
737                          * Attempt to clean up the files created for service by closing the file
738                          * descriptor and deleting the file. This is intended for error cases where
739                          * the descriptor could not be sent. If it was successfully sent, service
740                          * will handle closing it and requesting a delete if necessary.
741                          */
742                         private void tryToCleanupGeneratedFile(ParcelFileDescriptor fileDescriptor,
743                                 File file) {
744                             if (fileDescriptor != null) {
745                                 try {
746                                     fileDescriptor.close();
747                                 } catch (IOException e) {
748                                     // Nothing else we can do, ignore.
749                                     if (DEBUG) Log.d(TAG, "Failed to cleanup file descriptor", e);
750                                 }
751                             }
752 
753                             if (file != null) {
754                                 try {
755                                     file.delete();
756                                 } catch (SecurityException e) {
757                                     // Nothing else we can do, ignore.
758                                     if (DEBUG) Log.d(TAG, "Failed to cleanup file", e);
759                                 }
760                             }
761                         }
762 
763                         /**
764                          * Delete a file. To be used only for files created by {@link generateFile}.
765                          */
766                         @Override
767                         public void deleteFile(String relativeFilePathAndName) {
768                             try {
769                                 Files.delete(Path.of(getAppFileDir() + relativeFilePathAndName));
770                             } catch (Exception exception) {
771                                 if (DEBUG) Log.e(TAG, "Failed to delete file.", exception);
772                             }
773                         }
774 
775                         private String getAppFileDir() {
776                             return mContext.getFilesDir().getPath();
777                         }
778                     });
779         } catch (RemoteException e) {
780             if (DEBUG) Log.d(TAG, "Exception registering service callback", e);
781             throw new RuntimeException("Unable to register profiling result callback."
782                     + " All Profiling requests will fail.");
783         }
784         return mProfilingService;
785     }
786 
787     private static final class ProfilingRequestCallbackWrapper {
788         /** executor provided with callback request */
789         final @NonNull Executor mExecutor;
790 
791         /** listener provided with callback request */
792         final @NonNull Consumer<ProfilingResult> mListener;
793 
794         /**
795          * Unique key generated with each profiling request {@link #requestProfiling}, but not with
796          * requests to register a listener only {@link #registerForAllProfilingResults}.
797          *
798          * Key is used to match the result with the listener added with the request so that it can
799          * removed after being triggered while the general registered callbacks remain active.
800          */
801         final @Nullable UUID mKey;
802 
ProfilingRequestCallbackWrapper(@onNull Executor executor, @NonNull Consumer<ProfilingResult> listener, @Nullable UUID key)803         ProfilingRequestCallbackWrapper(@NonNull Executor executor,
804                 @NonNull Consumer<ProfilingResult> listener,
805                 @Nullable UUID key) {
806             mExecutor = executor;
807             mListener = listener;
808             mKey = key;
809         }
810     }
811 }
812