1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.service.voice;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.SystemApi;
22 import android.annotation.TestApi;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.text.TextUtils;
26 
27 import java.lang.annotation.Retention;
28 import java.lang.annotation.RetentionPolicy;
29 
30 /**
31  * This class is used by the assistant application to know what went wrong during using the
32  * sound trigger system service {@link com.android.server.soundtrigger.SoundTriggerService} and
33  * {@link com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareService}, and which
34  * action that the application should take.
35  *
36  * @hide
37  */
38 @SystemApi
39 public final class SoundTriggerFailure implements Parcelable {
40 
41     /**
42      * An error code which means an unknown error occurs.
43      */
44     public static final int ERROR_CODE_UNKNOWN = 0;
45 
46     /**
47      * Indicates that the underlying sound trigger module has died and will be restarted. All
48      * session state has been invalidated.
49      */
50     public static final int ERROR_CODE_MODULE_DIED = 1;
51 
52     /**
53      * Indicates that sound trigger service recognition resume has failed. The model is in the
54      * stopped state and will not be restarted by the framework.
55      */
56     public static final int ERROR_CODE_RECOGNITION_RESUME_FAILED = 2;
57 
58     /**
59      * Indicates that the sound trigger service has been unexpectedly preempted by another user.
60      * The model is in the stopped state and will not be restarted by the framework.
61      */
62     public static final int ERROR_CODE_UNEXPECTED_PREEMPTION = 3;
63 
64     /**
65      * @hide
66      */
67     @IntDef(prefix = {"ERROR_CODE_"}, value = {
68             ERROR_CODE_UNKNOWN,
69             ERROR_CODE_MODULE_DIED,
70             ERROR_CODE_RECOGNITION_RESUME_FAILED,
71             ERROR_CODE_UNEXPECTED_PREEMPTION
72     })
73     @Retention(RetentionPolicy.SOURCE)
74     public @interface SoundTriggerErrorCode {}
75 
76     private final int mErrorCode;
77     private final int mSuggestedAction;
78     private final String mErrorMessage;
79 
80     /**
81      * @hide
82      */
83     @TestApi
SoundTriggerFailure(@oundTriggerErrorCode int errorCode, @NonNull String errorMessage)84     public SoundTriggerFailure(@SoundTriggerErrorCode int errorCode, @NonNull String errorMessage) {
85         this(errorCode, errorMessage, getSuggestedActionBasedOnErrorCode(errorCode));
86     }
87 
88     /**
89      * @hide
90      */
SoundTriggerFailure(@oundTriggerErrorCode int errorCode, @NonNull String errorMessage, @FailureSuggestedAction.FailureSuggestedActionDef int suggestedAction)91     public SoundTriggerFailure(@SoundTriggerErrorCode int errorCode, @NonNull String errorMessage,
92             @FailureSuggestedAction.FailureSuggestedActionDef int suggestedAction) {
93         if (TextUtils.isEmpty(errorMessage)) {
94             throw new IllegalArgumentException("errorMessage is empty or null.");
95         }
96         switch (errorCode) {
97             case ERROR_CODE_UNKNOWN:
98             case ERROR_CODE_MODULE_DIED:
99             case ERROR_CODE_RECOGNITION_RESUME_FAILED:
100             case ERROR_CODE_UNEXPECTED_PREEMPTION:
101                 mErrorCode = errorCode;
102                 break;
103             default:
104                 throw new IllegalArgumentException("Invalid ErrorCode: " + errorCode);
105         }
106         if (suggestedAction != getSuggestedActionBasedOnErrorCode(errorCode)
107                 && errorCode != ERROR_CODE_UNKNOWN) {
108             throw new IllegalArgumentException("Invalid suggested next action: "
109                     + "errorCode=" + errorCode + ", suggestedAction=" + suggestedAction);
110         }
111         mErrorMessage = errorMessage;
112         mSuggestedAction = suggestedAction;
113     }
114 
115     /**
116      * Returns the error code.
117      */
118     @SoundTriggerErrorCode
getErrorCode()119     public int getErrorCode() {
120         return mErrorCode;
121     }
122 
123     /**
124      * Returns the error message.
125      */
126     @NonNull
getErrorMessage()127     public String getErrorMessage() {
128         return mErrorMessage;
129     }
130 
131     /**
132      * Returns the suggested action.
133      */
134     @FailureSuggestedAction.FailureSuggestedActionDef
getSuggestedAction()135     public int getSuggestedAction() {
136         return mSuggestedAction;
137     }
138 
getSuggestedActionBasedOnErrorCode(@oundTriggerErrorCode int errorCode)139     private static int getSuggestedActionBasedOnErrorCode(@SoundTriggerErrorCode int errorCode) {
140         switch (errorCode) {
141             case ERROR_CODE_UNKNOWN:
142             case ERROR_CODE_MODULE_DIED:
143             case ERROR_CODE_UNEXPECTED_PREEMPTION:
144                 return FailureSuggestedAction.RECREATE_DETECTOR;
145             case ERROR_CODE_RECOGNITION_RESUME_FAILED:
146                 return FailureSuggestedAction.RESTART_RECOGNITION;
147             default:
148                 throw new AssertionError("Unexpected error code");
149         }
150     }
151 
152     @Override
describeContents()153     public int describeContents() {
154         return 0;
155     }
156 
157     @Override
writeToParcel(@onNull Parcel dest, int flags)158     public void writeToParcel(@NonNull Parcel dest, int flags) {
159         dest.writeInt(mErrorCode);
160         dest.writeString8(mErrorMessage);
161     }
162 
163     @Override
toString()164     public String toString() {
165         return "SoundTriggerFailure {"
166                 + " errorCode = " + mErrorCode
167                 + ", errorMessage = " + mErrorMessage
168                 + ", suggestedNextAction = " + mSuggestedAction
169                 + " }";
170     }
171 
172     public static final @NonNull Parcelable.Creator<SoundTriggerFailure> CREATOR =
173             new Parcelable.Creator<SoundTriggerFailure>() {
174                 @Override
175                 public SoundTriggerFailure[] newArray(int size) {
176                     return new SoundTriggerFailure[size];
177                 }
178 
179                 @Override
180                 public SoundTriggerFailure createFromParcel(@NonNull Parcel in) {
181                     return new SoundTriggerFailure(in.readInt(), in.readString8());
182                 }
183             };
184 }
185