/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.service.controls.actions; import android.annotation.CallSuper; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Bundle; import android.service.controls.Control; import android.service.controls.ControlsProviderService; import android.service.controls.templates.ControlTemplate; import android.util.Log; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * An abstract action indicating a user interaction with a {@link Control}. * * In some cases, an action needs to be validated by the user, using a password, PIN or simple * acknowledgment. For those cases, an optional (nullable) parameter can be passed to send the user * input. This challenge value will be requested from the user and sent as part * of a {@link ControlAction} only if the service has responded to an action with one of: * */ public abstract class ControlAction { private static final String TAG = "ControlAction"; private static final String KEY_ACTION_TYPE = "key_action_type"; private static final String KEY_TEMPLATE_ID = "key_template_id"; private static final String KEY_CHALLENGE_VALUE = "key_challenge_value"; /** * @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({ TYPE_ERROR, TYPE_BOOLEAN, TYPE_FLOAT, TYPE_MODE, TYPE_COMMAND }) public @interface ActionType {}; /** * Object returned when there is an unparcelling error. * @hide */ public static final @NonNull ControlAction ERROR_ACTION = new ControlAction() { @Override public int getActionType() { return TYPE_ERROR; } }; /** * The identifier of the action returned by {@link #getErrorAction}. */ public static final @ActionType int TYPE_ERROR = -1; /** * The identifier of {@link BooleanAction}. */ public static final @ActionType int TYPE_BOOLEAN = 1; /** * The identifier of {@link FloatAction}. */ public static final @ActionType int TYPE_FLOAT = 2; /** * The identifier of {@link ModeAction}. */ public static final @ActionType int TYPE_MODE = 4; /** * The identifier of {@link CommandAction}. */ public static final @ActionType int TYPE_COMMAND = 5; public static final boolean isValidResponse(@ResponseResult int response) { return (response >= 0 && response < NUM_RESPONSE_TYPES); } private static final int NUM_RESPONSE_TYPES = 6; /** * @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({ RESPONSE_UNKNOWN, RESPONSE_OK, RESPONSE_FAIL, RESPONSE_CHALLENGE_ACK, RESPONSE_CHALLENGE_PIN, RESPONSE_CHALLENGE_PASSPHRASE }) public @interface ResponseResult {}; public static final @ResponseResult int RESPONSE_UNKNOWN = 0; /** * Response code for the {@code consumer} in * {@link ControlsProviderService#performControlAction} indicating that the action has been * performed. The action may still fail later and the state may not change. */ public static final @ResponseResult int RESPONSE_OK = 1; /** * Response code for the {@code consumer} in * {@link ControlsProviderService#performControlAction} indicating that the action has failed. */ public static final @ResponseResult int RESPONSE_FAIL = 2; /** * Response code for the {@code consumer} in * {@link ControlsProviderService#performControlAction} indicating that in order for the action * to be performed, acknowledgment from the user is required. Any non-empty string returned * from {@link #getChallengeValue} shall be treated as a positive acknowledgment. */ public static final @ResponseResult int RESPONSE_CHALLENGE_ACK = 3; /** * Response code for the {@code consumer} in * {@link ControlsProviderService#performControlAction} indicating that in order for the action * to be performed, a PIN is required. */ public static final @ResponseResult int RESPONSE_CHALLENGE_PIN = 4; /** * Response code for the {@code consumer} in * {@link ControlsProviderService#performControlAction} indicating that in order for the action * to be performed, an alphanumeric passphrase is required. */ public static final @ResponseResult int RESPONSE_CHALLENGE_PASSPHRASE = 5; /** * The action type associated with this class. */ public abstract @ActionType int getActionType(); private final @NonNull String mTemplateId; private final @Nullable String mChallengeValue; private ControlAction() { mTemplateId = ""; mChallengeValue = null; } /** * @hide */ ControlAction(@NonNull String templateId, @Nullable String challengeValue) { Preconditions.checkNotNull(templateId); mTemplateId = templateId; mChallengeValue = challengeValue; } /** * @hide */ ControlAction(Bundle b) { mTemplateId = b.getString(KEY_TEMPLATE_ID); mChallengeValue = b.getString(KEY_CHALLENGE_VALUE); } /** * The identifier of the {@link ControlTemplate} that originated this action */ @NonNull public String getTemplateId() { return mTemplateId; } /** * The challenge value used to authenticate certain actions, if available. */ @Nullable public String getChallengeValue() { return mChallengeValue; } /** * Obtain a {@link Bundle} describing this object populated with data. * * Implementations in subclasses should populate the {@link Bundle} returned by * {@link ControlAction}. * @return a {@link Bundle} containing the data that represents this object. * @hide */ @CallSuper @NonNull Bundle getDataBundle() { Bundle b = new Bundle(); b.putInt(KEY_ACTION_TYPE, getActionType()); b.putString(KEY_TEMPLATE_ID, mTemplateId); b.putString(KEY_CHALLENGE_VALUE, mChallengeValue); return b; } /** * @param bundle * @return * @hide */ @NonNull static ControlAction createActionFromBundle(@NonNull Bundle bundle) { if (bundle == null) { Log.e(TAG, "Null bundle"); return ERROR_ACTION; } int type = bundle.getInt(KEY_ACTION_TYPE, TYPE_ERROR); try { switch (type) { case TYPE_BOOLEAN: return new BooleanAction(bundle); case TYPE_FLOAT: return new FloatAction(bundle); case TYPE_MODE: return new ModeAction(bundle); case TYPE_COMMAND: return new CommandAction(bundle); case TYPE_ERROR: default: return ERROR_ACTION; } } catch (Exception e) { Log.e(TAG, "Error creating action", e); return ERROR_ACTION; } } /** * Returns a singleton {@link ControlAction} used for indicating an error in unparceling. */ @NonNull public static ControlAction getErrorAction() { return ERROR_ACTION; } }