1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.soundtrigger_middleware;
18 
19 import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.DETACH;
20 import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.FORCE_RECOGNITION;
21 import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.GET_MODEL_PARAMETER;
22 import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.LOAD_MODEL;
23 import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.LOAD_PHRASE_MODEL;
24 import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.MODEL_UNLOADED;
25 import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.MODULE_DIED;
26 import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.QUERY_MODEL_PARAMETER;
27 import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.RECOGNITION;
28 import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.RESOURCES_AVAILABLE;
29 import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.SET_MODEL_PARAMETER;
30 import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.START_RECOGNITION;
31 import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.STOP_RECOGNITION;
32 import static com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareLogging.SessionEvent.Type.UNLOAD_MODEL;
33 import static com.android.server.utils.EventLogger.Event.ALOGI;
34 import static com.android.server.utils.EventLogger.Event.ALOGW;
35 
36 import android.annotation.NonNull;
37 import android.content.Context;
38 import android.media.permission.Identity;
39 import android.media.permission.IdentityContext;
40 import android.media.soundtrigger.ModelParameterRange;
41 import android.media.soundtrigger.PhraseRecognitionEvent;
42 import android.media.soundtrigger.PhraseSoundModel;
43 import android.media.soundtrigger.RecognitionConfig;
44 import android.media.soundtrigger.RecognitionStatus;
45 import android.media.soundtrigger.SoundModel;
46 import android.media.soundtrigger_middleware.ISoundTriggerCallback;
47 import android.media.soundtrigger_middleware.ISoundTriggerModule;
48 import android.media.soundtrigger_middleware.PhraseRecognitionEventSys;
49 import android.media.soundtrigger_middleware.RecognitionEventSys;
50 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
51 import android.os.BatteryStatsInternal;
52 import android.os.IBinder;
53 import android.os.RemoteException;
54 import android.os.SystemClock;
55 import android.util.Slog;
56 
57 import com.android.internal.annotations.VisibleForTesting;
58 import com.android.internal.util.ArrayUtils;
59 import com.android.internal.util.LatencyTracker;
60 import com.android.server.LocalServices;
61 import com.android.server.utils.EventLogger;
62 import com.android.server.utils.EventLogger.Event;
63 
64 import java.io.PrintWriter;
65 import java.util.Arrays;
66 import java.util.Deque;
67 import java.util.Objects;
68 import java.util.Set;
69 import java.util.concurrent.ConcurrentHashMap;
70 import java.util.concurrent.LinkedBlockingDeque;
71 import java.util.concurrent.atomic.AtomicInteger;
72 import java.util.function.Supplier;
73 
74 
75 /**
76  * An ISoundTriggerMiddlewareService decorator, which adds logging of all API calls (and
77  * callbacks).
78  *
79  * All API methods should follow this structure:
80  * <pre><code>
81  * @Override
82  * public @NonNull ReturnType someMethod(ArgType1 arg1, ArgType2 arg2) throws ExceptionType {
83  *     try {
84  *         ReturnType result = mDelegate.someMethod(arg1, arg2);
85  *         logReturn("someMethod", result, arg1, arg2);
86  *         return result;
87  *     } catch (Exception e) {
88  *         logException("someMethod", e, arg1, arg2);
89  *         throw e;
90  *     }
91  * }
92  * </code></pre>
93  * The actual handling of these events is then done inside of
94  * {@link #logReturnWithObject(Object, Identity, String, Object, Object[])},
95  * {@link #logVoidReturnWithObject(Object, Identity, String, Object[])} and {@link
96  * #logExceptionWithObject(Object, Identity, String, Exception, Object[])}.
97  */
98 public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInternal, Dumpable {
99     private static final String TAG = "SoundTriggerMiddlewareLogging";
100     private static final int SESSION_MAX_EVENT_SIZE = 128;
101     private final @NonNull ISoundTriggerMiddlewareInternal mDelegate;
102     private final @NonNull LatencyTracker mLatencyTracker;
103     private final @NonNull Supplier<BatteryStatsInternal> mBatteryStatsInternalSupplier;
104     private final @NonNull EventLogger mServiceEventLogger = new EventLogger(256,
105             "Service Events");
106 
107     private final Set<EventLogger> mSessionEventLoggers = ConcurrentHashMap.newKeySet(4);
108     private final Deque<EventLogger> mDetachedSessionEventLoggers = new LinkedBlockingDeque<>(4);
109     private final AtomicInteger mSessionCount = new AtomicInteger(0);
110 
111 
SoundTriggerMiddlewareLogging(@onNull Context context, @NonNull ISoundTriggerMiddlewareInternal delegate)112     public SoundTriggerMiddlewareLogging(@NonNull Context context,
113             @NonNull ISoundTriggerMiddlewareInternal delegate) {
114         this(LatencyTracker.getInstance(context),
115                 () -> BatteryStatsHolder.INSTANCE,
116                 delegate);
117     }
118 
119     @VisibleForTesting
SoundTriggerMiddlewareLogging(@onNull LatencyTracker latencyTracker, @NonNull Supplier<BatteryStatsInternal> batteryStatsInternalSupplier, @NonNull ISoundTriggerMiddlewareInternal delegate)120     public SoundTriggerMiddlewareLogging(@NonNull LatencyTracker latencyTracker,
121             @NonNull Supplier<BatteryStatsInternal> batteryStatsInternalSupplier,
122             @NonNull ISoundTriggerMiddlewareInternal delegate) {
123         mDelegate = delegate;
124         mLatencyTracker = latencyTracker;
125         mBatteryStatsInternalSupplier = batteryStatsInternalSupplier;
126     }
127 
128     @Override
129     public @NonNull
listModules()130     SoundTriggerModuleDescriptor[] listModules() {
131         try {
132             SoundTriggerModuleDescriptor[] result = mDelegate.listModules();
133             var moduleSummary = Arrays.stream(result).map((descriptor) ->
134                     new ModulePropertySummary(descriptor.handle,
135                         descriptor.properties.implementor,
136                         descriptor.properties.version)).toArray(ModulePropertySummary[]::new);
137 
138             mServiceEventLogger.enqueue(ServiceEvent.createForReturn(
139                         ServiceEvent.Type.LIST_MODULE,
140                         IdentityContext.get().packageName, moduleSummary).printLog(ALOGI, TAG));
141             return result;
142         } catch (Exception e) {
143             mServiceEventLogger.enqueue(ServiceEvent.createForException(
144                         ServiceEvent.Type.LIST_MODULE,
145                         IdentityContext.get().packageName, e).printLog(ALOGW, TAG));
146             throw e;
147         }
148     }
149 
150     @Override
151     public @NonNull
attach(int handle, ISoundTriggerCallback callback, boolean isTrusted)152     ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback, boolean isTrusted) {
153         try {
154             var originatorIdentity = IdentityContext.getNonNull();
155             String packageIdentification = originatorIdentity.packageName
156                     + mSessionCount.getAndIncrement() + (isTrusted ? "trusted" : "");
157             ModuleLogging result = new ModuleLogging();
158             var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE,
159                 "Session logger for: " + packageIdentification);
160 
161             var callbackWrapper = new CallbackLogging(callback, eventLogger, originatorIdentity);
162 
163             result.attach(mDelegate.attach(handle, callbackWrapper, isTrusted), eventLogger);
164 
165             mServiceEventLogger.enqueue(ServiceEvent.createForReturn(
166                         ServiceEvent.Type.ATTACH,
167                         packageIdentification, result, handle, callback, isTrusted)
168                     .printLog(ALOGI, TAG));
169 
170             mSessionEventLoggers.add(eventLogger);
171             return result;
172         } catch (Exception e) {
173             mServiceEventLogger.enqueue(ServiceEvent.createForException(
174                         ServiceEvent.Type.ATTACH,
175                         IdentityContext.get().packageName, e, handle, callback)
176                     .printLog(ALOGW, TAG));
177             throw e;
178         }
179     }
180 
181     // Override toString() in order to have the delegate's ID in it.
182     @Override
toString()183     public String toString() {
184         return mDelegate.toString();
185     }
186 
187     private class ModuleLogging implements ISoundTriggerModule {
188         private ISoundTriggerModule mDelegate;
189         private EventLogger mEventLogger;
190 
attach(@onNull ISoundTriggerModule delegate, EventLogger eventLogger)191         void attach(@NonNull ISoundTriggerModule delegate, EventLogger eventLogger) {
192             mDelegate = delegate;
193             mEventLogger = eventLogger;
194         }
195 
196         @Override
loadModel(SoundModel model)197         public int loadModel(SoundModel model) throws RemoteException {
198             try {
199                 int result = mDelegate.loadModel(model);
200                 mEventLogger.enqueue(SessionEvent.createForReturn(
201                             LOAD_MODEL, result, model.uuid)
202                         .printLog(ALOGI, TAG));
203                 return result;
204             } catch (Exception e) {
205                 mEventLogger.enqueue(SessionEvent.createForReturn(
206                             LOAD_MODEL, e, model.uuid)
207                         .printLog(ALOGW, TAG));
208                 throw e;
209             }
210         }
211 
212         @Override
loadPhraseModel(PhraseSoundModel model)213         public int loadPhraseModel(PhraseSoundModel model) throws RemoteException {
214             try {
215                 int result = mDelegate.loadPhraseModel(model);
216                 mEventLogger.enqueue(SessionEvent.createForReturn(
217                             LOAD_PHRASE_MODEL, result, model.common.uuid)
218                         .printLog(ALOGI, TAG));
219                 return result;
220             } catch (Exception e) {
221                 mEventLogger.enqueue(SessionEvent.createForException(
222                             LOAD_PHRASE_MODEL, e, model.common.uuid)
223                         .printLog(ALOGW, TAG));
224                 throw e;
225             }
226         }
227 
228         @Override
unloadModel(int modelHandle)229         public void unloadModel(int modelHandle) throws RemoteException {
230             try {
231                 mDelegate.unloadModel(modelHandle);
232                 mEventLogger.enqueue(SessionEvent.createForVoid(
233                             UNLOAD_MODEL, modelHandle)
234                         .printLog(ALOGI, TAG));
235             } catch (Exception e) {
236                 mEventLogger.enqueue(SessionEvent.createForException(
237                             UNLOAD_MODEL, e, modelHandle)
238                         .printLog(ALOGW, TAG));
239                 throw e;
240             }
241         }
242 
243         @Override
startRecognition(int modelHandle, RecognitionConfig config)244         public IBinder startRecognition(int modelHandle, RecognitionConfig config)
245                 throws RemoteException {
246             try {
247                 var result = mDelegate.startRecognition(modelHandle, config);
248                 mEventLogger.enqueue(SessionEvent.createForReturn(
249                             START_RECOGNITION, result, modelHandle, config)
250                         .printLog(ALOGI, TAG));
251                 return result;
252             } catch (Exception e) {
253                 mEventLogger.enqueue(SessionEvent.createForException(
254                             START_RECOGNITION, e, modelHandle, config)
255                         .printLog(ALOGW, TAG));
256                 throw e;
257             }
258         }
259 
260         @Override
stopRecognition(int modelHandle)261         public void stopRecognition(int modelHandle) throws RemoteException {
262             try {
263                 mDelegate.stopRecognition(modelHandle);
264                 mEventLogger.enqueue(SessionEvent.createForVoid(
265                             STOP_RECOGNITION, modelHandle)
266                         .printLog(ALOGI, TAG));
267             } catch (Exception e) {
268                 mEventLogger.enqueue(SessionEvent.createForException(
269                             STOP_RECOGNITION, e, modelHandle)
270                         .printLog(ALOGW, TAG));
271                 throw e;
272             }
273         }
274 
275         @Override
forceRecognitionEvent(int modelHandle)276         public void forceRecognitionEvent(int modelHandle) throws RemoteException {
277             try {
278                 mDelegate.forceRecognitionEvent(modelHandle);
279                 mEventLogger.enqueue(SessionEvent.createForVoid(
280                             FORCE_RECOGNITION, modelHandle)
281                         .printLog(ALOGI, TAG));
282             } catch (Exception e) {
283                 mEventLogger.enqueue(SessionEvent.createForException(
284                             FORCE_RECOGNITION, e, modelHandle)
285                         .printLog(ALOGW, TAG));
286                 throw e;
287             }
288         }
289 
290         @Override
setModelParameter(int modelHandle, int modelParam, int value)291         public void setModelParameter(int modelHandle, int modelParam, int value)
292                 throws RemoteException {
293             try {
294                 mDelegate.setModelParameter(modelHandle, modelParam, value);
295                 mEventLogger.enqueue(SessionEvent.createForVoid(
296                             SET_MODEL_PARAMETER, modelHandle, modelParam, value)
297                         .printLog(ALOGI, TAG));
298             } catch (Exception e) {
299                 mEventLogger.enqueue(SessionEvent.createForException(
300                             SET_MODEL_PARAMETER, e, modelHandle, modelParam, value)
301                         .printLog(ALOGW, TAG));
302                 throw e;
303             }
304         }
305 
306         @Override
getModelParameter(int modelHandle, int modelParam)307         public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
308             try {
309                 int result = mDelegate.getModelParameter(modelHandle, modelParam);
310                 mEventLogger.enqueue(SessionEvent.createForReturn(
311                             GET_MODEL_PARAMETER, result, modelHandle, modelParam)
312                         .printLog(ALOGI, TAG));
313                 return result;
314             } catch (Exception e) {
315                 mEventLogger.enqueue(SessionEvent.createForException(
316                             GET_MODEL_PARAMETER, e, modelHandle, modelParam)
317                         .printLog(ALOGW, TAG));
318                 throw e;
319             }
320         }
321 
322         @Override
queryModelParameterSupport(int modelHandle, int modelParam)323         public ModelParameterRange queryModelParameterSupport(int modelHandle, int modelParam)
324                 throws RemoteException {
325             try {
326                 ModelParameterRange result = mDelegate.queryModelParameterSupport(modelHandle,
327                         modelParam);
328                 mEventLogger.enqueue(SessionEvent.createForReturn(
329                             QUERY_MODEL_PARAMETER, result, modelHandle, modelParam)
330                         .printLog(ALOGI, TAG));
331                 return result;
332             } catch (Exception e) {
333                 mEventLogger.enqueue(SessionEvent.createForException(
334                             QUERY_MODEL_PARAMETER, e, modelHandle, modelParam)
335                         .printLog(ALOGW, TAG));
336                 throw e;
337             }
338         }
339 
340         @Override
detach()341         public void detach() throws RemoteException {
342             try {
343                 if (mSessionEventLoggers.remove(mEventLogger)) {
344                     while (!mDetachedSessionEventLoggers.offerFirst(mEventLogger)) {
345                         // Remove the oldest element, if one still exists
346                         mDetachedSessionEventLoggers.pollLast();
347                     }
348                 }
349                 mDelegate.detach();
350                 mEventLogger.enqueue(SessionEvent.createForVoid(
351                             DETACH)
352                         .printLog(ALOGI, TAG));
353             } catch (Exception e) {
354                 mEventLogger.enqueue(SessionEvent.createForException(
355                             DETACH, e)
356                         .printLog(ALOGW, TAG));
357                 throw e;
358             }
359         }
360 
361         @Override
asBinder()362         public IBinder asBinder() {
363             return mDelegate.asBinder();
364         }
365 
366         // Override toString() in order to have the delegate's ID in it.
367         @Override
toString()368         public String toString() {
369             return Objects.toString(mDelegate);
370         }
371     }
372 
373     private class CallbackLogging implements ISoundTriggerCallback {
374         private final ISoundTriggerCallback mCallbackDelegate;
375         private final EventLogger mEventLogger;
376         private final Identity mOriginatorIdentity;
377 
CallbackLogging(ISoundTriggerCallback delegate, EventLogger eventLogger, Identity originatorIdentity)378         private CallbackLogging(ISoundTriggerCallback delegate,
379                 EventLogger eventLogger, Identity originatorIdentity) {
380             mCallbackDelegate = Objects.requireNonNull(delegate);
381             mEventLogger = Objects.requireNonNull(eventLogger);
382             mOriginatorIdentity = originatorIdentity;
383         }
384 
385         @Override
onRecognition(int modelHandle, RecognitionEventSys event, int captureSession)386         public void onRecognition(int modelHandle, RecognitionEventSys event, int captureSession)
387                 throws RemoteException {
388             try {
389                 mBatteryStatsInternalSupplier.get().noteWakingSoundTrigger(
390                         SystemClock.elapsedRealtime(), mOriginatorIdentity.uid);
391                 mCallbackDelegate.onRecognition(modelHandle, event, captureSession);
392                 mEventLogger.enqueue(SessionEvent.createForVoid(
393                             RECOGNITION, modelHandle, event, captureSession)
394                         .printLog(ALOGI, TAG));
395             } catch (Exception e) {
396                 mEventLogger.enqueue(SessionEvent.createForException(
397                             RECOGNITION, e, modelHandle, event, captureSession)
398                         .printLog(ALOGW, TAG));
399                 throw e;
400             }
401         }
402 
403         @Override
onPhraseRecognition(int modelHandle, PhraseRecognitionEventSys event, int captureSession)404         public void onPhraseRecognition(int modelHandle, PhraseRecognitionEventSys event,
405                 int captureSession)
406                 throws RemoteException {
407             try {
408                 mBatteryStatsInternalSupplier.get().noteWakingSoundTrigger(
409                         SystemClock.elapsedRealtime(), mOriginatorIdentity.uid);
410                 startKeyphraseEventLatencyTracking(event.phraseRecognitionEvent);
411                 mCallbackDelegate.onPhraseRecognition(modelHandle, event, captureSession);
412                 mEventLogger.enqueue(SessionEvent.createForVoid(
413                             RECOGNITION, modelHandle, event, captureSession)
414                         .printLog(ALOGI, TAG));
415             } catch (Exception e) {
416                 mEventLogger.enqueue(SessionEvent.createForException(
417                             RECOGNITION, e, modelHandle, event, captureSession)
418                         .printLog(ALOGW, TAG));
419                 throw e;
420             }
421         }
422 
423         @Override
onModelUnloaded(int modelHandle)424         public void onModelUnloaded(int modelHandle) throws RemoteException {
425             try {
426                 mCallbackDelegate.onModelUnloaded(modelHandle);
427                 mEventLogger.enqueue(SessionEvent.createForVoid(
428                             MODEL_UNLOADED, modelHandle)
429                         .printLog(ALOGI, TAG));
430             } catch (Exception e) {
431                 mEventLogger.enqueue(SessionEvent.createForException(
432                             MODEL_UNLOADED, e, modelHandle)
433                         .printLog(ALOGW, TAG));
434                 throw e;
435             }
436         }
437 
438         @Override
onResourcesAvailable()439         public void onResourcesAvailable() throws RemoteException {
440             try {
441                 mCallbackDelegate.onResourcesAvailable();
442                 mEventLogger.enqueue(SessionEvent.createForVoid(
443                             RESOURCES_AVAILABLE)
444                         .printLog(ALOGI, TAG));
445             } catch (Exception e) {
446                 mEventLogger.enqueue(SessionEvent.createForException(
447                             RESOURCES_AVAILABLE, e)
448                         .printLog(ALOGW, TAG));
449                 throw e;
450             }
451         }
452 
453         @Override
onModuleDied()454         public void onModuleDied() throws RemoteException {
455             try {
456                 mCallbackDelegate.onModuleDied();
457                 mEventLogger.enqueue(SessionEvent.createForVoid(
458                             MODULE_DIED)
459                         .printLog(ALOGW, TAG));
460             } catch (Exception e) {
461                 mEventLogger.enqueue(SessionEvent.createForException(
462                             MODULE_DIED, e)
463                         .printLog(ALOGW, TAG));
464                 throw e;
465             }
466         }
467 
468         @Override
asBinder()469         public IBinder asBinder() {
470             return mCallbackDelegate.asBinder();
471         }
472 
473         // Override toString() in order to have the delegate's ID in it.
474         @Override
toString()475         public String toString() {
476             return Objects.toString(mCallbackDelegate);
477         }
478     }
479 
480     private static class BatteryStatsHolder {
481         private static final BatteryStatsInternal INSTANCE =
482                 LocalServices.getService(BatteryStatsInternal.class);
483     }
484 
485     /**
486      * Starts the latency tracking log for keyphrase hotword invocation.
487      * The measurement covers from when the SoundTrigger HAL emits an event to when the
488      * {@link android.service.voice.VoiceInteractionSession} system UI view is shown.
489      *
490      * <p>The session is only started if the {@link PhraseRecognitionEvent} has a status of
491      * {@link RecognitionStatus#SUCCESS}
492      */
startKeyphraseEventLatencyTracking(PhraseRecognitionEvent event)493     private void startKeyphraseEventLatencyTracking(PhraseRecognitionEvent event) {
494         if (event.common.status != RecognitionStatus.SUCCESS
495                 || ArrayUtils.isEmpty(event.phraseExtras)) {
496             return;
497         }
498 
499         String latencyTrackerTag = "KeyphraseId=" + event.phraseExtras[0].id;
500         // To avoid adding cancel to all the different failure modes between here and
501         // showing the system UI, we defensively cancel once.
502         // Either we hit the LatencyTracker timeout of 15 seconds or we defensively cancel
503         // here if any error occurs.
504         mLatencyTracker.onActionCancel(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION);
505         mLatencyTracker.onActionStart(LatencyTracker.ACTION_SHOW_VOICE_INTERACTION,
506                 latencyTrackerTag);
507     }
508 
printArgs(StringBuilder builder, @NonNull Object[] args)509     private static StringBuilder printArgs(StringBuilder builder, @NonNull Object[] args) {
510         for (int i = 0; i < args.length; ++i) {
511             if (i > 0) {
512                 builder.append(", ");
513             }
514             ObjectPrinter.print(builder, args[i]);
515         }
516         return builder;
517     }
518 
519     @Override
dump(PrintWriter pw)520     public void dump(PrintWriter pw) {
521         // Event loggers
522         pw.println("##Service-Wide logs:");
523         mServiceEventLogger.dump(pw, /* indent = */ "  ");
524 
525         pw.println("\n##Active Session dumps:\n");
526         for (var sessionLogger : mSessionEventLoggers) {
527             sessionLogger.dump(pw, /* indent= */ "  ");
528             pw.println("");
529         }
530         pw.println("##Detached Session dumps:\n");
531         for (var sessionLogger : mDetachedSessionEventLoggers) {
532             sessionLogger.dump(pw, /* indent= */ "  ");
533             pw.println("");
534         }
535 
536         if (mDelegate instanceof Dumpable) {
537             ((Dumpable) mDelegate).dump(pw);
538         }
539     }
540 
printSystemLog(int type, String tag, String message, Exception e)541     public static void printSystemLog(int type, String tag, String message, Exception e) {
542         switch (type) {
543             case Event.ALOGI:
544                 Slog.i(tag, message, e);
545                 break;
546             case Event.ALOGE:
547                 Slog.e(tag, message, e);
548                 break;
549             case Event.ALOGW:
550                 Slog.w(tag, message, e);
551                 break;
552             case Event.ALOGV:
553             default:
554                 Slog.v(tag, message, e);
555         }
556     }
557 
558     public static class ServiceEvent extends Event {
559         private final Type mType;
560         private final String mPackageName;
561         private final Object mReturnValue;
562         private final Object[] mParams;
563         private final Exception mException;
564 
565         public enum Type {
566             ATTACH,
567             LIST_MODULE,
568         }
569 
createForException(Type type, String packageName, Exception exception, Object... params)570         public static ServiceEvent createForException(Type type, String packageName,
571                 Exception exception, Object... params) {
572             return new ServiceEvent(exception, type, packageName, null, params);
573         }
574 
createForReturn(Type type, String packageName, Object returnValue, Object... params)575         public static ServiceEvent createForReturn(Type type, String packageName,
576                 Object returnValue, Object... params) {
577             return new ServiceEvent(null , type, packageName, returnValue, params);
578         }
579 
ServiceEvent(Exception exception, Type type, String packageName, Object returnValue, Object... params)580         private ServiceEvent(Exception exception, Type type, String packageName, Object returnValue,
581                 Object... params) {
582             mException = exception;
583             mType = type;
584             mPackageName = packageName;
585             mReturnValue = returnValue;
586             mParams = params;
587         }
588 
589         @Override
printLog(int type, String tag)590         public Event printLog(int type, String tag) {
591             printSystemLog(type, tag, eventToString(), mException);
592             return this;
593         }
594 
595         @Override
eventToString()596         public String eventToString() {
597             var sb = new StringBuilder(mType.name()).append(" [client= ");
598             ObjectPrinter.print(sb, mPackageName);
599             sb.append("] (");
600             printArgs(sb, mParams);
601             sb.append(") -> ");
602             if (mException != null) {
603                 sb.append("ERROR: ");
604                 ObjectPrinter.print(sb, mException);
605             } else {
606                 ObjectPrinter.print(sb, mReturnValue);
607             }
608             return sb.toString();
609         }
610     }
611 
612     public static class SessionEvent extends Event {
613         public enum Type {
614             LOAD_MODEL,
615             LOAD_PHRASE_MODEL,
616             START_RECOGNITION,
617             STOP_RECOGNITION,
618             FORCE_RECOGNITION,
619             UNLOAD_MODEL,
620             GET_MODEL_PARAMETER,
621             SET_MODEL_PARAMETER,
622             QUERY_MODEL_PARAMETER,
623             DETACH,
624             RECOGNITION,
625             MODEL_UNLOADED,
626             MODULE_DIED,
627             RESOURCES_AVAILABLE,
628         }
629 
630         private final Type mType;
631         private final Exception mException;
632         private final Object mReturnValue;
633         private final Object[] mParams;
634 
createForException(Type type, Exception exception, Object... params)635         public static SessionEvent createForException(Type type, Exception exception,
636                 Object... params) {
637             return new SessionEvent(exception, type, null, params);
638         }
639 
createForReturn(Type type, Object returnValue, Object... params)640         public static SessionEvent createForReturn(Type type,
641                 Object returnValue, Object... params) {
642             return new SessionEvent(null , type, returnValue, params);
643         }
644 
createForVoid(Type type, Object... params)645         public static SessionEvent createForVoid(Type type, Object... params) {
646             return new SessionEvent(null, type, null, params);
647         }
648 
649 
SessionEvent(Exception exception, Type type, Object returnValue, Object... params)650         private SessionEvent(Exception exception, Type type, Object returnValue,
651                 Object... params) {
652             mException = exception;
653             mType = type;
654             mReturnValue = returnValue;
655             mParams = params;
656         }
657 
658         @Override
printLog(int type, String tag)659         public Event printLog(int type, String tag) {
660             printSystemLog(type, tag, eventToString(), mException);
661             return this;
662         }
663 
664         @Override
eventToString()665         public String eventToString() {
666             var sb = new StringBuilder(mType.name());
667             sb.append(" (");
668             printArgs(sb, mParams);
669             sb.append(")");
670             if (mException != null) {
671                 sb.append(" -> ERROR: ");
672                 ObjectPrinter.print(sb, mException);
673             } else if (mReturnValue != null) {
674                 sb.append(" -> ");
675                 ObjectPrinter.print(sb, mReturnValue);
676             }
677             return sb.toString();
678         }
679     }
680 
681     private static final class ModulePropertySummary {
682         private int mId;
683         private String mImplementor;
684         private int mVersion;
685 
ModulePropertySummary(int id, String implementor, int version)686         ModulePropertySummary(int id, String implementor, int version) {
687             mId = id;
688             mImplementor = implementor;
689             mVersion = version;
690         }
691 
692         @Override
toString()693        public String toString() {
694            return "{Id: " + mId + ", Implementor: " + mImplementor
695                + ", Version: " + mVersion + "}";
696        }
697     }
698 }
699