1 /*
2  * Copyright (C) 2019 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.controls.actions;
18 
19 import android.annotation.CallSuper;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.os.Bundle;
24 import android.service.controls.Control;
25 import android.service.controls.ControlsProviderService;
26 import android.service.controls.templates.ControlTemplate;
27 import android.util.Log;
28 
29 import com.android.internal.util.Preconditions;
30 
31 import java.lang.annotation.Retention;
32 import java.lang.annotation.RetentionPolicy;
33 
34 /**
35  * An abstract action indicating a user interaction with a {@link Control}.
36  *
37  * In some cases, an action needs to be validated by the user, using a password, PIN or simple
38  * acknowledgment. For those cases, an optional (nullable) parameter can be passed to send the user
39  * input. This <b>challenge value</b> will be requested from the user and sent as part
40  * of a {@link ControlAction} only if the service has responded to an action with one of:
41  * <ul>
42  *     <li> {@link #RESPONSE_CHALLENGE_ACK}
43  *     <li> {@link #RESPONSE_CHALLENGE_PIN}
44  *     <li> {@link #RESPONSE_CHALLENGE_PASSPHRASE}
45  * </ul>
46  */
47 public abstract class ControlAction {
48 
49     private static final String TAG = "ControlAction";
50 
51     private static final String KEY_ACTION_TYPE = "key_action_type";
52     private static final String KEY_TEMPLATE_ID = "key_template_id";
53     private static final String KEY_CHALLENGE_VALUE = "key_challenge_value";
54 
55     /**
56      * @hide
57      */
58     @Retention(RetentionPolicy.SOURCE)
59     @IntDef({
60             TYPE_ERROR,
61             TYPE_BOOLEAN,
62             TYPE_FLOAT,
63             TYPE_MODE,
64             TYPE_COMMAND
65     })
66     public @interface ActionType {};
67 
68     /**
69      * Object returned when there is an unparcelling error.
70      * @hide
71      */
72     public static final @NonNull ControlAction ERROR_ACTION = new ControlAction() {
73         @Override
74         public int getActionType() {
75             return TYPE_ERROR;
76         }
77     };
78 
79     /**
80      * The identifier of the action returned by {@link #getErrorAction}.
81      */
82     public static final @ActionType int TYPE_ERROR = -1;
83 
84     /**
85      * The identifier of {@link BooleanAction}.
86      */
87     public static final @ActionType int TYPE_BOOLEAN = 1;
88 
89     /**
90      * The identifier of {@link FloatAction}.
91      */
92     public static final @ActionType int TYPE_FLOAT = 2;
93 
94     /**
95      * The identifier of {@link ModeAction}.
96      */
97     public static final @ActionType int TYPE_MODE = 4;
98 
99     /**
100      * The identifier of {@link CommandAction}.
101      */
102     public static final @ActionType int TYPE_COMMAND = 5;
103 
104 
isValidResponse(@esponseResult int response)105     public static final boolean isValidResponse(@ResponseResult int response) {
106         return (response >= 0 && response < NUM_RESPONSE_TYPES);
107     }
108     private static final int NUM_RESPONSE_TYPES = 6;
109     /**
110      * @hide
111      */
112     @Retention(RetentionPolicy.SOURCE)
113     @IntDef({
114             RESPONSE_UNKNOWN,
115             RESPONSE_OK,
116             RESPONSE_FAIL,
117             RESPONSE_CHALLENGE_ACK,
118             RESPONSE_CHALLENGE_PIN,
119             RESPONSE_CHALLENGE_PASSPHRASE
120     })
121     public @interface ResponseResult {};
122 
123     public static final @ResponseResult int RESPONSE_UNKNOWN = 0;
124 
125     /**
126      * Response code for the {@code consumer} in
127      * {@link ControlsProviderService#performControlAction} indicating that the action has been
128      * performed. The action may still fail later and the state may not change.
129      */
130     public static final @ResponseResult int RESPONSE_OK = 1;
131     /**
132      * Response code for the {@code consumer} in
133      * {@link ControlsProviderService#performControlAction} indicating that the action has failed.
134      */
135     public static final @ResponseResult int RESPONSE_FAIL = 2;
136     /**
137      * Response code for the {@code consumer} in
138      * {@link ControlsProviderService#performControlAction} indicating that in order for the action
139      * to be performed, acknowledgment from the user is required. Any non-empty string returned
140      * from {@link #getChallengeValue} shall be treated as a positive acknowledgment.
141      */
142     public static final @ResponseResult int RESPONSE_CHALLENGE_ACK = 3;
143     /**
144      * Response code for the {@code consumer} in
145      * {@link ControlsProviderService#performControlAction} indicating that in order for the action
146      * to be performed, a PIN is required.
147      */
148     public static final @ResponseResult int RESPONSE_CHALLENGE_PIN = 4;
149     /**
150      * Response code for the {@code consumer} in
151      * {@link ControlsProviderService#performControlAction} indicating that in order for the action
152      * to be performed, an alphanumeric passphrase is required.
153      */
154     public static final @ResponseResult int RESPONSE_CHALLENGE_PASSPHRASE = 5;
155 
156     /**
157      * The {@link ActionType} associated with this class.
158      */
getActionType()159     public abstract @ActionType int getActionType();
160 
161     private final @NonNull String mTemplateId;
162     private final @Nullable String mChallengeValue;
163 
ControlAction()164     private ControlAction() {
165         mTemplateId = "";
166         mChallengeValue = null;
167     }
168 
169     /**
170      * @hide
171      */
ControlAction(@onNull String templateId, @Nullable String challengeValue)172     ControlAction(@NonNull String templateId, @Nullable String challengeValue) {
173         Preconditions.checkNotNull(templateId);
174         mTemplateId = templateId;
175         mChallengeValue = challengeValue;
176     }
177 
178     /**
179      * @hide
180      */
ControlAction(Bundle b)181     ControlAction(Bundle b) {
182         mTemplateId = b.getString(KEY_TEMPLATE_ID);
183         mChallengeValue = b.getString(KEY_CHALLENGE_VALUE);
184     }
185 
186     /**
187      * The identifier of the {@link ControlTemplate} that originated this action
188      */
189     @NonNull
getTemplateId()190     public String getTemplateId() {
191         return mTemplateId;
192     }
193 
194     /**
195      * The challenge value used to authenticate certain actions, if available.
196      */
197     @Nullable
getChallengeValue()198     public String getChallengeValue() {
199         return mChallengeValue;
200     }
201 
202     /**
203      * Obtain a {@link Bundle} describing this object populated with data.
204      *
205      * Implementations in subclasses should populate the {@link Bundle} returned by
206      * {@link ControlAction}.
207      * @return a {@link Bundle} containing the data that represents this object.
208      * @hide
209      */
210     @CallSuper
211     @NonNull
getDataBundle()212     Bundle getDataBundle() {
213         Bundle b = new Bundle();
214         b.putInt(KEY_ACTION_TYPE, getActionType());
215         b.putString(KEY_TEMPLATE_ID, mTemplateId);
216         b.putString(KEY_CHALLENGE_VALUE, mChallengeValue);
217         return b;
218     }
219 
220     /**
221      * @param bundle
222      * @return
223      * @hide
224      */
225     @NonNull
createActionFromBundle(@onNull Bundle bundle)226     static ControlAction createActionFromBundle(@NonNull Bundle bundle) {
227         if (bundle == null) {
228             Log.e(TAG, "Null bundle");
229             return ERROR_ACTION;
230         }
231         int type = bundle.getInt(KEY_ACTION_TYPE, TYPE_ERROR);
232         try {
233             switch (type) {
234                 case TYPE_BOOLEAN:
235                     return new BooleanAction(bundle);
236                 case TYPE_FLOAT:
237                     return new FloatAction(bundle);
238                 case TYPE_MODE:
239                     return new ModeAction(bundle);
240                 case TYPE_COMMAND:
241                     return new CommandAction(bundle);
242                 case TYPE_ERROR:
243                 default:
244                     return ERROR_ACTION;
245             }
246         } catch (Exception e) {
247             Log.e(TAG, "Error creating action", e);
248             return ERROR_ACTION;
249         }
250     }
251 
252     /**
253      * Returns a singleton {@link ControlAction} used for indicating an error in unparceling.
254      */
255     @NonNull
getErrorAction()256     public static ControlAction getErrorAction() {
257         return ERROR_ACTION;
258     }
259 }
260