diff options
318 files changed, 22651 insertions, 3705 deletions
diff --git a/Android.bp b/Android.bp index dd8473744e..70e0574f3d 100644 --- a/Android.bp +++ b/Android.bp @@ -45,4 +45,5 @@ cc_defaults { "-g", ], + require_root: true, } diff --git a/CleanSpec.mk b/CleanSpec.mk index edde1cbd33..da2635e0d2 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -80,3 +80,4 @@ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib64/vndk-Q/android.hardwar $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib64/android.hardware.configstore@1.2.so) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/vndk-Q/android.hardware.configstore@1.2.so) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/android.hardware.configstore@1.2.so) +$(call add-clean-step, rm -f $(PRODUCT_OUT)/etc/init/android.hardware.audio@2.0-service.rc $(PRODUCT_OUT)/vendor/bin/hw/android.hardware.audio@2.0-service) diff --git a/audio/4.0/config/api/current.txt b/audio/4.0/config/api/current.txt index d59cade5ea..34625688b4 100644 --- a/audio/4.0/config/api/current.txt +++ b/audio/4.0/config/api/current.txt @@ -114,11 +114,15 @@ package audio.policy.configuration.V4_0 { enum_constant public static final audio.policy.configuration.V4_0.AudioFormat AUDIO_FORMAT_EVRCNW; enum_constant public static final audio.policy.configuration.V4_0.AudioFormat AUDIO_FORMAT_EVRCWB; enum_constant public static final audio.policy.configuration.V4_0.AudioFormat AUDIO_FORMAT_E_AC3; + enum_constant public static final audio.policy.configuration.V4_0.AudioFormat AUDIO_FORMAT_E_AC3_JOC; enum_constant public static final audio.policy.configuration.V4_0.AudioFormat AUDIO_FORMAT_FLAC; enum_constant public static final audio.policy.configuration.V4_0.AudioFormat AUDIO_FORMAT_HE_AAC_V1; enum_constant public static final audio.policy.configuration.V4_0.AudioFormat AUDIO_FORMAT_HE_AAC_V2; enum_constant public static final audio.policy.configuration.V4_0.AudioFormat AUDIO_FORMAT_IEC61937; enum_constant public static final audio.policy.configuration.V4_0.AudioFormat AUDIO_FORMAT_LDAC; + enum_constant public static final audio.policy.configuration.V4_0.AudioFormat AUDIO_FORMAT_MAT_1_0; + enum_constant public static final audio.policy.configuration.V4_0.AudioFormat AUDIO_FORMAT_MAT_2_0; + enum_constant public static final audio.policy.configuration.V4_0.AudioFormat AUDIO_FORMAT_MAT_2_1; enum_constant public static final audio.policy.configuration.V4_0.AudioFormat AUDIO_FORMAT_MP2; enum_constant public static final audio.policy.configuration.V4_0.AudioFormat AUDIO_FORMAT_MP3; enum_constant public static final audio.policy.configuration.V4_0.AudioFormat AUDIO_FORMAT_OPUS; diff --git a/audio/4.0/config/audio_policy_configuration.xsd b/audio/4.0/config/audio_policy_configuration.xsd index 58bab227f7..f26e41b423 100644 --- a/audio/4.0/config/audio_policy_configuration.xsd +++ b/audio/4.0/config/audio_policy_configuration.xsd @@ -357,6 +357,10 @@ <xs:enumeration value="AUDIO_FORMAT_APTX_HD"/> <xs:enumeration value="AUDIO_FORMAT_AC4"/> <xs:enumeration value="AUDIO_FORMAT_LDAC"/> + <xs:enumeration value="AUDIO_FORMAT_E_AC3_JOC"/> + <xs:enumeration value="AUDIO_FORMAT_MAT_1_0"/> + <xs:enumeration value="AUDIO_FORMAT_MAT_2_0"/> + <xs:enumeration value="AUDIO_FORMAT_MAT_2_1"/> </xs:restriction> </xs:simpleType> <xs:simpleType name="extendableAudioFormat"> diff --git a/audio/6.0/Android.bp b/audio/6.0/Android.bp new file mode 100644 index 0000000000..dc6bb98a53 --- /dev/null +++ b/audio/6.0/Android.bp @@ -0,0 +1,27 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.audio@6.0", + root: "android.hardware", + vndk: { + enabled: true, + }, + srcs: [ + "types.hal", + "IDevice.hal", + "IDevicesFactory.hal", + "IPrimaryDevice.hal", + "IStream.hal", + "IStreamIn.hal", + "IStreamOut.hal", + "IStreamOutCallback.hal", + ], + interfaces: [ + "android.hardware.audio.common@6.0", + "android.hardware.audio.effect@6.0", + "android.hidl.base@1.0", + "android.hidl.safe_union@1.0", + ], + gen_java: false, + gen_java_constants: true, +} diff --git a/audio/6.0/IDevice.hal b/audio/6.0/IDevice.hal new file mode 100644 index 0000000000..42a545b0be --- /dev/null +++ b/audio/6.0/IDevice.hal @@ -0,0 +1,282 @@ +/* + * 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.hardware.audio@6.0; + +import android.hardware.audio.common@6.0; +import IStreamIn; +import IStreamOut; + +interface IDevice { + /** + * Returns whether the audio hardware interface has been initialized. + * + * @return retval OK on success, NOT_INITIALIZED on failure. + */ + initCheck() generates (Result retval); + + /** + * Sets the audio volume for all audio activities other than voice call. If + * NOT_SUPPORTED is returned, the software mixer will emulate this + * capability. + * + * @param volume 1.0f means unity, 0.0f is zero. + * @return retval operation completion status. + */ + setMasterVolume(float volume) generates (Result retval); + + /** + * Get the current master volume value for the HAL, if the HAL supports + * master volume control. For example, AudioFlinger will query this value + * from the primary audio HAL when the service starts and use the value for + * setting the initial master volume across all HALs. HALs which do not + * support this method must return NOT_SUPPORTED in 'retval'. + * + * @return retval operation completion status. + * @return volume 1.0f means unity, 0.0f is zero. + */ + getMasterVolume() generates (Result retval, float volume); + + /** + * Sets microphone muting state. + * + * @param mute whether microphone is muted. + * @return retval operation completion status. + */ + setMicMute(bool mute) generates (Result retval); + + /** + * Gets whether microphone is muted. + * + * @return retval operation completion status. + * @return mute whether microphone is muted. + */ + getMicMute() generates (Result retval, bool mute); + + /** + * Set the audio mute status for all audio activities. If the return value + * is NOT_SUPPORTED, the software mixer will emulate this capability. + * + * @param mute whether audio is muted. + * @return retval operation completion status. + */ + setMasterMute(bool mute) generates (Result retval); + + /** + * Get the current master mute status for the HAL, if the HAL supports + * master mute control. AudioFlinger will query this value from the primary + * audio HAL when the service starts and use the value for setting the + * initial master mute across all HALs. HAL must indicate that the feature + * is not supported by returning NOT_SUPPORTED status. + * + * @return retval operation completion status. + * @return mute whether audio is muted. + */ + getMasterMute() generates (Result retval, bool mute); + + /** + * Returns audio input buffer size according to parameters passed or + * INVALID_ARGUMENTS if one of the parameters is not supported. + * + * @param config audio configuration. + * @return retval operation completion status. + * @return bufferSize input buffer size in bytes. + */ + getInputBufferSize(AudioConfig config) + generates (Result retval, uint64_t bufferSize); + + /** + * This method creates and opens the audio hardware output stream. + * If the stream can not be opened with the proposed audio config, + * HAL must provide suggested values for the audio config. + * + * @param ioHandle handle assigned by AudioFlinger. + * @param device device type and (if needed) address. + * @param config stream configuration. + * @param flags additional flags. + * @param sourceMetadata Description of the audio that will be played. + May be used by implementations to configure hardware effects. + * @return retval operation completion status. + * @return outStream created output stream. + * @return suggestedConfig in case of invalid parameters, suggested config. + */ + openOutputStream( + AudioIoHandle ioHandle, + DeviceAddress device, + AudioConfig config, + bitfield<AudioOutputFlag> flags, + SourceMetadata sourceMetadata) generates ( + Result retval, + IStreamOut outStream, + AudioConfig suggestedConfig); + + /** + * This method creates and opens the audio hardware input stream. + * If the stream can not be opened with the proposed audio config, + * HAL must provide suggested values for the audio config. + * + * @param ioHandle handle assigned by AudioFlinger. + * @param device device type and (if needed) address. + * @param config stream configuration. + * @param flags additional flags. + * @param sinkMetadata Description of the audio that is suggested by the client. + * May be used by implementations to configure processing effects. + * @return retval operation completion status. + * @return inStream in case of success, created input stream. + * @return suggestedConfig in case of invalid parameters, suggested config. + */ + openInputStream( + AudioIoHandle ioHandle, + DeviceAddress device, + AudioConfig config, + bitfield<AudioInputFlag> flags, + SinkMetadata sinkMetadata) generates ( + Result retval, + IStreamIn inStream, + AudioConfig suggestedConfig); + + /** + * Returns whether HAL supports audio patches. + * + * @return supports true if audio patches are supported. + */ + supportsAudioPatches() generates (bool supports); + + /** + * Creates an audio patch between several source and sink ports. The handle + * is allocated by the HAL and must be unique for this audio HAL module. + * + * @param sources patch sources. + * @param sinks patch sinks. + * @return retval operation completion status. + * @return patch created patch handle. + */ + createAudioPatch(vec<AudioPortConfig> sources, vec<AudioPortConfig> sinks) + generates (Result retval, AudioPatchHandle patch); + + /** + * Release an audio patch. + * + * @param patch patch handle. + * @return retval operation completion status. + */ + releaseAudioPatch(AudioPatchHandle patch) generates (Result retval); + + /** + * Returns the list of supported attributes for a given audio port. + * + * As input, 'port' contains the information (type, role, address etc...) + * needed by the HAL to identify the port. + * + * As output, 'resultPort' contains possible attributes (sampling rates, + * formats, channel masks, gain controllers...) for this port. + * + * @param port port identifier. + * @return retval operation completion status. + * @return resultPort port descriptor with all parameters filled up. + */ + getAudioPort(AudioPort port) + generates (Result retval, AudioPort resultPort); + + /** + * Set audio port configuration. + * + * @param config audio port configuration. + * @return retval operation completion status. + */ + setAudioPortConfig(AudioPortConfig config) generates (Result retval); + + /** + * Gets the HW synchronization source of the device. Calling this method is + * equivalent to getting AUDIO_PARAMETER_HW_AV_SYNC on the legacy HAL. + * Optional method + * + * @return retval operation completion status: OK or NOT_SUPPORTED. + * @return hwAvSync HW synchronization source + */ + getHwAvSync() generates (Result retval, AudioHwSync hwAvSync); + + /** + * Sets whether the screen is on. Calling this method is equivalent to + * setting AUDIO_PARAMETER_KEY_SCREEN_STATE on the legacy HAL. + * Optional method + * + * @param turnedOn whether the screen is turned on. + * @return retval operation completion status. + */ + setScreenState(bool turnedOn) generates (Result retval); + + /** + * Generic method for retrieving vendor-specific parameter values. + * The framework does not interpret the parameters, they are passed + * in an opaque manner between a vendor application and HAL. + * + * Multiple parameters can be retrieved at the same time. + * The implementation should return as many requested parameters + * as possible, even if one or more is not supported + * + * @param context provides more information about the request + * @param keys keys of the requested parameters + * @return retval operation completion status. + * OK must be returned if keys is empty. + * NOT_SUPPORTED must be returned if at least one key is unknown. + * @return parameters parameter key value pairs. + * Must contain the value of all requested keys if retval == OK + */ + getParameters(vec<ParameterValue> context, vec<string> keys) + generates (Result retval, vec<ParameterValue> parameters); + + /** + * Generic method for setting vendor-specific parameter values. + * The framework does not interpret the parameters, they are passed + * in an opaque manner between a vendor application and HAL. + * + * Multiple parameters can be set at the same time though this is + * discouraged as it make failure analysis harder. + * + * If possible, a failed setParameters should not impact the platform state. + * + * @param context provides more information about the request + * @param parameters parameter key value pairs. + * @return retval operation completion status. + * All parameters must be successfully set for OK to be returned + */ + setParameters(vec<ParameterValue> context, vec<ParameterValue> parameters) + generates (Result retval); + + /** + * Returns an array with available microphones in device. + * + * @return retval INVALID_STATE if the call is not successful, + * OK otherwise. + * + * @return microphones array with microphones info + */ + getMicrophones() + generates(Result retval, vec<MicrophoneInfo> microphones); + + /** + * Notifies the device module about the connection state of an input/output + * device attached to it. Calling this method is equivalent to setting + * AUDIO_PARAMETER_DEVICE_[DIS]CONNECT on the legacy HAL. + * + * @param address audio device specification. + * @param connected whether the device is connected. + * @return retval operation completion status. + */ + setConnectedState(DeviceAddress address, bool connected) + generates (Result retval); +}; diff --git a/audio/6.0/IDevicesFactory.hal b/audio/6.0/IDevicesFactory.hal new file mode 100644 index 0000000000..04834734f1 --- /dev/null +++ b/audio/6.0/IDevicesFactory.hal @@ -0,0 +1,70 @@ +/* + * 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.hardware.audio@6.0; + +import android.hardware.audio.common@6.0; +import IDevice; +import IPrimaryDevice; + +/** This factory allows a HAL implementation to be split in multiple independent + * devices (called module in the pre-treble API). + * Note that this division is arbitrary and implementation are free + * to only have a Primary. + * The framework will query the devices according to audio_policy_configuration.xml + * + * Each device name is arbitrary, provided by the vendor's audio_policy_configuration.xml + * and only used to identify a device in this factory. + * The framework must not interpret the name, treating it as a vendor opaque data + * with the following exception: + * - the "r_submix" device that must be present to support policyMixes (Eg: Android projected). + * Note that this Device is included by default in a build derived from AOSP. + * + * Note that on AOSP Oreo (including MR1) the "a2dp" module is not using this API + * but is loaded directly from the system partition using the legacy API + * due to limitations with the Bluetooth framework. + */ +interface IDevicesFactory { + + /** + * Opens an audio device. To close the device, it is necessary to release + * references to the returned device object. + * + * @param device device name. + * @return retval operation completion status. Returns INVALID_ARGUMENTS + * if there is no corresponding hardware module found, + * NOT_INITIALIZED if an error occurred while opening the hardware + * module. + * @return result the interface for the created device. + */ + openDevice(string device) generates (Result retval, IDevice result); + + /** + * Opens the Primary audio device that must be present. + * This function is not optional and must return successfully the primary device. + * + * This device must have the name "primary". + * + * The telephony stack uses this device to control the audio during a voice call. + * + * @return retval operation completion status. Must be SUCCESS. + * For debugging, return INVALID_ARGUMENTS if there is no corresponding + * hardware module found, NOT_INITIALIZED if an error occurred + * while opening the hardware module. + * @return result the interface for the created device. + */ + openPrimaryDevice() generates (Result retval, IPrimaryDevice result); +}; diff --git a/audio/6.0/IPrimaryDevice.hal b/audio/6.0/IPrimaryDevice.hal new file mode 100644 index 0000000000..78cfc650b4 --- /dev/null +++ b/audio/6.0/IPrimaryDevice.hal @@ -0,0 +1,195 @@ +/* + * 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.hardware.audio@6.0; + +import android.hardware.audio.common@6.0; +import IDevice; + +interface IPrimaryDevice extends IDevice { + /** + * Sets the audio volume of a voice call. + * + * @param volume 1.0f means unity, 0.0f is zero. + * @return retval operation completion status. + */ + setVoiceVolume(float volume) generates (Result retval); + + /** + * This method is used to notify the HAL about audio mode changes. + * + * @param mode new mode. + * @return retval operation completion status. + */ + setMode(AudioMode mode) generates (Result retval); + + /** + * Sets the name of the current BT SCO headset. Calling this method + * is equivalent to setting legacy "bt_headset_name" parameter. + * The BT SCO headset name must only be used for debugging purposes. + * Optional method + * + * @param name the name of the current BT SCO headset (can be empty). + * @return retval operation completion status. + */ + setBtScoHeadsetDebugName(string name) generates (Result retval); + + /** + * Gets whether BT SCO Noise Reduction and Echo Cancellation are enabled. + * Calling this method is equivalent to getting AUDIO_PARAMETER_KEY_BT_NREC + * on the legacy HAL. + * + * @return retval operation completion status. + * @return enabled whether BT SCO NR + EC are enabled. + */ + getBtScoNrecEnabled() generates (Result retval, bool enabled); + + /** + * Sets whether BT SCO Noise Reduction and Echo Cancellation are enabled. + * Calling this method is equivalent to setting AUDIO_PARAMETER_KEY_BT_NREC + * on the legacy HAL. + * Optional method + * + * @param enabled whether BT SCO NR + EC are enabled. + * @return retval operation completion status. + */ + setBtScoNrecEnabled(bool enabled) generates (Result retval); + + /** + * Gets whether BT SCO Wideband mode is enabled. Calling this method is + * equivalent to getting AUDIO_PARAMETER_KEY_BT_SCO_WB on the legacy HAL. + * + * @return retval operation completion status. + * @return enabled whether BT Wideband is enabled. + */ + getBtScoWidebandEnabled() generates (Result retval, bool enabled); + + /** + * Sets whether BT SCO Wideband mode is enabled. Calling this method is + * equivalent to setting AUDIO_PARAMETER_KEY_BT_SCO_WB on the legacy HAL. + * Optional method + * + * @param enabled whether BT Wideband is enabled. + * @return retval operation completion status. + */ + setBtScoWidebandEnabled(bool enabled) generates (Result retval); + + /** + * Gets whether BT HFP (Hands-Free Profile) is enabled. Calling this method + * is equivalent to getting "hfp_enable" parameter value on the legacy HAL. + * + * @return retval operation completion status. + * @return enabled whether BT HFP is enabled. + */ + getBtHfpEnabled() generates (Result retval, bool enabled); + + /** + * Sets whether BT HFP (Hands-Free Profile) is enabled. Calling this method + * is equivalent to setting "hfp_enable" parameter on the legacy HAL. + * Optional method + * + * @param enabled whether BT HFP is enabled. + * @return retval operation completion status. + */ + setBtHfpEnabled(bool enabled) generates (Result retval); + + /** + * Sets the sampling rate of BT HFP (Hands-Free Profile). Calling this + * method is equivalent to setting "hfp_set_sampling_rate" parameter + * on the legacy HAL. + * Optional method + * + * @param sampleRateHz sample rate in Hz. + * @return retval operation completion status. + */ + setBtHfpSampleRate(uint32_t sampleRateHz) generates (Result retval); + + /** + * Sets the current output volume Hz for BT HFP (Hands-Free Profile). + * Calling this method is equivalent to setting "hfp_volume" parameter value + * on the legacy HAL (except that legacy HAL implementations expect + * an integer value in the range from 0 to 15.) + * Optional method + * + * @param volume 1.0f means unity, 0.0f is zero. + * @return retval operation completion status. + */ + setBtHfpVolume(float volume) generates (Result retval); + + enum TtyMode : int32_t { + OFF, + VCO, + HCO, + FULL + }; + + /** + * Gets current TTY mode selection. Calling this method is equivalent to + * getting AUDIO_PARAMETER_KEY_TTY_MODE on the legacy HAL. + * + * @return retval operation completion status. + * @return mode TTY mode. + */ + getTtyMode() generates (Result retval, TtyMode mode); + + /** + * Sets current TTY mode. Calling this method is equivalent to setting + * AUDIO_PARAMETER_KEY_TTY_MODE on the legacy HAL. + * + * @param mode TTY mode. + * @return retval operation completion status. + */ + setTtyMode(TtyMode mode) generates (Result retval); + + /** + * Gets whether Hearing Aid Compatibility - Telecoil (HAC-T) mode is + * enabled. Calling this method is equivalent to getting + * AUDIO_PARAMETER_KEY_HAC on the legacy HAL. + * + * @return retval operation completion status. + * @return enabled whether HAC mode is enabled. + */ + getHacEnabled() generates (Result retval, bool enabled); + + /** + * Sets whether Hearing Aid Compatibility - Telecoil (HAC-T) mode is + * enabled. Calling this method is equivalent to setting + * AUDIO_PARAMETER_KEY_HAC on the legacy HAL. + * Optional method + * + * @param enabled whether HAC mode is enabled. + * @return retval operation completion status. + */ + setHacEnabled(bool enabled) generates (Result retval); + + enum Rotation : int32_t { + DEG_0, + DEG_90, + DEG_180, + DEG_270 + }; + + /** + * Updates HAL on the current rotation of the device relative to natural + * orientation. Calling this method is equivalent to setting legacy + * parameter "rotation". + * + * @param rotation rotation in degrees relative to natural device + * orientation. + * @return retval operation completion status. + */ + updateRotation(Rotation rotation) generates (Result retval); +}; diff --git a/audio/6.0/IStream.hal b/audio/6.0/IStream.hal new file mode 100644 index 0000000000..f4c91f826d --- /dev/null +++ b/audio/6.0/IStream.hal @@ -0,0 +1,310 @@ +/* + * 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.hardware.audio@6.0; + +import android.hardware.audio.common@6.0; +import android.hardware.audio.effect@6.0::IEffect; + +interface IStream { + /** + * Return the frame size (number of bytes per sample). + * + * @return frameSize frame size in bytes. + */ + getFrameSize() generates (uint64_t frameSize); + + /** + * Return the frame count of the buffer. Calling this method is equivalent + * to getting AUDIO_PARAMETER_STREAM_FRAME_COUNT on the legacy HAL. + * + * @return count frame count. + */ + getFrameCount() generates (uint64_t count); + + /** + * Return the size of input/output buffer in bytes for this stream. + * It must be a multiple of the frame size. + * + * @return buffer buffer size in bytes. + */ + getBufferSize() generates (uint64_t bufferSize); + + /** + * Return the sampling rate in Hz. + * + * @return sampleRateHz sample rate in Hz. + */ + getSampleRate() generates (uint32_t sampleRateHz); + + /** + * Return supported native sampling rates of the stream for a given format. + * A supported native sample rate is a sample rate that can be efficiently + * played by the hardware (typically without sample-rate conversions). + * + * This function is only called for dynamic profile. If called for + * non-dynamic profile is should return NOT_SUPPORTED or the same list + * as in audio_policy_configuration.xml. + * + * Calling this method is equivalent to getting + * AUDIO_PARAMETER_STREAM_SUP_SAMPLING_RATES on the legacy HAL. + * + * + * @param format audio format for which the sample rates are supported. + * @return retval operation completion status. + * Must be OK if the format is supported. + * @return sampleRateHz supported sample rates. + */ + getSupportedSampleRates(AudioFormat format) + generates (Result retval, vec<uint32_t> sampleRates); + + /** + * Sets the sampling rate of the stream. Calling this method is equivalent + * to setting AUDIO_PARAMETER_STREAM_SAMPLING_RATE on the legacy HAL. + * Optional method. If implemented, only called on a stopped stream. + * + * @param sampleRateHz sample rate in Hz. + * @return retval operation completion status. + */ + setSampleRate(uint32_t sampleRateHz) generates (Result retval); + + /** + * Return the channel mask of the stream. + * + * @return mask channel mask. + */ + getChannelMask() generates (bitfield<AudioChannelMask> mask); + + /** + * Return supported channel masks of the stream. Calling this method is + * equivalent to getting AUDIO_PARAMETER_STREAM_SUP_CHANNELS on the legacy + * HAL. + * + * @param format audio format for which the channel masks are supported. + * @return retval operation completion status. + * Must be OK if the format is supported. + * @return masks supported audio masks. + */ + getSupportedChannelMasks(AudioFormat format) + generates (Result retval, vec<bitfield<AudioChannelMask>> masks); + + /** + * Sets the channel mask of the stream. Calling this method is equivalent to + * setting AUDIO_PARAMETER_STREAM_CHANNELS on the legacy HAL. + * Optional method + * + * @param format audio format. + * @return retval operation completion status. + */ + setChannelMask(bitfield<AudioChannelMask> mask) generates (Result retval); + + /** + * Return the audio format of the stream. + * + * @return format audio format. + */ + getFormat() generates (AudioFormat format); + + /** + * Return supported audio formats of the stream. Calling this method is + * equivalent to getting AUDIO_PARAMETER_STREAM_SUP_FORMATS on the legacy + * HAL. + * + * @return formats supported audio formats. + */ + getSupportedFormats() generates (vec<AudioFormat> formats); + + /** + * Sets the audio format of the stream. Calling this method is equivalent to + * setting AUDIO_PARAMETER_STREAM_FORMAT on the legacy HAL. + * Optional method + * + * @param format audio format. + * @return retval operation completion status. + */ + setFormat(AudioFormat format) generates (Result retval); + + /** + * Convenience method for retrieving several stream parameters in + * one transaction. + * + * @return sampleRateHz sample rate in Hz. + * @return mask channel mask. + * @return format audio format. + */ + getAudioProperties() generates ( + uint32_t sampleRateHz, bitfield<AudioChannelMask> mask, AudioFormat format); + + /** + * Applies audio effect to the stream. + * + * @param effectId effect ID (obtained from IEffectsFactory.createEffect) of + * the effect to apply. + * @return retval operation completion status. + */ + addEffect(uint64_t effectId) generates (Result retval); + + /** + * Stops application of the effect to the stream. + * + * @param effectId effect ID (obtained from IEffectsFactory.createEffect) of + * the effect to remove. + * @return retval operation completion status. + */ + removeEffect(uint64_t effectId) generates (Result retval); + + /** + * Put the audio hardware input/output into standby mode. + * Driver must exit from standby mode at the next I/O operation. + * + * @return retval operation completion status. + */ + standby() generates (Result retval); + + /** + * Return the set of devices which this stream is connected to. + * Optional method + * + * @return retval operation completion status: OK or NOT_SUPPORTED. + * @return device set of devices which this stream is connected to. + */ + getDevices() generates (Result retval, vec<DeviceAddress> devices); + + /** + * Connects the stream to one or multiple devices. + * + * This method must only be used for HALs that do not support + * 'IDevice.createAudioPatch' method. Calling this method is + * equivalent to setting AUDIO_PARAMETER_STREAM_ROUTING preceded + * with a device address in the legacy HAL interface. + * + * @param address device to connect the stream to. + * @return retval operation completion status. + */ + setDevices(vec<DeviceAddress> devices) generates (Result retval); + + /** + * Sets the HW synchronization source. Calling this method is equivalent to + * setting AUDIO_PARAMETER_STREAM_HW_AV_SYNC on the legacy HAL. + * Optional method + * + * @param hwAvSync HW synchronization source + * @return retval operation completion status. + */ + setHwAvSync(AudioHwSync hwAvSync) generates (Result retval); + + /** + * Generic method for retrieving vendor-specific parameter values. + * The framework does not interpret the parameters, they are passed + * in an opaque manner between a vendor application and HAL. + * + * Multiple parameters can be retrieved at the same time. + * The implementation should return as many requested parameters + * as possible, even if one or more is not supported + * + * @param context provides more information about the request + * @param keys keys of the requested parameters + * @return retval operation completion status. + * OK must be returned if keys is empty. + * NOT_SUPPORTED must be returned if at least one key is unknown. + * @return parameters parameter key value pairs. + * Must contain the value of all requested keys if retval == OK + */ + getParameters(vec<ParameterValue> context, vec<string> keys) + generates (Result retval, vec<ParameterValue> parameters); + + /** + * Generic method for setting vendor-specific parameter values. + * The framework does not interpret the parameters, they are passed + * in an opaque manner between a vendor application and HAL. + * + * Multiple parameters can be set at the same time though this is + * discouraged as it make failure analysis harder. + * + * If possible, a failed setParameters should not impact the platform state. + * + * @param context provides more information about the request + * @param parameters parameter key value pairs. + * @return retval operation completion status. + * All parameters must be successfully set for OK to be returned + */ + setParameters(vec<ParameterValue> context, vec<ParameterValue> parameters) + generates (Result retval); + + /** + * Called by the framework to start a stream operating in mmap mode. + * createMmapBuffer() must be called before calling start(). + * Function only implemented by streams operating in mmap mode. + * + * @return retval OK in case the success. + * NOT_SUPPORTED on non mmap mode streams + * INVALID_STATE if called out of sequence + */ + start() generates (Result retval); + + /** + * Called by the framework to stop a stream operating in mmap mode. + * Function only implemented by streams operating in mmap mode. + * + * @return retval OK in case the success. + * NOT_SUPPORTED on non mmap mode streams + * INVALID_STATE if called out of sequence + */ + stop() generates (Result retval) ; + + /** + * Called by the framework to retrieve information on the mmap buffer used for audio + * samples transfer. + * Function only implemented by streams operating in mmap mode. + * + * @param minSizeFrames minimum buffer size requested. The actual buffer + * size returned in struct MmapBufferInfo can be larger. + * @return retval OK in case the success. + * NOT_SUPPORTED on non mmap mode streams + * NOT_INITIALIZED in case of memory allocation error + * INVALID_ARGUMENTS if the requested buffer size is too large + * INVALID_STATE if called out of sequence + * @return info a MmapBufferInfo struct containing information on the MMMAP buffer created. + */ + createMmapBuffer(int32_t minSizeFrames) + generates (Result retval, MmapBufferInfo info); + + /** + * Called by the framework to read current read/write position in the mmap buffer + * with associated time stamp. + * Function only implemented by streams operating in mmap mode. + * + * @return retval OK in case the success. + * NOT_SUPPORTED on non mmap mode streams + * INVALID_STATE if called out of sequence + * @return position a MmapPosition struct containing current HW read/write position in frames + * with associated time stamp. + */ + getMmapPosition() + generates (Result retval, MmapPosition position); + + /** + * Called by the framework to deinitialize the stream and free up + * all the currently allocated resources. It is recommended to close + * the stream on the client side as soon as it is becomes unused. + * + * @return retval OK in case the success. + * NOT_SUPPORTED if called on IStream instead of input or + * output stream interface. + * INVALID_STATE if the stream was already closed. + */ + close() generates (Result retval); +}; diff --git a/audio/6.0/IStreamIn.hal b/audio/6.0/IStreamIn.hal new file mode 100644 index 0000000000..aadc3700d5 --- /dev/null +++ b/audio/6.0/IStreamIn.hal @@ -0,0 +1,199 @@ +/* + * 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.hardware.audio@6.0; + +import android.hardware.audio.common@6.0; +import IStream; + +interface IStreamIn extends IStream { + /** + * Returns the source descriptor of the input stream. Calling this method is + * equivalent to getting AUDIO_PARAMETER_STREAM_INPUT_SOURCE on the legacy + * HAL. + * Optional method + * + * @return retval operation completion status. + * @return source audio source. + */ + getAudioSource() generates (Result retval, AudioSource source); + + /** + * Set the input gain for the audio driver. + * Optional method + * + * @param gain 1.0f is unity, 0.0f is zero. + * @result retval operation completion status. + */ + setGain(float gain) generates (Result retval); + + /** + * Commands that can be executed on the driver reader thread. + */ + enum ReadCommand : int32_t { + READ, + GET_CAPTURE_POSITION + }; + + /** + * Data structure passed to the driver for executing commands + * on the driver reader thread. + */ + struct ReadParameters { + ReadCommand command; // discriminator + union Params { + uint64_t read; // READ command, amount of bytes to read, >= 0. + // No parameters for GET_CAPTURE_POSITION. + } params; + }; + + /** + * Data structure passed back to the client via status message queue + * of 'read' operation. + * + * Possible values of 'retval' field: + * - OK, read operation was successful; + * - INVALID_ARGUMENTS, stream was not configured properly; + * - INVALID_STATE, stream is in a state that doesn't allow reads. + */ + struct ReadStatus { + Result retval; + ReadCommand replyTo; // discriminator + union Reply { + uint64_t read; // READ command, amount of bytes read, >= 0. + struct CapturePosition { // same as generated by getCapturePosition. + uint64_t frames; + uint64_t time; + } capturePosition; + } reply; + }; + + /** + * Called when the metadata of the stream's sink has been changed. + * @param sinkMetadata Description of the audio that is suggested by the clients. + */ + updateSinkMetadata(SinkMetadata sinkMetadata); + + /** + * Set up required transports for receiving audio buffers from the driver. + * + * The transport consists of three message queues: + * -- command queue is used to instruct the reader thread what operation + * to perform; + * -- data queue is used for passing audio data from the driver + * to the client; + * -- status queue is used for reporting operation status + * (e.g. amount of bytes actually read or error code). + * + * The driver operates on a dedicated thread. The client must ensure that + * the thread is given an appropriate priority and assigned to correct + * scheduler and cgroup. For this purpose, the method returns identifiers + * of the driver thread. + * + * @param frameSize the size of a single frame, in bytes. + * @param framesCount the number of frames in a buffer. + * @param threadPriority priority of the driver thread. + * @return retval OK if both message queues were created successfully. + * INVALID_STATE if the method was already called. + * INVALID_ARGUMENTS if there was a problem setting up + * the queues. + * @return commandMQ a message queue used for passing commands. + * @return dataMQ a message queue used for passing audio data in the format + * specified at the stream opening. + * @return statusMQ a message queue used for passing status from the driver + * using ReadStatus structures. + * @return threadInfo identifiers of the driver's dedicated thread. + */ + prepareForReading(uint32_t frameSize, uint32_t framesCount) + generates ( + Result retval, + fmq_sync<ReadParameters> commandMQ, + fmq_sync<uint8_t> dataMQ, + fmq_sync<ReadStatus> statusMQ, + ThreadInfo threadInfo); + + /** + * Return the amount of input frames lost in the audio driver since the last + * call of this function. + * + * Audio driver is expected to reset the value to 0 and restart counting + * upon returning the current value by this function call. Such loss + * typically occurs when the user space process is blocked longer than the + * capacity of audio driver buffers. + * + * @return framesLost the number of input audio frames lost. + */ + getInputFramesLost() generates (uint32_t framesLost); + + /** + * Return a recent count of the number of audio frames received and the + * clock time associated with that frame count. + * + * @return retval INVALID_STATE if the device is not ready/available, + * NOT_SUPPORTED if the command is not supported, + * OK otherwise. + * @return frames the total frame count received. This must be as early in + * the capture pipeline as possible. In general, frames + * must be non-negative and must not go "backwards". + * @return time is the clock monotonic time when frames was measured. In + * general, time must be a positive quantity and must not + * go "backwards". + */ + getCapturePosition() + generates (Result retval, uint64_t frames, uint64_t time); + + /** + * Returns an array with active microphones in the stream. + * + * @return retval INVALID_STATE if the call is not successful, + * OK otherwise. + * + * @return microphones array with microphones info + */ + getActiveMicrophones() + generates(Result retval, vec<MicrophoneInfo> microphones); + + /** + * Specifies the logical microphone (for processing). + * + * If the feature is not supported an error should be returned + * If multiple microphones are present, this should be treated as a preference + * for their combined direction. + * + * Optional method + * + * @param Direction constant + * @return retval OK if the call is successful, an error code otherwise. + */ + setMicrophoneDirection(MicrophoneDirection direction) + generates(Result retval); + + /** + * Specifies the zoom factor for the selected microphone (for processing). + * + * If the feature is not supported an error should be returned + * If multiple microphones are present, this should be treated as a preference + * for their combined field dimension. + * + * Optional method + * + * @param the desired field dimension of microphone capture. Range is from -1 (wide angle), + * though 0 (no zoom) to 1 (maximum zoom). + * + * @return retval OK if the call is not successful, an error code otherwise. + */ + setMicrophoneFieldDimension(float zoom) generates(Result retval); +}; diff --git a/audio/6.0/IStreamOut.hal b/audio/6.0/IStreamOut.hal new file mode 100644 index 0000000000..941ba61006 --- /dev/null +++ b/audio/6.0/IStreamOut.hal @@ -0,0 +1,279 @@ +/* + * 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.hardware.audio@6.0; + +import android.hardware.audio.common@6.0; +import IStream; +import IStreamOutCallback; + +interface IStreamOut extends IStream { + /** + * Return the audio hardware driver estimated latency in milliseconds. + * + * @return latencyMs latency in milliseconds. + */ + getLatency() generates (uint32_t latencyMs); + + /** + * This method is used in situations where audio mixing is done in the + * hardware. This method serves as a direct interface with hardware, + * allowing to directly set the volume as apposed to via the framework. + * This method might produce multiple PCM outputs or hardware accelerated + * codecs, such as MP3 or AAC. + * Optional method + * + * @param left left channel attenuation, 1.0f is unity, 0.0f is zero. + * @param right right channel attenuation, 1.0f is unity, 0.0f is zero. + * @return retval operation completion status. + * If a volume is outside [0,1], return INVALID_ARGUMENTS + */ + setVolume(float left, float right) generates (Result retval); + + /** + * Commands that can be executed on the driver writer thread. + */ + enum WriteCommand : int32_t { + WRITE, + GET_PRESENTATION_POSITION, + GET_LATENCY + }; + + /** + * Data structure passed back to the client via status message queue + * of 'write' operation. + * + * Possible values of 'retval' field: + * - OK, write operation was successful; + * - INVALID_ARGUMENTS, stream was not configured properly; + * - INVALID_STATE, stream is in a state that doesn't allow writes; + * - INVALID_OPERATION, retrieving presentation position isn't supported. + */ + struct WriteStatus { + Result retval; + WriteCommand replyTo; // discriminator + union Reply { + uint64_t written; // WRITE command, amount of bytes written, >= 0. + struct PresentationPosition { // same as generated by + uint64_t frames; // getPresentationPosition. + TimeSpec timeStamp; + } presentationPosition; + uint32_t latencyMs; // Same as generated by getLatency. + } reply; + }; + + /** + * Called when the metadata of the stream's source has been changed. + * @param sourceMetadata Description of the audio that is played by the clients. + */ + updateSourceMetadata(SourceMetadata sourceMetadata); + + /** + * Set up required transports for passing audio buffers to the driver. + * + * The transport consists of three message queues: + * -- command queue is used to instruct the writer thread what operation + * to perform; + * -- data queue is used for passing audio data from the client + * to the driver; + * -- status queue is used for reporting operation status + * (e.g. amount of bytes actually written or error code). + * + * The driver operates on a dedicated thread. The client must ensure that + * the thread is given an appropriate priority and assigned to correct + * scheduler and cgroup. For this purpose, the method returns identifiers + * of the driver thread. + * + * @param frameSize the size of a single frame, in bytes. + * @param framesCount the number of frames in a buffer. + * @return retval OK if both message queues were created successfully. + * INVALID_STATE if the method was already called. + * INVALID_ARGUMENTS if there was a problem setting up + * the queues. + * @return commandMQ a message queue used for passing commands. + * @return dataMQ a message queue used for passing audio data in the format + * specified at the stream opening. + * @return statusMQ a message queue used for passing status from the driver + * using WriteStatus structures. + * @return threadInfo identifiers of the driver's dedicated thread. + */ + prepareForWriting(uint32_t frameSize, uint32_t framesCount) + generates ( + Result retval, + fmq_sync<WriteCommand> commandMQ, + fmq_sync<uint8_t> dataMQ, + fmq_sync<WriteStatus> statusMQ, + ThreadInfo threadInfo); + + /** + * Return the number of audio frames written by the audio DSP to DAC since + * the output has exited standby. + * Optional method + * + * @return retval operation completion status. + * @return dspFrames number of audio frames written. + */ + getRenderPosition() generates (Result retval, uint32_t dspFrames); + + /** + * Get the local time at which the next write to the audio driver will be + * presented. The units are microseconds, where the epoch is decided by the + * local audio HAL. + * Optional method + * + * @return retval operation completion status. + * @return timestampUs time of the next write. + */ + getNextWriteTimestamp() generates (Result retval, int64_t timestampUs); + + /** + * Set the callback interface for notifying completion of non-blocking + * write and drain. + * + * Calling this function implies that all future 'write' and 'drain' + * must be non-blocking and use the callback to signal completion. + * + * 'clearCallback' method needs to be called in order to release the local + * callback proxy on the server side and thus dereference the callback + * implementation on the client side. + * + * @return retval operation completion status. + */ + setCallback(IStreamOutCallback callback) generates (Result retval); + + /** + * Clears the callback previously set via 'setCallback' method. + * + * Warning: failure to call this method results in callback implementation + * on the client side being held until the HAL server termination. + * + * If no callback was previously set, the method should be a no-op + * and return OK. + * + * @return retval operation completion status: OK or NOT_SUPPORTED. + */ + clearCallback() generates (Result retval); + + /** + * Returns whether HAL supports pausing and resuming of streams. + * + * @return supportsPause true if pausing is supported. + * @return supportsResume true if resume is supported. + */ + supportsPauseAndResume() + generates (bool supportsPause, bool supportsResume); + + /** + * Notifies to the audio driver to stop playback however the queued buffers + * are retained by the hardware. Useful for implementing pause/resume. Empty + * implementation if not supported however must be implemented for hardware + * with non-trivial latency. In the pause state, some audio hardware may + * still be using power. Client code may consider calling 'suspend' after a + * timeout to prevent that excess power usage. + * + * Implementation of this function is mandatory for offloaded playback. + * + * @return retval operation completion status. + */ + pause() generates (Result retval); + + /** + * Notifies to the audio driver to resume playback following a pause. + * Returns error INVALID_STATE if called without matching pause. + * + * Implementation of this function is mandatory for offloaded playback. + * + * @return retval operation completion status. + */ + resume() generates (Result retval); + + /** + * Returns whether HAL supports draining of streams. + * + * @return supports true if draining is supported. + */ + supportsDrain() generates (bool supports); + + /** + * Requests notification when data buffered by the driver/hardware has been + * played. If 'setCallback' has previously been called to enable + * non-blocking mode, then 'drain' must not block, instead it must return + * quickly and completion of the drain is notified through the callback. If + * 'setCallback' has not been called, then 'drain' must block until + * completion. + * + * If 'type' is 'ALL', the drain completes when all previously written data + * has been played. + * + * If 'type' is 'EARLY_NOTIFY', the drain completes shortly before all data + * for the current track has played to allow time for the framework to + * perform a gapless track switch. + * + * Drain must return immediately on 'stop' and 'flush' calls. + * + * Implementation of this function is mandatory for offloaded playback. + * + * @param type type of drain. + * @return retval operation completion status. + */ + drain(AudioDrain type) generates (Result retval); + + /** + * Notifies to the audio driver to flush the queued data. Stream must + * already be paused before calling 'flush'. + * Optional method + * + * Implementation of this function is mandatory for offloaded playback. + * + * @return retval operation completion status. + */ + flush() generates (Result retval); + + /** + * Return a recent count of the number of audio frames presented to an + * external observer. This excludes frames which have been written but are + * still in the pipeline. The count is not reset to zero when output enters + * standby. Also returns the value of CLOCK_MONOTONIC as of this + * presentation count. The returned count is expected to be 'recent', but + * does not need to be the most recent possible value. However, the + * associated time must correspond to whatever count is returned. + * + * Example: assume that N+M frames have been presented, where M is a 'small' + * number. Then it is permissible to return N instead of N+M, and the + * timestamp must correspond to N rather than N+M. The terms 'recent' and + * 'small' are not defined. They reflect the quality of the implementation. + * + * Optional method + * + * @return retval operation completion status. + * @return frames count of presented audio frames. + * @return timeStamp associated clock time. + */ + getPresentationPosition() + generates (Result retval, uint64_t frames, TimeSpec timeStamp); + + /** + * Selects a presentation for decoding from a next generation media stream + * (as defined per ETSI TS 103 190-2) and a program within the presentation. + * Optional method + * + * @param presentationId selected audio presentation. + * @param programId refinement for the presentation. + * @return retval operation completion status. + */ + selectPresentation(int32_t presentationId, int32_t programId) + generates (Result retval); +}; diff --git a/audio/6.0/IStreamOutCallback.hal b/audio/6.0/IStreamOutCallback.hal new file mode 100644 index 0000000000..e393d9a7d5 --- /dev/null +++ b/audio/6.0/IStreamOutCallback.hal @@ -0,0 +1,37 @@ +/* + * 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.hardware.audio@6.0; + +/** + * Asynchronous write callback interface. + */ +interface IStreamOutCallback { + /** + * Non blocking write completed. + */ + oneway onWriteReady(); + + /** + * Drain completed. + */ + oneway onDrainReady(); + + /** + * Stream hit an error. + */ + oneway onError(); +}; diff --git a/audio/6.0/config/Android.bp b/audio/6.0/config/Android.bp new file mode 100644 index 0000000000..182dfcc9b5 --- /dev/null +++ b/audio/6.0/config/Android.bp @@ -0,0 +1,7 @@ + +xsd_config { + name: "audio_policy_configuration_V6_0", + srcs: ["audio_policy_configuration.xsd"], + package_name: "audio.policy.configuration.V6_0", +} + diff --git a/audio/6.0/config/api/current.txt b/audio/6.0/config/api/current.txt new file mode 100644 index 0000000000..7aa147cbb5 --- /dev/null +++ b/audio/6.0/config/api/current.txt @@ -0,0 +1,422 @@ +// Signature format: 2.0 +package audio.policy.configuration.V6_0 { + + public class AttachedDevices { + ctor public AttachedDevices(); + method public java.util.List<java.lang.String> getItem(); + } + + public enum AudioDevice { + method public String getRawName(); + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_AMBIENT; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_AUX_DIGITAL; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_BACK_MIC; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_BLUETOOTH_A2DP; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_BLUETOOTH_BLE; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_BUILTIN_MIC; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_BUS; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_COMMUNICATION; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_DEFAULT; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_ECHO_REFERENCE; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_FM_TUNER; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_HDMI; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_HDMI_ARC; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_IP; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_LINE; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_LOOPBACK; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_PROXY; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_REMOTE_SUBMIX; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_SPDIF; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_STUB; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_TELEPHONY_RX; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_TV_TUNER; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_USB_ACCESSORY; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_USB_DEVICE; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_USB_HEADSET; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_VOICE_CALL; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_IN_WIRED_HEADSET; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_NONE; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_AUX_DIGITAL; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_AUX_LINE; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_BLUETOOTH_A2DP; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_BLUETOOTH_SCO; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_BUS; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_DEFAULT; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_EARPIECE; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_ECHO_CANCELLER; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_FM; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_HDMI; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_HDMI_ARC; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_HEARING_AID; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_IP; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_LINE; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_PROXY; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_REMOTE_SUBMIX; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_SPDIF; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_SPEAKER; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_SPEAKER_SAFE; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_STUB; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_TELEPHONY_TX; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_USB_ACCESSORY; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_USB_DEVICE; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_USB_HEADSET; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_WIRED_HEADPHONE; + enum_constant public static final audio.policy.configuration.V6_0.AudioDevice AUDIO_DEVICE_OUT_WIRED_HEADSET; + } + + public enum AudioFormat { + method public String getRawName(); + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_ADIF; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_ADTS; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_ADTS_ELD; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_ADTS_ERLC; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_ADTS_HE_V1; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_ADTS_HE_V2; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_ADTS_LC; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_ADTS_LD; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_ADTS_LTP; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_ADTS_MAIN; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_ADTS_SCALABLE; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_ADTS_SSR; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_ADTS_XHE; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_ELD; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_ERLC; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_HE_V1; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_HE_V2; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_LATM; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_LATM_HE_V1; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_LATM_HE_V2; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_LATM_LC; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_LC; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_LD; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_LTP; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_MAIN; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_SCALABLE; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_SSR; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AAC_XHE; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AC3; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AC4; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_ALAC; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AMR_NB; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AMR_WB; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_AMR_WB_PLUS; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_APE; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_APTX; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_APTX_ADAPTIVE; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_APTX_HD; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_APTX_TWSP; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_CELT; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_DOLBY_TRUEHD; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_DSD; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_DTS; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_DTS_HD; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_EVRC; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_EVRCB; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_EVRCNW; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_EVRCWB; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_E_AC3; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_E_AC3_JOC; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_FLAC; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_HE_AAC_V1; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_HE_AAC_V2; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_IEC61937; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_LDAC; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_LHDC; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_LHDC_LL; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_MAT_1_0; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_MAT_2_0; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_MAT_2_1; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_MP2; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_MP3; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_OPUS; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_PCM_16_BIT; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_PCM_24_BIT_PACKED; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_PCM_32_BIT; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_PCM_8_24_BIT; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_PCM_8_BIT; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_PCM_FLOAT; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_QCELP; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_SBC; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_VORBIS; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_WMA; + enum_constant public static final audio.policy.configuration.V6_0.AudioFormat AUDIO_FORMAT_WMA_PRO; + } + + public class AudioPolicyConfiguration { + ctor public AudioPolicyConfiguration(); + method public audio.policy.configuration.V6_0.GlobalConfiguration getGlobalConfiguration(); + method public java.util.List<audio.policy.configuration.V6_0.Modules> getModules(); + method public audio.policy.configuration.V6_0.SurroundSound getSurroundSound(); + method public audio.policy.configuration.V6_0.Version getVersion(); + method public java.util.List<audio.policy.configuration.V6_0.Volumes> getVolumes(); + method public void setGlobalConfiguration(audio.policy.configuration.V6_0.GlobalConfiguration); + method public void setSurroundSound(audio.policy.configuration.V6_0.SurroundSound); + method public void setVersion(audio.policy.configuration.V6_0.Version); + } + + public enum AudioUsage { + method public String getRawName(); + enum_constant public static final audio.policy.configuration.V6_0.AudioUsage AUDIO_USAGE_ALARM; + enum_constant public static final audio.policy.configuration.V6_0.AudioUsage AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY; + enum_constant public static final audio.policy.configuration.V6_0.AudioUsage AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE; + enum_constant public static final audio.policy.configuration.V6_0.AudioUsage AUDIO_USAGE_ASSISTANCE_SONIFICATION; + enum_constant public static final audio.policy.configuration.V6_0.AudioUsage AUDIO_USAGE_ASSISTANT; + enum_constant public static final audio.policy.configuration.V6_0.AudioUsage AUDIO_USAGE_GAME; + enum_constant public static final audio.policy.configuration.V6_0.AudioUsage AUDIO_USAGE_MEDIA; + enum_constant public static final audio.policy.configuration.V6_0.AudioUsage AUDIO_USAGE_NOTIFICATION; + enum_constant public static final audio.policy.configuration.V6_0.AudioUsage AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE; + enum_constant public static final audio.policy.configuration.V6_0.AudioUsage AUDIO_USAGE_UNKNOWN; + enum_constant public static final audio.policy.configuration.V6_0.AudioUsage AUDIO_USAGE_VIRTUAL_SOURCE; + enum_constant public static final audio.policy.configuration.V6_0.AudioUsage AUDIO_USAGE_VOICE_COMMUNICATION; + enum_constant public static final audio.policy.configuration.V6_0.AudioUsage AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING; + } + + public enum DeviceCategory { + method public String getRawName(); + enum_constant public static final audio.policy.configuration.V6_0.DeviceCategory DEVICE_CATEGORY_EARPIECE; + enum_constant public static final audio.policy.configuration.V6_0.DeviceCategory DEVICE_CATEGORY_EXT_MEDIA; + enum_constant public static final audio.policy.configuration.V6_0.DeviceCategory DEVICE_CATEGORY_HEADSET; + enum_constant public static final audio.policy.configuration.V6_0.DeviceCategory DEVICE_CATEGORY_HEARING_AID; + enum_constant public static final audio.policy.configuration.V6_0.DeviceCategory DEVICE_CATEGORY_SPEAKER; + } + + public class DevicePorts { + ctor public DevicePorts(); + method public java.util.List<audio.policy.configuration.V6_0.DevicePorts.DevicePort> getDevicePort(); + } + + public static class DevicePorts.DevicePort { + ctor public DevicePorts.DevicePort(); + method public String getAddress(); + method public java.util.List<audio.policy.configuration.V6_0.AudioFormat> getEncodedFormats(); + method public audio.policy.configuration.V6_0.Gains getGains(); + method public java.util.List<audio.policy.configuration.V6_0.Profile> getProfile(); + method public audio.policy.configuration.V6_0.Role getRole(); + method public String getTagName(); + method public String getType(); + method public boolean get_default(); + method public void setAddress(String); + method public void setEncodedFormats(java.util.List<audio.policy.configuration.V6_0.AudioFormat>); + method public void setGains(audio.policy.configuration.V6_0.Gains); + method public void setRole(audio.policy.configuration.V6_0.Role); + method public void setTagName(String); + method public void setType(String); + method public void set_default(boolean); + } + + public enum GainMode { + method public String getRawName(); + enum_constant public static final audio.policy.configuration.V6_0.GainMode AUDIO_GAIN_MODE_CHANNELS; + enum_constant public static final audio.policy.configuration.V6_0.GainMode AUDIO_GAIN_MODE_JOINT; + enum_constant public static final audio.policy.configuration.V6_0.GainMode AUDIO_GAIN_MODE_RAMP; + } + + public class Gains { + ctor public Gains(); + method public java.util.List<audio.policy.configuration.V6_0.Gains.Gain> getGain(); + } + + public static class Gains.Gain { + ctor public Gains.Gain(); + method public String getChannel_mask(); + method public int getDefaultValueMB(); + method public int getMaxRampMs(); + method public int getMaxValueMB(); + method public int getMinRampMs(); + method public int getMinValueMB(); + method public audio.policy.configuration.V6_0.GainMode getMode(); + method public String getName(); + method public int getStepValueMB(); + method public void setChannel_mask(String); + method public void setDefaultValueMB(int); + method public void setMaxRampMs(int); + method public void setMaxValueMB(int); + method public void setMinRampMs(int); + method public void setMinValueMB(int); + method public void setMode(audio.policy.configuration.V6_0.GainMode); + method public void setName(String); + method public void setStepValueMB(int); + } + + public class GlobalConfiguration { + ctor public GlobalConfiguration(); + method public boolean getSpeaker_drc_enabled(); + method public void setSpeaker_drc_enabled(boolean); + } + + public enum HalVersion { + method public String getRawName(); + enum_constant public static final audio.policy.configuration.V6_0.HalVersion _2_0; + enum_constant public static final audio.policy.configuration.V6_0.HalVersion _3_0; + } + + public class MixPorts { + ctor public MixPorts(); + method public java.util.List<audio.policy.configuration.V6_0.MixPorts.MixPort> getMixPort(); + } + + public static class MixPorts.MixPort { + ctor public MixPorts.MixPort(); + method public String getFlags(); + method public audio.policy.configuration.V6_0.Gains getGains(); + method public long getMaxActiveCount(); + method public long getMaxOpenCount(); + method public String getName(); + method public java.util.List<audio.policy.configuration.V6_0.AudioUsage> getPreferredUsage(); + method public java.util.List<audio.policy.configuration.V6_0.Profile> getProfile(); + method public audio.policy.configuration.V6_0.Role getRole(); + method public void setFlags(String); + method public void setGains(audio.policy.configuration.V6_0.Gains); + method public void setMaxActiveCount(long); + method public void setMaxOpenCount(long); + method public void setName(String); + method public void setPreferredUsage(java.util.List<audio.policy.configuration.V6_0.AudioUsage>); + method public void setRole(audio.policy.configuration.V6_0.Role); + } + + public enum MixType { + method public String getRawName(); + enum_constant public static final audio.policy.configuration.V6_0.MixType mix; + enum_constant public static final audio.policy.configuration.V6_0.MixType mux; + } + + public class Modules { + ctor public Modules(); + method public java.util.List<audio.policy.configuration.V6_0.Modules.Module> getModule(); + } + + public static class Modules.Module { + ctor public Modules.Module(); + method public audio.policy.configuration.V6_0.AttachedDevices getAttachedDevices(); + method public String getDefaultOutputDevice(); + method public audio.policy.configuration.V6_0.DevicePorts getDevicePorts(); + method public audio.policy.configuration.V6_0.HalVersion getHalVersion(); + method public audio.policy.configuration.V6_0.MixPorts getMixPorts(); + method public String getName(); + method public audio.policy.configuration.V6_0.Routes getRoutes(); + method public void setAttachedDevices(audio.policy.configuration.V6_0.AttachedDevices); + method public void setDefaultOutputDevice(String); + method public void setDevicePorts(audio.policy.configuration.V6_0.DevicePorts); + method public void setHalVersion(audio.policy.configuration.V6_0.HalVersion); + method public void setMixPorts(audio.policy.configuration.V6_0.MixPorts); + method public void setName(String); + method public void setRoutes(audio.policy.configuration.V6_0.Routes); + } + + public class Profile { + ctor public Profile(); + method public String getChannelMasks(); + method public String getFormat(); + method public String getName(); + method public String getSamplingRates(); + method public void setChannelMasks(String); + method public void setFormat(String); + method public void setName(String); + method public void setSamplingRates(String); + } + + public class Reference { + ctor public Reference(); + method public String getName(); + method public java.util.List<java.lang.String> getPoint(); + method public void setName(String); + } + + public enum Role { + method public String getRawName(); + enum_constant public static final audio.policy.configuration.V6_0.Role sink; + enum_constant public static final audio.policy.configuration.V6_0.Role source; + } + + public class Routes { + ctor public Routes(); + method public java.util.List<audio.policy.configuration.V6_0.Routes.Route> getRoute(); + } + + public static class Routes.Route { + ctor public Routes.Route(); + method public String getSink(); + method public String getSources(); + method public audio.policy.configuration.V6_0.MixType getType(); + method public void setSink(String); + method public void setSources(String); + method public void setType(audio.policy.configuration.V6_0.MixType); + } + + public enum Stream { + method public String getRawName(); + enum_constant public static final audio.policy.configuration.V6_0.Stream AUDIO_STREAM_ACCESSIBILITY; + enum_constant public static final audio.policy.configuration.V6_0.Stream AUDIO_STREAM_ALARM; + enum_constant public static final audio.policy.configuration.V6_0.Stream AUDIO_STREAM_BLUETOOTH_SCO; + enum_constant public static final audio.policy.configuration.V6_0.Stream AUDIO_STREAM_DTMF; + enum_constant public static final audio.policy.configuration.V6_0.Stream AUDIO_STREAM_ENFORCED_AUDIBLE; + enum_constant public static final audio.policy.configuration.V6_0.Stream AUDIO_STREAM_MUSIC; + enum_constant public static final audio.policy.configuration.V6_0.Stream AUDIO_STREAM_NOTIFICATION; + enum_constant public static final audio.policy.configuration.V6_0.Stream AUDIO_STREAM_PATCH; + enum_constant public static final audio.policy.configuration.V6_0.Stream AUDIO_STREAM_REROUTING; + enum_constant public static final audio.policy.configuration.V6_0.Stream AUDIO_STREAM_RING; + enum_constant public static final audio.policy.configuration.V6_0.Stream AUDIO_STREAM_SYSTEM; + enum_constant public static final audio.policy.configuration.V6_0.Stream AUDIO_STREAM_TTS; + enum_constant public static final audio.policy.configuration.V6_0.Stream AUDIO_STREAM_VOICE_CALL; + } + + public class SurroundFormats { + ctor public SurroundFormats(); + method public java.util.List<audio.policy.configuration.V6_0.SurroundFormats.Format> getFormat(); + } + + public static class SurroundFormats.Format { + ctor public SurroundFormats.Format(); + method public audio.policy.configuration.V6_0.AudioFormat getName(); + method public java.util.List<audio.policy.configuration.V6_0.AudioFormat> getSubformats(); + method public void setName(audio.policy.configuration.V6_0.AudioFormat); + method public void setSubformats(java.util.List<audio.policy.configuration.V6_0.AudioFormat>); + } + + public class SurroundSound { + ctor public SurroundSound(); + method public audio.policy.configuration.V6_0.SurroundFormats getFormats(); + method public void setFormats(audio.policy.configuration.V6_0.SurroundFormats); + } + + public enum Version { + method public String getRawName(); + enum_constant public static final audio.policy.configuration.V6_0.Version _1_0; + } + + public class Volume { + ctor public Volume(); + method public audio.policy.configuration.V6_0.DeviceCategory getDeviceCategory(); + method public java.util.List<java.lang.String> getPoint(); + method public String getRef(); + method public audio.policy.configuration.V6_0.Stream getStream(); + method public void setDeviceCategory(audio.policy.configuration.V6_0.DeviceCategory); + method public void setRef(String); + method public void setStream(audio.policy.configuration.V6_0.Stream); + } + + public class Volumes { + ctor public Volumes(); + method public java.util.List<audio.policy.configuration.V6_0.Reference> getReference(); + method public java.util.List<audio.policy.configuration.V6_0.Volume> getVolume(); + } + + public class XmlParser { + ctor public XmlParser(); + method public static audio.policy.configuration.V6_0.AudioPolicyConfiguration read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + } + +} + diff --git a/audio/6.0/config/api/last_current.txt b/audio/6.0/config/api/last_current.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/audio/6.0/config/api/last_current.txt diff --git a/audio/6.0/config/api/last_removed.txt b/audio/6.0/config/api/last_removed.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/audio/6.0/config/api/last_removed.txt diff --git a/audio/6.0/config/api/removed.txt b/audio/6.0/config/api/removed.txt new file mode 100644 index 0000000000..d802177e24 --- /dev/null +++ b/audio/6.0/config/api/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/audio/6.0/config/audio_policy_configuration.xsd b/audio/6.0/config/audio_policy_configuration.xsd new file mode 100644 index 0000000000..0dc89bb1a7 --- /dev/null +++ b/audio/6.0/config/audio_policy_configuration.xsd @@ -0,0 +1,624 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- 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. +--> +<!-- TODO: define a targetNamespace. Note that it will break retrocompatibility --> +<xs:schema version="2.0" + elementFormDefault="qualified" + attributeFormDefault="unqualified" + xmlns:xs="http://www.w3.org/2001/XMLSchema"> + <!-- List the config versions supported by audio policy. --> + <xs:simpleType name="version"> + <xs:restriction base="xs:decimal"> + <xs:enumeration value="1.0"/> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="halVersion"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Version of the interface the hal implements. + </xs:documentation> + </xs:annotation> + <xs:restriction base="xs:decimal"> + <!-- List of HAL versions supported by the framework. --> + <xs:enumeration value="2.0"/> + <xs:enumeration value="3.0"/> + </xs:restriction> + </xs:simpleType> + <xs:element name="audioPolicyConfiguration"> + <xs:complexType> + <xs:sequence> + <xs:element name="globalConfiguration" type="globalConfiguration"/> + <xs:element name="modules" type="modules" maxOccurs="unbounded"/> + <xs:element name="volumes" type="volumes" maxOccurs="unbounded"/> + <xs:element name="surroundSound" type="surroundSound" minOccurs="0" /> + </xs:sequence> + <xs:attribute name="version" type="version"/> + </xs:complexType> + <xs:key name="moduleNameKey"> + <xs:selector xpath="modules/module"/> + <xs:field xpath="@name"/> + </xs:key> + <xs:unique name="volumeTargetUniqueness"> + <xs:selector xpath="volumes/volume"/> + <xs:field xpath="@stream"/> + <xs:field xpath="@deviceCategory"/> + </xs:unique> + <xs:key name="volumeCurveNameKey"> + <xs:selector xpath="volumes/reference"/> + <xs:field xpath="@name"/> + </xs:key> + <xs:keyref name="volumeCurveRef" refer="volumeCurveNameKey"> + <xs:selector xpath="volumes/volume"/> + <xs:field xpath="@ref"/> + </xs:keyref> + </xs:element> + <xs:complexType name="globalConfiguration"> + <xs:attribute name="speaker_drc_enabled" type="xs:boolean" use="required"/> + </xs:complexType> + <xs:complexType name="modules"> + <xs:annotation> + <xs:documentation xml:lang="en"> + There should be one section per audio HW module present on the platform. + Each <module/> contains two mandatory tags: “halVersion” and “name”. + The module "name" is the same as in previous .conf file. + Each module must contain the following sections: + - <devicePorts/>: a list of device descriptors for all + input and output devices accessible via this module. + This contains both permanently attached devices and removable devices. + - <mixPorts/>: listing all output and input streams exposed by the audio HAL + - <routes/>: list of possible connections between input + and output devices or between stream and devices. + A <route/> is defined by a set of 3 attributes: + -"type": mux|mix means all sources are mutual exclusive (mux) or can be mixed (mix) + -"sink": the sink involved in this route + -"sources": all the sources than can be connected to the sink via this route + - <attachedDevices/>: permanently attached devices. + The attachedDevices section is a list of devices names. + Their names correspond to device names defined in "devicePorts" section. + - <defaultOutputDevice/> is the device to be used when no policy rule applies + </xs:documentation> + </xs:annotation> + <xs:sequence> + <xs:element name="module" maxOccurs="unbounded"> + <xs:complexType> + <xs:sequence> + <xs:element name="attachedDevices" type="attachedDevices" minOccurs="0"> + <xs:unique name="attachedDevicesUniqueness"> + <xs:selector xpath="item"/> + <xs:field xpath="."/> + </xs:unique> + </xs:element> + <xs:element name="defaultOutputDevice" type="xs:token" minOccurs="0"/> + <xs:element name="mixPorts" type="mixPorts" minOccurs="0"/> + <xs:element name="devicePorts" type="devicePorts" minOccurs="0"/> + <xs:element name="routes" type="routes" minOccurs="0"/> + </xs:sequence> + <xs:attribute name="name" type="xs:string" use="required"/> + <xs:attribute name="halVersion" type="halVersion" use="required"/> + </xs:complexType> + <xs:unique name="mixPortNameUniqueness"> + <xs:selector xpath="mixPorts/mixPort"/> + <xs:field xpath="@name"/> + </xs:unique> + <xs:key name="devicePortNameKey"> + <xs:selector xpath="devicePorts/devicePort"/> + <xs:field xpath="@tagName"/> + </xs:key> + <xs:unique name="devicePortUniqueness"> + <xs:selector xpath="devicePorts/devicePort"/> + <xs:field xpath="@type"/> + <xs:field xpath="@address"/> + </xs:unique> + <xs:keyref name="defaultOutputDeviceRef" refer="devicePortNameKey"> + <xs:selector xpath="defaultOutputDevice"/> + <xs:field xpath="."/> + </xs:keyref> + <xs:keyref name="attachedDeviceRef" refer="devicePortNameKey"> + <xs:selector xpath="attachedDevices/item"/> + <xs:field xpath="."/> + </xs:keyref> + <!-- The following 3 constraints try to make sure each sink port + is reference in one an only one route. --> + <xs:key name="routeSinkKey"> + <!-- predicate [@type='sink'] does not work in xsd 1.0 --> + <xs:selector xpath="devicePorts/devicePort|mixPorts/mixPort"/> + <xs:field xpath="@tagName|@name"/> + </xs:key> + <xs:keyref name="routeSinkRef" refer="routeSinkKey"> + <xs:selector xpath="routes/route"/> + <xs:field xpath="@sink"/> + </xs:keyref> + <xs:unique name="routeUniqueness"> + <xs:selector xpath="routes/route"/> + <xs:field xpath="@sink"/> + </xs:unique> + </xs:element> + </xs:sequence> + </xs:complexType> + <xs:complexType name="attachedDevices"> + <xs:sequence> + <xs:element name="item" type="xs:token" minOccurs="0" maxOccurs="unbounded"/> + </xs:sequence> + </xs:complexType> + <!-- TODO: separate values by space for better xsd validations. --> + <xs:simpleType name="audioInOutFlags"> + <xs:annotation> + <xs:documentation xml:lang="en"> + "|" separated list of audio_output_flags_t or audio_input_flags_t. + </xs:documentation> + </xs:annotation> + <xs:restriction base="xs:string"> + <xs:pattern value="|[_A-Z]+(\|[_A-Z]+)*"/> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="role"> + <xs:restriction base="xs:string"> + <xs:enumeration value="sink"/> + <xs:enumeration value="source"/> + </xs:restriction> + </xs:simpleType> + <xs:complexType name="mixPorts"> + <xs:sequence> + <xs:element name="mixPort" minOccurs="0" maxOccurs="unbounded"> + <xs:complexType> + <xs:sequence> + <xs:element name="profile" type="profile" minOccurs="0" maxOccurs="unbounded"/> + <xs:element name="gains" type="gains" minOccurs="0"/> + </xs:sequence> + <xs:attribute name="name" type="xs:token" use="required"/> + <xs:attribute name="role" type="role" use="required"/> + <xs:attribute name="flags" type="audioInOutFlags"/> + <xs:attribute name="maxOpenCount" type="xs:unsignedInt"/> + <xs:attribute name="maxActiveCount" type="xs:unsignedInt"/> + <xs:attribute name="preferredUsage" type="audioUsageList"> + <xs:annotation> + <xs:documentation xml:lang="en"> + When choosing the mixPort of an audio track, the audioPolicy + first considers the mixPorts with a preferredUsage including + the track AudioUsage preferred . + If non support the track format, the other mixPorts are considered. + Eg: a <mixPort preferredUsage="AUDIO_USAGE_MEDIA" /> will receive + the audio of all apps playing with a MEDIA usage. + It may receive audio from ALARM if there are no audio compatible + <mixPort preferredUsage="AUDIO_USAGE_ALARM" />. + </xs:documentation> + </xs:annotation> + </xs:attribute> + </xs:complexType> + <xs:unique name="mixPortProfileUniqueness"> + <xs:selector xpath="profile"/> + <xs:field xpath="format"/> + <xs:field xpath="samplingRate"/> + <xs:field xpath="channelMasks"/> + </xs:unique> + <xs:unique name="mixPortGainUniqueness"> + <xs:selector xpath="gains/gain"/> + <xs:field xpath="@name"/> + </xs:unique> + </xs:element> + </xs:sequence> + </xs:complexType> + <!-- Enum values of audio_device_t in audio.h + TODO: generate from hidl to avoid manual sync. + TODO: separate source and sink in the xml for better xsd validations. --> + <xs:simpleType name="audioDevice"> + <xs:restriction base="xs:string"> + <xs:enumeration value="AUDIO_DEVICE_NONE"/> + + <xs:enumeration value="AUDIO_DEVICE_OUT_EARPIECE"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_SPEAKER"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_WIRED_HEADSET"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_WIRED_HEADPHONE"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_BLUETOOTH_SCO"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_BLUETOOTH_A2DP"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_AUX_DIGITAL"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_HDMI"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_USB_ACCESSORY"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_USB_DEVICE"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_REMOTE_SUBMIX"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_TELEPHONY_TX"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_LINE"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_HDMI_ARC"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_SPDIF"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_FM"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_AUX_LINE"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_SPEAKER_SAFE"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_IP"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_BUS"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_PROXY"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_USB_HEADSET"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_HEARING_AID"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_ECHO_CANCELLER"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_DEFAULT"/> + <xs:enumeration value="AUDIO_DEVICE_OUT_STUB"/> + + <!-- Due to the xml format, IN types can not be a separated from OUT types --> + <xs:enumeration value="AUDIO_DEVICE_IN_COMMUNICATION"/> + <xs:enumeration value="AUDIO_DEVICE_IN_AMBIENT"/> + <xs:enumeration value="AUDIO_DEVICE_IN_BUILTIN_MIC"/> + <xs:enumeration value="AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET"/> + <xs:enumeration value="AUDIO_DEVICE_IN_WIRED_HEADSET"/> + <xs:enumeration value="AUDIO_DEVICE_IN_AUX_DIGITAL"/> + <xs:enumeration value="AUDIO_DEVICE_IN_HDMI"/> + <xs:enumeration value="AUDIO_DEVICE_IN_VOICE_CALL"/> + <xs:enumeration value="AUDIO_DEVICE_IN_TELEPHONY_RX"/> + <xs:enumeration value="AUDIO_DEVICE_IN_BACK_MIC"/> + <xs:enumeration value="AUDIO_DEVICE_IN_REMOTE_SUBMIX"/> + <xs:enumeration value="AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET"/> + <xs:enumeration value="AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET"/> + <xs:enumeration value="AUDIO_DEVICE_IN_USB_ACCESSORY"/> + <xs:enumeration value="AUDIO_DEVICE_IN_USB_DEVICE"/> + <xs:enumeration value="AUDIO_DEVICE_IN_FM_TUNER"/> + <xs:enumeration value="AUDIO_DEVICE_IN_TV_TUNER"/> + <xs:enumeration value="AUDIO_DEVICE_IN_LINE"/> + <xs:enumeration value="AUDIO_DEVICE_IN_SPDIF"/> + <xs:enumeration value="AUDIO_DEVICE_IN_BLUETOOTH_A2DP"/> + <xs:enumeration value="AUDIO_DEVICE_IN_LOOPBACK"/> + <xs:enumeration value="AUDIO_DEVICE_IN_IP"/> + <xs:enumeration value="AUDIO_DEVICE_IN_BUS"/> + <xs:enumeration value="AUDIO_DEVICE_IN_PROXY"/> + <xs:enumeration value="AUDIO_DEVICE_IN_USB_HEADSET"/> + <xs:enumeration value="AUDIO_DEVICE_IN_BLUETOOTH_BLE"/> + <xs:enumeration value="AUDIO_DEVICE_IN_HDMI_ARC"/> + <xs:enumeration value="AUDIO_DEVICE_IN_ECHO_REFERENCE"/> + <xs:enumeration value="AUDIO_DEVICE_IN_DEFAULT"/> + <xs:enumeration value="AUDIO_DEVICE_IN_STUB"/> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="vendorExtension"> + <!-- Vendor extension names must be prefixed by "VX_" to distinguish them from AOSP values. + Vendor are encouraged to namespace their module names to avoid conflicts. + Example for an hypothetical Google virtual reality device: + <devicePort tagName="VR" type="VX_GOOGLE_VR" role="sink"> + --> + <xs:restriction base="xs:string"> + <xs:pattern value="VX_[_a-zA-Z0-9]+"/> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="extendableAudioDevice"> + <xs:union memberTypes="audioDevice vendorExtension"/> + </xs:simpleType> + <!-- Enum values of audio_format_t in audio.h + TODO: generate from hidl to avoid manual sync. --> + <xs:simpleType name="audioFormat"> + <xs:restriction base="xs:string"> + <xs:enumeration value="AUDIO_FORMAT_PCM_16_BIT" /> + <xs:enumeration value="AUDIO_FORMAT_PCM_8_BIT"/> + <xs:enumeration value="AUDIO_FORMAT_PCM_32_BIT"/> + <xs:enumeration value="AUDIO_FORMAT_PCM_8_24_BIT"/> + <xs:enumeration value="AUDIO_FORMAT_PCM_FLOAT"/> + <xs:enumeration value="AUDIO_FORMAT_PCM_24_BIT_PACKED"/> + <xs:enumeration value="AUDIO_FORMAT_MP3"/> + <xs:enumeration value="AUDIO_FORMAT_AMR_NB"/> + <xs:enumeration value="AUDIO_FORMAT_AMR_WB"/> + <xs:enumeration value="AUDIO_FORMAT_AAC"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_MAIN"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_LC"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_SSR"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_LTP"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_HE_V1"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_SCALABLE"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_ERLC"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_LD"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_HE_V2"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_ELD"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_ADTS_MAIN"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_ADTS_LC"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_ADTS_SSR"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_ADTS_LTP"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_ADTS_HE_V1"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_ADTS_SCALABLE"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_ADTS_ERLC"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_ADTS_LD"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_ADTS_HE_V2"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_ADTS_ELD"/> + <xs:enumeration value="AUDIO_FORMAT_VORBIS"/> + <xs:enumeration value="AUDIO_FORMAT_HE_AAC_V1"/> + <xs:enumeration value="AUDIO_FORMAT_HE_AAC_V2"/> + <xs:enumeration value="AUDIO_FORMAT_OPUS"/> + <xs:enumeration value="AUDIO_FORMAT_AC3"/> + <xs:enumeration value="AUDIO_FORMAT_E_AC3"/> + <xs:enumeration value="AUDIO_FORMAT_DTS"/> + <xs:enumeration value="AUDIO_FORMAT_DTS_HD"/> + <xs:enumeration value="AUDIO_FORMAT_IEC61937"/> + <xs:enumeration value="AUDIO_FORMAT_DOLBY_TRUEHD"/> + <xs:enumeration value="AUDIO_FORMAT_EVRC"/> + <xs:enumeration value="AUDIO_FORMAT_EVRCB"/> + <xs:enumeration value="AUDIO_FORMAT_EVRCWB"/> + <xs:enumeration value="AUDIO_FORMAT_EVRCNW"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_ADIF"/> + <xs:enumeration value="AUDIO_FORMAT_WMA"/> + <xs:enumeration value="AUDIO_FORMAT_WMA_PRO"/> + <xs:enumeration value="AUDIO_FORMAT_AMR_WB_PLUS"/> + <xs:enumeration value="AUDIO_FORMAT_MP2"/> + <xs:enumeration value="AUDIO_FORMAT_QCELP"/> + <xs:enumeration value="AUDIO_FORMAT_DSD"/> + <xs:enumeration value="AUDIO_FORMAT_FLAC"/> + <xs:enumeration value="AUDIO_FORMAT_ALAC"/> + <xs:enumeration value="AUDIO_FORMAT_APE"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_ADTS"/> + <xs:enumeration value="AUDIO_FORMAT_SBC"/> + <xs:enumeration value="AUDIO_FORMAT_APTX"/> + <xs:enumeration value="AUDIO_FORMAT_APTX_HD"/> + <xs:enumeration value="AUDIO_FORMAT_AC4"/> + <xs:enumeration value="AUDIO_FORMAT_LDAC"/> + <xs:enumeration value="AUDIO_FORMAT_E_AC3_JOC"/> + <xs:enumeration value="AUDIO_FORMAT_MAT_1_0"/> + <xs:enumeration value="AUDIO_FORMAT_MAT_2_0"/> + <xs:enumeration value="AUDIO_FORMAT_MAT_2_1"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_XHE"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_ADTS_XHE"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_LATM"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_LATM_LC"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_LATM_HE_V1"/> + <xs:enumeration value="AUDIO_FORMAT_AAC_LATM_HE_V2"/> + <xs:enumeration value="AUDIO_FORMAT_CELT"/> + <xs:enumeration value="AUDIO_FORMAT_APTX_ADAPTIVE"/> + <xs:enumeration value="AUDIO_FORMAT_LHDC"/> + <xs:enumeration value="AUDIO_FORMAT_LHDC_LL"/> + <xs:enumeration value="AUDIO_FORMAT_APTX_TWSP"/> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="extendableAudioFormat"> + <xs:union memberTypes="audioFormat vendorExtension"/> + </xs:simpleType> + <!-- Enum values of audio::common::4_0::AudioUsage + TODO: generate from HIDL to avoid manual sync. --> + <xs:simpleType name="audioUsage"> + <xs:restriction base="xs:string"> + <xs:enumeration value="AUDIO_USAGE_UNKNOWN" /> + <xs:enumeration value="AUDIO_USAGE_MEDIA" /> + <xs:enumeration value="AUDIO_USAGE_VOICE_COMMUNICATION" /> + <xs:enumeration value="AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING" /> + <xs:enumeration value="AUDIO_USAGE_ALARM" /> + <xs:enumeration value="AUDIO_USAGE_NOTIFICATION" /> + <xs:enumeration value="AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE" /> + <xs:enumeration value="AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY" /> + <xs:enumeration value="AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE" /> + <xs:enumeration value="AUDIO_USAGE_ASSISTANCE_SONIFICATION" /> + <xs:enumeration value="AUDIO_USAGE_GAME" /> + <xs:enumeration value="AUDIO_USAGE_VIRTUAL_SOURCE" /> + <xs:enumeration value="AUDIO_USAGE_ASSISTANT" /> + </xs:restriction> + </xs:simpleType> + <xs:simpleType name="audioUsageList"> + <xs:list itemType="audioUsage"/> + </xs:simpleType> + <!-- TODO: Change to a space separated list to xsd enforce correctness. --> + <xs:simpleType name="samplingRates"> + <xs:restriction base="xs:string"> + <xs:pattern value="[0-9]+(,[0-9]+)*"/> + </xs:restriction> + </xs:simpleType> + <!-- TODO: Change to a space separated list to xsd enforce correctness. --> + <xs:simpleType name="channelMask"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Comma (",") separated list of channel flags + from audio_channel_mask_t. + </xs:documentation> + </xs:annotation> + <xs:restriction base="xs:string"> + <xs:pattern value="[_A-Z][_A-Z0-9]*(,[_A-Z][_A-Z0-9]*)*"/> + </xs:restriction> + </xs:simpleType> + <xs:complexType name="profile"> + <xs:attribute name="name" type="xs:token" use="optional"/> + <xs:attribute name="format" type="extendableAudioFormat" use="optional"/> + <xs:attribute name="samplingRates" type="samplingRates" use="optional"/> + <xs:attribute name="channelMasks" type="channelMask" use="optional"/> + </xs:complexType> + <xs:simpleType name="gainMode"> + <xs:restriction base="xs:string"> + <xs:enumeration value="AUDIO_GAIN_MODE_JOINT"/> + <xs:enumeration value="AUDIO_GAIN_MODE_CHANNELS"/> + <xs:enumeration value="AUDIO_GAIN_MODE_RAMP"/> + </xs:restriction> + </xs:simpleType> + <xs:complexType name="gains"> + <xs:sequence> + <xs:element name="gain" minOccurs="0" maxOccurs="unbounded"> + <xs:complexType> + <xs:attribute name="name" type="xs:token" use="required"/> + <xs:attribute name="mode" type="gainMode" use="required"/> + <xs:attribute name="channel_mask" type="channelMask" use="optional"/> + <xs:attribute name="minValueMB" type="xs:int" use="optional"/> + <xs:attribute name="maxValueMB" type="xs:int" use="optional"/> + <xs:attribute name="defaultValueMB" type="xs:int" use="optional"/> + <xs:attribute name="stepValueMB" type="xs:int" use="optional"/> + <xs:attribute name="minRampMs" type="xs:int" use="optional"/> + <xs:attribute name="maxRampMs" type="xs:int" use="optional"/> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + <xs:complexType name="devicePorts"> + <xs:sequence> + <xs:element name="devicePort" minOccurs="0" maxOccurs="unbounded"> + <xs:complexType> + <xs:sequence> + <xs:element name="profile" type="profile" minOccurs="0" maxOccurs="unbounded"/> + <xs:element name="gains" type="gains" minOccurs="0"/> + </xs:sequence> + <xs:attribute name="tagName" type="xs:token" use="required"/> + <xs:attribute name="type" type="extendableAudioDevice" use="required"/> + <xs:attribute name="role" type="role" use="required"/> + <xs:attribute name="address" type="xs:string" use="optional" default=""/> + <!-- Note that XSD 1.0 can not check that a type only has one default. --> + <xs:attribute name="default" type="xs:boolean" use="optional"> + <xs:annotation> + <xs:documentation xml:lang="en"> + The default device will be used if multiple have the same type + and no explicit route request exists for a specific device of + that type. + </xs:documentation> + </xs:annotation> + </xs:attribute> + <xs:attribute name="encodedFormats" type="audioFormatsList" use="optional" + default="" /> + </xs:complexType> + <xs:unique name="devicePortProfileUniqueness"> + <xs:selector xpath="profile"/> + <xs:field xpath="format"/> + <xs:field xpath="samplingRate"/> + <xs:field xpath="channelMasks"/> + </xs:unique> + <xs:unique name="devicePortGainUniqueness"> + <xs:selector xpath="gains/gain"/> + <xs:field xpath="@name"/> + </xs:unique> + </xs:element> + </xs:sequence> + </xs:complexType> + <xs:simpleType name="mixType"> + <xs:restriction base="xs:string"> + <xs:enumeration value="mix"/> + <xs:enumeration value="mux"/> + </xs:restriction> + </xs:simpleType> + <xs:complexType name="routes"> + <xs:sequence> + <xs:element name="route" minOccurs="0" maxOccurs="unbounded"> + <xs:annotation> + <xs:documentation xml:lang="en"> + List all available sources for a given sink. + </xs:documentation> + </xs:annotation> + <xs:complexType> + <xs:attribute name="type" type="mixType" use="required"/> + <xs:attribute name="sink" type="xs:string" use="required"/> + <xs:attribute name="sources" type="xs:string" use="required"/> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> + <xs:complexType name="volumes"> + <xs:sequence> + <xs:element name="volume" type="volume" minOccurs="0" maxOccurs="unbounded"/> + <xs:element name="reference" type="reference" minOccurs="0" maxOccurs="unbounded"> + </xs:element> + </xs:sequence> + </xs:complexType> + <!-- TODO: Always require a ref for better xsd validations. + Currently a volume could have no points nor ref + as it can not be forbidden by xsd 1.0.--> + <xs:simpleType name="volumePoint"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Comma separated pair of number. + The fist one is the framework level (between 0 and 100). + The second one is the volume to send to the HAL. + The framework will interpolate volumes not specified. + Their MUST be at least 2 points specified. + </xs:documentation> + </xs:annotation> + <xs:restriction base="xs:string"> + <xs:pattern value="([0-9]{1,2}|100),-?[0-9]+"/> + </xs:restriction> + </xs:simpleType> + <!-- Enum values of audio_stream_type_t in audio-base.h + TODO: generate from hidl to avoid manual sync. --> + <xs:simpleType name="stream"> + <xs:restriction base="xs:string"> + <xs:enumeration value="AUDIO_STREAM_VOICE_CALL"/> + <xs:enumeration value="AUDIO_STREAM_SYSTEM"/> + <xs:enumeration value="AUDIO_STREAM_RING"/> + <xs:enumeration value="AUDIO_STREAM_MUSIC"/> + <xs:enumeration value="AUDIO_STREAM_ALARM"/> + <xs:enumeration value="AUDIO_STREAM_NOTIFICATION"/> + <xs:enumeration value="AUDIO_STREAM_BLUETOOTH_SCO"/> + <xs:enumeration value="AUDIO_STREAM_ENFORCED_AUDIBLE"/> + <xs:enumeration value="AUDIO_STREAM_DTMF"/> + <xs:enumeration value="AUDIO_STREAM_TTS"/> + <xs:enumeration value="AUDIO_STREAM_ACCESSIBILITY"/> + <xs:enumeration value="AUDIO_STREAM_REROUTING"/> + <xs:enumeration value="AUDIO_STREAM_PATCH"/> + </xs:restriction> + </xs:simpleType> + <!-- Enum values of device_category from Volume.h. + TODO: generate from hidl to avoid manual sync. --> + <xs:simpleType name="deviceCategory"> + <xs:restriction base="xs:string"> + <xs:enumeration value="DEVICE_CATEGORY_HEADSET"/> + <xs:enumeration value="DEVICE_CATEGORY_SPEAKER"/> + <xs:enumeration value="DEVICE_CATEGORY_EARPIECE"/> + <xs:enumeration value="DEVICE_CATEGORY_EXT_MEDIA"/> + <xs:enumeration value="DEVICE_CATEGORY_HEARING_AID"/> + </xs:restriction> + </xs:simpleType> + <xs:complexType name="volume"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Volume section defines a volume curve for a given use case and device category. + It contains a list of points of this curve expressing the attenuation in Millibels + for a given volume index from 0 to 100. + <volume stream="AUDIO_STREAM_MUSIC" deviceCategory="DEVICE_CATEGORY_SPEAKER"> + <point>0,-9600</point> + <point>100,0</point> + </volume> + + It may also reference a reference/@name to avoid duplicating curves. + <volume stream="AUDIO_STREAM_MUSIC" deviceCategory="DEVICE_CATEGORY_SPEAKER" + ref="DEFAULT_MEDIA_VOLUME_CURVE"/> + <reference name="DEFAULT_MEDIA_VOLUME_CURVE"> + <point>0,-9600</point> + <point>100,0</point> + </reference> + </xs:documentation> + </xs:annotation> + <xs:sequence> + <xs:element name="point" type="volumePoint" minOccurs="0" maxOccurs="unbounded"/> + </xs:sequence> + <xs:attribute name="stream" type="stream"/> + <xs:attribute name="deviceCategory" type="deviceCategory"/> + <xs:attribute name="ref" type="xs:token" use="optional"/> + </xs:complexType> + <xs:complexType name="reference"> + <xs:sequence> + <xs:element name="point" type="volumePoint" minOccurs="2" maxOccurs="unbounded"/> + </xs:sequence> + <xs:attribute name="name" type="xs:token" use="required"/> + </xs:complexType> + <xs:complexType name="surroundSound"> + <xs:annotation> + <xs:documentation xml:lang="en"> + Surround Sound section provides configuration related to handling of + multi-channel formats. + </xs:documentation> + </xs:annotation> + <xs:sequence> + <xs:element name="formats" type="surroundFormats"/> + </xs:sequence> + </xs:complexType> + <xs:simpleType name="audioFormatsList"> + <xs:list itemType="audioFormat" /> + </xs:simpleType> + <xs:complexType name="surroundFormats"> + <xs:sequence> + <xs:element name="format" minOccurs="0" maxOccurs="unbounded"> + <xs:complexType> + <xs:attribute name="name" type="audioFormat" use="required"/> + <xs:attribute name="subformats" type="audioFormatsList" /> + </xs:complexType> + </xs:element> + </xs:sequence> + </xs:complexType> +</xs:schema> diff --git a/audio/6.0/types.hal b/audio/6.0/types.hal new file mode 100644 index 0000000000..1a704f8b32 --- /dev/null +++ b/audio/6.0/types.hal @@ -0,0 +1,249 @@ +/* + * 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.hardware.audio@6.0; + +import android.hardware.audio.common@6.0; + +enum Result : int32_t { + OK, + NOT_INITIALIZED, + INVALID_ARGUMENTS, + INVALID_STATE, + /** + * Methods marked as "Optional method" must return this result value + * if the operation is not supported by HAL. + */ + NOT_SUPPORTED +}; + +@export(name="audio_drain_type_t", value_prefix="AUDIO_DRAIN_") +enum AudioDrain : int32_t { + /** drain() returns when all data has been played. */ + ALL, + /** + * drain() returns a short time before all data from the current track has + * been played to give time for gapless track switch. + */ + EARLY_NOTIFY +}; + +/** + * A substitute for POSIX timespec. + */ +struct TimeSpec { + uint64_t tvSec; // seconds + uint64_t tvNSec; // nanoseconds +}; + +struct ParameterValue { + string key; + string value; +}; + +enum MmapBufferFlag : uint32_t { + NONE = 0x0, + /** + * If the buffer can be securely shared to untrusted applications + * through the AAudio exclusive mode. + * Only set this flag if applications are restricted from accessing the + * memory surrounding the audio data buffer by a kernel mechanism. + * See Linux kernel's dma_buf. + */ + APPLICATION_SHAREABLE = 0x1, +}; + +/** + * Mmap buffer descriptor returned by IStream.createMmapBuffer(). + * Used by streams opened in mmap mode. + */ +struct MmapBufferInfo { + /** Mmap memory buffer */ + memory sharedMemory; + /** Total buffer size in frames */ + uint32_t bufferSizeFrames; + /** Transfer size granularity in frames */ + uint32_t burstSizeFrames; + /** Attributes describing the buffer. */ + bitfield<MmapBufferFlag> flags; +}; + +/** + * Mmap buffer read/write position returned by IStream.getMmapPosition(). + * Used by streams opened in mmap mode. + */ +struct MmapPosition { + int64_t timeNanoseconds; // time stamp in ns, CLOCK_MONOTONIC + int32_t positionFrames; // increasing 32 bit frame count reset when IStream.stop() is called +}; + +/** + * The message queue flags used to synchronize reads and writes from + * message queues used by StreamIn and StreamOut. + */ +enum MessageQueueFlagBits : uint32_t { + NOT_EMPTY = 1 << 0, + NOT_FULL = 1 << 1 +}; + +/* + * Microphone information + * + */ + +/** + * A 3D point used to represent position or orientation of a microphone. + * + * Position: Coordinates of the microphone's capsule, in meters, from the + * bottom-left-back corner of the bounding box of android device in natural + * orientation (PORTRAIT for phones, LANDSCAPE for tablets, tvs, etc). + * The orientation musth match the reported by the api Display.getRotation(). + * + * Orientation: Normalized vector to signal the main orientation of the + * microphone's capsule. Magnitude = sqrt(x^2 + y^2 + z^2) = 1 + */ +struct AudioMicrophoneCoordinate { + float x; + float y; + float z; +}; + +/** + * Enum to identify the type of channel mapping for active microphones. + * Used channels further identify if the microphone has any significative + * process (e.g. High Pass Filtering, dynamic compression) + * Simple processing as constant gain adjustment must be DIRECT. + */ +enum AudioMicrophoneChannelMapping : uint32_t { + UNUSED = 0, /* Channel not used */ + DIRECT = 1, /* Channel used and signal not processed */ + PROCESSED = 2, /* Channel used and signal has some process */ +}; + +/** + * Enum to identify locations of microphones in regards to the body of the + * android device. + */ +enum AudioMicrophoneLocation : uint32_t { + UNKNOWN = 0, + MAINBODY = 1, + MAINBODY_MOVABLE = 2, + PERIPHERAL = 3, +}; + +/** + * Identifier to help group related microphones together + * e.g. microphone arrays should belong to the same group + */ +typedef int32_t AudioMicrophoneGroup; + +/** + * Enum with standard polar patterns of microphones + */ +enum AudioMicrophoneDirectionality : uint32_t { + UNKNOWN = 0, + OMNI = 1, + BI_DIRECTIONAL = 2, + CARDIOID = 3, + HYPER_CARDIOID = 4, + SUPER_CARDIOID = 5, +}; + +/** + * A (frequency, level) pair. Used to represent frequency response. + */ +struct AudioFrequencyResponsePoint { + /** In Hz */ + float frequency; + /** In dB */ + float level; +}; + +/** + * Structure used by the HAL to describe microphone's characteristics + * Used by StreamIn and Device + */ +struct MicrophoneInfo { + /** Unique alphanumeric id for microphone. Guaranteed to be the same + * even after rebooting. + */ + string deviceId; + /** + * Device specific information + */ + DeviceAddress deviceAddress; + /** Each element of the vector must describe the channel with the same + * index. + */ + vec<AudioMicrophoneChannelMapping> channelMapping; + /** Location of the microphone in regard to the body of the device */ + AudioMicrophoneLocation location; + /** Identifier to help group related microphones together + * e.g. microphone arrays should belong to the same group + */ + AudioMicrophoneGroup group; + /** Index of this microphone within the group. + * (group, index) must be unique within the same device. + */ + uint32_t indexInTheGroup; + /** Level in dBFS produced by a 1000 Hz tone at 94 dB SPL */ + float sensitivity; + /** Level in dB of the max SPL supported at 1000 Hz */ + float maxSpl; + /** Level in dB of the min SPL supported at 1000 Hz */ + float minSpl; + /** Standard polar pattern of the microphone */ + AudioMicrophoneDirectionality directionality; + /** Vector with ordered frequency responses (from low to high frequencies) + * with the frequency response of the microphone. + * Levels are in dB, relative to level at 1000 Hz + */ + vec<AudioFrequencyResponsePoint> frequencyResponse; + /** Position of the microphone's capsule in meters, from the + * bottom-left-back corner of the bounding box of device. + */ + AudioMicrophoneCoordinate position; + /** Normalized point to signal the main orientation of the microphone's + * capsule. sqrt(x^2 + y^2 + z^2) = 1 + */ + AudioMicrophoneCoordinate orientation; +}; + +/** + * Constants used by the HAL to determine how to select microphones and process those inputs in + * order to optimize for capture in the specified direction. + * + * MicrophoneDirection Constants are defined in MicrophoneDirection.java. + */ +@export(name="audio_microphone_direction_t", value_prefix="MIC_DIRECTION_") +enum MicrophoneDirection : int32_t { + /** + * Don't do any directionality processing of the activated microphone(s). + */ + UNSPECIFIED = 0, + /** + * Optimize capture for audio coming from the screen-side of the device. + */ + FRONT = 1, + /** + * Optimize capture for audio coming from the side of the device opposite the screen. + */ + BACK = 2, + /** + * Optimize capture for audio coming from an off-device microphone. + */ + EXTERNAL = 3, +}; diff --git a/audio/common/6.0/Android.bp b/audio/common/6.0/Android.bp new file mode 100644 index 0000000000..1a4e0548ea --- /dev/null +++ b/audio/common/6.0/Android.bp @@ -0,0 +1,17 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.audio.common@6.0", + root: "android.hardware", + vndk: { + enabled: true, + }, + srcs: [ + "types.hal", + ], + interfaces: [ + "android.hidl.safe_union@1.0", + ], + gen_java: false, + gen_java_constants: true, +} diff --git a/audio/common/6.0/types.hal b/audio/common/6.0/types.hal new file mode 100644 index 0000000000..132f86dc4e --- /dev/null +++ b/audio/common/6.0/types.hal @@ -0,0 +1,1032 @@ +/* + * 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.hardware.audio.common@6.0; + +import android.hidl.safe_union@1.0; + +/* + * + * IDs and Handles + * + */ + +/** + * Handle type for identifying audio sources and sinks. + */ +typedef int32_t AudioIoHandle; + +/** + * Audio hw module handle functions or structures referencing a module. + */ +typedef int32_t AudioModuleHandle; + +/** + * Each port has a unique ID or handle allocated by policy manager. + */ +typedef int32_t AudioPortHandle; + +/** + * Each patch is identified by a handle at the interface used to create that + * patch. For instance, when a patch is created by the audio HAL, the HAL + * allocates and returns a handle. This handle is unique to a given audio HAL + * hardware module. But the same patch receives another system wide unique + * handle allocated by the framework. This unique handle is used for all + * transactions inside the framework. + */ +typedef int32_t AudioPatchHandle; + +/** + * A HW synchronization source returned by the audio HAL. + */ +typedef uint32_t AudioHwSync; + +/** + * Each port has a unique ID or handle allocated by policy manager. + */ +@export(name="") +enum AudioHandleConsts : int32_t { + AUDIO_IO_HANDLE_NONE = 0, + AUDIO_MODULE_HANDLE_NONE = 0, + AUDIO_PORT_HANDLE_NONE = 0, + AUDIO_PATCH_HANDLE_NONE = 0, +}; + +/** + * Commonly used structure for passing unique identifieds (UUID). + * For the definition of UUID, refer to ITU-T X.667 spec. + */ +struct Uuid { + uint32_t timeLow; + uint16_t timeMid; + uint16_t versionAndTimeHigh; + uint16_t variantAndClockSeqHigh; + uint8_t[6] node; +}; + + +/* + * + * Audio streams + * + */ + +/** + * Audio stream type describing the intended use case of a stream. + */ +@export(name="audio_stream_type_t", value_prefix="AUDIO_STREAM_") +enum AudioStreamType : int32_t { + // These values must kept in sync with + // frameworks/base/media/java/android/media/AudioSystem.java + DEFAULT = -1, + MIN = 0, + VOICE_CALL = 0, + SYSTEM = 1, + RING = 2, + MUSIC = 3, + ALARM = 4, + NOTIFICATION = 5, + BLUETOOTH_SCO = 6, + ENFORCED_AUDIBLE = 7, // Sounds that cannot be muted by user and must be + // routed to speaker + DTMF = 8, + TTS = 9, // Transmitted Through Speaker. Plays over speaker + // only, silent on other devices + ACCESSIBILITY = 10, // For accessibility talk back prompts +}; + +@export(name="audio_source_t", value_prefix="AUDIO_SOURCE_") +enum AudioSource : int32_t { + // These values must kept in sync with + // frameworks/base/media/java/android/media/MediaRecorder.java, + // frameworks/av/services/audiopolicy/AudioPolicyService.cpp, + // system/media/audio_effects/include/audio_effects/audio_effects_conf.h + DEFAULT = 0, + MIC = 1, + VOICE_UPLINK = 2, + VOICE_DOWNLINK = 3, + VOICE_CALL = 4, + CAMCORDER = 5, + VOICE_RECOGNITION = 6, + VOICE_COMMUNICATION = 7, + /** + * Source for the mix to be presented remotely. An example of remote + * presentation is Wifi Display where a dongle attached to a TV can be used + * to play the mix captured by this audio source. + */ + REMOTE_SUBMIX = 8, + /** + * Source for unprocessed sound. Usage examples include level measurement + * and raw signal analysis. + */ + UNPROCESSED = 9, + /** + * Source for capturing audio meant to be processed in real time and played back for live + * performance (e.g karaoke). The capture path will minimize latency and coupling with + * playback path. + */ + VOICE_PERFORMANCE = 10, + /** + * Source for an echo canceller to capture the reference signal to be cancelled. + * The echo reference signal will be captured as close as possible to the DAC in order + * to include all post processing applied to the playback path. + */ + ECHO_REFERENCE = 1997, + FM_TUNER = 1998, + HOTWORD = 1999, +}; + +typedef int32_t AudioSession; +/** + * Special audio session values. + */ +@export(name="audio_session_t", value_prefix="AUDIO_SESSION_") +enum AudioSessionConsts : int32_t { + /** + * Session for effects attached to a particular output stream + * (value must be less than 0) + */ + OUTPUT_STAGE = -1, + /** + * Session for effects applied to output mix. These effects can + * be moved by audio policy manager to another output stream + * (value must be 0) + */ + OUTPUT_MIX = 0, + /** + * Application does not specify an explicit session ID to be used, and + * requests a new session ID to be allocated. Corresponds to + * AudioManager.AUDIO_SESSION_ID_GENERATE and + * AudioSystem.AUDIO_SESSION_ALLOCATE. + */ + ALLOCATE = 0, + /** + * For use with AudioRecord::start(), this indicates no trigger session. + * It is also used with output tracks and patch tracks, which never have a + * session. + */ + NONE = 0 +}; + +/** + * Audio format is a 32-bit word that consists of: + * main format field (upper 8 bits) + * sub format field (lower 24 bits). + * + * The main format indicates the main codec type. The sub format field indicates + * options and parameters for each format. The sub format is mainly used for + * record to indicate for instance the requested bitrate or profile. It can + * also be used for certain formats to give informations not present in the + * encoded audio stream (e.g. octet alignement for AMR). + */ +@export(name="audio_format_t", value_prefix="AUDIO_FORMAT_") +enum AudioFormat : uint32_t { + INVALID = 0xFFFFFFFFUL, + DEFAULT = 0, + PCM = 0x00000000UL, + MP3 = 0x01000000UL, + AMR_NB = 0x02000000UL, + AMR_WB = 0x03000000UL, + AAC = 0x04000000UL, + /** Deprecated, Use AAC_HE_V1 */ + HE_AAC_V1 = 0x05000000UL, + /** Deprecated, Use AAC_HE_V2 */ + HE_AAC_V2 = 0x06000000UL, + VORBIS = 0x07000000UL, + OPUS = 0x08000000UL, + AC3 = 0x09000000UL, + E_AC3 = 0x0A000000UL, + DTS = 0x0B000000UL, + DTS_HD = 0x0C000000UL, + /** IEC61937 is encoded audio wrapped in 16-bit PCM. */ + IEC61937 = 0x0D000000UL, + DOLBY_TRUEHD = 0x0E000000UL, + EVRC = 0x10000000UL, + EVRCB = 0x11000000UL, + EVRCWB = 0x12000000UL, + EVRCNW = 0x13000000UL, + AAC_ADIF = 0x14000000UL, + WMA = 0x15000000UL, + WMA_PRO = 0x16000000UL, + AMR_WB_PLUS = 0x17000000UL, + MP2 = 0x18000000UL, + QCELP = 0x19000000UL, + DSD = 0x1A000000UL, + FLAC = 0x1B000000UL, + ALAC = 0x1C000000UL, + APE = 0x1D000000UL, + AAC_ADTS = 0x1E000000UL, + SBC = 0x1F000000UL, + APTX = 0x20000000UL, + APTX_HD = 0x21000000UL, + AC4 = 0x22000000UL, + LDAC = 0x23000000UL, + /** Dolby Metadata-enhanced Audio Transmission */ + MAT = 0x24000000UL, + AAC_LATM = 0x25000000UL, + CELT = 0x26000000UL, + APTX_ADAPTIVE = 0x27000000UL, + LHDC = 0x28000000UL, + LHDC_LL = 0x29000000UL, + APTX_TWSP = 0x2A000000UL, + + /** Deprecated */ + MAIN_MASK = 0xFF000000UL, + SUB_MASK = 0x00FFFFFFUL, + + /* Subformats */ + PCM_SUB_16_BIT = 0x1, // PCM signed 16 bits + PCM_SUB_8_BIT = 0x2, // PCM unsigned 8 bits + PCM_SUB_32_BIT = 0x3, // PCM signed .31 fixed point + PCM_SUB_8_24_BIT = 0x4, // PCM signed 8.23 fixed point + PCM_SUB_FLOAT = 0x5, // PCM single-precision float pt + PCM_SUB_24_BIT_PACKED = 0x6, // PCM signed .23 fix pt (3 bytes) + + MP3_SUB_NONE = 0x0, + + AMR_SUB_NONE = 0x0, + + AAC_SUB_MAIN = 0x1, + AAC_SUB_LC = 0x2, + AAC_SUB_SSR = 0x4, + AAC_SUB_LTP = 0x8, + AAC_SUB_HE_V1 = 0x10, + AAC_SUB_SCALABLE = 0x20, + AAC_SUB_ERLC = 0x40, + AAC_SUB_LD = 0x80, + AAC_SUB_HE_V2 = 0x100, + AAC_SUB_ELD = 0x200, + AAC_SUB_XHE = 0x300, + + VORBIS_SUB_NONE = 0x0, + + E_AC3_SUB_JOC = 0x1, + + MAT_SUB_1_0 = 0x1, + MAT_SUB_2_0 = 0x2, + MAT_SUB_2_1 = 0x3, + + /* Aliases */ + /** note != AudioFormat.ENCODING_PCM_16BIT */ + PCM_16_BIT = (PCM | PCM_SUB_16_BIT), + /** note != AudioFormat.ENCODING_PCM_8BIT */ + PCM_8_BIT = (PCM | PCM_SUB_8_BIT), + PCM_32_BIT = (PCM | PCM_SUB_32_BIT), + PCM_8_24_BIT = (PCM | PCM_SUB_8_24_BIT), + PCM_FLOAT = (PCM | PCM_SUB_FLOAT), + PCM_24_BIT_PACKED = (PCM | PCM_SUB_24_BIT_PACKED), + AAC_MAIN = (AAC | AAC_SUB_MAIN), + AAC_LC = (AAC | AAC_SUB_LC), + AAC_SSR = (AAC | AAC_SUB_SSR), + AAC_LTP = (AAC | AAC_SUB_LTP), + AAC_HE_V1 = (AAC | AAC_SUB_HE_V1), + AAC_SCALABLE = (AAC | AAC_SUB_SCALABLE), + AAC_ERLC = (AAC | AAC_SUB_ERLC), + AAC_LD = (AAC | AAC_SUB_LD), + AAC_HE_V2 = (AAC | AAC_SUB_HE_V2), + AAC_ELD = (AAC | AAC_SUB_ELD), + AAC_XHE = (AAC | AAC_SUB_XHE), + AAC_ADTS_MAIN = (AAC_ADTS | AAC_SUB_MAIN), + AAC_ADTS_LC = (AAC_ADTS | AAC_SUB_LC), + AAC_ADTS_SSR = (AAC_ADTS | AAC_SUB_SSR), + AAC_ADTS_LTP = (AAC_ADTS | AAC_SUB_LTP), + AAC_ADTS_HE_V1 = (AAC_ADTS | AAC_SUB_HE_V1), + AAC_ADTS_SCALABLE = (AAC_ADTS | AAC_SUB_SCALABLE), + AAC_ADTS_ERLC = (AAC_ADTS | AAC_SUB_ERLC), + AAC_ADTS_LD = (AAC_ADTS | AAC_SUB_LD), + AAC_ADTS_HE_V2 = (AAC_ADTS | AAC_SUB_HE_V2), + AAC_ADTS_ELD = (AAC_ADTS | AAC_SUB_ELD), + AAC_ADTS_XHE = (AAC_ADTS | AAC_SUB_XHE), + E_AC3_JOC = (E_AC3 | E_AC3_SUB_JOC), + MAT_1_0 = (MAT | MAT_SUB_1_0), + MAT_2_0 = (MAT | MAT_SUB_2_0), + MAT_2_1 = (MAT | MAT_SUB_2_1), + AAC_LATM_LC = (AAC_LATM | AAC_SUB_LC), + AAC_LATM_HE_V1 = (AAC_LATM | AAC_SUB_HE_V1), + AAC_LATM_HE_V2 = (AAC_LATM | AAC_SUB_HE_V2), +}; + +/** + * Usage of these values highlights places in the code that use 2- or 8- channel + * assumptions. + */ +@export(name="") +enum FixedChannelCount : int32_t { + FCC_2 = 2, // This is typically due to legacy implementation of stereo I/O + FCC_8 = 8 // This is typically due to audio mixer and resampler limitations +}; + +/** + * A channel mask per se only defines the presence or absence of a channel, not + * the order. + * + * The channel order convention is that channels are interleaved in order from + * least significant channel mask bit to most significant channel mask bit, + * with unused bits skipped. For example for stereo, LEFT would be first, + * followed by RIGHT. + * Any exceptions to this convention are noted at the appropriate API. + * + * AudioChannelMask is an opaque type and its internal layout should not be + * assumed as it may change in the future. Instead, always use functions + * to examine it. + * + * These are the current representations: + * + * REPRESENTATION_POSITION + * is a channel mask representation for position assignment. Each low-order + * bit corresponds to the spatial position of a transducer (output), or + * interpretation of channel (input). The user of a channel mask needs to + * know the context of whether it is for output or input. The constants + * OUT_* or IN_* apply to the bits portion. It is not permitted for no bits + * to be set. + * + * REPRESENTATION_INDEX + * is a channel mask representation for index assignment. Each low-order + * bit corresponds to a selected channel. There is no platform + * interpretation of the various bits. There is no concept of output or + * input. It is not permitted for no bits to be set. + * + * All other representations are reserved for future use. + * + * Warning: current representation distinguishes between input and output, but + * this will not the be case in future revisions of the platform. Wherever there + * is an ambiguity between input and output that is currently resolved by + * checking the channel mask, the implementer should look for ways to fix it + * with additional information outside of the mask. + */ +@export(name="", value_prefix="AUDIO_CHANNEL_") +enum AudioChannelMask : uint32_t { + /** must be 0 for compatibility */ + REPRESENTATION_POSITION = 0, + /** 1 is reserved for future use */ + REPRESENTATION_INDEX = 2, + /* 3 is reserved for future use */ + + /** These can be a complete value of AudioChannelMask */ + NONE = 0x0, + INVALID = 0xC0000000, + + /* + * These can be the bits portion of an AudioChannelMask + * with representation REPRESENTATION_POSITION. + */ + + /** output channels */ + OUT_FRONT_LEFT = 0x1, + OUT_FRONT_RIGHT = 0x2, + OUT_FRONT_CENTER = 0x4, + OUT_LOW_FREQUENCY = 0x8, + OUT_BACK_LEFT = 0x10, + OUT_BACK_RIGHT = 0x20, + OUT_FRONT_LEFT_OF_CENTER = 0x40, + OUT_FRONT_RIGHT_OF_CENTER = 0x80, + OUT_BACK_CENTER = 0x100, + OUT_SIDE_LEFT = 0x200, + OUT_SIDE_RIGHT = 0x400, + OUT_TOP_CENTER = 0x800, + OUT_TOP_FRONT_LEFT = 0x1000, + OUT_TOP_FRONT_CENTER = 0x2000, + OUT_TOP_FRONT_RIGHT = 0x4000, + OUT_TOP_BACK_LEFT = 0x8000, + OUT_TOP_BACK_CENTER = 0x10000, + OUT_TOP_BACK_RIGHT = 0x20000, + OUT_TOP_SIDE_LEFT = 0x40000, + OUT_TOP_SIDE_RIGHT = 0x80000, + + /** + * Haptic channel characteristics are specific to a device and + * only used to play device specific resources (eg: ringtones). + * The HAL can freely map A and B to haptic controllers, the + * framework shall not interpret those values and forward them + * from the device audio assets. + */ + OUT_HAPTIC_A = 0x20000000, + OUT_HAPTIC_B = 0x10000000, + + OUT_MONO = OUT_FRONT_LEFT, + OUT_STEREO = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT), + OUT_2POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_LOW_FREQUENCY), + OUT_2POINT0POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | + OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT), + OUT_2POINT1POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | + OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT | + OUT_LOW_FREQUENCY), + OUT_3POINT0POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | + OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT), + OUT_3POINT1POINT2 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_FRONT_CENTER | + OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT | + OUT_LOW_FREQUENCY), + OUT_QUAD = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | + OUT_BACK_LEFT | OUT_BACK_RIGHT), + OUT_QUAD_BACK = OUT_QUAD, + /** like OUT_QUAD_BACK with *_SIDE_* instead of *_BACK_* */ + OUT_QUAD_SIDE = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | + OUT_SIDE_LEFT | OUT_SIDE_RIGHT), + OUT_SURROUND = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | + OUT_FRONT_CENTER | OUT_BACK_CENTER), + OUT_PENTA = (OUT_QUAD | OUT_FRONT_CENTER), + OUT_5POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | + OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | + OUT_BACK_LEFT | OUT_BACK_RIGHT), + OUT_5POINT1_BACK = OUT_5POINT1, + /** like OUT_5POINT1_BACK with *_SIDE_* instead of *_BACK_* */ + OUT_5POINT1_SIDE = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | + OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | + OUT_SIDE_LEFT | OUT_SIDE_RIGHT), + OUT_5POINT1POINT2 = (OUT_5POINT1 | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT), + OUT_5POINT1POINT4 = (OUT_5POINT1 | + OUT_TOP_FRONT_LEFT | OUT_TOP_FRONT_RIGHT | + OUT_TOP_BACK_LEFT | OUT_TOP_BACK_RIGHT), + OUT_6POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | + OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | + OUT_BACK_LEFT | OUT_BACK_RIGHT | + OUT_BACK_CENTER), + /** matches the correct AudioFormat.CHANNEL_OUT_7POINT1_SURROUND */ + OUT_7POINT1 = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | + OUT_FRONT_CENTER | OUT_LOW_FREQUENCY | + OUT_BACK_LEFT | OUT_BACK_RIGHT | + OUT_SIDE_LEFT | OUT_SIDE_RIGHT), + OUT_7POINT1POINT2 = (OUT_7POINT1 | OUT_TOP_SIDE_LEFT | OUT_TOP_SIDE_RIGHT), + OUT_7POINT1POINT4 = (OUT_7POINT1 | + OUT_TOP_FRONT_LEFT | OUT_TOP_FRONT_RIGHT | + OUT_TOP_BACK_LEFT | OUT_TOP_BACK_RIGHT), + OUT_MONO_HAPTIC_A = (OUT_FRONT_LEFT | OUT_HAPTIC_A), + OUT_STEREO_HAPTIC_A = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | OUT_HAPTIC_A), + OUT_HAPTIC_AB = (OUT_HAPTIC_A | OUT_HAPTIC_B), + OUT_MONO_HAPTIC_AB = (OUT_FRONT_LEFT | OUT_HAPTIC_A | OUT_HAPTIC_B), + OUT_STEREO_HAPTIC_AB = (OUT_FRONT_LEFT | OUT_FRONT_RIGHT | + OUT_HAPTIC_A | OUT_HAPTIC_B), + // Note that the 2.0 OUT_ALL* have been moved to helper functions + + /* These are bits only, not complete values */ + + /** input channels */ + IN_LEFT = 0x4, + IN_RIGHT = 0x8, + IN_FRONT = 0x10, + IN_BACK = 0x20, + IN_LEFT_PROCESSED = 0x40, + IN_RIGHT_PROCESSED = 0x80, + IN_FRONT_PROCESSED = 0x100, + IN_BACK_PROCESSED = 0x200, + IN_PRESSURE = 0x400, + IN_X_AXIS = 0x800, + IN_Y_AXIS = 0x1000, + IN_Z_AXIS = 0x2000, + IN_BACK_LEFT = 0x10000, + IN_BACK_RIGHT = 0x20000, + IN_CENTER = 0x40000, + IN_LOW_FREQUENCY = 0x100000, + IN_TOP_LEFT = 0x200000, + IN_TOP_RIGHT = 0x400000, + + IN_VOICE_UPLINK = 0x4000, + IN_VOICE_DNLINK = 0x8000, + + IN_MONO = IN_FRONT, + IN_STEREO = (IN_LEFT | IN_RIGHT), + IN_FRONT_BACK = (IN_FRONT | IN_BACK), + IN_6 = (IN_LEFT | IN_RIGHT | + IN_FRONT | IN_BACK | + IN_LEFT_PROCESSED | IN_RIGHT_PROCESSED), + IN_2POINT0POINT2 = (IN_LEFT | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT), + IN_2POINT1POINT2 = (IN_LEFT | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT | + IN_LOW_FREQUENCY), + IN_3POINT0POINT2 = (IN_LEFT | IN_CENTER | IN_RIGHT | IN_TOP_LEFT | IN_TOP_RIGHT), + IN_3POINT1POINT2 = (IN_LEFT | IN_CENTER | IN_RIGHT | + IN_TOP_LEFT | IN_TOP_RIGHT | IN_LOW_FREQUENCY), + IN_5POINT1 = (IN_LEFT | IN_CENTER | IN_RIGHT | + IN_BACK_LEFT | IN_BACK_RIGHT | IN_LOW_FREQUENCY), + IN_VOICE_UPLINK_MONO = (IN_VOICE_UPLINK | IN_MONO), + IN_VOICE_DNLINK_MONO = (IN_VOICE_DNLINK | IN_MONO), + IN_VOICE_CALL_MONO = (IN_VOICE_UPLINK_MONO | + IN_VOICE_DNLINK_MONO), + // Note that the 2.0 IN_ALL* have been moved to helper functions + + COUNT_MAX = 30, + INDEX_HDR = REPRESENTATION_INDEX << COUNT_MAX, + INDEX_MASK_1 = INDEX_HDR | ((1 << 1) - 1), + INDEX_MASK_2 = INDEX_HDR | ((1 << 2) - 1), + INDEX_MASK_3 = INDEX_HDR | ((1 << 3) - 1), + INDEX_MASK_4 = INDEX_HDR | ((1 << 4) - 1), + INDEX_MASK_5 = INDEX_HDR | ((1 << 5) - 1), + INDEX_MASK_6 = INDEX_HDR | ((1 << 6) - 1), + INDEX_MASK_7 = INDEX_HDR | ((1 << 7) - 1), + INDEX_MASK_8 = INDEX_HDR | ((1 << 8) - 1), + INDEX_MASK_9 = INDEX_HDR | ((1 << 9) - 1), + INDEX_MASK_10 = INDEX_HDR | ((1 << 10) - 1), + INDEX_MASK_11 = INDEX_HDR | ((1 << 11) - 1), + INDEX_MASK_12 = INDEX_HDR | ((1 << 12) - 1), + INDEX_MASK_13 = INDEX_HDR | ((1 << 13) - 1), + INDEX_MASK_14 = INDEX_HDR | ((1 << 14) - 1), + INDEX_MASK_15 = INDEX_HDR | ((1 << 15) - 1), + INDEX_MASK_16 = INDEX_HDR | ((1 << 16) - 1), + INDEX_MASK_17 = INDEX_HDR | ((1 << 17) - 1), + INDEX_MASK_18 = INDEX_HDR | ((1 << 18) - 1), + INDEX_MASK_19 = INDEX_HDR | ((1 << 19) - 1), + INDEX_MASK_20 = INDEX_HDR | ((1 << 20) - 1), + INDEX_MASK_21 = INDEX_HDR | ((1 << 21) - 1), + INDEX_MASK_22 = INDEX_HDR | ((1 << 22) - 1), + INDEX_MASK_23 = INDEX_HDR | ((1 << 23) - 1), + INDEX_MASK_24 = INDEX_HDR | ((1 << 24) - 1), +}; + +/** + * Major modes for a mobile device. The current mode setting affects audio + * routing. + */ +@export(name="audio_mode_t", value_prefix="AUDIO_MODE_") +enum AudioMode : int32_t { + NORMAL = 0, + RINGTONE = 1, + /** Calls handled by the telephony stack (Eg: PSTN). */ + IN_CALL = 2, + /** Calls handled by apps (Eg: Hangout). */ + IN_COMMUNICATION = 3, +}; + +@export(name="", value_prefix="AUDIO_DEVICE_") +enum AudioDevice : uint32_t { + NONE = 0x0, + /** reserved bits */ + BIT_IN = 0x80000000, + BIT_DEFAULT = 0x40000000, + /** output devices */ + OUT_EARPIECE = 0x1, + OUT_SPEAKER = 0x2, + OUT_WIRED_HEADSET = 0x4, + OUT_WIRED_HEADPHONE = 0x8, + OUT_BLUETOOTH_SCO = 0x10, + OUT_BLUETOOTH_SCO_HEADSET = 0x20, + OUT_BLUETOOTH_SCO_CARKIT = 0x40, + OUT_BLUETOOTH_A2DP = 0x80, + OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100, + OUT_BLUETOOTH_A2DP_SPEAKER = 0x200, + OUT_AUX_DIGITAL = 0x400, + OUT_HDMI = OUT_AUX_DIGITAL, + /** uses an analog connection (multiplexed over the USB pins for instance) */ + OUT_ANLG_DOCK_HEADSET = 0x800, + OUT_DGTL_DOCK_HEADSET = 0x1000, + /** USB accessory mode: Android device is USB device and dock is USB host */ + OUT_USB_ACCESSORY = 0x2000, + /** USB host mode: Android device is USB host and dock is USB device */ + OUT_USB_DEVICE = 0x4000, + OUT_REMOTE_SUBMIX = 0x8000, + /** Telephony voice TX path */ + OUT_TELEPHONY_TX = 0x10000, + /** Analog jack with line impedance detected */ + OUT_LINE = 0x20000, + /** HDMI Audio Return Channel */ + OUT_HDMI_ARC = 0x40000, + /** S/PDIF out */ + OUT_SPDIF = 0x80000, + /** FM transmitter out */ + OUT_FM = 0x100000, + /** Line out for av devices */ + OUT_AUX_LINE = 0x200000, + /** limited-output speaker device for acoustic safety */ + OUT_SPEAKER_SAFE = 0x400000, + OUT_IP = 0x800000, + /** audio bus implemented by the audio system (e.g an MOST stereo channel) */ + OUT_BUS = 0x1000000, + OUT_PROXY = 0x2000000, + OUT_USB_HEADSET = 0x4000000, + OUT_HEARING_AID = 0x8000000, + OUT_ECHO_CANCELLER = 0x10000000, + OUT_DEFAULT = BIT_DEFAULT, + // Note that the 2.0 OUT_ALL* have been moved to helper functions + + /** input devices */ + IN_COMMUNICATION = BIT_IN | 0x1, + IN_AMBIENT = BIT_IN | 0x2, + IN_BUILTIN_MIC = BIT_IN | 0x4, + IN_BLUETOOTH_SCO_HEADSET = BIT_IN | 0x8, + IN_WIRED_HEADSET = BIT_IN | 0x10, + IN_AUX_DIGITAL = BIT_IN | 0x20, + IN_HDMI = IN_AUX_DIGITAL, + /** Telephony voice RX path */ + IN_VOICE_CALL = BIT_IN | 0x40, + IN_TELEPHONY_RX = IN_VOICE_CALL, + IN_BACK_MIC = BIT_IN | 0x80, + IN_REMOTE_SUBMIX = BIT_IN | 0x100, + IN_ANLG_DOCK_HEADSET = BIT_IN | 0x200, + IN_DGTL_DOCK_HEADSET = BIT_IN | 0x400, + IN_USB_ACCESSORY = BIT_IN | 0x800, + IN_USB_DEVICE = BIT_IN | 0x1000, + /** FM tuner input */ + IN_FM_TUNER = BIT_IN | 0x2000, + /** TV tuner input */ + IN_TV_TUNER = BIT_IN | 0x4000, + /** Analog jack with line impedance detected */ + IN_LINE = BIT_IN | 0x8000, + /** S/PDIF in */ + IN_SPDIF = BIT_IN | 0x10000, + IN_BLUETOOTH_A2DP = BIT_IN | 0x20000, + IN_LOOPBACK = BIT_IN | 0x40000, + IN_IP = BIT_IN | 0x80000, + /** audio bus implemented by the audio system (e.g an MOST stereo channel) */ + IN_BUS = BIT_IN | 0x100000, + IN_PROXY = BIT_IN | 0x1000000, + IN_USB_HEADSET = BIT_IN | 0x2000000, + IN_BLUETOOTH_BLE = BIT_IN | 0x4000000, + IN_ECHO_REFERENCE = BIT_IN | 0x10000000, + IN_DEFAULT = BIT_IN | BIT_DEFAULT, + + // Note that the 2.0 IN_ALL* have been moved to helper functions +}; + +/** + * IEEE 802 MAC address. + */ +typedef uint8_t[6] MacAddress; + +/** + * Specifies a device address in case when several devices of the same type + * can be connected (e.g. BT A2DP, USB). + */ +struct DeviceAddress { + AudioDevice device; // discriminator + union Address { + MacAddress mac; // used for BLUETOOTH_A2DP_* + uint8_t[4] ipv4; // used for IP + struct Alsa { + int32_t card; + int32_t device; + } alsa; // used for USB_* + } address; + /** Arbitrary BUS device unique address. Should not be interpreted by the framework. */ + string busAddress; + /** Arbitrary REMOTE_SUBMIX device unique address. Should not be interpreted by the HAL. */ + string rSubmixAddress; +}; + +/** + * The audio output flags serve two purposes: + * + * - when an AudioTrack is created they indicate a "wish" to be connected to an + * output stream with attributes corresponding to the specified flags; + * + * - when present in an output profile descriptor listed for a particular audio + * hardware module, they indicate that an output stream can be opened that + * supports the attributes indicated by the flags. + * + * The audio policy manager will try to match the flags in the request + * (when getOuput() is called) to an available output stream. + */ +@export(name="audio_output_flags_t", value_prefix="AUDIO_OUTPUT_FLAG_") +enum AudioOutputFlag : int32_t { + NONE = 0x0, // no attributes + DIRECT = 0x1, // this output directly connects a track + // to one output stream: no software mixer + PRIMARY = 0x2, // this output is the primary output of the device. It is + // unique and must be present. It is opened by default and + // receives routing, audio mode and volume controls related + // to voice calls. + FAST = 0x4, // output supports "fast tracks", defined elsewhere + DEEP_BUFFER = 0x8, // use deep audio buffers + COMPRESS_OFFLOAD = 0x10, // offload playback of compressed streams to + // hardware codec + NON_BLOCKING = 0x20, // use non-blocking write + HW_AV_SYNC = 0x40, // output uses a hardware A/V sync + TTS = 0x80, // output for streams transmitted through speaker at a + // sample rate high enough to accommodate lower-range + // ultrasonic p/b + RAW = 0x100, // minimize signal processing + SYNC = 0x200, // synchronize I/O streams + IEC958_NONAUDIO = 0x400, // Audio stream contains compressed audio in SPDIF + // data bursts, not PCM. + DIRECT_PCM = 0x2000, // Audio stream containing PCM data that needs + // to pass through compress path for DSP post proc. + MMAP_NOIRQ = 0x4000, // output operates in MMAP no IRQ mode. + VOIP_RX = 0x8000, // preferred output for VoIP calls. + /** preferred output for call music */ + INCALL_MUSIC = 0x10000, +}; + +/** + * The audio input flags are analogous to audio output flags. + * Currently they are used only when an AudioRecord is created, + * to indicate a preference to be connected to an input stream with + * attributes corresponding to the specified flags. + */ +@export(name="audio_input_flags_t", value_prefix="AUDIO_INPUT_FLAG_") +enum AudioInputFlag : int32_t { + NONE = 0x0, // no attributes + FAST = 0x1, // prefer an input that supports "fast tracks" + HW_HOTWORD = 0x2, // prefer an input that captures from hw hotword source + RAW = 0x4, // minimize signal processing + SYNC = 0x8, // synchronize I/O streams + MMAP_NOIRQ = 0x10, // input operates in MMAP no IRQ mode. + VOIP_TX = 0x20, // preferred input for VoIP calls. + HW_AV_SYNC = 0x40, // input connected to an output that uses a hardware A/V sync +}; + +@export(name="audio_usage_t", value_prefix="AUDIO_USAGE_") +enum AudioUsage : int32_t { + // These values must kept in sync with + // frameworks/base/media/java/android/media/AudioAttributes.java + // Note that not all framework values are exposed + UNKNOWN = 0, + MEDIA = 1, + VOICE_COMMUNICATION = 2, + VOICE_COMMUNICATION_SIGNALLING = 3, + ALARM = 4, + NOTIFICATION = 5, + NOTIFICATION_TELEPHONY_RINGTONE = 6, + ASSISTANCE_ACCESSIBILITY = 11, + ASSISTANCE_NAVIGATION_GUIDANCE = 12, + ASSISTANCE_SONIFICATION = 13, + GAME = 14, + VIRTUAL_SOURCE = 15, + ASSISTANT = 16, +}; + +/** Type of audio generated by an application. */ +@export(name="audio_content_type_t", value_prefix="AUDIO_CONTENT_TYPE_") +enum AudioContentType : uint32_t { + // Do not change these values without updating their counterparts + // in frameworks/base/media/java/android/media/AudioAttributes.java + UNKNOWN = 0, + SPEECH = 1, + MUSIC = 2, + MOVIE = 3, + SONIFICATION = 4, +}; + +/** + * Additional information about the stream passed to hardware decoders. + */ +struct AudioOffloadInfo { + uint32_t sampleRateHz; + bitfield<AudioChannelMask> channelMask; + AudioFormat format; + AudioStreamType streamType; + uint32_t bitRatePerSecond; + int64_t durationMicroseconds; // -1 if unknown + bool hasVideo; + bool isStreaming; + uint32_t bitWidth; + uint32_t bufferSize; + AudioUsage usage; +}; + +/** + * Commonly used audio stream configuration parameters. + */ +struct AudioConfig { + uint32_t sampleRateHz; + bitfield<AudioChannelMask> channelMask; + AudioFormat format; + AudioOffloadInfo offloadInfo; + uint64_t frameCount; +}; + +/** Metadata of a playback track for a StreamOut. */ +struct PlaybackTrackMetadata { + AudioUsage usage; + AudioContentType contentType; + /** + * Positive linear gain applied to the track samples. 0 being muted and 1 is no attenuation, + * 2 means double amplification... + * Must not be negative. + */ + float gain; +}; + +/** Metadatas of the source of a StreamOut. */ +struct SourceMetadata { + vec<PlaybackTrackMetadata> tracks; +}; + +/** Metadata of a record track for a StreamIn. */ +struct RecordTrackMetadata { + AudioSource source; + /** + * Positive linear gain applied to the track samples. 0 being muted and 1 is no attenuation, + * 2 means double amplification... + * Must not be negative. + */ + float gain; + /** + * Indicates the destination of an input stream, can be left unspecified. + */ + safe_union Destination { + Monostate unspecified; + DeviceAddress device; + }; + Destination destination; +}; + +/** Metadatas of the sink of a StreamIn. */ +struct SinkMetadata { + vec<RecordTrackMetadata> tracks; +}; + + +/* + * + * Volume control + * + */ + +/** + * Type of gain control exposed by an audio port. + */ +@export(name="", value_prefix="AUDIO_GAIN_MODE_") +enum AudioGainMode : uint32_t { + JOINT = 0x1, // supports joint channel gain control + CHANNELS = 0x2, // supports separate channel gain control + RAMP = 0x4 // supports gain ramps +}; + +/** + * An audio_gain struct is a representation of a gain stage. + * A gain stage is always attached to an audio port. + */ +struct AudioGain { + bitfield<AudioGainMode> mode; + bitfield<AudioChannelMask> channelMask; // channels which gain an be controlled + int32_t minValue; // minimum gain value in millibels + int32_t maxValue; // maximum gain value in millibels + int32_t defaultValue; // default gain value in millibels + uint32_t stepValue; // gain step in millibels + uint32_t minRampMs; // minimum ramp duration in ms + uint32_t maxRampMs; // maximum ramp duration in ms +}; + +/** + * The gain configuration structure is used to get or set the gain values of a + * given port. + */ +struct AudioGainConfig { + int32_t index; // index of the corresponding AudioGain in AudioPort.gains + AudioGainMode mode; + AudioChannelMask channelMask; // channels which gain value follows + /** + * 4 = sizeof(AudioChannelMask), + * 8 is not "FCC_8", so it won't need to be changed for > 8 channels. + * Gain values in millibels for each channel ordered from LSb to MSb in + * channel mask. The number of values is 1 in joint mode or + * popcount(channel_mask). + */ + int32_t[4 * 8] values; + uint32_t rampDurationMs; // ramp duration in ms +}; + + +/* + * + * Routing control + * + */ + +/* + * Types defined here are used to describe an audio source or sink at internal + * framework interfaces (audio policy, patch panel) or at the audio HAL. + * Sink and sources are grouped in a concept of “audio port” representing an + * audio end point at the edge of the system managed by the module exposing + * the interface. + */ + +/** Audio port role: either source or sink */ +@export(name="audio_port_role_t", value_prefix="AUDIO_PORT_ROLE_") +enum AudioPortRole : int32_t { + NONE, + SOURCE, + SINK, +}; + +/** + * Audio port type indicates if it is a session (e.g AudioTrack), a mix (e.g + * PlaybackThread output) or a physical device (e.g OUT_SPEAKER) + */ +@export(name="audio_port_type_t", value_prefix="AUDIO_PORT_TYPE_") +enum AudioPortType : int32_t { + NONE, + DEVICE, + MIX, + SESSION, +}; + +/** + * Extension for audio port configuration structure when the audio port is a + * hardware device. + */ +struct AudioPortConfigDeviceExt { + AudioModuleHandle hwModule; // module the device is attached to + AudioDevice type; // device type (e.g OUT_SPEAKER) + uint8_t[32] address; // device address. "" if N/A +}; + +/** + * Extension for audio port configuration structure when the audio port is an + * audio session. + */ +struct AudioPortConfigSessionExt { + AudioSession session; +}; + +/** + * Flags indicating which fields are to be considered in AudioPortConfig. + */ +@export(name="", value_prefix="AUDIO_PORT_CONFIG_") +enum AudioPortConfigMask : uint32_t { + SAMPLE_RATE = 0x1, + CHANNEL_MASK = 0x2, + FORMAT = 0x4, + GAIN = 0x8, +}; + +/** + * Audio port configuration structure used to specify a particular configuration + * of an audio port. + */ +struct AudioPortConfig { + AudioPortHandle id; + bitfield<AudioPortConfigMask> configMask; + uint32_t sampleRateHz; + bitfield<AudioChannelMask> channelMask; + AudioFormat format; + AudioGainConfig gain; + AudioPortType type; // type is used as a discriminator for Ext union + AudioPortRole role; // role is used as a discriminator for UseCase union + union Ext { + AudioPortConfigDeviceExt device; + struct AudioPortConfigMixExt { + AudioModuleHandle hwModule; // module the stream is attached to + AudioIoHandle ioHandle; // I/O handle of the input/output stream + union UseCase { + AudioStreamType stream; + AudioSource source; + } useCase; + } mix; + AudioPortConfigSessionExt session; + } ext; +}; + +/** + * Extension for audio port structure when the audio port is a hardware device. + */ +struct AudioPortDeviceExt { + AudioModuleHandle hwModule; // module the device is attached to + AudioDevice type; + /** 32 byte string identifying the port. */ + uint8_t[32] address; +}; + +/** + * Latency class of the audio mix. + */ +@export(name="audio_mix_latency_class_t", value_prefix="AUDIO_LATENCY_") +enum AudioMixLatencyClass : int32_t { + LOW, + NORMAL +}; + +struct AudioPortMixExt { + AudioModuleHandle hwModule; // module the stream is attached to + AudioIoHandle ioHandle; // I/O handle of the stream + AudioMixLatencyClass latencyClass; +}; + +/** + * Extension for audio port structure when the audio port is an audio session. + */ +struct AudioPortSessionExt { + AudioSession session; +}; + +struct AudioPort { + AudioPortHandle id; + AudioPortRole role; + string name; + vec<uint32_t> sampleRates; + vec<bitfield<AudioChannelMask>> channelMasks; + vec<AudioFormat> formats; + vec<AudioGain> gains; + AudioPortConfig activeConfig; // current audio port configuration + AudioPortType type; // type is used as a discriminator + union Ext { + AudioPortDeviceExt device; + AudioPortMixExt mix; + AudioPortSessionExt session; + } ext; +}; + +struct ThreadInfo { + int64_t pid; + int64_t tid; +}; diff --git a/audio/common/all-versions/copyHAL.sh b/audio/common/all-versions/copyHAL.sh new file mode 100755 index 0000000000..d07012fe04 --- /dev/null +++ b/audio/common/all-versions/copyHAL.sh @@ -0,0 +1,204 @@ +#/usr/bin/env bash + +set -euo pipefail + +if (echo "$@" |grep -qe -h); then + echo "This script will generate a new HAL version identical to an other one." + echo "This helps creating the boilerplate for a new version." + echo + echo "USAGE: $0 [BASE_VERSION] [NEW_VERSION]" + echo " BASE_VERSION default value is the highest version currently existing" + echo " NEW_VERSION default value is BASE_VERSION + 1" + echo + echo "Example: to generate a V6.0 by copying V5, do: $0 5.0 6.0" + exit +fi +readonly HAL_DIRECTORY=hardware/interfaces/audio +readonly HAL_VTS_DIRECTORY=core/all-versions/vts/functional +readonly HAL_VTS_FILE=AudioPrimaryHidlHalTest.cpp +readonly HAL_SERVICE_DIRECTORY=common/all-versions/default/service/ +readonly HAL_SERVICE_CPP=service.cpp + +readonly FWK_DIRECTORY=frameworks/av/media/libaudiohal +readonly IMPL_DIRECTORY=impl +readonly IMPL_FACTORYHAL=$IMPL_DIRECTORY/include/libaudiohal/FactoryHalHidl.h + +readonly VTS_DIRECTORY=test/vts-testcase/hal/audio +readonly VTS_LIST=test/vts/tools/build/tasks/list/vts_test_lib_hidl_package_list.mk +readonly WATCHDOG=frameworks/base/services/core/java/com/android/server/Watchdog.cpp +readonly GSI_CURRENT=build/make/target/product/gsi/current.txt + +readonly BASE_VERSION=${1:-$(ls $ANDROID_BUILD_TOP/$HAL_DIRECTORY | grep -E '[0-9]+\.[0-9]+' | + sort -n |tail -n1)} +readonly BASE_MAJOR_VERSION=${BASE_VERSION%.*} +readonly BASE_MINOR_VERSION=${BASE_VERSION##*.} + +readonly NEW_VERSION="${2:-$((${BASE_MAJOR_VERSION} + 1)).0}" +readonly NEW_MAJOR_VERSION=${NEW_VERSION%.*} +readonly NEW_MINOR_VERSION=${NEW_VERSION##*.} + + +readonly BASE_VERSION_REGEX="${BASE_MAJOR_VERSION}[._]${BASE_MINOR_VERSION}" +readonly NEW_VERSION_REGEX="${NEW_MAJOR_VERSION}[._]${NEW_MINOR_VERSION}" + +readonly BASE_VERSION_ESCAPE="${BASE_MAJOR_VERSION}\.${BASE_MINOR_VERSION}" +readonly BASE_VERSION_UNDERSCORE="${BASE_MAJOR_VERSION}_${BASE_MINOR_VERSION}" +readonly NEW_VERSION_UNDERSCORE="${NEW_MAJOR_VERSION}_${NEW_MINOR_VERSION}" +updateVersion() { + if [ $1 == "-e" ]; then + local -r REGEX="$2"; shift 2 + else + local -r REGEX="$BASE_VERSION_REGEX" + fi + awk -i inplace -e "{if (!/$REGEX/) print; else { + if (original_before) print + if (original_after) original_line=\$0; + + gsub(/$BASE_VERSION_ESCAPE/,\"$NEW_VERSION\"); + gsub(/$BASE_VERSION_UNDERSCORE/,\"$NEW_VERSION_UNDERSCORE\"); + gsub(/MAJOR_VERSION=$BASE_MAJOR_VERSION/, + \"MAJOR_VERSION=$NEW_MAJOR_VERSION\"); + gsub(/MINOR_VERSION=$BASE_MINOR_VERSION/, + \"MINOR_VERSION=$NEW_MINOR_VERSION\"); + + print + if (original_after) print original_line + }}" "$@" +} + +updateAudioVersion() { + updateVersion -e "audio.*$BASE_VERSION_REGEX" "$@" +} + +updateLicenceDates() { + # Update date on the 2 first lines + sed -i "1,2 s/20[0-9][0-9]/$(date +"%Y")/g" "$@" +} + +echo "Creating new audio HAL V$NEW_VERSION based on V$BASE_VERSION" +echo "Press Ctrl-C to cancel, Enter to continue" +read + +MODIFIED= +runIfNeeded() { + local -r TARGET=$1; shift + cd $ANDROID_BUILD_TOP/$TARGET + if grep -q -r "audio.*$NEW_VERSION_REGEX"; then + echo " Skipping $TARGET as already up to date" + else + echo " Updating $PWD" + MODIFIED+=$'\n - '$TARGET + "$@" + fi +} + +createHALVersion() { + local -r DIRS=". common effect" + local COPY= + echo "Copy $BASE_VERSION to $NEW_VERSION in $DIRS" + for DIR in $DIRS; do + cp -Tar $DIR/$BASE_VERSION $DIR/$NEW_VERSION + COPY+=" $DIR/$NEW_VERSION" + done + + echo "Replacing $BASE_VERSION by $NEW_VERSION in the copied files" + updateVersion $(find $COPY -type f) + updateLicenceDates $(find $COPY -type f) + + echo "Update implementation and VTS generic code" + local -r FILES="*/all-versions/default/Android.bp */all-versions/vts/functional/Android.bp" + updateVersion -v original_before=1 -v RS= -v ORS='\n\n' $FILES + sed -i '${/^$/d}' $FILES # Remove \n at the end of the files + + updateVersion -v original_before=1 $HAL_SERVICE_DIRECTORY/Android.bp + + updateVersion -e "audio::.*$BASE_VERSION_REGEX" -v original_after=1 \ + $HAL_SERVICE_DIRECTORY/$HAL_SERVICE_CPP + updateVersion -e "audio\/.*$BASE_VERSION_REGEX" -v original_before=1 \ + $HAL_SERVICE_DIRECTORY/$HAL_SERVICE_CPP + + local -r HAL_VTS_PATH=$HAL_VTS_DIRECTORY/$NEW_VERSION/$HAL_VTS_FILE + mkdir -p $(dirname $HAL_VTS_PATH) + cat > $HAL_VTS_PATH <<EOF +/* + * Copyright (C) $(date +"%Y") 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. + */ + +// pull in all the <= $BASE_VERSION tests +#include "$BASE_VERSION/$(basename AudioPrimaryHidlHalTest.cpp)" +EOF + + echo "New HAL version $NEW_VERSION successfully created" +} + +echo "Creating new audio HAL definition, default impl and VTS" +runIfNeeded $HAL_DIRECTORY createHALVersion + + +createFrameworkAdapter() { + updateVersion -v original_before=1 Android.bp + updateVersion -v original_before=1 -v RS= -v ORS='\n\n' $IMPL_FACTORYHAL/Android.bp + updateVersion -v original_after=1 $IMPL_FACTORYHAL +} +echo "Now creating the framework adapter version" +runIfNeeded $FWK_DIRECTORY createFrameworkAdapter + +createVTSXML() { + cp -Tar V$BASE_VERSION_UNDERSCORE V$NEW_VERSION_UNDERSCORE + cp -Tar effect/{V$BASE_VERSION_UNDERSCORE,V$NEW_VERSION_UNDERSCORE} + local -r FILES=$(find {.,effect}/V$NEW_VERSION_UNDERSCORE -type f) + updateVersion $FILES + updateLicenceDates $FILES +} +echo "Now update VTS XML" +runIfNeeded $VTS_DIRECTORY createVTSXML + +echo "Now register new VTS" +runIfNeeded $(dirname $VTS_LIST) updateAudioVersion -v original_before=1 $(basename $VTS_LIST) + +echo "Now update watchdog" +runIfNeeded $(dirname $WATCHDOG) updateAudioVersion -v original_before=1 $(basename $WATCHDOG) + +echo "Now update GSI current.txt" +runIfNeeded $(dirname $GSI_CURRENT) update-vndk-list.sh + +if ! [ "$MODIFIED" ]; then + echo + echo "$NEW_VERSION already exist, nothing to do" + exit +fi + +cat << EOF + +All File generated successfully. Please submit a patch in all those directories: $MODIFIED + +----------------------------------------------------------- +WHAT WAS *NOT* DONE, and you need to do now: + 1) You need to choose if the new HAL is optional or not for new devices. + Either add or replace $BASE_VERSION by $NEW_VERSION in + compatibility_matrices/compatibility_matrix.current.xml + Do not forget to update both the "audio" and "audio.effects" HAL' + + 2) Then you need to choose a device to update its audio HAL implementation: + a) Update the HAL manifest of your device: open your device manifest.xml + and replace $BASE_VERSION by $NEW_VERSION for both + - android.hardware.audio + - android.hardware.audio.effect + b) Go to your device device.mk (usually next to the manifest) and replace: + - android.hardware.audio@$BASE_VERSION-impl by + android.hardware.audio@$NEW_VERSION-impl + - android.hardware.audio.effect@$BASE_VERSION-impl by + android.hardware.audio.effect@$NEW_VERSION-impl +EOF diff --git a/audio/common/all-versions/default/Android.bp b/audio/common/all-versions/default/Android.bp index c0bd34c45f..0eb4a71348 100644 --- a/audio/common/all-versions/default/Android.bp +++ b/audio/common/all-versions/default/Android.bp @@ -69,7 +69,6 @@ cc_defaults { cc_library_shared { name: "android.hardware.audio.common@2.0-util", defaults: ["android.hardware.audio.common-util_default"], - shared_libs: [ "android.hardware.audio.common@2.0", ], @@ -83,7 +82,6 @@ cc_library_shared { cc_library_shared { name: "android.hardware.audio.common@4.0-util", defaults: ["android.hardware.audio.common-util_default"], - shared_libs: [ "android.hardware.audio.common@4.0", ], @@ -97,7 +95,6 @@ cc_library_shared { cc_library_shared { name: "android.hardware.audio.common@5.0-util", defaults: ["android.hardware.audio.common-util_default"], - shared_libs: [ "android.hardware.audio.common@5.0", ], @@ -107,3 +104,16 @@ cc_library_shared { "-include common/all-versions/VersionMacro.h", ] } + +cc_library_shared { + name: "android.hardware.audio.common@6.0-util", + defaults: ["android.hardware.audio.common-util_default"], + shared_libs: [ + "android.hardware.audio.common@6.0", + ], + cflags: [ + "-DMAJOR_VERSION=6", + "-DMINOR_VERSION=0", + "-include common/all-versions/VersionMacro.h", + ] +} diff --git a/audio/common/all-versions/default/service/Android.bp b/audio/common/all-versions/default/service/Android.bp new file mode 100644 index 0000000000..5c1ea4721b --- /dev/null +++ b/audio/common/all-versions/default/service/Android.bp @@ -0,0 +1,60 @@ +cc_binary { + name: "android.hardware.audio.service", + + init_rc: ["android.hardware.audio.service.rc"], + relative_install_path: "hw", + vendor: true, + // Only support 32 bit as the binary must always be installed at the same + // location for init to start it and the build system does not support + // having two binaries installable to the same location even if they are + // not installed in the same build. + compile_multilib: "32", + srcs: ["service.cpp"], + + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + ], + + shared_libs: [ + "libcutils", + "libbinder", + "libhwbinder", + "libhidlbase", + "liblog", + "libutils", + "libhardware", + "android.hardware.audio@2.0", + "android.hardware.audio@4.0", + "android.hardware.audio@5.0", + "android.hardware.audio@6.0", + "android.hardware.audio.common@2.0", + "android.hardware.audio.common@4.0", + "android.hardware.audio.common@5.0", + "android.hardware.audio.common@6.0", + "android.hardware.audio.effect@2.0", + "android.hardware.audio.effect@4.0", + "android.hardware.audio.effect@5.0", + "android.hardware.audio.effect@6.0", + "android.hardware.bluetooth.a2dp@1.0", + "android.hardware.bluetooth.audio@2.0", + "android.hardware.soundtrigger@2.0", + "android.hardware.soundtrigger@2.1", + "android.hardware.soundtrigger@2.2", + "com.qualcomm.qti.bluetooth_audio@1.0", + ], + arch : { + arm : { + cflags: [ + "-DARCH_ARM_32", + ] + } + }, +} + +// Legacy service name, use android.hardware.audio.service instead +phony { + name: "android.hardware.audio@2.0-service", + required: ["android.hardware.audio.service"], +} diff --git a/audio/common/all-versions/default/service/Android.mk b/audio/common/all-versions/default/service/Android.mk deleted file mode 100644 index 4fe4aec013..0000000000 --- a/audio/common/all-versions/default/service/Android.mk +++ /dev/null @@ -1,70 +0,0 @@ -# -# Copyright (C) 2016 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. - - -LOCAL_PATH := $(call my-dir) - -# -# Service -# - -include $(CLEAR_VARS) -LOCAL_MODULE := android.hardware.audio@2.0-service -LOCAL_INIT_RC := android.hardware.audio@2.0-service.rc -LOCAL_MODULE_RELATIVE_PATH := hw -LOCAL_PROPRIETARY_MODULE := true -LOCAL_SRC_FILES := \ - service.cpp - -LOCAL_CFLAGS := -Wall -Werror - -LOCAL_SHARED_LIBRARIES := \ - libcutils \ - libbinder \ - libhidlbase \ - liblog \ - libutils \ - libhardware \ - android.hardware.audio@2.0 \ - android.hardware.audio@4.0 \ - android.hardware.audio@5.0 \ - android.hardware.audio.common@2.0 \ - android.hardware.audio.common@4.0 \ - android.hardware.audio.common@5.0 \ - android.hardware.audio.effect@2.0 \ - android.hardware.audio.effect@4.0 \ - android.hardware.audio.effect@5.0 \ - android.hardware.bluetooth.a2dp@1.0 \ - android.hardware.bluetooth.audio@2.0 \ - android.hardware.soundtrigger@2.0 \ - android.hardware.soundtrigger@2.1 \ - android.hardware.soundtrigger@2.2 \ - com.qualcomm.qti.bluetooth_audio@1.0 \ - libhwbinder \ - libcutils - -# Can not switch to Android.bp until AUDIOSERVER_MULTILIB -# is deprecated as build config variable are not supported -ifeq ($(strip $(AUDIOSERVER_MULTILIB)),) -LOCAL_MULTILIB := 32 -else -LOCAL_MULTILIB := $(AUDIOSERVER_MULTILIB) -endif - -ifeq ($(TARGET_ARCH),arm) - LOCAL_CFLAGS += -DARCH_ARM_32 -endif - -include $(BUILD_EXECUTABLE) diff --git a/audio/common/all-versions/default/service/android.hardware.audio@2.0-service.rc b/audio/common/all-versions/default/service/android.hardware.audio.service.rc index 72b4d197dd..63d2542498 100644 --- a/audio/common/all-versions/default/service/android.hardware.audio@2.0-service.rc +++ b/audio/common/all-versions/default/service/android.hardware.audio.service.rc @@ -1,4 +1,4 @@ -service vendor.audio-hal-2-0 /vendor/bin/hw/android.hardware.audio@2.0-service +service vendor.audio-hal /vendor/bin/hw/android.hardware.audio.service class hal user audioserver # media gid needed for /dev/fm (radio) and for /data/misc/media (tee) diff --git a/audio/common/all-versions/default/service/service.cpp b/audio/common/all-versions/default/service/service.cpp index bf78a43ef8..65ad4acc8a 100644 --- a/audio/common/all-versions/default/service/service.cpp +++ b/audio/common/all-versions/default/service/service.cpp @@ -19,9 +19,11 @@ #include <android/hardware/audio/2.0/IDevicesFactory.h> #include <android/hardware/audio/4.0/IDevicesFactory.h> #include <android/hardware/audio/5.0/IDevicesFactory.h> +#include <android/hardware/audio/6.0/IDevicesFactory.h> #include <android/hardware/audio/effect/2.0/IEffectsFactory.h> #include <android/hardware/audio/effect/4.0/IEffectsFactory.h> #include <android/hardware/audio/effect/5.0/IEffectsFactory.h> +#include <android/hardware/audio/effect/6.0/IEffectsFactory.h> #include <android/hardware/bluetooth/a2dp/1.0/IBluetoothAudioOffload.h> #include <android/hardware/bluetooth/audio/2.0/IBluetoothAudioProvidersFactory.h> #include <android/hardware/soundtrigger/2.0/ISoundTriggerHw.h> @@ -74,16 +76,22 @@ int main(int /* argc */, char* /* argv */ []) { } configureRpcThreadpool(16, true /*callerWillJoin*/); - LOG_ALWAYS_FATAL_IF((registerPassthroughServiceImplementations<audio::V5_0::IDevicesFactory, - audio::V4_0::IDevicesFactory, - audio::V2_0::IDevicesFactory>()), + // Keep versions on a separate line for easier parsing + // clang-format off + LOG_ALWAYS_FATAL_IF((registerPassthroughServiceImplementations< + audio::V6_0::IDevicesFactory, + audio::V5_0::IDevicesFactory, + audio::V4_0::IDevicesFactory, + audio::V2_0::IDevicesFactory>()), "Could not register audio core API"); - LOG_ALWAYS_FATAL_IF( - (registerPassthroughServiceImplementations<audio::effect::V5_0::IEffectsFactory, - audio::effect::V4_0::IEffectsFactory, - audio::effect::V2_0::IEffectsFactory>()), - "Could not register audio effect API"); + LOG_ALWAYS_FATAL_IF((registerPassthroughServiceImplementations< + audio::effect::V6_0::IEffectsFactory, + audio::effect::V5_0::IEffectsFactory, + audio::effect::V4_0::IEffectsFactory, + audio::effect::V2_0::IEffectsFactory>()), + "Could not register audio effect API"); + // clang-format on ALOGW_IF((registerPassthroughServiceImplementations<soundtrigger::V2_2::ISoundTriggerHw, soundtrigger::V2_1::ISoundTriggerHw, diff --git a/audio/core/all-versions/default/Android.bp b/audio/core/all-versions/default/Android.bp index c4548c3f45..b8b7feeed0 100644 --- a/audio/core/all-versions/default/Android.bp +++ b/audio/core/all-versions/default/Android.bp @@ -43,13 +43,11 @@ cc_defaults { cc_library_shared { name: "android.hardware.audio@2.0-impl", defaults: ["android.hardware.audio-impl_default"], - shared_libs: [ "android.hardware.audio@2.0", "android.hardware.audio.common@2.0", "android.hardware.audio.common@2.0-util", ], - cflags: [ "-DMAJOR_VERSION=2", "-DMINOR_VERSION=0", @@ -66,7 +64,6 @@ cc_library_shared { "android.hardware.audio.common@4.0", "android.hardware.audio.common@4.0-util", ], - cflags: [ "-DMAJOR_VERSION=4", "-DMINOR_VERSION=0", @@ -77,16 +74,29 @@ cc_library_shared { cc_library_shared { name: "android.hardware.audio@5.0-impl", defaults: ["android.hardware.audio-impl_default"], - shared_libs: [ "android.hardware.audio@5.0", "android.hardware.audio.common@5.0", "android.hardware.audio.common@5.0-util", ], - cflags: [ "-DMAJOR_VERSION=5", "-DMINOR_VERSION=0", "-include common/all-versions/VersionMacro.h", ] } + +cc_library_shared { + name: "android.hardware.audio@6.0-impl", + defaults: ["android.hardware.audio-impl_default"], + shared_libs: [ + "android.hardware.audio@6.0", + "android.hardware.audio.common@6.0", + "android.hardware.audio.common@6.0-util", + ], + cflags: [ + "-DMAJOR_VERSION=6", + "-DMINOR_VERSION=0", + "-include common/all-versions/VersionMacro.h", + ] +} diff --git a/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp index 022f75e849..15be3bf470 100644 --- a/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp +++ b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp @@ -24,23 +24,26 @@ static void waitForDeviceDestruction() { // flushCommand makes sure all local command are sent, thus should reduce // the latency between local and remote destruction. IPCThreadState::self()->flushCommands(); - usleep(100); + usleep(100 * 1000); } TEST_F(AudioHidlTest, OpenPrimaryDeviceUsingGetDevice) { doc::test("Calling openDevice(\"primary\") should return the primary device."); - { - Result result; - sp<IDevice> baseDevice; - ASSERT_OK(devicesFactory->openDevice("primary", returnIn(result, baseDevice))); - ASSERT_OK(result); - ASSERT_TRUE(baseDevice != nullptr); + struct WaitExecutor { + ~WaitExecutor() { waitForDeviceDestruction(); } + } waitExecutor; // Make sure we wait for the device destruction on exiting from the test. + Result result; + sp<IDevice> baseDevice; + ASSERT_OK(devicesFactory->openDevice("primary", returnIn(result, baseDevice))); + if (result != Result::OK && isPrimaryDeviceOptional()) { + GTEST_SKIP() << "No primary device on this factory"; // returns + } + ASSERT_OK(result); + ASSERT_TRUE(baseDevice != nullptr); - Return<sp<IPrimaryDevice>> primaryDevice = IPrimaryDevice::castFrom(baseDevice); - ASSERT_TRUE(primaryDevice.isOk()); - ASSERT_TRUE(sp<IPrimaryDevice>(primaryDevice) != nullptr); - } // Destroy local IDevice proxy - waitForDeviceDestruction(); + Return<sp<IPrimaryDevice>> primaryDevice = IPrimaryDevice::castFrom(baseDevice); + ASSERT_TRUE(primaryDevice.isOk()); + ASSERT_TRUE(sp<IPrimaryDevice>(primaryDevice) != nullptr); } ////////////////////////////////////////////////////////////////////////////// @@ -117,6 +120,10 @@ TEST_F(AudioPrimaryHidlTest, GetMicrophonesTest) { ASSERT_NE(0U, activeMicrophones.size()); } stream->close(); + // Workaround for b/139329877. Ensures the stream gets closed on the audio hal side. + stream.clear(); + IPCThreadState::self()->flushCommands(); + usleep(1000); if (efGroup) { EventFlag::deleteEventFlag(&efGroup); } @@ -148,6 +155,7 @@ TEST_F(AudioPrimaryHidlTest, SetConnectedState) { // initial state. To workaround this, destroy the HAL at the end of this test. device.clear(); waitForDeviceDestruction(); + ASSERT_NO_FATAL_FAILURE(initPrimaryDevice()); } static void testGetDevices(IStream* stream, AudioDevice expectedDevice) { diff --git a/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp new file mode 100644 index 0000000000..6314ea72ec --- /dev/null +++ b/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp @@ -0,0 +1,18 @@ +/* + * 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. + */ + +// pull in all the <= 5.0 tests +#include "5.0/AudioPrimaryHidlHalTest.cpp" diff --git a/audio/core/all-versions/vts/functional/Android.bp b/audio/core/all-versions/vts/functional/Android.bp index 5e17ade365..1818e36112 100644 --- a/audio/core/all-versions/vts/functional/Android.bp +++ b/audio/core/all-versions/vts/functional/Android.bp @@ -19,12 +19,13 @@ cc_defaults { defaults: ["VtsHalTargetTestDefaults"], static_libs: [ "android.hardware.audio.common.test.utility", + "libaudiofoundation", "libaudiopolicycomponents", "libmedia_helper", "libxml2", ], shared_libs: [ - "libaudiofoundation", + "libbinder", "libfmq", ], header_libs: [ @@ -83,3 +84,20 @@ cc_test { "-include common/all-versions/VersionMacro.h", ] } + +cc_test { + name: "VtsHalAudioV6_0TargetTest", + defaults: ["VtsHalAudioTargetTest_defaults"], + srcs: [ + "6.0/AudioPrimaryHidlHalTest.cpp", + ], + static_libs: [ + "android.hardware.audio@6.0", + "android.hardware.audio.common@6.0", + ], + cflags: [ + "-DMAJOR_VERSION=6", + "-DMINOR_VERSION=0", + "-include common/all-versions/VersionMacro.h", + ] +} diff --git a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h index 7f8b6a0646..0778720bb0 100644 --- a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h +++ b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h @@ -215,25 +215,32 @@ TEST_F(AudioPolicyConfigTest, LoadAudioPolicyXMLConfiguration) { // Test all audio devices class AudioHidlTest : public AudioPolicyConfigTest { public: - void SetUp() override { - ASSERT_NO_FATAL_FAILURE(HidlTest::SetUp()); // setup base + static void SetUpTestSuite() { + devicesFactory = ::testing::VtsHalHidlTargetTestBase::getService<IDevicesFactory>( + environment->getServiceName<IDevicesFactory>()); + } - if (devicesFactory == nullptr) { - environment->registerTearDown([] { devicesFactory.clear(); }); - devicesFactory = ::testing::VtsHalHidlTargetTestBase::getService<IDevicesFactory>( - environment->getServiceName<IDevicesFactory>("default")); - } - ASSERT_TRUE(devicesFactory != nullptr); - } + static void TearDownTestSuite() { devicesFactory.clear(); } + + void SetUp() override { + ASSERT_NO_FATAL_FAILURE(AudioPolicyConfigTest::SetUp()); // setup base + // Failures during SetUpTestSuite do not cause test termination. + ASSERT_TRUE(devicesFactory != nullptr); + } protected: // Cache the devicesFactory retrieval to speed up each test by ~0.5s static sp<IDevicesFactory> devicesFactory; + + static bool isPrimaryDeviceOptional() { + // It's OK not to have "primary" device on non-default audio HAL service. + return environment->getServiceName<IDevicesFactory>() != kDefaultServiceName; + } }; sp<IDevicesFactory> AudioHidlTest::devicesFactory; TEST_F(AudioHidlTest, GetAudioDevicesFactoryService) { - doc::test("Test the getService (called in SetUp)"); + doc::test("Test the getService"); } TEST_F(AudioHidlTest, OpenDeviceInvalidParameter) { @@ -257,23 +264,31 @@ TEST_F(AudioHidlTest, OpenDeviceInvalidParameter) { // Test the primary device class AudioPrimaryHidlTest : public AudioHidlTest { public: - /** Primary HAL test are NOT thread safe. */ + static void SetUpTestSuite() { + ASSERT_NO_FATAL_FAILURE(AudioHidlTest::SetUpTestSuite()); + ASSERT_NO_FATAL_FAILURE(initPrimaryDevice()); + } + + static void TearDownTestSuite() { + device.clear(); + AudioHidlTest::TearDownTestSuite(); + } + void SetUp() override { ASSERT_NO_FATAL_FAILURE(AudioHidlTest::SetUp()); // setup base - - if (device == nullptr) { - initPrimaryDevice(); - ASSERT_TRUE(device != nullptr); - environment->registerTearDown([] { device.clear(); }); + if (device == nullptr && isPrimaryDeviceOptional()) { + GTEST_SKIP() << "No primary device on this factory"; } + ASSERT_TRUE(device != nullptr); } protected: // Cache the device opening to speed up each test by ~0.5s static sp<IPrimaryDevice> device; - private: - void initPrimaryDevice() { + static void initPrimaryDevice() { + // Failures during test suite set up do not cause test termination. + ASSERT_TRUE(devicesFactory != nullptr); Result result; #if MAJOR_VERSION == 2 sp<IDevice> baseDevice; @@ -292,7 +307,7 @@ class AudioPrimaryHidlTest : public AudioHidlTest { sp<IPrimaryDevice> AudioPrimaryHidlTest::device; TEST_F(AudioPrimaryHidlTest, OpenPrimaryDevice) { - doc::test("Test the openDevice (called in SetUp)"); + doc::test("Test the openDevice (called during setup)"); } TEST_F(AudioPrimaryHidlTest, Init) { @@ -684,14 +699,29 @@ class OpenStreamTest : public AudioConfigPrimaryTest, Return<Result> closeStream() { open = false; - return stream->close(); + auto res = stream->close(); + stream.clear(); + waitForStreamDestruction(); + return res; + } + + void waitForStreamDestruction() { + // FIXME: there is no way to know when the remote IStream is being destroyed + // Binder does not support testing if an object is alive, thus + // wait for 100ms to let the binder destruction propagates and + // the remote device has the time to be destroyed. + // flushCommand makes sure all local command are sent, thus should reduce + // the latency between local and remote destruction. + IPCThreadState::self()->flushCommands(); + usleep(100 * 1000); } private: void TearDown() override { if (open) { - ASSERT_OK(stream->close()); + ASSERT_OK(closeStream()); } + AudioConfigPrimaryTest::TearDown(); } protected: @@ -704,8 +734,9 @@ class OpenStreamTest : public AudioConfigPrimaryTest, ////////////////////////////// openOutputStream ////////////////////////////// class OutputStreamTest : public OpenStreamTest<IStreamOut> { - virtual void SetUp() override { + void SetUp() override { ASSERT_NO_FATAL_FAILURE(OpenStreamTest::SetUp()); // setup base + if (IsSkipped()) return; // do not attempt to use 'device' address.device = AudioDevice::OUT_DEFAULT; const AudioConfig& config = GetParam(); // TODO: test all flag combination @@ -752,8 +783,9 @@ INSTANTIATE_TEST_CASE_P( ////////////////////////////// openInputStream ////////////////////////////// class InputStreamTest : public OpenStreamTest<IStreamIn> { - virtual void SetUp() override { + void SetUp() override { ASSERT_NO_FATAL_FAILURE(OpenStreamTest::SetUp()); // setup base + if (IsSkipped()) return; // do not attempt to use 'device' address.device = AudioDevice::IN_DEFAULT; const AudioConfig& config = GetParam(); // TODO: test all supported flags and source @@ -985,8 +1017,14 @@ TEST_IO_STREAM(getMmapPositionNoMmap, "Get a stream Mmap position before mapping ASSERT_RESULT(invalidStateOrNotSupported, stream->stop())) TEST_IO_STREAM(close, "Make sure a stream can be closed", ASSERT_OK(closeStream())) -TEST_IO_STREAM(closeTwice, "Make sure a stream can not be closed twice", ASSERT_OK(closeStream()); - ASSERT_RESULT(Result::INVALID_STATE, closeStream())) +// clang-format off +TEST_IO_STREAM(closeTwice, "Make sure a stream can not be closed twice", + auto streamCopy = stream; + ASSERT_OK(closeStream()); + ASSERT_RESULT(Result::INVALID_STATE, streamCopy->close()); + streamCopy.clear(); + waitForStreamDestruction()) +// clang-format on static void testCreateTooBigMmapBuffer(IStream* stream) { MmapBufferInfo info; diff --git a/audio/effect/6.0/Android.bp b/audio/effect/6.0/Android.bp new file mode 100644 index 0000000000..b6184f3a58 --- /dev/null +++ b/audio/effect/6.0/Android.bp @@ -0,0 +1,33 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.audio.effect@6.0", + root: "android.hardware", + vndk: { + enabled: true, + }, + srcs: [ + "types.hal", + "IAcousticEchoCancelerEffect.hal", + "IAutomaticGainControlEffect.hal", + "IBassBoostEffect.hal", + "IDownmixEffect.hal", + "IEffect.hal", + "IEffectBufferProviderCallback.hal", + "IEffectsFactory.hal", + "IEnvironmentalReverbEffect.hal", + "IEqualizerEffect.hal", + "ILoudnessEnhancerEffect.hal", + "INoiseSuppressionEffect.hal", + "IPresetReverbEffect.hal", + "IVirtualizerEffect.hal", + "IVisualizerEffect.hal", + ], + interfaces: [ + "android.hardware.audio.common@6.0", + "android.hidl.base@1.0", + "android.hidl.safe_union@1.0", + ], + gen_java: false, + gen_java_constants: true, +} diff --git a/audio/effect/6.0/IAcousticEchoCancelerEffect.hal b/audio/effect/6.0/IAcousticEchoCancelerEffect.hal new file mode 100644 index 0000000000..f7d769fc50 --- /dev/null +++ b/audio/effect/6.0/IAcousticEchoCancelerEffect.hal @@ -0,0 +1,32 @@ +/* + * 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.hardware.audio.effect@6.0; + +import android.hardware.audio.common@6.0; +import IEffect; + +interface IAcousticEchoCancelerEffect extends IEffect { + /** + * Sets echo delay value in milliseconds. + */ + setEchoDelay(uint32_t echoDelayMs) generates (Result retval); + + /** + * Gets echo delay value in milliseconds. + */ + getEchoDelay() generates (Result retval, uint32_t echoDelayMs); +}; diff --git a/audio/effect/6.0/IAutomaticGainControlEffect.hal b/audio/effect/6.0/IAutomaticGainControlEffect.hal new file mode 100644 index 0000000000..d264cd4911 --- /dev/null +++ b/audio/effect/6.0/IAutomaticGainControlEffect.hal @@ -0,0 +1,68 @@ +/* + * 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.hardware.audio.effect@6.0; + +import android.hardware.audio.common@6.0; +import IEffect; + +interface IAutomaticGainControlEffect extends IEffect { + /** + * Sets target level in millibels. + */ + setTargetLevel(int16_t targetLevelMb) generates (Result retval); + + /** + * Gets target level. + */ + getTargetLevel() generates (Result retval, int16_t targetLevelMb); + + /** + * Sets gain in the compression range in millibels. + */ + setCompGain(int16_t compGainMb) generates (Result retval); + + /** + * Gets gain in the compression range. + */ + getCompGain() generates (Result retval, int16_t compGainMb); + + /** + * Enables or disables limiter. + */ + setLimiterEnabled(bool enabled) generates (Result retval); + + /** + * Returns whether limiter is enabled. + */ + isLimiterEnabled() generates (Result retval, bool enabled); + + struct AllProperties { + int16_t targetLevelMb; + int16_t compGainMb; + bool limiterEnabled; + }; + + /** + * Sets all properties at once. + */ + setAllProperties(AllProperties properties) generates (Result retval); + + /** + * Gets all properties at once. + */ + getAllProperties() generates (Result retval, AllProperties properties); +}; diff --git a/audio/effect/6.0/IBassBoostEffect.hal b/audio/effect/6.0/IBassBoostEffect.hal new file mode 100644 index 0000000000..91c409249f --- /dev/null +++ b/audio/effect/6.0/IBassBoostEffect.hal @@ -0,0 +1,48 @@ +/* + * 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.hardware.audio.effect@6.0; + +import android.hardware.audio.common@6.0; +import IEffect; + +interface IBassBoostEffect extends IEffect { + /** + * Returns whether setting bass boost strength is supported. + */ + isStrengthSupported() generates (Result retval, bool strengthSupported); + + enum StrengthRange : uint16_t { + MIN = 0, + MAX = 1000 + }; + + /** + * Sets bass boost strength. + * + * @param strength strength of the effect. The valid range for strength + * strength is [0, 1000], where 0 per mille designates the + * mildest effect and 1000 per mille designates the + * strongest. + * @return retval operation completion status. + */ + setStrength(uint16_t strength) generates (Result retval); + + /** + * Gets virtualization strength. + */ + getStrength() generates (Result retval, uint16_t strength); +}; diff --git a/audio/effect/6.0/IDownmixEffect.hal b/audio/effect/6.0/IDownmixEffect.hal new file mode 100644 index 0000000000..e3a38e25aa --- /dev/null +++ b/audio/effect/6.0/IDownmixEffect.hal @@ -0,0 +1,37 @@ +/* + * 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.hardware.audio.effect@6.0; + +import android.hardware.audio.common@6.0; +import IEffect; + +interface IDownmixEffect extends IEffect { + enum Type : int32_t { + STRIP, // throw away the extra channels + FOLD // mix the extra channels with FL/FR + }; + + /** + * Sets the current downmix preset. + */ + setType(Type preset) generates (Result retval); + + /** + * Gets the current downmix preset. + */ + getType() generates (Result retval, Type preset); +}; diff --git a/audio/effect/6.0/IEffect.hal b/audio/effect/6.0/IEffect.hal new file mode 100644 index 0000000000..b35afee260 --- /dev/null +++ b/audio/effect/6.0/IEffect.hal @@ -0,0 +1,418 @@ +/* + * 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.hardware.audio.effect@6.0; + +import android.hardware.audio.common@6.0; +import IEffectBufferProviderCallback; + +interface IEffect { + /** + * Initialize effect engine--all configurations return to default. + * + * @return retval operation completion status. + */ + @entry + init() generates (Result retval); + + /** + * Apply new audio parameters configurations for input and output buffers. + * The provider callbacks may be empty, but in this case the buffer + * must be provided in the EffectConfig structure. + * + * @param config configuration descriptor. + * @param inputBufferProvider optional buffer provider reference. + * @param outputBufferProvider optional buffer provider reference. + * @return retval operation completion status. + */ + setConfig(EffectConfig config, + IEffectBufferProviderCallback inputBufferProvider, + IEffectBufferProviderCallback outputBufferProvider) + generates (Result retval); + + /** + * Reset the effect engine. Keep configuration but resets state and buffer + * content. + * + * @return retval operation completion status. + */ + reset() generates (Result retval); + + /** + * Enable processing. + * + * @return retval operation completion status. + */ + @callflow(next={"prepareForProcessing"}) + enable() generates (Result retval); + + /** + * Disable processing. + * + * @return retval operation completion status. + */ + @callflow(next={"close"}) + disable() generates (Result retval); + + /** + * Set the rendering device the audio output path is connected to. The + * effect implementation must set EFFECT_FLAG_DEVICE_IND flag in its + * descriptor to receive this command when the device changes. + * + * Note: this method is only supported for effects inserted into + * the output chain. + * + * @param device output device specification. + * @return retval operation completion status. + */ + setDevice(bitfield<AudioDevice> device) generates (Result retval); + + /** + * Set and get volume. Used by audio framework to delegate volume control to + * effect engine. The effect implementation must set EFFECT_FLAG_VOLUME_CTRL + * flag in its descriptor to receive this command. The effect engine must + * return the volume that should be applied before the effect is + * processed. The overall volume (the volume actually applied by the effect + * engine multiplied by the returned value) should match the value indicated + * in the command. + * + * @param volumes vector containing volume for each channel defined in + * EffectConfig for output buffer expressed in 8.24 fixed + * point format. + * @return result updated volume values. + * @return retval operation completion status. + */ + setAndGetVolume(vec<uint32_t> volumes) + generates (Result retval, vec<uint32_t> result); + + /** + * Notify the effect of the volume change. The effect implementation must + * set EFFECT_FLAG_VOLUME_IND flag in its descriptor to receive this + * command. + * + * @param volumes vector containing volume for each channel defined in + * EffectConfig for output buffer expressed in 8.24 fixed + * point format. + * @return retval operation completion status. + */ + volumeChangeNotification(vec<uint32_t> volumes) + generates (Result retval); + + /** + * Set the audio mode. The effect implementation must set + * EFFECT_FLAG_AUDIO_MODE_IND flag in its descriptor to receive this command + * when the audio mode changes. + * + * @param mode desired audio mode. + * @return retval operation completion status. + */ + setAudioMode(AudioMode mode) generates (Result retval); + + /** + * Apply new audio parameters configurations for input and output buffers of + * reverse stream. An example of reverse stream is the echo reference + * supplied to an Acoustic Echo Canceler. + * + * @param config configuration descriptor. + * @param inputBufferProvider optional buffer provider reference. + * @param outputBufferProvider optional buffer provider reference. + * @return retval operation completion status. + */ + setConfigReverse(EffectConfig config, + IEffectBufferProviderCallback inputBufferProvider, + IEffectBufferProviderCallback outputBufferProvider) + generates (Result retval); + + /** + * Set the capture device the audio input path is connected to. The effect + * implementation must set EFFECT_FLAG_DEVICE_IND flag in its descriptor to + * receive this command when the device changes. + * + * Note: this method is only supported for effects inserted into + * the input chain. + * + * @param device input device specification. + * @return retval operation completion status. + */ + setInputDevice(bitfield<AudioDevice> device) generates (Result retval); + + /** + * Read audio parameters configurations for input and output buffers. + * + * @return retval operation completion status. + * @return config configuration descriptor. + */ + getConfig() generates (Result retval, EffectConfig config); + + /** + * Read audio parameters configurations for input and output buffers of + * reverse stream. + * + * @return retval operation completion status. + * @return config configuration descriptor. + */ + getConfigReverse() generates (Result retval, EffectConfig config); + + /** + * Queries for supported combinations of main and auxiliary channels + * (e.g. for a multi-microphone noise suppressor). + * + * @param maxConfigs maximum number of the combinations to return. + * @return retval absence of the feature support is indicated using + * NOT_SUPPORTED code. RESULT_TOO_BIG is returned if + * the number of supported combinations exceeds 'maxConfigs'. + * @return result list of configuration descriptors. + */ + getSupportedAuxChannelsConfigs(uint32_t maxConfigs) + generates (Result retval, vec<EffectAuxChannelsConfig> result); + + /** + * Retrieves the current configuration of main and auxiliary channels. + * + * @return retval absence of the feature support is indicated using + * NOT_SUPPORTED code. + * @return result configuration descriptor. + */ + getAuxChannelsConfig() + generates (Result retval, EffectAuxChannelsConfig result); + + /** + * Sets the current configuration of main and auxiliary channels. + * + * @return retval operation completion status; absence of the feature + * support is indicated using NOT_SUPPORTED code. + */ + setAuxChannelsConfig(EffectAuxChannelsConfig config) + generates (Result retval); + + /** + * Set the audio source the capture path is configured for (Camcorder, voice + * recognition...). + * + * Note: this method is only supported for effects inserted into + * the input chain. + * + * @param source source descriptor. + * @return retval operation completion status. + */ + setAudioSource(AudioSource source) generates (Result retval); + + /** + * This command indicates if the playback thread the effect is attached to + * is offloaded or not, and updates the I/O handle of the playback thread + * the effect is attached to. + * + * @param param effect offload descriptor. + * @return retval operation completion status. + */ + offload(EffectOffloadParameter param) generates (Result retval); + + /** + * Returns the effect descriptor. + * + * @return retval operation completion status. + * @return descriptor effect descriptor. + */ + getDescriptor() generates (Result retval, EffectDescriptor descriptor); + + /** + * Set up required transports for passing audio buffers to the effect. + * + * The transport consists of shared memory and a message queue for reporting + * effect processing operation status. The shared memory is set up + * separately using 'setProcessBuffers' method. + * + * Processing is requested by setting 'REQUEST_PROCESS' or + * 'REQUEST_PROCESS_REVERSE' EventFlags associated with the status message + * queue. The result of processing may be one of the following: + * OK if there were no errors during processing; + * INVALID_ARGUMENTS if audio buffers are invalid; + * INVALID_STATE if the engine has finished the disable phase; + * NOT_INITIALIZED if the audio buffers were not set; + * NOT_SUPPORTED if the requested processing type is not supported by + * the effect. + * + * @return retval OK if both message queues were created successfully. + * INVALID_STATE if the method was already called. + * INVALID_ARGUMENTS if there was a problem setting up + * the queue. + * @return statusMQ a message queue used for passing status from the effect. + */ + @callflow(next={"setProcessBuffers"}) + prepareForProcessing() generates (Result retval, fmq_sync<Result> statusMQ); + + /** + * Set up input and output buffers for processing audio data. The effect + * may modify both the input and the output buffer during the operation. + * Buffers may be set multiple times during effect lifetime. + * + * The input and the output buffer may be reused between different effects, + * and the input buffer may be used as an output buffer. Buffers are + * distinguished using 'AudioBuffer.id' field. + * + * @param inBuffer input audio buffer. + * @param outBuffer output audio buffer. + * @return retval OK if both buffers were mapped successfully. + * INVALID_ARGUMENTS if there was a problem with mapping + * any of the buffers. + */ + setProcessBuffers(AudioBuffer inBuffer, AudioBuffer outBuffer) + generates (Result retval); + + /** + * Execute a vendor specific command on the effect. The command code + * and data, as well as result data are not interpreted by Android + * Framework and are passed as-is between the application and the effect. + * + * The effect must use standard POSIX.1-2001 error codes for the operation + * completion status. + * + * Use this method only if the effect is provided by a third party, and + * there is no interface defined for it. This method only works for effects + * implemented in software. + * + * @param commandId the ID of the command. + * @param data command data. + * @param resultMaxSize maximum size in bytes of the result; can be 0. + * @return status command completion status. + * @return result result data. + */ + command(uint32_t commandId, vec<uint8_t> data, uint32_t resultMaxSize) + generates (int32_t status, vec<uint8_t> result); + + /** + * Set a vendor-specific parameter and apply it immediately. The parameter + * code and data are not interpreted by Android Framework and are passed + * as-is between the application and the effect. + * + * The effect must use INVALID_ARGUMENTS return code if the parameter ID is + * unknown or if provided parameter data is invalid. If the effect does not + * support setting vendor-specific parameters, it must return NOT_SUPPORTED. + * + * Use this method only if the effect is provided by a third party, and + * there is no interface defined for it. This method only works for effects + * implemented in software. + * + * @param parameter identifying data of the parameter. + * @param value the value of the parameter. + * @return retval operation completion status. + */ + setParameter(vec<uint8_t> parameter, vec<uint8_t> value) + generates (Result retval); + + /** + * Get a vendor-specific parameter value. The parameter code and returned + * data are not interpreted by Android Framework and are passed as-is + * between the application and the effect. + * + * The effect must use INVALID_ARGUMENTS return code if the parameter ID is + * unknown. If the effect does not support setting vendor-specific + * parameters, it must return NOT_SUPPORTED. + * + * Use this method only if the effect is provided by a third party, and + * there is no interface defined for it. This method only works for effects + * implemented in software. + * + * @param parameter identifying data of the parameter. + * @param valueMaxSize maximum size in bytes of the value. + * @return retval operation completion status. + * @return result the value of the parameter. + */ + getParameter(vec<uint8_t> parameter, uint32_t valueMaxSize) + generates (Result retval, vec<uint8_t> value); + + /** + * Get supported configs for a vendor-specific feature. The configs returned + * are not interpreted by Android Framework and are passed as-is between the + * application and the effect. + * + * The effect must use INVALID_ARGUMENTS return code if the feature ID is + * unknown. If the effect does not support getting vendor-specific feature + * configs, it must return NOT_SUPPORTED. If the feature is supported but + * the total number of supported configurations exceeds the maximum number + * indicated by the caller, the method must return RESULT_TOO_BIG. + * + * Use this method only if the effect is provided by a third party, and + * there is no interface defined for it. This method only works for effects + * implemented in software. + * + * @param featureId feature identifier. + * @param maxConfigs maximum number of configs to return. + * @param configSize size of each config in bytes. + * @return retval operation completion status. + * @return configsCount number of configs returned. + * @return configsData data for all the configs returned. + */ + getSupportedConfigsForFeature( + uint32_t featureId, + uint32_t maxConfigs, + uint32_t configSize) generates ( + Result retval, + uint32_t configsCount, + vec<uint8_t> configsData); + + /** + * Get the current config for a vendor-specific feature. The config returned + * is not interpreted by Android Framework and is passed as-is between the + * application and the effect. + * + * The effect must use INVALID_ARGUMENTS return code if the feature ID is + * unknown. If the effect does not support getting vendor-specific + * feature configs, it must return NOT_SUPPORTED. + * + * Use this method only if the effect is provided by a third party, and + * there is no interface defined for it. This method only works for effects + * implemented in software. + * + * @param featureId feature identifier. + * @param configSize size of the config in bytes. + * @return retval operation completion status. + * @return configData config data. + */ + getCurrentConfigForFeature(uint32_t featureId, uint32_t configSize) + generates (Result retval, vec<uint8_t> configData); + + /** + * Set the current config for a vendor-specific feature. The config data + * is not interpreted by Android Framework and is passed as-is between the + * application and the effect. + * + * The effect must use INVALID_ARGUMENTS return code if the feature ID is + * unknown. If the effect does not support getting vendor-specific + * feature configs, it must return NOT_SUPPORTED. + * + * Use this method only if the effect is provided by a third party, and + * there is no interface defined for it. This method only works for effects + * implemented in software. + * + * @param featureId feature identifier. + * @param configData config data. + * @return retval operation completion status. + */ + setCurrentConfigForFeature(uint32_t featureId, vec<uint8_t> configData) + generates (Result retval); + + /** + * Called by the framework to deinitialize the effect and free up + * all the currently allocated resources. It is recommended to close + * the effect on the client side as soon as it is becomes unused. + * + * @return retval OK in case the success. + * INVALID_STATE if the effect was already closed. + */ + @exit + close() generates (Result retval); +}; diff --git a/audio/effect/6.0/IEffectBufferProviderCallback.hal b/audio/effect/6.0/IEffectBufferProviderCallback.hal new file mode 100644 index 0000000000..097528de41 --- /dev/null +++ b/audio/effect/6.0/IEffectBufferProviderCallback.hal @@ -0,0 +1,38 @@ +/* + * 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.hardware.audio.effect@6.0; + +/** + * This callback interface contains functions that can be used by the effect + * engine 'process' function to exchange input and output audio buffers. + */ +interface IEffectBufferProviderCallback { + /** + * Called to retrieve a buffer where data should read from by 'process' + * function. + * + * @return buffer audio buffer for processing + */ + getBuffer() generates (AudioBuffer buffer); + + /** + * Called to provide a buffer with the data written by 'process' function. + * + * @param buffer audio buffer for processing + */ + putBuffer(AudioBuffer buffer); +}; diff --git a/audio/effect/6.0/IEffectsFactory.hal b/audio/effect/6.0/IEffectsFactory.hal new file mode 100644 index 0000000000..e08b2deb4b --- /dev/null +++ b/audio/effect/6.0/IEffectsFactory.hal @@ -0,0 +1,58 @@ +/* + * 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.hardware.audio.effect@6.0; + +import android.hardware.audio.common@6.0; +import IEffect; + +interface IEffectsFactory { + /** + * Returns descriptors of different effects in all loaded libraries. + * + * @return retval operation completion status. + * @return result list of effect descriptors. + */ + getAllDescriptors() generates(Result retval, vec<EffectDescriptor> result); + + /** + * Returns a descriptor of a particular effect. + * + * @return retval operation completion status. + * @return result effect descriptor. + */ + getDescriptor(Uuid uid) generates(Result retval, EffectDescriptor result); + + /** + * Creates an effect engine of the specified type. To release the effect + * engine, it is necessary to release references to the returned effect + * object. + * + * @param uid effect uuid. + * @param session audio session to which this effect instance will be + * attached. All effects created with the same session ID + * are connected in series and process the same signal + * stream. + * @param ioHandle identifies the output or input stream this effect is + * directed to in audio HAL. + * @return retval operation completion status. + * @return result the interface for the created effect. + * @return effectId the unique ID of the effect to be used with + * IStream::addEffect and IStream::removeEffect methods. + */ + createEffect(Uuid uid, AudioSession session, AudioIoHandle ioHandle) + generates (Result retval, IEffect result, uint64_t effectId); +}; diff --git a/audio/effect/6.0/IEnvironmentalReverbEffect.hal b/audio/effect/6.0/IEnvironmentalReverbEffect.hal new file mode 100644 index 0000000000..8887533478 --- /dev/null +++ b/audio/effect/6.0/IEnvironmentalReverbEffect.hal @@ -0,0 +1,178 @@ +/* + * 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.hardware.audio.effect@6.0; + +import android.hardware.audio.common@6.0; +import IEffect; + +interface IEnvironmentalReverbEffect extends IEffect { + /** + * Sets whether the effect should be bypassed. + */ + setBypass(bool bypass) generates (Result retval); + + /** + * Gets whether the effect should be bypassed. + */ + getBypass() generates (Result retval, bool bypass); + + enum ParamRange : int16_t { + ROOM_LEVEL_MIN = -6000, + ROOM_LEVEL_MAX = 0, + ROOM_HF_LEVEL_MIN = -4000, + ROOM_HF_LEVEL_MAX = 0, + DECAY_TIME_MIN = 100, + DECAY_TIME_MAX = 20000, + DECAY_HF_RATIO_MIN = 100, + DECAY_HF_RATIO_MAX = 1000, + REFLECTIONS_LEVEL_MIN = -6000, + REFLECTIONS_LEVEL_MAX = 0, + REFLECTIONS_DELAY_MIN = 0, + REFLECTIONS_DELAY_MAX = 65, + REVERB_LEVEL_MIN = -6000, + REVERB_LEVEL_MAX = 0, + REVERB_DELAY_MIN = 0, + REVERB_DELAY_MAX = 65, + DIFFUSION_MIN = 0, + DIFFUSION_MAX = 1000, + DENSITY_MIN = 0, + DENSITY_MAX = 1000 + }; + + /** + * Sets the room level. + */ + setRoomLevel(int16_t roomLevel) generates (Result retval); + + /** + * Gets the room level. + */ + getRoomLevel() generates (Result retval, int16_t roomLevel); + + /** + * Sets the room high frequencies level. + */ + setRoomHfLevel(int16_t roomHfLevel) generates (Result retval); + + /** + * Gets the room high frequencies level. + */ + getRoomHfLevel() generates (Result retval, int16_t roomHfLevel); + + /** + * Sets the room decay time. + */ + setDecayTime(uint32_t decayTime) generates (Result retval); + + /** + * Gets the room decay time. + */ + getDecayTime() generates (Result retval, uint32_t decayTime); + + /** + * Sets the ratio of high frequencies decay. + */ + setDecayHfRatio(int16_t decayHfRatio) generates (Result retval); + + /** + * Gets the ratio of high frequencies decay. + */ + getDecayHfRatio() generates (Result retval, int16_t decayHfRatio); + + /** + * Sets the level of reflections in the room. + */ + setReflectionsLevel(int16_t reflectionsLevel) generates (Result retval); + + /** + * Gets the level of reflections in the room. + */ + getReflectionsLevel() generates (Result retval, int16_t reflectionsLevel); + + /** + * Sets the reflections delay in the room. + */ + setReflectionsDelay(uint32_t reflectionsDelay) generates (Result retval); + + /** + * Gets the reflections delay in the room. + */ + getReflectionsDelay() generates (Result retval, uint32_t reflectionsDelay); + + /** + * Sets the reverb level of the room. + */ + setReverbLevel(int16_t reverbLevel) generates (Result retval); + + /** + * Gets the reverb level of the room. + */ + getReverbLevel() generates (Result retval, int16_t reverbLevel); + + /** + * Sets the reverb delay of the room. + */ + setReverbDelay(uint32_t reverDelay) generates (Result retval); + + /** + * Gets the reverb delay of the room. + */ + getReverbDelay() generates (Result retval, uint32_t reverbDelay); + + /** + * Sets room diffusion. + */ + setDiffusion(int16_t diffusion) generates (Result retval); + + /** + * Gets room diffusion. + */ + getDiffusion() generates (Result retval, int16_t diffusion); + + /** + * Sets room wall density. + */ + setDensity(int16_t density) generates (Result retval); + + /** + * Gets room wall density. + */ + getDensity() generates (Result retval, int16_t density); + + struct AllProperties { + int16_t roomLevel; // in millibels, range -6000 to 0 + int16_t roomHfLevel; // in millibels, range -4000 to 0 + uint32_t decayTime; // in milliseconds, range 100 to 20000 + int16_t decayHfRatio; // in permilles, range 100 to 1000 + int16_t reflectionsLevel; // in millibels, range -6000 to 0 + uint32_t reflectionsDelay; // in milliseconds, range 0 to 65 + int16_t reverbLevel; // in millibels, range -6000 to 0 + uint32_t reverbDelay; // in milliseconds, range 0 to 65 + int16_t diffusion; // in permilles, range 0 to 1000 + int16_t density; // in permilles, range 0 to 1000 + }; + + /** + * Sets all properties at once. + */ + setAllProperties(AllProperties properties) generates (Result retval); + + /** + * Gets all properties at once. + */ + getAllProperties() generates (Result retval, AllProperties properties); +}; diff --git a/audio/effect/6.0/IEqualizerEffect.hal b/audio/effect/6.0/IEqualizerEffect.hal new file mode 100644 index 0000000000..962d518e4d --- /dev/null +++ b/audio/effect/6.0/IEqualizerEffect.hal @@ -0,0 +1,93 @@ +/* + * 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.hardware.audio.effect@6.0; + +import android.hardware.audio.common@6.0; +import IEffect; + +interface IEqualizerEffect extends IEffect { + /** + * Gets the number of frequency bands that the equalizer supports. + */ + getNumBands() generates (Result retval, uint16_t numBands); + + /** + * Returns the minimum and maximum band levels supported. + */ + getLevelRange() + generates (Result retval, int16_t minLevel, int16_t maxLevel); + + /** + * Sets the gain for the given equalizer band. + */ + setBandLevel(uint16_t band, int16_t level) generates (Result retval); + + /** + * Gets the gain for the given equalizer band. + */ + getBandLevel(uint16_t band) generates (Result retval, int16_t level); + + /** + * Gets the center frequency of the given band, in milliHertz. + */ + getBandCenterFrequency(uint16_t band) + generates (Result retval, uint32_t centerFreqmHz); + + /** + * Gets the frequency range of the given frequency band, in milliHertz. + */ + getBandFrequencyRange(uint16_t band) + generates (Result retval, uint32_t minFreqmHz, uint32_t maxFreqmHz); + + /** + * Gets the band that has the most effect on the given frequency + * in milliHertz. + */ + getBandForFrequency(uint32_t freqmHz) + generates (Result retval, uint16_t band); + + /** + * Gets the names of all presets the equalizer supports. + */ + getPresetNames() generates (Result retval, vec<string> names); + + /** + * Sets the current preset using the index of the preset in the names + * vector returned via 'getPresetNames'. + */ + setCurrentPreset(uint16_t preset) generates (Result retval); + + /** + * Gets the current preset. + */ + getCurrentPreset() generates (Result retval, uint16_t preset); + + struct AllProperties { + uint16_t curPreset; + vec<int16_t> bandLevels; + }; + + /** + * Sets all properties at once. + */ + setAllProperties(AllProperties properties) generates (Result retval); + + /** + * Gets all properties at once. + */ + getAllProperties() generates (Result retval, AllProperties properties); +}; diff --git a/audio/effect/6.0/ILoudnessEnhancerEffect.hal b/audio/effect/6.0/ILoudnessEnhancerEffect.hal new file mode 100644 index 0000000000..73dc818909 --- /dev/null +++ b/audio/effect/6.0/ILoudnessEnhancerEffect.hal @@ -0,0 +1,32 @@ +/* + * 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.hardware.audio.effect@6.0; + +import android.hardware.audio.common@6.0; +import IEffect; + +interface ILoudnessEnhancerEffect extends IEffect { + /** + * Sets target gain expressed in millibels. + */ + setTargetGain(int32_t targetGainMb) generates (Result retval); + + /** + * Gets target gain expressed in millibels. + */ + getTargetGain() generates (Result retval, int32_t targetGainMb); +}; diff --git a/audio/effect/6.0/INoiseSuppressionEffect.hal b/audio/effect/6.0/INoiseSuppressionEffect.hal new file mode 100644 index 0000000000..16dee57397 --- /dev/null +++ b/audio/effect/6.0/INoiseSuppressionEffect.hal @@ -0,0 +1,68 @@ +/* + * 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.hardware.audio.effect@6.0; + +import android.hardware.audio.common@6.0; +import IEffect; + +interface INoiseSuppressionEffect extends IEffect { + enum Level : int32_t { + LOW, + MEDIUM, + HIGH + }; + + /** + * Sets suppression level. + */ + setSuppressionLevel(Level level) generates (Result retval); + + /** + * Gets suppression level. + */ + getSuppressionLevel() generates (Result retval, Level level); + + enum Type : int32_t { + SINGLE_CHANNEL, + MULTI_CHANNEL + }; + + /** + * Set suppression type. + */ + setSuppressionType(Type type) generates (Result retval); + + /** + * Get suppression type. + */ + getSuppressionType() generates (Result retval, Type type); + + struct AllProperties { + Level level; + Type type; + }; + + /** + * Sets all properties at once. + */ + setAllProperties(AllProperties properties) generates (Result retval); + + /** + * Gets all properties at once. + */ + getAllProperties() generates (Result retval, AllProperties properties); +}; diff --git a/audio/effect/6.0/IPresetReverbEffect.hal b/audio/effect/6.0/IPresetReverbEffect.hal new file mode 100644 index 0000000000..d00c8af00a --- /dev/null +++ b/audio/effect/6.0/IPresetReverbEffect.hal @@ -0,0 +1,43 @@ +/* + * 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.hardware.audio.effect@6.0; + +import android.hardware.audio.common@6.0; +import IEffect; + +interface IPresetReverbEffect extends IEffect { + enum Preset : int32_t { + NONE, // no reverb or reflections + SMALLROOM, // a small room less than five meters in length + MEDIUMROOM, // a medium room with a length of ten meters or less + LARGEROOM, // a large-sized room suitable for live performances + MEDIUMHALL, // a medium-sized hall + LARGEHALL, // a large-sized hall suitable for a full orchestra + PLATE, // synthesis of the traditional plate reverb + LAST = PLATE + }; + + /** + * Sets the current preset. + */ + setPreset(Preset preset) generates (Result retval); + + /** + * Gets the current preset. + */ + getPreset() generates (Result retval, Preset preset); +}; diff --git a/audio/effect/6.0/IVirtualizerEffect.hal b/audio/effect/6.0/IVirtualizerEffect.hal new file mode 100644 index 0000000000..5967b33db2 --- /dev/null +++ b/audio/effect/6.0/IVirtualizerEffect.hal @@ -0,0 +1,77 @@ +/* + * 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.hardware.audio.effect@6.0; + +import android.hardware.audio.common@6.0; +import IEffect; + +interface IVirtualizerEffect extends IEffect { + /** + * Returns whether setting virtualization strength is supported. + */ + isStrengthSupported() generates (bool strengthSupported); + + enum StrengthRange : uint16_t { + MIN = 0, + MAX = 1000 + }; + + /** + * Sets virtualization strength. + * + * @param strength strength of the effect. The valid range for strength + * strength is [0, 1000], where 0 per mille designates the + * mildest effect and 1000 per mille designates the + * strongest. + * @return retval operation completion status. + */ + setStrength(uint16_t strength) generates (Result retval); + + /** + * Gets virtualization strength. + */ + getStrength() generates (Result retval, uint16_t strength); + + struct SpeakerAngle { + /** Speaker channel mask */ + bitfield<AudioChannelMask> mask; + // all angles are expressed in degrees and + // are relative to the listener. + int16_t azimuth; // 0 is the direction the listener faces + // 180 is behind the listener + // -90 is to their left + int16_t elevation; // 0 is the horizontal plane + // +90 is above the listener, -90 is below + }; + /** + * Retrieves virtual speaker angles for the given channel mask on the + * specified device. + */ + getVirtualSpeakerAngles(bitfield<AudioChannelMask> mask, AudioDevice device) + generates (Result retval, vec<SpeakerAngle> speakerAngles); + + /** + * Forces the virtualizer effect for the given output device. + */ + forceVirtualizationMode(AudioDevice device) generates (Result retval); + + /** + * Returns audio device reflecting the current virtualization mode, + * AUDIO_DEVICE_NONE when not virtualizing. + */ + getVirtualizationMode() generates (Result retval, AudioDevice device); +}; diff --git a/audio/effect/6.0/IVisualizerEffect.hal b/audio/effect/6.0/IVisualizerEffect.hal new file mode 100644 index 0000000000..3df3e6f491 --- /dev/null +++ b/audio/effect/6.0/IVisualizerEffect.hal @@ -0,0 +1,110 @@ +/* + * 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.hardware.audio.effect@6.0; + +import android.hardware.audio.common@6.0; +import IEffect; + +interface IVisualizerEffect extends IEffect { + enum CaptureSizeRange : int32_t { + MAX = 1024, // maximum capture size in samples + MIN = 128 // minimum capture size in samples + }; + + /** + * Sets the number PCM samples in the capture. + */ + setCaptureSize(uint16_t captureSize) generates (Result retval); + + /** + * Gets the number PCM samples in the capture. + */ + getCaptureSize() generates (Result retval, uint16_t captureSize); + + enum ScalingMode : int32_t { + // Keep in sync with SCALING_MODE_... in + // frameworks/base/media/java/android/media/audiofx/Visualizer.java + NORMALIZED = 0, + AS_PLAYED = 1 + }; + + /** + * Specifies the way the captured data is scaled. + */ + setScalingMode(ScalingMode scalingMode) generates (Result retval); + + /** + * Retrieves the way the captured data is scaled. + */ + getScalingMode() generates (Result retval, ScalingMode scalingMode); + + /** + * Informs the visualizer about the downstream latency. + */ + setLatency(uint32_t latencyMs) generates (Result retval); + + /** + * Gets the downstream latency. + */ + getLatency() generates (Result retval, uint32_t latencyMs); + + enum MeasurementMode : int32_t { + // Keep in sync with MEASUREMENT_MODE_... in + // frameworks/base/media/java/android/media/audiofx/Visualizer.java + NONE = 0x0, + PEAK_RMS = 0x1 + }; + + /** + * Specifies which measurements are to be made. + */ + setMeasurementMode(MeasurementMode measurementMode) + generates (Result retval); + + /** + * Retrieves which measurements are to be made. + */ + getMeasurementMode() generates ( + Result retval, MeasurementMode measurementMode); + + /** + * Retrieves the latest PCM snapshot captured by the visualizer engine. The + * number of samples to capture is specified by 'setCaptureSize' parameter. + * + * @return retval operation completion status. + * @return samples samples in 8 bit unsigned format (0 = 0x80) + */ + capture() generates (Result retval, vec<uint8_t> samples); + + struct Measurement { + MeasurementMode mode; // discriminator + union Values { + struct PeakAndRms { + int32_t peakMb; // millibels + int32_t rmsMb; // millibels + } peakAndRms; + } value; + }; + /** + * Retrieves the latest measurements. The measurements to be made + * are specified by 'setMeasurementMode' parameter. + * + * @return retval operation completion status. + * @return result measurement. + */ + measure() generates (Result retval, Measurement result); +}; diff --git a/audio/effect/6.0/types.hal b/audio/effect/6.0/types.hal new file mode 100644 index 0000000000..dd5a4ad5ab --- /dev/null +++ b/audio/effect/6.0/types.hal @@ -0,0 +1,301 @@ +/* + * 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.hardware.audio.effect@6.0; + +import android.hardware.audio.common@6.0; + +enum Result : int32_t { + OK, + NOT_INITIALIZED, + INVALID_ARGUMENTS, + INVALID_STATE, + NOT_SUPPORTED, + RESULT_TOO_BIG +}; + +/** + * Effect engine capabilities/requirements flags. + * + * Definitions for flags field of effect descriptor. + * + * +----------------+--------+-------------------------------------------------- + * | description | bits | values + * +----------------+--------+-------------------------------------------------- + * | connection | 0..2 | 0 insert: after track process + * | mode | | 1 auxiliary: connect to track auxiliary + * | | | output and use send level + * | | | 2 replace: replaces track process function; + * | | | must implement SRC, volume and mono to stereo. + * | | | 3 pre processing: applied below audio HAL on in + * | | | 4 post processing: applied below audio HAL on out + * | | | 5 - 7 reserved + * +----------------+--------+-------------------------------------------------- + * | insertion | 3..5 | 0 none + * | preference | | 1 first of the chain + * | | | 2 last of the chain + * | | | 3 exclusive (only effect in the insert chain) + * | | | 4..7 reserved + * +----------------+--------+-------------------------------------------------- + * | Volume | 6..8 | 0 none + * | management | | 1 implements volume control + * | | | 2 requires volume indication + * | | | 3 monitors requested volume + * | | | 4 reserved + * +----------------+--------+-------------------------------------------------- + * | Device | 9..11 | 0 none + * | indication | | 1 requires device updates + * | | | 2, 4 reserved + * +----------------+--------+-------------------------------------------------- + * | Sample input | 12..13 | 1 direct: process() function or + * | mode | | EFFECT_CMD_SET_CONFIG command must specify + * | | | a buffer descriptor + * | | | 2 provider: process() function uses the + * | | | bufferProvider indicated by the + * | | | EFFECT_CMD_SET_CONFIG command to request input. + * | | | buffers. + * | | | 3 both: both input modes are supported + * +----------------+--------+-------------------------------------------------- + * | Sample output | 14..15 | 1 direct: process() function or + * | mode | | EFFECT_CMD_SET_CONFIG command must specify + * | | | a buffer descriptor + * | | | 2 provider: process() function uses the + * | | | bufferProvider indicated by the + * | | | EFFECT_CMD_SET_CONFIG command to request output + * | | | buffers. + * | | | 3 both: both output modes are supported + * +----------------+--------+-------------------------------------------------- + * | Hardware | 16..17 | 0 No hardware acceleration + * | acceleration | | 1 non tunneled hw acceleration: the process() + * | | | function reads the samples, send them to HW + * | | | accelerated effect processor, reads back + * | | | the processed samples and returns them + * | | | to the output buffer. + * | | | 2 tunneled hw acceleration: the process() + * | | | function is transparent. The effect interface + * | | | is only used to control the effect engine. + * | | | This mode is relevant for global effects + * | | | actually applied by the audio hardware on + * | | | the output stream. + * +----------------+--------+-------------------------------------------------- + * | Audio Mode | 18..19 | 0 none + * | indication | | 1 requires audio mode updates + * | | | 2..3 reserved + * +----------------+--------+-------------------------------------------------- + * | Audio source | 20..21 | 0 none + * | indication | | 1 requires audio source updates + * | | | 2..3 reserved + * +----------------+--------+-------------------------------------------------- + * | Effect offload | 22 | 0 The effect cannot be offloaded to an audio DSP + * | supported | | 1 The effect can be offloaded to an audio DSP + * +----------------+--------+-------------------------------------------------- + * | Process | 23 | 0 The effect implements a process function. + * | function | | 1 The effect does not implement a process + * | not | | function: enabling the effect has no impact + * | implemented | | on latency or CPU load. + * | | | Effect implementations setting this flag do not + * | | | have to implement a process function. + * +----------------+--------+-------------------------------------------------- + */ +@export(name="", value_prefix="EFFECT_FLAG_") +enum EffectFlags : int32_t { + // Insert mode + TYPE_SHIFT = 0, + TYPE_SIZE = 3, + TYPE_MASK = ((1 << TYPE_SIZE) -1) << TYPE_SHIFT, + TYPE_INSERT = 0 << TYPE_SHIFT, + TYPE_AUXILIARY = 1 << TYPE_SHIFT, + TYPE_REPLACE = 2 << TYPE_SHIFT, + TYPE_PRE_PROC = 3 << TYPE_SHIFT, + TYPE_POST_PROC = 4 << TYPE_SHIFT, + + // Insert preference + INSERT_SHIFT = TYPE_SHIFT + TYPE_SIZE, + INSERT_SIZE = 3, + INSERT_MASK = ((1 << INSERT_SIZE) -1) << INSERT_SHIFT, + INSERT_ANY = 0 << INSERT_SHIFT, + INSERT_FIRST = 1 << INSERT_SHIFT, + INSERT_LAST = 2 << INSERT_SHIFT, + INSERT_EXCLUSIVE = 3 << INSERT_SHIFT, + + // Volume control + VOLUME_SHIFT = INSERT_SHIFT + INSERT_SIZE, + VOLUME_SIZE = 3, + VOLUME_MASK = ((1 << VOLUME_SIZE) -1) << VOLUME_SHIFT, + VOLUME_CTRL = 1 << VOLUME_SHIFT, + VOLUME_IND = 2 << VOLUME_SHIFT, + VOLUME_MONITOR = 3 << VOLUME_SHIFT, + VOLUME_NONE = 0 << VOLUME_SHIFT, + + // Device indication + DEVICE_SHIFT = VOLUME_SHIFT + VOLUME_SIZE, + DEVICE_SIZE = 3, + DEVICE_MASK = ((1 << DEVICE_SIZE) -1) << DEVICE_SHIFT, + DEVICE_IND = 1 << DEVICE_SHIFT, + DEVICE_NONE = 0 << DEVICE_SHIFT, + + // Sample input modes + INPUT_SHIFT = DEVICE_SHIFT + DEVICE_SIZE, + INPUT_SIZE = 2, + INPUT_MASK = ((1 << INPUT_SIZE) -1) << INPUT_SHIFT, + INPUT_DIRECT = 1 << INPUT_SHIFT, + INPUT_PROVIDER = 2 << INPUT_SHIFT, + INPUT_BOTH = 3 << INPUT_SHIFT, + + // Sample output modes + OUTPUT_SHIFT = INPUT_SHIFT + INPUT_SIZE, + OUTPUT_SIZE = 2, + OUTPUT_MASK = ((1 << OUTPUT_SIZE) -1) << OUTPUT_SHIFT, + OUTPUT_DIRECT = 1 << OUTPUT_SHIFT, + OUTPUT_PROVIDER = 2 << OUTPUT_SHIFT, + OUTPUT_BOTH = 3 << OUTPUT_SHIFT, + + // Hardware acceleration mode + HW_ACC_SHIFT = OUTPUT_SHIFT + OUTPUT_SIZE, + HW_ACC_SIZE = 2, + HW_ACC_MASK = ((1 << HW_ACC_SIZE) -1) << HW_ACC_SHIFT, + HW_ACC_SIMPLE = 1 << HW_ACC_SHIFT, + HW_ACC_TUNNEL = 2 << HW_ACC_SHIFT, + + // Audio mode indication + AUDIO_MODE_SHIFT = HW_ACC_SHIFT + HW_ACC_SIZE, + AUDIO_MODE_SIZE = 2, + AUDIO_MODE_MASK = ((1 << AUDIO_MODE_SIZE) -1) << AUDIO_MODE_SHIFT, + AUDIO_MODE_IND = 1 << AUDIO_MODE_SHIFT, + AUDIO_MODE_NONE = 0 << AUDIO_MODE_SHIFT, + + // Audio source indication + AUDIO_SOURCE_SHIFT = AUDIO_MODE_SHIFT + AUDIO_MODE_SIZE, + AUDIO_SOURCE_SIZE = 2, + AUDIO_SOURCE_MASK = ((1 << AUDIO_SOURCE_SIZE) -1) << AUDIO_SOURCE_SHIFT, + AUDIO_SOURCE_IND = 1 << AUDIO_SOURCE_SHIFT, + AUDIO_SOURCE_NONE = 0 << AUDIO_SOURCE_SHIFT, + + // Effect offload indication + OFFLOAD_SHIFT = AUDIO_SOURCE_SHIFT + AUDIO_SOURCE_SIZE, + OFFLOAD_SIZE = 1, + OFFLOAD_MASK = ((1 << OFFLOAD_SIZE) -1) << OFFLOAD_SHIFT, + OFFLOAD_SUPPORTED = 1 << OFFLOAD_SHIFT, + + // Effect has no process indication + NO_PROCESS_SHIFT = OFFLOAD_SHIFT + OFFLOAD_SIZE, + NO_PROCESS_SIZE = 1, + NO_PROCESS_MASK = ((1 << NO_PROCESS_SIZE) -1) << NO_PROCESS_SHIFT, + NO_PROCESS = 1 << NO_PROCESS_SHIFT +}; + +/** + * The effect descriptor contains necessary information to facilitate the + * enumeration of the effect engines present in a library. + */ +struct EffectDescriptor { + Uuid type; // UUID of to the OpenSL ES interface implemented + // by this effect + Uuid uuid; // UUID for this particular implementation + bitfield<EffectFlags> flags; // effect engine capabilities/requirements flags + uint16_t cpuLoad; // CPU load indication expressed in 0.1 MIPS units + // as estimated on an ARM9E core (ARMv5TE) with 0 WS + uint16_t memoryUsage; // data memory usage expressed in KB and includes + // only dynamically allocated memory + uint8_t[64] name; // human readable effect name + uint8_t[64] implementor; // human readable effect implementor name +}; + +/** + * A buffer is a chunk of audio data for processing. Multi-channel audio is + * always interleaved. The channel order is from LSB to MSB with regard to the + * channel mask definition in audio.h, audio_channel_mask_t, e.g.: + * Stereo: L, R; 5.1: FL, FR, FC, LFE, BL, BR. + * + * The buffer size is expressed in frame count, a frame being composed of + * samples for all channels at a given time. Frame size for unspecified format + * (AUDIO_FORMAT_OTHER) is 8 bit by definition. + */ +struct AudioBuffer { + uint64_t id; + uint32_t frameCount; + memory data; +}; + +@export(name="effect_buffer_access_e", value_prefix="EFFECT_BUFFER_") +enum EffectBufferAccess : int32_t { + ACCESS_WRITE, + ACCESS_READ, + ACCESS_ACCUMULATE +}; + +/** + * Determines what fields of EffectBufferConfig need to be considered. + */ +@export(name="", value_prefix="EFFECT_CONFIG_") +enum EffectConfigParameters : int32_t { + BUFFER = 0x0001, // buffer field + SMP_RATE = 0x0002, // samplingRate + CHANNELS = 0x0004, // channels + FORMAT = 0x0008, // format + ACC_MODE = 0x0010, // accessMode + // Note that the 2.0 ALL have been moved to an helper function +}; + +/** + * The buffer config structure specifies the input or output audio format + * to be used by the effect engine. + */ +struct EffectBufferConfig { + AudioBuffer buffer; + uint32_t samplingRateHz; + bitfield<AudioChannelMask> channels; + AudioFormat format; + EffectBufferAccess accessMode; + bitfield<EffectConfigParameters> mask; +}; + +struct EffectConfig { + EffectBufferConfig inputCfg; + EffectBufferConfig outputCfg; +}; + +@export(name="effect_feature_e", value_prefix="EFFECT_FEATURE_") +enum EffectFeature : int32_t { + AUX_CHANNELS, // supports auxiliary channels + // (e.g. dual mic noise suppressor) + CNT +}; + +struct EffectAuxChannelsConfig { + bitfield<AudioChannelMask> mainChannels; // channel mask for main channels + bitfield<AudioChannelMask> auxChannels; // channel mask for auxiliary channels +}; + +struct EffectOffloadParameter { + bool isOffload; // true if the playback thread the effect + // is attached to is offloaded + AudioIoHandle ioHandle; // io handle of the playback thread + // the effect is attached to +}; + +/** + * The message queue flags used to synchronize reads and writes from + * the status message queue used by effects. + */ +enum MessageQueueFlagBits : uint32_t { + DONE_PROCESSING = 1 << 0, + REQUEST_PROCESS = 1 << 1, + REQUEST_PROCESS_REVERSE = 1 << 2, + REQUEST_QUIT = 1 << 3, + REQUEST_PROCESS_ALL = + REQUEST_PROCESS | REQUEST_PROCESS_REVERSE | REQUEST_QUIT +}; diff --git a/audio/effect/6.0/xml/Android.bp b/audio/effect/6.0/xml/Android.bp new file mode 100644 index 0000000000..353686bd32 --- /dev/null +++ b/audio/effect/6.0/xml/Android.bp @@ -0,0 +1,6 @@ + +xsd_config { + name: "audio_effects_conf_V6_0", + srcs: ["audio_effects_conf.xsd"], + package_name: "audio.effects.V6_0", +} diff --git a/audio/effect/6.0/xml/api/current.txt b/audio/effect/6.0/xml/api/current.txt new file mode 100644 index 0000000000..2021639630 --- /dev/null +++ b/audio/effect/6.0/xml/api/current.txt @@ -0,0 +1,132 @@ +// Signature format: 2.0 +package audio.effects.V6_0 { + + public class AudioEffectsConf { + ctor public AudioEffectsConf(); + method public audio.effects.V6_0.EffectsType getEffects(); + method public audio.effects.V6_0.LibrariesType getLibraries(); + method public audio.effects.V6_0.AudioEffectsConf.Postprocess getPostprocess(); + method public audio.effects.V6_0.AudioEffectsConf.Preprocess getPreprocess(); + method public audio.effects.V6_0.VersionType getVersion(); + method public void setEffects(audio.effects.V6_0.EffectsType); + method public void setLibraries(audio.effects.V6_0.LibrariesType); + method public void setPostprocess(audio.effects.V6_0.AudioEffectsConf.Postprocess); + method public void setPreprocess(audio.effects.V6_0.AudioEffectsConf.Preprocess); + method public void setVersion(audio.effects.V6_0.VersionType); + } + + public static class AudioEffectsConf.Postprocess { + ctor public AudioEffectsConf.Postprocess(); + method public java.util.List<audio.effects.V6_0.StreamPostprocessType> getStream(); + } + + public static class AudioEffectsConf.Preprocess { + ctor public AudioEffectsConf.Preprocess(); + method public java.util.List<audio.effects.V6_0.StreamPreprocessType> getStream(); + } + + public class EffectImplType { + ctor public EffectImplType(); + method public String getLibrary(); + method public String getUuid(); + method public void setLibrary(String); + method public void setUuid(String); + } + + public class EffectProxyType extends audio.effects.V6_0.EffectType { + ctor public EffectProxyType(); + method public audio.effects.V6_0.EffectImplType getLibhw(); + method public audio.effects.V6_0.EffectImplType getLibsw(); + method public void setLibhw(audio.effects.V6_0.EffectImplType); + method public void setLibsw(audio.effects.V6_0.EffectImplType); + } + + public class EffectType extends audio.effects.V6_0.EffectImplType { + ctor public EffectType(); + method public String getName(); + method public void setName(String); + } + + public class EffectsType { + ctor public EffectsType(); + method public java.util.List<audio.effects.V6_0.EffectProxyType> getEffectProxy_optional(); + method public java.util.List<audio.effects.V6_0.EffectType> getEffect_optional(); + } + + public class LibrariesType { + ctor public LibrariesType(); + method public java.util.List<audio.effects.V6_0.LibrariesType.Library> getLibrary(); + } + + public static class LibrariesType.Library { + ctor public LibrariesType.Library(); + method public String getName(); + method public String getPath(); + method public void setName(String); + method public void setPath(String); + } + + public enum StreamInputType { + method public String getRawName(); + enum_constant public static final audio.effects.V6_0.StreamInputType camcorder; + enum_constant public static final audio.effects.V6_0.StreamInputType mic; + enum_constant public static final audio.effects.V6_0.StreamInputType unprocessed; + enum_constant public static final audio.effects.V6_0.StreamInputType voice_call; + enum_constant public static final audio.effects.V6_0.StreamInputType voice_communication; + enum_constant public static final audio.effects.V6_0.StreamInputType voice_downlink; + enum_constant public static final audio.effects.V6_0.StreamInputType voice_performance; + enum_constant public static final audio.effects.V6_0.StreamInputType voice_recognition; + enum_constant public static final audio.effects.V6_0.StreamInputType voice_uplink; + } + + public enum StreamOutputType { + method public String getRawName(); + enum_constant public static final audio.effects.V6_0.StreamOutputType alarm; + enum_constant public static final audio.effects.V6_0.StreamOutputType bluetooth_sco; + enum_constant public static final audio.effects.V6_0.StreamOutputType dtmf; + enum_constant public static final audio.effects.V6_0.StreamOutputType enforced_audible; + enum_constant public static final audio.effects.V6_0.StreamOutputType music; + enum_constant public static final audio.effects.V6_0.StreamOutputType notification; + enum_constant public static final audio.effects.V6_0.StreamOutputType ring; + enum_constant public static final audio.effects.V6_0.StreamOutputType system; + enum_constant public static final audio.effects.V6_0.StreamOutputType tts; + enum_constant public static final audio.effects.V6_0.StreamOutputType voice_call; + } + + public class StreamPostprocessType extends audio.effects.V6_0.StreamProcessingType { + ctor public StreamPostprocessType(); + method public audio.effects.V6_0.StreamOutputType getType(); + method public void setType(audio.effects.V6_0.StreamOutputType); + } + + public class StreamPreprocessType extends audio.effects.V6_0.StreamProcessingType { + ctor public StreamPreprocessType(); + method public audio.effects.V6_0.StreamInputType getType(); + method public void setType(audio.effects.V6_0.StreamInputType); + } + + public class StreamProcessingType { + ctor public StreamProcessingType(); + method public java.util.List<audio.effects.V6_0.StreamProcessingType.Apply> getApply(); + } + + public static class StreamProcessingType.Apply { + ctor public StreamProcessingType.Apply(); + method public String getEffect(); + method public void setEffect(String); + } + + public enum VersionType { + method public String getRawName(); + enum_constant public static final audio.effects.V6_0.VersionType _2_0; + } + + public class XmlParser { + ctor public XmlParser(); + method public static audio.effects.V6_0.AudioEffectsConf read(java.io.InputStream) throws javax.xml.datatype.DatatypeConfigurationException, java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static String readText(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static void skip(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + } + +} + diff --git a/audio/effect/6.0/xml/api/last_current.txt b/audio/effect/6.0/xml/api/last_current.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/audio/effect/6.0/xml/api/last_current.txt diff --git a/audio/effect/6.0/xml/api/last_removed.txt b/audio/effect/6.0/xml/api/last_removed.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/audio/effect/6.0/xml/api/last_removed.txt diff --git a/audio/effect/6.0/xml/api/removed.txt b/audio/effect/6.0/xml/api/removed.txt new file mode 100644 index 0000000000..d802177e24 --- /dev/null +++ b/audio/effect/6.0/xml/api/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/audio/effect/6.0/xml/audio_effects_conf.xsd b/audio/effect/6.0/xml/audio_effects_conf.xsd new file mode 120000 index 0000000000..9d85fa7c68 --- /dev/null +++ b/audio/effect/6.0/xml/audio_effects_conf.xsd @@ -0,0 +1 @@ +../../2.0/xml/audio_effects_conf.xsd
\ No newline at end of file diff --git a/audio/effect/all-versions/default/Android.bp b/audio/effect/all-versions/default/Android.bp index 653b30c124..d9bb78b700 100644 --- a/audio/effect/all-versions/default/Android.bp +++ b/audio/effect/all-versions/default/Android.bp @@ -52,7 +52,6 @@ cc_library_shared { "android.hardware.audio.common@2.0-util", "android.hardware.audio.effect@2.0", ], - cflags: [ "-DMAJOR_VERSION=2", "-DMINOR_VERSION=0", @@ -68,7 +67,6 @@ cc_library_shared { "android.hardware.audio.common@4.0-util", "android.hardware.audio.effect@4.0", ], - cflags: [ "-DMAJOR_VERSION=4", "-DMINOR_VERSION=0", @@ -84,10 +82,24 @@ cc_library_shared { "android.hardware.audio.common@5.0-util", "android.hardware.audio.effect@5.0", ], - cflags: [ "-DMAJOR_VERSION=5", "-DMINOR_VERSION=0", "-include common/all-versions/VersionMacro.h", ] } + +cc_library_shared { + name: "android.hardware.audio.effect@6.0-impl", + defaults: ["android.hardware.audio.effect-impl_default"], + shared_libs: [ + "android.hardware.audio.common@6.0", + "android.hardware.audio.common@6.0-util", + "android.hardware.audio.effect@6.0", + ], + cflags: [ + "-DMAJOR_VERSION=6", + "-DMINOR_VERSION=0", + "-include common/all-versions/VersionMacro.h", + ] +} diff --git a/audio/effect/all-versions/vts/functional/Android.bp b/audio/effect/all-versions/vts/functional/Android.bp index cccb5c86aa..edc9076905 100644 --- a/audio/effect/all-versions/vts/functional/Android.bp +++ b/audio/effect/all-versions/vts/functional/Android.bp @@ -76,3 +76,16 @@ cc_test { ] } +cc_test { + name: "VtsHalAudioEffectV6_0TargetTest", + defaults: ["VtsHalAudioEffectTargetTest_default"], + static_libs: [ + "android.hardware.audio.common@6.0", + "android.hardware.audio.effect@6.0", + ], + cflags: [ + "-DMAJOR_VERSION=6", + "-DMINOR_VERSION=0", + "-include common/all-versions/VersionMacro.h", + ] +} diff --git a/authsecret/1.0/vts/functional/Android.bp b/authsecret/1.0/vts/functional/Android.bp index f2b3a8a53a..9ce9cda2f9 100644 --- a/authsecret/1.0/vts/functional/Android.bp +++ b/authsecret/1.0/vts/functional/Android.bp @@ -19,5 +19,9 @@ cc_test { defaults: ["VtsHalTargetTestDefaults"], srcs: ["VtsHalAuthSecretV1_0TargetTest.cpp"], static_libs: ["android.hardware.authsecret@1.0"], - test_suites: ["general-tests"], + test_suites: [ + "general-tests", + "vts-core", + ], + require_root: true, } diff --git a/authsecret/1.0/vts/functional/VtsHalAuthSecretV1_0TargetTest.cpp b/authsecret/1.0/vts/functional/VtsHalAuthSecretV1_0TargetTest.cpp index 255d4de3f5..0bff9df7dd 100644 --- a/authsecret/1.0/vts/functional/VtsHalAuthSecretV1_0TargetTest.cpp +++ b/authsecret/1.0/vts/functional/VtsHalAuthSecretV1_0TargetTest.cpp @@ -16,36 +16,22 @@ #include <android/hardware/authsecret/1.0/IAuthSecret.h> -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> using ::android::hardware::hidl_vec; using ::android::hardware::authsecret::V1_0::IAuthSecret; using ::android::sp; -// Test environment for Boot HIDL HAL. -class AuthSecretHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static AuthSecretHidlEnvironment* Instance() { - static AuthSecretHidlEnvironment* instance = new AuthSecretHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IAuthSecret>(); } - - private: - AuthSecretHidlEnvironment() {} -}; - /** * There is no expected behaviour that can be tested so these tests check the * HAL doesn't crash with different execution orders. */ -struct AuthSecretHidlTest : public ::testing::VtsHalHidlTargetTestBase { +class AuthSecretHidlTest : public testing::TestWithParam<std::string> { + public: virtual void SetUp() override { - authsecret = ::testing::VtsHalHidlTargetTestBase::getService<IAuthSecret>( - AuthSecretHidlEnvironment::Instance()->getServiceName<IAuthSecret>()); + authsecret = IAuthSecret::getService(GetParam()); ASSERT_NE(authsecret, nullptr); // All tests must enroll the correct secret first as this cannot be changed @@ -59,18 +45,18 @@ struct AuthSecretHidlTest : public ::testing::VtsHalHidlTargetTestBase { }; /* Provision the primary user with a secret. */ -TEST_F(AuthSecretHidlTest, provisionPrimaryUserCredential) { +TEST_P(AuthSecretHidlTest, provisionPrimaryUserCredential) { // Secret provisioned by SetUp() } /* Provision the primary user with a secret and pass the secret again. */ -TEST_F(AuthSecretHidlTest, provisionPrimaryUserCredentialAndPassAgain) { +TEST_P(AuthSecretHidlTest, provisionPrimaryUserCredentialAndPassAgain) { // Secret provisioned by SetUp() authsecret->primaryUserCredential(CORRECT_SECRET); } /* Provision the primary user with a secret and pass the secret again repeatedly. */ -TEST_F(AuthSecretHidlTest, provisionPrimaryUserCredentialAndPassAgainMultipleTimes) { +TEST_P(AuthSecretHidlTest, provisionPrimaryUserCredentialAndPassAgainMultipleTimes) { // Secret provisioned by SetUp() constexpr int N = 5; for (int i = 0; i < N; ++i) { @@ -82,16 +68,12 @@ TEST_F(AuthSecretHidlTest, provisionPrimaryUserCredentialAndPassAgainMultipleTim * should never happen and is an framework bug if it does. As the secret is * wrong, the HAL implementation may not be able to function correctly but it * should fail gracefully. */ -TEST_F(AuthSecretHidlTest, provisionPrimaryUserCredentialAndWrongSecret) { +TEST_P(AuthSecretHidlTest, provisionPrimaryUserCredentialAndWrongSecret) { // Secret provisioned by SetUp() authsecret->primaryUserCredential(WRONG_SECRET); } -int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(AuthSecretHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - AuthSecretHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - ALOGI("Test result = %d", status); - return status; -} +INSTANTIATE_TEST_SUITE_P( + PerInstance, AuthSecretHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IAuthSecret::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/automotive/OWNERS b/automotive/OWNERS index 3cf4489e9a..83ee63c397 100644 --- a/automotive/OWNERS +++ b/automotive/OWNERS @@ -1,4 +1,5 @@ -randolphs@google.com pirozzoj@google.com twasilczyk@google.com pfg@google.com +gurunagarajan@google.com +keunyoung@google.com diff --git a/automotive/can/1.0/default/Android.bp b/automotive/can/1.0/default/Android.bp index 0a4afd6e37..8aa1d6bff7 100644 --- a/automotive/can/1.0/default/Android.bp +++ b/automotive/can/1.0/default/Android.bp @@ -47,7 +47,6 @@ cc_binary { shared_libs: [ "android.hardware.automotive.can@1.0", "libhidlbase", - "libhidltransport", ], static_libs: [ "android.hardware.automotive.can@libnetdevice", diff --git a/automotive/can/1.0/tools/Android.bp b/automotive/can/1.0/tools/Android.bp index 8c26985d51..21f364b629 100644 --- a/automotive/can/1.0/tools/Android.bp +++ b/automotive/can/1.0/tools/Android.bp @@ -23,7 +23,6 @@ cc_binary { shared_libs: [ "android.hardware.automotive.can@1.0", "libhidlbase", - "libhidltransport", ], header_libs: [ "android.hardware.automotive.can@hidl-utils-lib", @@ -39,7 +38,6 @@ cc_binary { shared_libs: [ "android.hardware.automotive.can@1.0", "libhidlbase", - "libhidltransport", ], header_libs: [ "android.hardware.automotive.can@hidl-utils-lib", @@ -55,6 +53,5 @@ cc_binary { shared_libs: [ "android.hardware.automotive.can@1.0", "libhidlbase", - "libhidltransport", ], } diff --git a/automotive/evs/1.1/Android.bp b/automotive/evs/1.1/Android.bp index d2e85f1304..c850c91b21 100644 --- a/automotive/evs/1.1/Android.bp +++ b/automotive/evs/1.1/Android.bp @@ -10,13 +10,15 @@ hidl_interface { "types.hal", "IEvsCamera.hal", "IEvsCameraStream.hal", + "IEvsEnumerator.hal", ], interfaces: [ "android.hardware.automotive.evs@1.0", + "android.hardware.camera.device@3.2", "android.hardware.graphics.common@1.0", "android.hardware.graphics.common@1.1", "android.hardware.graphics.common@1.2", "android.hidl.base@1.0", ], - gen_java: true, + gen_java: false, } diff --git a/automotive/evs/1.1/IEvsCamera.hal b/automotive/evs/1.1/IEvsCamera.hal index 21ca79e91f..975b6c6cae 100644 --- a/automotive/evs/1.1/IEvsCamera.hal +++ b/automotive/evs/1.1/IEvsCamera.hal @@ -26,6 +26,14 @@ import IEvsCameraStream; */ interface IEvsCamera extends @1.0::IEvsCamera { /** + * Returns the description of this camera. + * + * @return info The description of this camera. This must be the same value as + * reported by EvsEnumerator::getCameraList_1_1(). + */ + getCameraInfo_1_1() generates (CameraDesc info); + + /** * Requests to pause EVS camera stream events. * * Like stopVideoStream(), events may continue to arrive for some time @@ -100,7 +108,27 @@ interface IEvsCamera extends @1.0::IEvsCamera { unsetMaster() generates (EvsResult result); /** - * Requests to set a camera parameter. + * Retrieves a list of parameters this camera supports. + * + * @return params A list of CameraParam that this camera supports. + */ + getParameterList() generates (vec<CameraParam> params); + + /** + * Requests a valid value range of a camera parameter + * + * @param id The identifier of camera parameter, CameraParam enum. + * + * @return min The lower bound of valid parameter value range. + * @return max The upper bound of valid parameter value range. + * @return step The resolution of values in valid range. + */ + getIntParameterRange(CameraParam id) + generates (int32_t min, int32_t max, int32_t step); + + /** + * Requests to set a camera parameter. Only a request from the master + * client will be processed successfully. * * @param id The identifier of camera parameter, CameraParam enum. * value A desired parameter value. @@ -114,7 +142,7 @@ interface IEvsCamera extends @1.0::IEvsCamera { * from what the client gives if, for example, the * driver does not support a target parameter. */ - setParameter(CameraParam id, int32_t value) + setIntParameter(CameraParam id, int32_t value) generates (EvsResult result, int32_t effectiveValue); /** @@ -126,5 +154,5 @@ interface IEvsCamera extends @1.0::IEvsCamera { * not supported. * value A value of requested camera parameter. */ - getParameter(CameraParam id) generates(EvsResult result, int32_t value); + getIntParameter(CameraParam id) generates(EvsResult result, int32_t value); }; diff --git a/automotive/evs/1.1/IEvsCameraStream.hal b/automotive/evs/1.1/IEvsCameraStream.hal index 7c7f832103..9e4ea19f1d 100644 --- a/automotive/evs/1.1/IEvsCameraStream.hal +++ b/automotive/evs/1.1/IEvsCameraStream.hal @@ -17,15 +17,32 @@ package android.hardware.automotive.evs@1.1; import @1.0::IEvsCameraStream; +import @1.1::BufferDesc; +import @1.1::EvsEvent; /** * Implemented on client side to receive asynchronous streaming event deliveries. */ interface IEvsCameraStream extends @1.0::IEvsCameraStream { + + /** + * Receives calls from the HAL each time a video frame is ready for inspection. + * Buffer handles received by this method must be returned via calls to + * IEvsCamera::doneWithFrame_1_1(). When the video stream is stopped via a call + * to IEvsCamera::stopVideoStream(), this callback may continue to happen for + * some time as the pipeline drains. Each frame must still be returned. + * When the last frame in the stream has been delivered, STREAM_STOPPED + * event must be delivered. No further frame deliveries may happen + * thereafter. + * + * @param buffer a buffer descriptor of a delivered image frame. + */ + oneway deliverFrame_1_1(BufferDesc buffer); + /** * Receives calls from the HAL each time an event happens. * * @param event EVS event with possible event information. */ - oneway notifyEvent(EvsEvent event); + oneway notify(EvsEvent event); }; diff --git a/automotive/evs/1.1/IEvsEnumerator.hal b/automotive/evs/1.1/IEvsEnumerator.hal new file mode 100644 index 0000000000..1695821baa --- /dev/null +++ b/automotive/evs/1.1/IEvsEnumerator.hal @@ -0,0 +1,50 @@ +/* + * 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.hardware.automotive.evs@1.1; + +import IEvsCamera; +import @1.0::IEvsEnumerator; +import @1.0::EvsResult; +import android.hardware.camera.device@3.2::Stream; + +/** + * Provides the mechanism for EVS camera discovery + */ +interface IEvsEnumerator extends @1.0::IEvsEnumerator { + /** + * Returns a list of all EVS cameras available to the system + * + * @return cameras A list of cameras availale for EVS service. + */ + getCameraList_1_1() generates (vec<CameraDesc> cameras); + + /** + * Gets the IEvsCamera associated with a cameraId from a CameraDesc + * + * Given a camera's unique cameraId from CameraDesc, returns the + * IEvsCamera interface associated with the specified camera. When + * done using the camera, the caller may release it by calling closeCamera(). + * + * @param cameraId A unique identifier of the camera. + * @param streamCfg A stream configuration the client wants to use. + * @return evsCamera EvsCamera object associated with a given cameraId. + * Returned object would be null if a camera device does + * not support a given stream configuration or is already + * configured differently by another client. + */ + openCamera_1_1(string cameraId, Stream streamCfg) generates (IEvsCamera evsCamera); +}; diff --git a/automotive/evs/1.1/default/Android.bp b/automotive/evs/1.1/default/Android.bp index 411f0ff742..41cb4265e5 100644 --- a/automotive/evs/1.1/default/Android.bp +++ b/automotive/evs/1.1/default/Android.bp @@ -7,26 +7,41 @@ cc_binary { "service.cpp", "EvsCamera.cpp", "EvsEnumerator.cpp", - "EvsDisplay.cpp" + "EvsDisplay.cpp", + "ConfigManager.cpp", + "ConfigManagerUtil.cpp", ], init_rc: ["android.hardware.automotive.evs@1.1-service.rc"], shared_libs: [ "android.hardware.automotive.evs@1.0", "android.hardware.automotive.evs@1.1", + "android.hardware.camera.device@3.2", "libbase", "libbinder", - "libcutils", + "liblog", "libhardware", "libhidlbase", - "libhidltransport", "liblog", "libui", "libutils", + "libcamera_metadata", + "libtinyxml2", ], cflags: [ "-O0", "-g", ], + + required: [ + "evs_default_configuration.xml", + ], +} + +prebuilt_etc { + name: "evs_default_configuration.xml", + + src: "resources/evs_default_configuration.xml", + sub_dir: "automotive/evs", } diff --git a/automotive/evs/1.1/default/ConfigManager.cpp b/automotive/evs/1.1/default/ConfigManager.cpp new file mode 100644 index 0000000000..96a2f98576 --- /dev/null +++ b/automotive/evs/1.1/default/ConfigManager.cpp @@ -0,0 +1,487 @@ +/* + * 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. + */ + +#include <sstream> +#include <fstream> +#include <thread> + +#include <hardware/gralloc.h> +#include <utils/SystemClock.h> +#include <android/hardware/camera/device/3.2/ICameraDevice.h> + +#include "ConfigManager.h" + +using ::android::hardware::camera::device::V3_2::StreamRotation; + + +ConfigManager::~ConfigManager() { + /* Nothing to do */ +} + + +void ConfigManager::readCameraInfo(const XMLElement * const aCameraElem) { + if (aCameraElem == nullptr) { + ALOGW("XML file does not have required camera element"); + return; + } + + const XMLElement *curElem = aCameraElem->FirstChildElement(); + while (curElem != nullptr) { + if (!strcmp(curElem->Name(), "group")) { + /* camera group identifier */ + const char *group_id = curElem->FindAttribute("group_id")->Value(); + + /* create CameraGroup */ + unique_ptr<ConfigManager::CameraGroup> aCameraGroup(new ConfigManager::CameraGroup()); + + /* add a camera device to its group */ + addCameraDevices(curElem->FindAttribute("device_id")->Value(), aCameraGroup); + + /* a list of camera stream configurations */ + const XMLElement *childElem = + curElem->FirstChildElement("caps")->FirstChildElement("stream"); + while (childElem != nullptr) { + /* read 5 attributes */ + const XMLAttribute *idAttr = childElem->FindAttribute("id"); + const XMLAttribute *widthAttr = childElem->FindAttribute("width"); + const XMLAttribute *heightAttr = childElem->FindAttribute("height"); + const XMLAttribute *fmtAttr = childElem->FindAttribute("format"); + const XMLAttribute *fpsAttr = childElem->FindAttribute("framerate"); + + const int32_t id = stoi(idAttr->Value()); + int32_t framerate = 0; + if (fpsAttr != nullptr) { + framerate = stoi(fpsAttr->Value()); + } + + int32_t pixFormat; + if (ConfigManagerUtil::convertToPixelFormat(fmtAttr->Value(), + pixFormat)) { + RawStreamConfiguration cfg = { + id, + stoi(widthAttr->Value()), + stoi(heightAttr->Value()), + pixFormat, + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT, + framerate + }; + aCameraGroup->streamConfigurations[id] = cfg; + } + + childElem = childElem->NextSiblingElement("stream"); + } + + /* camera group synchronization */ + const char *sync = curElem->FindAttribute("synchronized")->Value(); + aCameraGroup->synchronized = + static_cast<bool>(strcmp(sync, "false")); + + /* add a group to hash map */ + mCameraGroups[group_id] = std::move(aCameraGroup); + } else if (!strcmp(curElem->Name(), "device")) { + /* camera unique identifier */ + const char *id = curElem->FindAttribute("id")->Value(); + + /* camera mount location */ + const char *pos = curElem->FindAttribute("position")->Value(); + + /* store read camera module information */ + mCameraInfo[id] = readCameraDeviceInfo(curElem); + + /* assign a camera device to a position group */ + mCameraPosition[pos].emplace(id); + } else { + /* ignore other device types */ + ALOGD("Unknown element %s is ignored", curElem->Name()); + } + + curElem = curElem->NextSiblingElement(); + } +} + + +unique_ptr<ConfigManager::CameraInfo> +ConfigManager::readCameraDeviceInfo(const XMLElement *aDeviceElem) { + if (aDeviceElem == nullptr) { + return nullptr; + } + + /* create a CameraInfo to be filled */ + unique_ptr<ConfigManager::CameraInfo> aCamera(new ConfigManager::CameraInfo()); + + /* size information to allocate camera_metadata_t */ + size_t totalEntries = 0; + size_t totalDataSize = 0; + + /* read device capabilities */ + totalEntries += + readCameraCapabilities(aDeviceElem->FirstChildElement("caps"), + aCamera, + totalDataSize); + + + /* read camera metadata */ + totalEntries += + readCameraMetadata(aDeviceElem->FirstChildElement("characteristics"), + aCamera, + totalDataSize); + + /* construct camera_metadata_t */ + if (!constructCameraMetadata(aCamera, totalEntries, totalDataSize)) { + ALOGW("Either failed to allocate memory or " + "allocated memory was not large enough"); + } + + return aCamera; +} + + +size_t ConfigManager::readCameraCapabilities(const XMLElement * const aCapElem, + unique_ptr<ConfigManager::CameraInfo> &aCamera, + size_t &dataSize) { + if (aCapElem == nullptr) { + return 0; + } + + string token; + const XMLElement *curElem = nullptr; + + /* a list of supported camera parameters/controls */ + curElem = aCapElem->FirstChildElement("supported_controls"); + if (curElem != nullptr) { + const XMLElement *ctrlElem = curElem->FirstChildElement("control"); + while (ctrlElem != nullptr) { + const char *nameAttr = ctrlElem->FindAttribute("name")->Value();; + const int32_t minVal = stoi(ctrlElem->FindAttribute("min")->Value()); + const int32_t maxVal = stoi(ctrlElem->FindAttribute("max")->Value()); + + int32_t stepVal = 1; + const XMLAttribute *stepAttr = ctrlElem->FindAttribute("step"); + if (stepAttr != nullptr) { + stepVal = stoi(stepAttr->Value()); + } + + CameraParam aParam; + if (ConfigManagerUtil::convertToEvsCameraParam(nameAttr, + aParam)) { + aCamera->controls.emplace( + aParam, + make_tuple(minVal, maxVal, stepVal) + ); + } + + ctrlElem = ctrlElem->NextSiblingElement("control"); + } + } + + /* a list of camera stream configurations */ + curElem = aCapElem->FirstChildElement("stream"); + while (curElem != nullptr) { + /* read 5 attributes */ + const XMLAttribute *idAttr = curElem->FindAttribute("id"); + const XMLAttribute *widthAttr = curElem->FindAttribute("width"); + const XMLAttribute *heightAttr = curElem->FindAttribute("height"); + const XMLAttribute *fmtAttr = curElem->FindAttribute("format"); + const XMLAttribute *fpsAttr = curElem->FindAttribute("framerate"); + + const int32_t id = stoi(idAttr->Value()); + int32_t framerate = 0; + if (fpsAttr != nullptr) { + framerate = stoi(fpsAttr->Value()); + } + + int32_t pixFormat; + if (ConfigManagerUtil::convertToPixelFormat(fmtAttr->Value(), + pixFormat)) { + RawStreamConfiguration cfg = { + id, + stoi(widthAttr->Value()), + stoi(heightAttr->Value()), + pixFormat, + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT, + framerate + }; + aCamera->streamConfigurations[id] = cfg; + } + + curElem = curElem->NextSiblingElement("stream"); + } + + dataSize = calculate_camera_metadata_entry_data_size( + get_camera_metadata_tag_type( + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS + ), + aCamera->streamConfigurations.size() * kStreamCfgSz + ); + + /* a single camera metadata entry contains multiple stream configurations */ + return dataSize > 0 ? 1 : 0; +} + + +size_t ConfigManager::readCameraMetadata(const XMLElement * const aParamElem, + unique_ptr<ConfigManager::CameraInfo> &aCamera, + size_t &dataSize) { + if (aParamElem == nullptr) { + return 0; + } + + const XMLElement *curElem = aParamElem->FirstChildElement("parameter"); + size_t numEntries = 0; + camera_metadata_tag_t tag; + while (curElem != nullptr) { + if (!ConfigManagerUtil::convertToMetadataTag(curElem->FindAttribute("name")->Value(), + tag)) { + switch(tag) { + case ANDROID_LENS_DISTORTION: + case ANDROID_LENS_POSE_ROTATION: + case ANDROID_LENS_POSE_TRANSLATION: + case ANDROID_LENS_INTRINSIC_CALIBRATION: { + /* float[] */ + size_t count = 0; + void *data = ConfigManagerUtil::convertFloatArray( + curElem->FindAttribute("size")->Value(), + curElem->FindAttribute("value")->Value(), + count + ); + + aCamera->cameraMetadata[tag] = + make_pair(make_unique<void *>(data), count); + + ++numEntries; + dataSize += calculate_camera_metadata_entry_data_size( + get_camera_metadata_tag_type(tag), count + ); + + break; + } + + default: + ALOGW("Parameter %s is not supported", + curElem->FindAttribute("name")->Value()); + break; + } + } + + curElem = curElem->NextSiblingElement("parameter"); + } + + return numEntries; +} + + +bool ConfigManager::constructCameraMetadata(unique_ptr<CameraInfo> &aCamera, + const size_t totalEntries, + const size_t totalDataSize) { + if (!aCamera->allocate(totalEntries, totalDataSize)) { + ALOGE("Failed to allocate memory for camera metadata"); + return false; + } + + const size_t numStreamConfigs = aCamera->streamConfigurations.size(); + unique_ptr<int32_t[]> data(new int32_t[kStreamCfgSz * numStreamConfigs]); + int32_t *ptr = data.get(); + for (auto &cfg : aCamera->streamConfigurations) { + for (auto i = 0; i < kStreamCfgSz; ++i) { + *ptr++ = cfg.second[i]; + } + } + int32_t err = add_camera_metadata_entry(aCamera->characteristics, + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, + data.get(), + numStreamConfigs * kStreamCfgSz); + + if (err) { + ALOGE("Failed to add stream configurations to metadata, ignored"); + return false; + } + + bool success = true; + for (auto &[tag, entry] : aCamera->cameraMetadata) { + /* try to add new camera metadata entry */ + int32_t err = add_camera_metadata_entry(aCamera->characteristics, + tag, + entry.first.get(), + entry.second); + if (err) { + ALOGE("Failed to add an entry with a tag 0x%X", tag); + + /* may exceed preallocated capacity */ + ALOGE("Camera metadata has %ld / %ld entries and %ld / %ld bytes are filled", + (long)get_camera_metadata_entry_count(aCamera->characteristics), + (long)get_camera_metadata_entry_capacity(aCamera->characteristics), + (long)get_camera_metadata_data_count(aCamera->characteristics), + (long)get_camera_metadata_data_capacity(aCamera->characteristics)); + ALOGE("\tCurrent metadata entry requires %ld bytes", + (long)calculate_camera_metadata_entry_data_size(tag, entry.second)); + + success = false; + } + } + + ALOGV("Camera metadata has %ld / %ld entries and %ld / %ld bytes are filled", + (long)get_camera_metadata_entry_count(aCamera->characteristics), + (long)get_camera_metadata_entry_capacity(aCamera->characteristics), + (long)get_camera_metadata_data_count(aCamera->characteristics), + (long)get_camera_metadata_data_capacity(aCamera->characteristics)); + + return success; +} + + +void ConfigManager::readSystemInfo(const XMLElement * const aSysElem) { + if (aSysElem == nullptr) { + return; + } + + /* + * Please note that this function assumes that a given system XML element + * and its child elements follow DTD. If it does not, it will cause a + * segmentation fault due to the failure of finding expected attributes. + */ + + /* read number of cameras available in the system */ + const XMLElement *xmlElem = aSysElem->FirstChildElement("num_cameras"); + if (xmlElem != nullptr) { + mSystemInfo.numCameras = + stoi(xmlElem->FindAttribute("value")->Value()); + } +} + + +void ConfigManager::readDisplayInfo(const XMLElement * const aDisplayElem) { + if (aDisplayElem == nullptr) { + ALOGW("XML file does not have required camera element"); + return; + } + + const XMLElement *curDev = aDisplayElem->FirstChildElement("device"); + while (curDev != nullptr) { + const char *id = curDev->FindAttribute("id")->Value(); + //const char *pos = curDev->FirstAttribute("position")->Value(); + + unique_ptr<DisplayInfo> dpy(new DisplayInfo()); + if (dpy == nullptr) { + ALOGE("Failed to allocate memory for DisplayInfo"); + return; + } + + const XMLElement *cap = curDev->FirstChildElement("caps"); + if (cap != nullptr) { + const XMLElement *curStream = cap->FirstChildElement("stream"); + while (curStream != nullptr) { + /* read 4 attributes */ + const XMLAttribute *idAttr = curStream->FindAttribute("id"); + const XMLAttribute *widthAttr = curStream->FindAttribute("width"); + const XMLAttribute *heightAttr = curStream->FindAttribute("height"); + const XMLAttribute *fmtAttr = curStream->FindAttribute("format"); + + const int32_t id = stoi(idAttr->Value()); + int32_t pixFormat; + if (ConfigManagerUtil::convertToPixelFormat(fmtAttr->Value(), + pixFormat)) { + RawStreamConfiguration cfg = { + id, + stoi(widthAttr->Value()), + stoi(heightAttr->Value()), + pixFormat, + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_INPUT, + 0 // unused + }; + dpy->streamConfigurations[id] = cfg; + } + + curStream = curStream->NextSiblingElement("stream"); + } + } + + mDisplayInfo[id] = std::move(dpy); + curDev = curDev->NextSiblingElement("device"); + } + + return; +} + + +bool ConfigManager::readConfigDataFromXML() noexcept { + XMLDocument xmlDoc; + + const int64_t parsingStart = android::elapsedRealtimeNano(); + + /* load and parse a configuration file */ + xmlDoc.LoadFile(mConfigFilePath); + if (xmlDoc.ErrorID() != XML_SUCCESS) { + ALOGE("Failed to load and/or parse a configuration file, %s", xmlDoc.ErrorStr()); + return false; + } + + /* retrieve the root element */ + const XMLElement *rootElem = xmlDoc.RootElement(); + if (strcmp(rootElem->Name(), "configuration")) { + ALOGE("A configuration file is not in the required format. " + "See /etc/automotive/evs/evs_configuration.dtd"); + return false; + } + + /* + * parse camera information; this needs to be done before reading system + * information + */ + readCameraInfo(rootElem->FirstChildElement("camera")); + + /* parse system information */ + readSystemInfo(rootElem->FirstChildElement("system")); + + /* parse display information */ + readDisplayInfo(rootElem->FirstChildElement("display")); + + const int64_t parsingEnd = android::elapsedRealtimeNano(); + ALOGI("Parsing configuration file takes %lf (ms)", + (double)(parsingEnd - parsingStart) / 1000000.0); + + + return true; +} + + +void ConfigManager::addCameraDevices(const char *devices, + unique_ptr<CameraGroup> &aGroup) { + stringstream device_list(devices); + string token; + while (getline(device_list, token, ',')) { + aGroup->devices.emplace(token); + } +} + + +std::unique_ptr<ConfigManager> ConfigManager::Create(const char *path) { + unique_ptr<ConfigManager> cfgMgr(new ConfigManager(path)); + + /* + * Read a configuration from XML file + * + * If this is too slow, ConfigManager::readConfigDataFromBinary() and + * ConfigManager::writeConfigDataToBinary()can serialize CameraInfo object + * to the filesystem and construct CameraInfo instead; this was + * evaluated as 10x faster. + */ + if (!cfgMgr->readConfigDataFromXML()) { + return nullptr; + } else { + return cfgMgr; + } +} + diff --git a/automotive/evs/1.1/default/ConfigManager.h b/automotive/evs/1.1/default/ConfigManager.h new file mode 100644 index 0000000000..0275f904e5 --- /dev/null +++ b/automotive/evs/1.1/default/ConfigManager.h @@ -0,0 +1,336 @@ +/* + * 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. + */ +#ifndef CONFIG_MANAGER_H +#define CONFIG_MANAGER_H + +#include <vector> +#include <string> +#include <unordered_map> +#include <unordered_set> + +#include <tinyxml2.h> + +#include <system/camera_metadata.h> +#include <log/log.h> +#include <android/hardware/automotive/evs/1.1/types.h> + +#include "ConfigManagerUtil.h" + +using namespace std; +using namespace tinyxml2; + +using ::android::hardware::hidl_vec; +using ::android::hardware::camera::device::V3_2::Stream; +using ::android::hardware::automotive::evs::V1_1::CameraParam; + +/* + * Plese note that this is different from what is defined in + * libhardware/modules/camera/3_4/metadata/types.h; this has one additional + * field to store a framerate. + */ +const size_t kStreamCfgSz = 6; +typedef std::array<int32_t, kStreamCfgSz> RawStreamConfiguration; + +class ConfigManager { +public: + static std::unique_ptr<ConfigManager> Create(const char *path = ""); + ConfigManager(const ConfigManager&) = delete; + ConfigManager& operator=(const ConfigManager&) = delete; + + virtual ~ConfigManager(); + + /* Camera device's capabilities and metadata */ + class CameraInfo { + public: + CameraInfo() : + characteristics(nullptr) { + /* Nothing to do */ + } + + virtual ~CameraInfo() { + free_camera_metadata(characteristics); + } + + /* Allocate memory for camera_metadata_t */ + bool allocate(size_t entry_cap, size_t data_cap) { + if (characteristics != nullptr) { + ALOGE("Camera metadata is already allocated"); + return false; + } + + characteristics = allocate_camera_metadata(entry_cap, data_cap); + return characteristics != nullptr; + } + + /* + * List of supported controls that the master client can program. + * Paraemters are stored with its valid range + */ + unordered_map<CameraParam, + tuple<int32_t, int32_t, int32_t>> controls; + + /* List of supported frame rates */ + unordered_set<int32_t> frameRates; + + /* + * List of supported output stream configurations; each array stores + * format, width, height, and direction values in the order. + */ + unordered_map<int32_t, RawStreamConfiguration> streamConfigurations; + + /* + * Internal storage for camera metadata. Each entry holds a pointer to + * data and number of elements + */ + unordered_map<camera_metadata_tag_t, + pair<unique_ptr<void *>, size_t>> cameraMetadata; + + /* Camera module characteristics */ + camera_metadata_t *characteristics; + }; + + class CameraGroup { + public: + CameraGroup() {} + + /* ID of member camera devices */ + unordered_set<string> devices; + + /* The capture operation of member camera devices are synchronized */ + bool synchronized = false; + + /* + * List of stream configurations that are supposed by all camera devices + * in this group. + */ + unordered_map<int32_t, RawStreamConfiguration> streamConfigurations; + }; + + class SystemInfo { + public: + /* number of available cameras */ + int32_t numCameras = 0; + }; + + class DisplayInfo { + public: + /* + * List of supported input stream configurations; each array stores + * format, width, height, and direction values in the order. + */ + unordered_map<int32_t, RawStreamConfiguration> streamConfigurations; + }; + + /* + * Return system information + * + * @return SystemInfo + * Constant reference of SystemInfo. + */ + const SystemInfo &getSystemInfo() { + return mSystemInfo; + } + + /* + * Return a list of cameras + * + * This function assumes that it is not being called frequently. + * + * @return vector<string> + * A vector that contains unique camera device identifiers. + */ + vector<string> getCameraList() { + vector<string> aList; + for (auto &v : mCameraInfo) { + aList.emplace_back(v.first); + } + + return aList; + } + + + /* + * Return a list of cameras + * + * @return CameraGroup + * A pointer to a camera group identified by a given id. + */ + unique_ptr<CameraGroup>& getCameraGroup(const string& gid) { + return mCameraGroups[gid]; + } + + + /* + * Return a camera metadata + * + * @param cameraId + * Unique camera node identifier in string + * + * @return unique_ptr<CameraInfo> + * A pointer to CameraInfo that is associated with a given camera + * ID. This returns a null pointer if this does not recognize a + * given camera identifier. + */ + unique_ptr<CameraInfo>& getCameraInfo(const string cameraId) noexcept { + return mCameraInfo[cameraId]; + } + +private: + /* Constructors */ + ConfigManager(const char *xmlPath) : + mConfigFilePath(xmlPath) { + } + + /* System configuration */ + SystemInfo mSystemInfo; + + /* Internal data structure for camera device information */ + unordered_map<string, unique_ptr<CameraInfo>> mCameraInfo; + + /* Internal data structure for camera device information */ + unordered_map<string, unique_ptr<DisplayInfo>> mDisplayInfo; + + /* Camera groups are stored in <groud id, CameraGroup> hash map */ + unordered_map<string, unique_ptr<CameraGroup>> mCameraGroups; + + /* + * Camera positions are stored in <position, camera id set> hash map. + * The position must be one of front, rear, left, and right. + */ + unordered_map<string, unordered_set<string>> mCameraPosition; + + /* A path to XML configuration file */ + const char *mConfigFilePath; + + /* + * Parse a given EVS configuration file and store the information + * internally. + * + * @return bool + * True if it completes parsing a file successfully. + */ + bool readConfigDataFromXML() noexcept; + + /* + * read the information of the vehicle + * + * @param aSysElem + * A pointer to "system" XML element. + */ + void readSystemInfo(const XMLElement * const aSysElem); + + /* + * read the information of camera devices + * + * @param aCameraElem + * A pointer to "camera" XML element that may contain multiple + * "device" elements. + */ + void readCameraInfo(const XMLElement * const aCameraElem); + + /* + * read display device information + * + * @param aDisplayElem + * A pointer to "display" XML element that may contain multiple + * "device" elements. + */ + void readDisplayInfo(const XMLElement * const aDisplayElem); + + /* + * read camera device information + * + * @param aDeviceElem + * A pointer to "device" XML element that contains camera module + * capability info and its characteristics. + * + * @return unique_ptr<CameraInfo> + * A pointer to CameraInfo class that contains camera module + * capability and characteristics. Please note that this transfers + * the ownership of created CameraInfo to the caller. + */ + unique_ptr<CameraInfo> readCameraDeviceInfo(const XMLElement *aDeviceElem); + + /* + * read camera metadata + * + * @param aCapElem + * A pointer to "cap" XML element. + * @param aCamera + * A pointer to CameraInfo that is being filled by this method. + * @param dataSize + * Required size of memory to store camera metadata found in this + * method. This is calculated in this method and returned to the + * caller for camera_metadata allocation. + * + * @return size_t + * Number of camera metadata entries + */ + size_t readCameraCapabilities(const XMLElement * const aCapElem, + unique_ptr<CameraInfo> &aCamera, + size_t &dataSize); + + /* + * read camera metadata + * + * @param aParamElem + * A pointer to "characteristics" XML element. + * @param aCamera + * A pointer to CameraInfo that is being filled by this method. + * @param dataSize + * Required size of memory to store camera metadata found in this + * method. + * + * @return size_t + * Number of camera metadata entries + */ + size_t readCameraMetadata(const XMLElement * const aParamElem, + unique_ptr<CameraInfo> &aCamera, + size_t &dataSize); + + /* + * construct camera_metadata_t from camera capabilities and metadata + * + * @param aCamera + * A pointer to CameraInfo that is being filled by this method. + * @param totalEntries + * Number of camera metadata entries to be added. + * @param totalDataSize + * Sum of sizes of camera metadata entries to be added. + * + * @return bool + * False if either it fails to allocate memory for camera metadata + * or its size is not large enough to add all found camera metadata + * entries. + */ + bool constructCameraMetadata(unique_ptr<CameraInfo> &aCamera, + const size_t totalEntries, + const size_t totalDataSize); + + /* + * parse a comma-separated list of camera devices and add them to + * CameraGroup. + * + * @param devices + * A comma-separated list of camera device identifiers. + * @param aGroup + * Camera group which cameras will be added to. + */ + void addCameraDevices(const char *devices, + unique_ptr<CameraGroup> &aGroup); +}; +#endif // CONFIG_MANAGER_H + diff --git a/automotive/evs/1.1/default/ConfigManagerUtil.cpp b/automotive/evs/1.1/default/ConfigManagerUtil.cpp new file mode 100644 index 0000000000..8206daa6d7 --- /dev/null +++ b/automotive/evs/1.1/default/ConfigManagerUtil.cpp @@ -0,0 +1,131 @@ +/* + * 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. + */ + +#include "ConfigManagerUtil.h" + +#include <string> +#include <sstream> +#include <linux/videodev2.h> + +#include <log/log.h> +#include <system/graphics-base-v1.0.h> + + +bool ConfigManagerUtil::convertToEvsCameraParam(const string &id, + CameraParam &camParam) { + string trimmed = ConfigManagerUtil::trimString(id); + bool success = true; + + if (!trimmed.compare("BRIGHTNESS")) { + camParam = CameraParam::BRIGHTNESS; + } else if (!trimmed.compare("CONTRAST")) { + camParam = CameraParam::CONTRAST; + } else if (!trimmed.compare("AUTOGAIN")) { + camParam = CameraParam::AUTOGAIN; + } else if (!trimmed.compare("GAIN")) { + camParam = CameraParam::GAIN; + } else if (!trimmed.compare("AUTO_WHITE_BALANCE")) { + camParam = CameraParam::AUTO_WHITE_BALANCE; + } else if (!trimmed.compare("WHITE_BALANCE_TEMPERATURE")) { + camParam = CameraParam::WHITE_BALANCE_TEMPERATURE; + } else if (!trimmed.compare("SHARPNESS")) { + camParam = CameraParam::SHARPNESS; + } else if (!trimmed.compare("AUTO_EXPOSURE")) { + camParam = CameraParam::AUTO_EXPOSURE; + } else if (!trimmed.compare("ABSOLUTE_EXPOSURE")) { + camParam = CameraParam::ABSOLUTE_EXPOSURE; + } else if (!trimmed.compare("ABSOLUTE_FOCUS")) { + camParam = CameraParam::ABSOLUTE_FOCUS; + } else if (!trimmed.compare("AUTO_FOCUS")) { + camParam = CameraParam::AUTO_FOCUS; + } else if (!trimmed.compare("ABSOLUTE_ZOOM")) { + camParam = CameraParam::ABSOLUTE_ZOOM; + } else { + success = false; + } + + return success; +} + + +bool ConfigManagerUtil::convertToPixelFormat(const string &format, + int32_t &pixFormat) { + string trimmed = ConfigManagerUtil::trimString(format); + bool success = true; + + if (!trimmed.compare("RGBA_8888")) { + pixFormat = HAL_PIXEL_FORMAT_RGBA_8888; + } else if (!trimmed.compare("YCRCB_420_SP")) { + pixFormat = HAL_PIXEL_FORMAT_YCRCB_420_SP; + } else if (!trimmed.compare("YCBCR_422_I")) { + pixFormat = HAL_PIXEL_FORMAT_YCBCR_422_I; + } else { + success = false; + } + + return success; +} + + +bool ConfigManagerUtil::convertToMetadataTag(const char *name, + camera_metadata_tag &aTag) { + if (!strcmp(name, "LENS_DISTORTION")) { + aTag = ANDROID_LENS_DISTORTION; + } else if (!strcmp(name, "LENS_INTRINSIC_CALIBRATION")) { + aTag = ANDROID_LENS_INTRINSIC_CALIBRATION; + } else if (!strcmp(name, "LENS_POSE_ROTATION")) { + aTag = ANDROID_LENS_POSE_ROTATION; + } else if (!strcmp(name, "LENS_POSE_TRANSLATION")) { + aTag = ANDROID_LENS_POSE_TRANSLATION; + } else { + return false; + } + + return true; +} + + +float *ConfigManagerUtil::convertFloatArray(const char *sz, const char *vals, + size_t &count, const char delimiter) { + string size_string(sz); + string value_string(vals); + + count = stoi(size_string); + float *result = new float[count]; + stringstream values(value_string); + + int32_t idx = 0; + string token; + while (getline(values, token, delimiter)) { + result[idx++] = stof(token); + } + + return result; +} + + +string ConfigManagerUtil::trimString(const string &src, const string &ws) { + const auto s = src.find_first_not_of(ws); + if (s == string::npos) { + return ""; + } + + const auto e = src.find_last_not_of(ws); + const auto r = e - s + 1; + + return src.substr(s, r); +} + diff --git a/automotive/evs/1.1/default/ConfigManagerUtil.h b/automotive/evs/1.1/default/ConfigManagerUtil.h new file mode 100644 index 0000000000..8c89ae7745 --- /dev/null +++ b/automotive/evs/1.1/default/ConfigManagerUtil.h @@ -0,0 +1,61 @@ +/* + * 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. + */ +#ifndef CONFIG_MANAGER_UTIL_H +#define CONFIG_MANAGER_UTIL_H + +#include <utility> +#include <string> +#include <system/camera_metadata.h> +#include <android/hardware/automotive/evs/1.1/types.h> + +using namespace std; +using ::android::hardware::automotive::evs::V1_1::CameraParam; + + +class ConfigManagerUtil { +public: + /** + * Convert a given string into V4L2_CID_* + */ + static bool convertToEvsCameraParam(const string &id, + CameraParam &camParam); + /** + * Convert a given string into android.hardware.graphics.common.PixelFormat + */ + static bool convertToPixelFormat(const string &format, + int32_t &pixelFormat); + /** + * Convert a given string into corresponding camera metadata data tag defined in + * system/media/camera/include/system/camera_metadta_tags.h + */ + static bool convertToMetadataTag(const char *name, + camera_metadata_tag &aTag); + /** + * Convert a given string into a floating value array + */ + static float *convertFloatArray(const char *sz, + const char *vals, + size_t &count, + const char delimiter = ','); + /** + * Trim a string + */ + static string trimString(const string &src, + const string &ws = " \n\r\t\f\v"); +}; + +#endif // CONFIG_MANAGER_UTIL_H + diff --git a/automotive/evs/1.1/default/EvsCamera.cpp b/automotive/evs/1.1/default/EvsCamera.cpp index 2d55566349..5ba753da2e 100644 --- a/automotive/evs/1.1/default/EvsCamera.cpp +++ b/automotive/evs/1.1/default/EvsCamera.cpp @@ -40,28 +40,21 @@ const char EvsCamera::kCameraName_Backup[] = "backup"; const unsigned MAX_BUFFERS_IN_FLIGHT = 100; -EvsCamera::EvsCamera(const char *id) : +EvsCamera::EvsCamera(const char *id, + unique_ptr<ConfigManager::CameraInfo> &camInfo) : mFramesAllowed(0), mFramesInUse(0), - mStreamState(STOPPED) { + mStreamState(STOPPED), + mCameraInfo(camInfo) { ALOGD("EvsCamera instantiated"); - mDescription.cameraId = id; + /* set a camera id */ + mDescription.v1.cameraId = id; - // Set up dummy data for testing - if (mDescription.cameraId == kCameraName_Backup) { - mWidth = 640; // full NTSC/VGA - mHeight = 480; // full NTSC/VGA - mDescription.vendorFlags = 0xFFFFFFFF; // Arbitrary value - } else { - mWidth = 320; // 1/2 NTSC/VGA - mHeight = 240; // 1/2 NTSC/VGA - } - - mFormat = HAL_PIXEL_FORMAT_RGBA_8888; - mUsage = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_CAMERA_WRITE | - GRALLOC_USAGE_SW_READ_RARELY | GRALLOC_USAGE_SW_WRITE_RARELY; + /* set camera metadata */ + mDescription.metadata.setToExternal((uint8_t *)camInfo->characteristics, + get_camera_metadata_size(camInfo->characteristics)); } @@ -109,7 +102,7 @@ Return<void> EvsCamera::getCameraInfo(getCameraInfo_cb _hidl_cb) { ALOGD("getCameraInfo"); // Send back our self description - _hidl_cb(mDescription); + _hidl_cb(mDescription.v1); return Void(); } @@ -194,7 +187,7 @@ Return<void> EvsCamera::stopVideoStream() { // Block outside the mutex until the "stop" flag has been acknowledged // We won't send any more frames, but the client might still get some already in flight - ALOGD("Waiting for stream thread to end.."); + ALOGD("Waiting for stream thread to end..."); lock.unlock(); mCaptureThread.join(); lock.lock(); @@ -238,6 +231,15 @@ Return<EvsResult> EvsCamera::setExtendedInfo(uint32_t /*opaqueIdentifier*/, int3 // Methods from ::android::hardware::automotive::evs::V1_1::IEvsCamera follow. +Return<void> EvsCamera::getCameraInfo_1_1(getCameraInfo_1_1_cb _hidl_cb) { + ALOGD("getCameraInfo_1_1"); + + // Send back our self description + _hidl_cb(mDescription); + return Void(); +} + + Return<EvsResult> EvsCamera::doneWithFrame_1_1(const BufferDesc_1_1& bufDesc) { std::lock_guard <std::mutex> lock(mAccessLock); returnBuffer(bufDesc.bufferId, bufDesc.buffer.nativeHandle); @@ -278,8 +280,29 @@ Return<EvsResult> EvsCamera::unsetMaster() { } -Return<void> EvsCamera::setParameter(CameraParam id, int32_t value, - setParameter_cb _hidl_cb) { +Return<void> EvsCamera::getParameterList(getParameterList_cb _hidl_cb) { + hidl_vec<CameraParam> hidlCtrls; + hidlCtrls.resize(mCameraInfo->controls.size()); + unsigned idx = 0; + for (auto& [cid, cfg] : mCameraInfo->controls) { + hidlCtrls[idx++] = cid; + } + + _hidl_cb(hidlCtrls); + return Void(); +} + + +Return<void> EvsCamera::getIntParameterRange(CameraParam id, + getIntParameterRange_cb _hidl_cb) { + auto range = mCameraInfo->controls[id]; + _hidl_cb(get<0>(range), get<1>(range), get<2>(range)); + return Void(); +} + + +Return<void> EvsCamera::setIntParameter(CameraParam id, int32_t value, + setIntParameter_cb _hidl_cb) { // Default implementation does not support this. (void)id; (void)value; @@ -288,7 +311,8 @@ Return<void> EvsCamera::setParameter(CameraParam id, int32_t value, } -Return<void> EvsCamera::getParameter(CameraParam id, getParameter_cb _hidl_cb) { +Return<void> EvsCamera::getIntParameter(CameraParam id, + getIntParameter_cb _hidl_cb) { // Default implementation does not support this. (void)id; _hidl_cb(EvsResult::INVALID_ARG, 0); @@ -471,9 +495,7 @@ void EvsCamera::generateFrames() { fillTestFrame(newBuffer); // Issue the (asynchronous) callback to the client -- can't be holding the lock - EvsEvent event; - event.buffer(newBuffer); - auto result = mStream->notifyEvent(event); + auto result = mStream->deliverFrame_1_1(newBuffer); if (result.isOk()) { ALOGD("Delivered %p as id %d", newBuffer.buffer.nativeHandle.getNativeHandle(), newBuffer.bufferId); @@ -506,10 +528,8 @@ void EvsCamera::generateFrames() { // If we've been asked to stop, send an event to signal the actual end of stream EvsEvent event; - InfoEventDesc desc = {}; - desc.aType = InfoEventType::STREAM_STOPPED; - event.info(desc); - auto result = mStream->notifyEvent(event); + event.aType = EvsEventType::STREAM_STOPPED; + auto result = mStream->notify(event); if (!result.isOk()) { ALOGE("Error delivering end of stream marker"); } @@ -616,6 +636,38 @@ void EvsCamera::returnBuffer(const uint32_t bufferId, const buffer_handle_t memH } +sp<EvsCamera> EvsCamera::Create(const char *deviceName) { + unique_ptr<ConfigManager::CameraInfo> nullCamInfo = nullptr; + + return Create(deviceName, nullCamInfo); +} + + +sp<EvsCamera> EvsCamera::Create(const char *deviceName, + unique_ptr<ConfigManager::CameraInfo> &camInfo, + const Stream *streamCfg) { + sp<EvsCamera> evsCamera = new EvsCamera(deviceName, camInfo); + if (evsCamera == nullptr) { + return nullptr; + } + + /* default implementation does not use a given configuration */ + (void)streamCfg; + + /* Use the first resolution from the list for the testing */ + auto it = camInfo->streamConfigurations.begin(); + evsCamera->mWidth = it->second[1]; + evsCamera->mHeight = it->second[2]; + evsCamera->mDescription.v1.vendorFlags = 0xFFFFFFFF; // Arbitrary test value + + evsCamera->mFormat = HAL_PIXEL_FORMAT_RGBA_8888; + evsCamera->mUsage = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_CAMERA_WRITE | + GRALLOC_USAGE_SW_READ_RARELY | GRALLOC_USAGE_SW_WRITE_RARELY; + + return evsCamera; +} + + } // namespace implementation } // namespace V1_0 } // namespace evs diff --git a/automotive/evs/1.1/default/EvsCamera.h b/automotive/evs/1.1/default/EvsCamera.h index 47a3164892..c15b4b117b 100644 --- a/automotive/evs/1.1/default/EvsCamera.h +++ b/automotive/evs/1.1/default/EvsCamera.h @@ -25,6 +25,8 @@ #include <thread> +#include "ConfigManager.h" + using BufferDesc_1_0 = ::android::hardware::automotive::evs::V1_0::BufferDesc; using BufferDesc_1_1 = ::android::hardware::automotive::evs::V1_1::BufferDesc; using IEvsCameraStream_1_0 = ::android::hardware::automotive::evs::V1_0::IEvsCameraStream; @@ -59,18 +61,28 @@ public: Return<EvsResult> setExtendedInfo(uint32_t opaqueIdentifier, int32_t opaqueValue) override; // Methods from ::android::hardware::automotive::evs::V1_1::IEvsCamera follow. + Return<void> getCameraInfo_1_1(getCameraInfo_1_1_cb _hidl_cb) override; Return<EvsResult> pauseVideoStream() override; Return<EvsResult> resumeVideoStream() override; Return<EvsResult> doneWithFrame_1_1(const BufferDesc_1_1& buffer) override; Return<EvsResult> setMaster() override; Return<EvsResult> forceMaster(const sp<IEvsDisplay>& display) override; Return<EvsResult> unsetMaster() override; - Return<void> setParameter(CameraParam id, int32_t value, - setParameter_cb _hidl_cb) override; - Return<void> getParameter(CameraParam id, getParameter_cb _hidl_cb) override; + Return<void> getParameterList(getParameterList_cb _hidl_cb) override; + Return<void> getIntParameterRange(CameraParam id, + getIntParameterRange_cb _hidl_cb) override; + Return<void> setIntParameter(CameraParam id, int32_t value, + setIntParameter_cb _hidl_cb) override; + Return<void> getIntParameter(CameraParam id, + getIntParameter_cb _hidl_cb) override; + + static sp<EvsCamera> Create(const char *deviceName); + static sp<EvsCamera> Create(const char *deviceName, + unique_ptr<ConfigManager::CameraInfo> &camInfo, + const Stream *streamCfg = nullptr); + EvsCamera(const EvsCamera&) = delete; + EvsCamera& operator=(const EvsCamera&) = delete; - // Implementation details - EvsCamera(const char *id); virtual ~EvsCamera() override; void forceShutdown(); // This gets called if another caller "steals" ownership of the camera @@ -79,7 +91,10 @@ public: static const char kCameraName_Backup[]; private: + EvsCamera(const char *id, + unique_ptr<ConfigManager::CameraInfo> &camInfo); // These three functions are expected to be called while mAccessLock is held + // bool setAvailableFrames_Locked(unsigned bufferCount); unsigned increaseAvailableFrames_Locked(unsigned numToAdd); unsigned decreaseAvailableFrames_Locked(unsigned numToRemove); @@ -124,6 +139,9 @@ private: // Synchronization necessary to deconflict mCaptureThread from the main service thread std::mutex mAccessLock; + + // Static camera module information + unique_ptr<ConfigManager::CameraInfo> &mCameraInfo; }; } // namespace implementation diff --git a/automotive/evs/1.1/default/EvsEnumerator.cpp b/automotive/evs/1.1/default/EvsEnumerator.cpp index b3249071ae..a010729ce6 100644 --- a/automotive/evs/1.1/default/EvsEnumerator.cpp +++ b/automotive/evs/1.1/default/EvsEnumerator.cpp @@ -33,6 +33,7 @@ namespace implementation { // constructs a new instance for each client. std::list<EvsEnumerator::CameraRecord> EvsEnumerator::sCameraList; wp<EvsDisplay> EvsEnumerator::sActiveDisplay; +unique_ptr<ConfigManager> EvsEnumerator::sConfigManager; EvsEnumerator::EvsEnumerator() { @@ -40,9 +41,11 @@ EvsEnumerator::EvsEnumerator() { // Add sample camera data to our list of cameras // In a real driver, this would be expected to can the available hardware - sCameraList.emplace_back(EvsCamera::kCameraName_Backup); - sCameraList.emplace_back("LaneView"); - sCameraList.emplace_back("right turn"); + sConfigManager = + ConfigManager::Create("/etc/automotive/evs/evs_sample_configuration.xml"); + for (auto v : sConfigManager->getCameraList()) { + sCameraList.emplace_back(v.c_str()); + } } @@ -57,7 +60,7 @@ Return<void> EvsEnumerator::getCameraList(getCameraList_cb _hidl_cb) { std::vector<CameraDesc_1_0> descriptions; descriptions.reserve(numCameras); for (const auto& cam : sCameraList) { - descriptions.push_back( cam.desc ); + descriptions.push_back( cam.desc.v1 ); } // Encapsulate our camera descriptions in the HIDL vec type @@ -78,7 +81,7 @@ Return<sp<IEvsCamera_1_0>> EvsEnumerator::openCamera(const hidl_string& cameraId // Find the named camera CameraRecord *pRecord = nullptr; for (auto &&cam : sCameraList) { - if (cam.desc.cameraId == cameraId) { + if (cam.desc.v1.cameraId == cameraId) { // Found a match! pRecord = &cam; break; @@ -99,7 +102,12 @@ Return<sp<IEvsCamera_1_0>> EvsEnumerator::openCamera(const hidl_string& cameraId } // Construct a camera instance for the caller - pActiveCamera = new EvsCamera(cameraId.c_str()); + if (sConfigManager == nullptr) { + pActiveCamera = EvsCamera::Create(cameraId.c_str()); + } else { + pActiveCamera = EvsCamera::Create(cameraId.c_str(), + sConfigManager->getCameraInfo(cameraId)); + } pRecord->activeInstance = pActiveCamera; if (pActiveCamera == nullptr) { ALOGE("Failed to allocate new EvsCamera object for %s\n", cameraId.c_str()); @@ -120,15 +128,15 @@ Return<void> EvsEnumerator::closeCamera(const ::android::sp<IEvsCamera_1_0>& pCa // Get the camera id so we can find it in our list std::string cameraId; - pCamera_1_1->getCameraInfo([&cameraId](CameraDesc desc) { - cameraId = desc.cameraId; + pCamera_1_1->getCameraInfo_1_1([&cameraId](CameraDesc desc) { + cameraId = desc.v1.cameraId; } ); // Find the named camera CameraRecord *pRecord = nullptr; for (auto &&cam : sCameraList) { - if (cam.desc.cameraId == cameraId) { + if (cam.desc.v1.cameraId == cameraId) { // Found a match! pRecord = &cam; break; @@ -209,6 +217,89 @@ Return<DisplayState> EvsEnumerator::getDisplayState() { } +// Methods from ::android::hardware::automotive::evs::V1_1::IEvsEnumerator follow. +Return<void> EvsEnumerator::getCameraList_1_1(getCameraList_1_1_cb _hidl_cb) { + ALOGD("getCameraList"); + + const unsigned numCameras = sCameraList.size(); + + // Build up a packed array of CameraDesc for return + // NOTE: Only has to live until the callback returns + std::vector<CameraDesc_1_1> descriptions; + descriptions.reserve(numCameras); + for (const auto& cam : sCameraList) { + descriptions.push_back( cam.desc ); + } + + // Encapsulate our camera descriptions in the HIDL vec type + hidl_vec<CameraDesc_1_1> hidlCameras(descriptions); + + // Send back the results + ALOGD("reporting %zu cameras available", hidlCameras.size()); + _hidl_cb(hidlCameras); + + // HIDL convention says we return Void if we sent our result back via callback + return Void(); +} + +Return<sp<IEvsCamera_1_1>> +EvsEnumerator::openCamera_1_1(const hidl_string& cameraId, + const Stream& streamCfg) { + // Find the named camera + CameraRecord *pRecord = nullptr; + for (auto &&cam : sCameraList) { + if (cam.desc.v1.cameraId == cameraId) { + // Found a match! + pRecord = &cam; + break; + } + } + + // Is this a recognized camera id? + if (!pRecord) { + ALOGE("Requested camera %s not found", cameraId.c_str()); + return nullptr; + } + + // Has this camera already been instantiated by another caller? + sp<EvsCamera> pActiveCamera = pRecord->activeInstance.promote(); + if (pActiveCamera != nullptr) { + ALOGW("Killing previous camera because of new caller"); + closeCamera(pActiveCamera); + } + + // Construct a camera instance for the caller + if (sConfigManager == nullptr) { + pActiveCamera = EvsCamera::Create(cameraId.c_str()); + } else { + pActiveCamera = EvsCamera::Create(cameraId.c_str(), + sConfigManager->getCameraInfo(cameraId), + &streamCfg); + } + + pRecord->activeInstance = pActiveCamera; + if (pActiveCamera == nullptr) { + ALOGE("Failed to allocate new EvsCamera object for %s\n", cameraId.c_str()); + } + + return pActiveCamera; +} + + +EvsEnumerator::CameraRecord* EvsEnumerator::findCameraById(const std::string& cameraId) { + // Find the named camera + CameraRecord *pRecord = nullptr; + for (auto &&cam : sCameraList) { + if (cam.desc.v1.cameraId == cameraId) { + // Found a match! + pRecord = &cam; + break; + } + } + + return pRecord; +} + } // namespace implementation } // namespace V1_1 } // namespace evs diff --git a/automotive/evs/1.1/default/EvsEnumerator.h b/automotive/evs/1.1/default/EvsEnumerator.h index 11c2170632..475ec76b93 100644 --- a/automotive/evs/1.1/default/EvsEnumerator.h +++ b/automotive/evs/1.1/default/EvsEnumerator.h @@ -17,18 +17,20 @@ #ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H #define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H -#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h> +#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h> #include <android/hardware/automotive/evs/1.1/IEvsCamera.h> #include <list> +#include "ConfigManager.h" + using ::android::hardware::automotive::evs::V1_0::EvsResult; using ::android::hardware::automotive::evs::V1_0::IEvsDisplay; using ::android::hardware::automotive::evs::V1_0::DisplayState; -using ::android::hardware::automotive::evs::V1_0::IEvsEnumerator; using IEvsCamera_1_0 = ::android::hardware::automotive::evs::V1_0::IEvsCamera; using IEvsCamera_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsCamera; using CameraDesc_1_0 = ::android::hardware::automotive::evs::V1_0::CameraDesc; +using CameraDesc_1_1 = ::android::hardware::automotive::evs::V1_1::CameraDesc; namespace android { @@ -53,6 +55,11 @@ public: Return<void> closeDisplay(const ::android::sp<IEvsDisplay>& display) override; Return<DisplayState> getDisplayState() override; + // Methods from ::android::hardware::automotive::evs::V1_1::IEvsEnumerator follow. + Return<void> getCameraList_1_1(getCameraList_1_1_cb _hidl_cb) override; + Return<sp<IEvsCamera_1_1>> openCamera_1_1(const hidl_string& cameraId, + const Stream& streamCfg) override; + // Implementation details EvsEnumerator(); @@ -61,14 +68,20 @@ private: // That is to say, this is effectively a singleton despite the fact that HIDL // constructs a new instance for each client. struct CameraRecord { - CameraDesc_1_0 desc; + CameraDesc_1_1 desc; wp<EvsCamera> activeInstance; - CameraRecord(const char *cameraId) : desc() { desc.cameraId = cameraId; } + CameraRecord(const char *cameraId) : desc() { desc.v1.cameraId = cameraId; } }; - static std::list<CameraRecord> sCameraList; - static wp<EvsDisplay> sActiveDisplay; // Weak pointer. Object destructs if client dies. + static CameraRecord* findCameraById(const std::string& cameraId); + + static std::list<CameraRecord> sCameraList; + + // Weak pointer. Object destructs if client dies. + static wp<EvsDisplay> sActiveDisplay; + + static unique_ptr<ConfigManager> sConfigManager; }; } // namespace implementation diff --git a/automotive/evs/1.1/default/resources/evs_default_configuration.xml b/automotive/evs/1.1/default/resources/evs_default_configuration.xml new file mode 100644 index 0000000000..692102ed38 --- /dev/null +++ b/automotive/evs/1.1/default/resources/evs_default_configuration.xml @@ -0,0 +1,68 @@ +<?xml version='1.0' encoding='utf-8'?> +<!-- 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. +--> + +<!-- Exterior View System Example Configuration + + Android Automotive axes are used to define coordinates. + See https://source.android.com/devices/sensors/sensor-types#auto_axes + + Use evs_configuration.dtd with xmllint tool, to validate XML configuration file +--> + +<configuration> + <!-- system configuration --> + <system> + <!-- number of cameras available to EVS --> + <num_cameras value='1'/> + </system> + + <!-- camera device information --> + <camera> + <!-- camera device starts --> + <device id='/dev/video1' position='rear'> + <caps> + <!-- list of supported controls --> + <supported_controls> + <control name='BRIGHTNESS' min='0' max='255'/> + <control name='CONTRAST' min='0' max='255'/> + </supported_controls> + + <stream id='0' width='640' height='360' format='RGBA_8888' framerate='30'/> + </caps> + + <!-- list of parameters --> + <characteristics> + <!-- Camera intrinsic calibration matrix. See + https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#LENS_INTRINSIC_CALIBRATION + --> + <parameter + name='LENS_INTRINSIC_CALIBRATION' + type='float' + size='5' + value='0.0,0.0,0.0,0.0,0.0' + /> + </characteristics> + </device> + </camera> + <display> + <device id='display0' position='driver'> + <caps> + <!-- list of supported inpu stream configurations --> + <stream id='0' width='1280' height='720' format='RGBA_8888' framerate='30'/> + </caps> + </device> + </display> +</configuration> + diff --git a/automotive/evs/1.1/default/service.cpp b/automotive/evs/1.1/default/service.cpp index 128a14aa30..5135864e88 100644 --- a/automotive/evs/1.1/default/service.cpp +++ b/automotive/evs/1.1/default/service.cpp @@ -33,7 +33,7 @@ using android::hardware::configureRpcThreadpool; using android::hardware::joinRpcThreadpool; // Generated HIDL files -using android::hardware::automotive::evs::V1_0::IEvsEnumerator; +using android::hardware::automotive::evs::V1_1::IEvsEnumerator; using android::hardware::automotive::evs::V1_0::IEvsDisplay; // The namespace in which all our implementation code lives diff --git a/automotive/evs/1.1/types.hal b/automotive/evs/1.1/types.hal index 2c6b2ed589..dcb2abb0e9 100644 --- a/automotive/evs/1.1/types.hal +++ b/automotive/evs/1.1/types.hal @@ -21,6 +21,22 @@ import @1.0::DisplayDesc; import @1.0::DisplayState; import @1.0::EvsResult; import android.hardware.graphics.common@1.2::HardwareBuffer; +import android.hardware.camera.device@3.2::CameraMetadata; + +/** + * Structure describing the basic properties of an EVS camera, extended from its + * v1.0 declaration. + * + * The HAL is responsible for filling out this structure for each + * EVS camera in the system. + */ +struct CameraDesc { + @1.0::CameraDesc v1; + /** + * Store camera metadata such as lens characteristics. + */ + CameraMetadata metadata; +}; /** * Structure representing an image buffer through our APIs @@ -50,7 +66,7 @@ struct BufferDesc { /** * Types of informative streaming events */ -enum InfoEventType : uint32_t { +enum EvsEventType : uint32_t { /** * Video stream is started */ @@ -81,11 +97,11 @@ enum InfoEventType : uint32_t { /** * Structure that describes informative events occurred during EVS is streaming */ -struct InfoEventDesc { +struct EvsEvent { /** * Type of an informative event */ - InfoEventType aType; + EvsEventType aType; /** * Possible additional information */ @@ -93,20 +109,6 @@ struct InfoEventDesc { }; /** - * EVS event definition - */ -safe_union EvsEvent { - /** - * A buffer descriptor of an image frame - */ - BufferDesc buffer; - /** - * General streaming events - */ - InfoEventDesc info; -}; - -/** * EVS Camera Parameter */ enum CameraParam : uint32_t { @@ -127,14 +129,6 @@ enum CameraParam : uint32_t { */ GAIN, /** - * Mirror the image horizontally - */ - HFLIP, - /** - * Mirror the image vertically - */ - VFLIP, - /** * Automatic Whitebalance */ AUTO_WHITE_BALANCE, @@ -156,11 +150,6 @@ enum CameraParam : uint32_t { */ ABSOLUTE_EXPOSURE, /** - * When AEC is running in either auto or aperture priority, this parameter - * sets whether a frame rate varies. - */ - AUTO_EXPOSURE_PRIORITY, - /** * Set the focal point of the camera to the specified position. This * parameter may not be effective when auto focus is enabled. */ diff --git a/automotive/evs/1.1/vts/functional/Android.bp b/automotive/evs/1.1/vts/functional/Android.bp index 55c50a42b8..4753933f7f 100644 --- a/automotive/evs/1.1/vts/functional/Android.bp +++ b/automotive/evs/1.1/vts/functional/Android.bp @@ -23,6 +23,7 @@ cc_test { defaults: ["VtsHalTargetTestDefaults"], shared_libs: [ "libui", + "libcamera_metadata", ], static_libs: [ "android.hardware.automotive.evs@1.0", @@ -31,6 +32,7 @@ cc_test { "android.hardware.graphics.common@1.0", "android.hardware.graphics.common@1.1", "android.hardware.graphics.common@1.2", + "android.hardware.camera.device@3.2", ], test_suites: ["general-tests"], cflags: [ diff --git a/automotive/evs/1.1/vts/functional/FrameHandler.cpp b/automotive/evs/1.1/vts/functional/FrameHandler.cpp index 16276891f0..6d53652f86 100644 --- a/automotive/evs/1.1/vts/functional/FrameHandler.cpp +++ b/automotive/evs/1.1/vts/functional/FrameHandler.cpp @@ -138,92 +138,92 @@ Return<void> FrameHandler::deliverFrame(const BufferDesc_1_0& bufferArg) { } -Return<void> FrameHandler::notifyEvent(const EvsEvent& event) { - // Local flag we use to keep track of when the stream is stopping - auto type = event.getDiscriminator(); - if (type == EvsEvent::hidl_discriminator::info) { - mLock.lock(); - mLatestEventDesc = event.info(); - if (mLatestEventDesc.aType == InfoEventType::STREAM_STOPPED) { - // Signal that the last frame has been received and the stream is stopped - mRunning = false; - } else if (mLatestEventDesc.aType == InfoEventType::PARAMETER_CHANGED) { - ALOGD("Camera parameter 0x%X is changed to 0x%X", - mLatestEventDesc.payload[0], mLatestEventDesc.payload[1]); +Return<void> FrameHandler::deliverFrame_1_1(const BufferDesc_1_1& bufDesc) { + const AHardwareBuffer_Desc* pDesc = + reinterpret_cast<const AHardwareBuffer_Desc *>(&bufDesc.buffer.description); + ALOGD("Received a frame from the camera (%p)", + bufDesc.buffer.nativeHandle.getNativeHandle()); + + // Store a dimension of a received frame. + mFrameWidth = pDesc->width; + mFrameHeight = pDesc->height; + + // If we were given an opened display at construction time, then send the received + // image back down the camera. + if (mDisplay.get()) { + // Get the output buffer we'll use to display the imagery + BufferDesc_1_0 tgtBuffer = {}; + mDisplay->getTargetBuffer([&tgtBuffer](const BufferDesc_1_0& buff) { + tgtBuffer = buff; + } + ); + + if (tgtBuffer.memHandle == nullptr) { + printf("Didn't get target buffer - frame lost\n"); + ALOGE("Didn't get requested output buffer -- skipping this frame."); } else { - ALOGD("Received an event %s", eventToString(mLatestEventDesc.aType)); - } - mLock.unlock(); - mEventSignal.notify_all(); - } else { - auto bufDesc = event.buffer(); - const AHardwareBuffer_Desc* pDesc = - reinterpret_cast<const AHardwareBuffer_Desc *>(&bufDesc.buffer.description); - ALOGD("Received a frame from the camera (%p)", - bufDesc.buffer.nativeHandle.getNativeHandle()); - - // Store a dimension of a received frame. - mFrameWidth = pDesc->width; - mFrameHeight = pDesc->height; - - // If we were given an opened display at construction time, then send the received - // image back down the camera. - if (mDisplay.get()) { - // Get the output buffer we'll use to display the imagery - BufferDesc_1_0 tgtBuffer = {}; - mDisplay->getTargetBuffer([&tgtBuffer](const BufferDesc_1_0& buff) { - tgtBuffer = buff; - } - ); - - if (tgtBuffer.memHandle == nullptr) { - printf("Didn't get target buffer - frame lost\n"); - ALOGE("Didn't get requested output buffer -- skipping this frame."); + // Copy the contents of the of buffer.memHandle into tgtBuffer + copyBufferContents(tgtBuffer, bufDesc); + + // Send the target buffer back for display + Return<EvsResult> result = mDisplay->returnTargetBufferForDisplay(tgtBuffer); + if (!result.isOk()) { + printf("HIDL error on display buffer (%s)- frame lost\n", + result.description().c_str()); + ALOGE("Error making the remote function call. HIDL said %s", + result.description().c_str()); + } else if (result != EvsResult::OK) { + printf("Display reported error - frame lost\n"); + ALOGE("We encountered error %d when returning a buffer to the display!", + (EvsResult) result); } else { - // Copy the contents of the of buffer.memHandle into tgtBuffer - copyBufferContents(tgtBuffer, bufDesc); - - // Send the target buffer back for display - Return<EvsResult> result = mDisplay->returnTargetBufferForDisplay(tgtBuffer); - if (!result.isOk()) { - printf("HIDL error on display buffer (%s)- frame lost\n", - result.description().c_str()); - ALOGE("Error making the remote function call. HIDL said %s", - result.description().c_str()); - } else if (result != EvsResult::OK) { - printf("Display reported error - frame lost\n"); - ALOGE("We encountered error %d when returning a buffer to the display!", - (EvsResult) result); - } else { - // Everything looks good! - // Keep track so tests or watch dogs can monitor progress - mLock.lock(); - mFramesDisplayed++; - mLock.unlock(); - } + // Everything looks good! + // Keep track so tests or watch dogs can monitor progress + mLock.lock(); + mFramesDisplayed++; + mLock.unlock(); } } + } - switch (mReturnMode) { - case eAutoReturn: - // Send the camera buffer back now that the client has seen it - ALOGD("Calling doneWithFrame"); - // TODO: Why is it that we get a HIDL crash if we pass back the cloned buffer? - mCamera->doneWithFrame_1_1(bufDesc); - break; - case eNoAutoReturn: - // Hang onto the buffer handle for now -- the client will return it explicitly later - mHeldBuffers.push(bufDesc); - } + switch (mReturnMode) { + case eAutoReturn: + // Send the camera buffer back now that the client has seen it + ALOGD("Calling doneWithFrame"); + mCamera->doneWithFrame_1_1(bufDesc); + break; + case eNoAutoReturn: + // Hang onto the buffer handle for now -- the client will return it explicitly later + mHeldBuffers.push(bufDesc); + } + + mLock.lock(); + ++mFramesReceived; + mLock.unlock(); + mFrameSignal.notify_all(); - mLock.lock(); - ++mFramesReceived; - mLock.unlock(); - mFrameSignal.notify_all(); + ALOGD("Frame handling complete"); - ALOGD("Frame handling complete"); + return Void(); +} + + +Return<void> FrameHandler::notify(const EvsEvent& event) { + // Local flag we use to keep track of when the stream is stopping + mLock.lock(); + mLatestEventDesc = event; + if (mLatestEventDesc.aType == EvsEventType::STREAM_STOPPED) { + // Signal that the last frame has been received and the stream is stopped + mRunning = false; + } else if (mLatestEventDesc.aType == EvsEventType::PARAMETER_CHANGED) { + ALOGD("Camera parameter 0x%X is changed to 0x%X", + mLatestEventDesc.payload[0], mLatestEventDesc.payload[1]); + } else { + ALOGD("Received an event %s", eventToString(mLatestEventDesc.aType)); } + mLock.unlock(); + mEventSignal.notify_all(); return Void(); } @@ -342,18 +342,18 @@ void FrameHandler::getFrameDimension(unsigned* width, unsigned* height) { } } -bool FrameHandler::waitForEvent(const InfoEventType aTargetEvent, - InfoEventDesc &eventDesc) { +bool FrameHandler::waitForEvent(const EvsEventType aTargetEvent, + EvsEvent &event) { // Wait until we get an expected parameter change event. std::unique_lock<std::mutex> lock(mLock); auto now = std::chrono::system_clock::now(); bool result = mEventSignal.wait_until(lock, now + 5s, - [this, aTargetEvent, &eventDesc](){ + [this, aTargetEvent, &event](){ bool flag = mLatestEventDesc.aType == aTargetEvent; if (flag) { - eventDesc.aType = mLatestEventDesc.aType; - eventDesc.payload[0] = mLatestEventDesc.payload[0]; - eventDesc.payload[1] = mLatestEventDesc.payload[1]; + event.aType = mLatestEventDesc.aType; + event.payload[0] = mLatestEventDesc.payload[0]; + event.payload[1] = mLatestEventDesc.payload[1]; } return flag; @@ -363,21 +363,22 @@ bool FrameHandler::waitForEvent(const InfoEventType aTargetEvent, return !result; } -const char *FrameHandler::eventToString(const InfoEventType aType) { +const char *FrameHandler::eventToString(const EvsEventType aType) { switch (aType) { - case InfoEventType::STREAM_STARTED: + case EvsEventType::STREAM_STARTED: return "STREAM_STARTED"; - case InfoEventType::STREAM_STOPPED: + case EvsEventType::STREAM_STOPPED: return "STREAM_STOPPED"; - case InfoEventType::FRAME_DROPPED: + case EvsEventType::FRAME_DROPPED: return "FRAME_DROPPED"; - case InfoEventType::TIMEOUT: + case EvsEventType::TIMEOUT: return "TIMEOUT"; - case InfoEventType::PARAMETER_CHANGED: + case EvsEventType::PARAMETER_CHANGED: return "PARAMETER_CHANGED"; - case InfoEventType::MASTER_RELEASED: + case EvsEventType::MASTER_RELEASED: return "MASTER_RELEASED"; default: return "Unknown"; } } + diff --git a/automotive/evs/1.1/vts/functional/FrameHandler.h b/automotive/evs/1.1/vts/functional/FrameHandler.h index 7f87cb4409..e5f1b8f112 100644 --- a/automotive/evs/1.1/vts/functional/FrameHandler.h +++ b/automotive/evs/1.1/vts/functional/FrameHandler.h @@ -33,7 +33,6 @@ using ::android::hardware::hidl_handle; using ::android::sp; using ::android::hardware::automotive::evs::V1_0::IEvsDisplay; using ::android::hardware::automotive::evs::V1_0::EvsResult; -using ::android::hardware::automotive::evs::V1_0::CameraDesc; using BufferDesc_1_0 = ::android::hardware::automotive::evs::V1_0::BufferDesc; using BufferDesc_1_1 = ::android::hardware::automotive::evs::V1_1::BufferDesc; @@ -56,6 +55,13 @@ public: FrameHandler(android::sp <IEvsCamera> pCamera, CameraDesc cameraInfo, android::sp <IEvsDisplay> pDisplay = nullptr, BufferControlFlag mode = eAutoReturn); + virtual ~FrameHandler() { + if (mCamera != nullptr) { + /* shutdown a camera explicitly */ + shutdown(); + } + } + void shutdown(); bool startStream(); @@ -67,19 +73,22 @@ public: bool isRunning(); void waitForFrameCount(unsigned frameCount); - bool waitForEvent(const InfoEventType aTargetEvent, - InfoEventDesc &eventDesc); + bool waitForEvent(const EvsEventType aTargetEvent, + EvsEvent &eventDesc); void getFramesCounters(unsigned* received, unsigned* displayed); void getFrameDimension(unsigned* width, unsigned* height); private: - // Implementation for ::android::hardware::automotive::evs::V1_1::IEvsCameraStream + // Implementation for ::android::hardware::automotive::evs::V1_0::IEvsCameraStream Return<void> deliverFrame(const BufferDesc_1_0& buffer) override; - Return<void> notifyEvent(const EvsEvent& event) override; + + // Implementation for ::android::hardware::automotive::evs::V1_1::IEvsCameraStream + Return<void> deliverFrame_1_1(const BufferDesc_1_1& buffer) override; + Return<void> notify(const EvsEvent& event) override; // Local implementation details bool copyBufferContents(const BufferDesc_1_0& tgtBuffer, const BufferDesc_1_1& srcBuffer); - const char *eventToString(const InfoEventType aType); + const char *eventToString(const EvsEventType aType); // Values initialized as startup android::sp <IEvsCamera> mCamera; @@ -100,7 +109,7 @@ private: unsigned mFramesDisplayed = 0; // Simple counter -- rolls over eventually! unsigned mFrameWidth = 0; unsigned mFrameHeight = 0; - InfoEventDesc mLatestEventDesc; + EvsEvent mLatestEventDesc; }; diff --git a/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp b/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp index a6e4881d4d..1d3fd87356 100644 --- a/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp +++ b/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp @@ -38,8 +38,9 @@ static const float kNanoToSeconds = 0.000000001f; #include "FrameHandler.h" -#include <stdio.h> -#include <string.h> +#include <cstdio> +#include <cstring> +#include <cstdlib> #include <hidl/HidlTransportSupport.h> #include <hwbinder/ProcessState.h> @@ -50,8 +51,10 @@ static const float kNanoToSeconds = 0.000000001f; #include <android/log.h> #include <android/hardware/automotive/evs/1.1/IEvsCamera.h> #include <android/hardware/automotive/evs/1.1/IEvsCameraStream.h> -#include <android/hardware/automotive/evs/1.0/IEvsEnumerator.h> +#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h> #include <android/hardware/automotive/evs/1.0/IEvsDisplay.h> +#include <android/hardware/camera/device/3.2/ICameraDevice.h> +#include <system/camera_metadata.h> #include <VtsHalHidlTargetTestBase.h> #include <VtsHalHidlTargetTestEnvBase.h> @@ -64,13 +67,28 @@ using ::android::hardware::hidl_vec; using ::android::hardware::hidl_handle; using ::android::hardware::hidl_string; using ::android::sp; -using ::android::hardware::automotive::evs::V1_0::CameraDesc; +using ::android::hardware::camera::device::V3_2::Stream; using ::android::hardware::automotive::evs::V1_0::DisplayDesc; using ::android::hardware::automotive::evs::V1_0::DisplayState; -using ::android::hardware::automotive::evs::V1_0::IEvsEnumerator; +using ::android::hardware::graphics::common::V1_0::PixelFormat; using IEvsCamera_1_0 = ::android::hardware::automotive::evs::V1_0::IEvsCamera; using IEvsCamera_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsCamera; +/* + * Plese note that this is different from what is defined in + * libhardware/modules/camera/3_4/metadata/types.h; this has one additional + * field to store a framerate. + */ +const size_t kStreamCfgSz = 5; +typedef struct { + int32_t width; + int32_t height; + int32_t format; + int32_t direction; + int32_t framerate; +} RawStreamConfig; + + // Test environment for Evs HIDL HAL. class EvsHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { public: @@ -107,15 +125,16 @@ protected: assert(pEnumerator != nullptr); // Get the camera list - pEnumerator->getCameraList([this](hidl_vec <CameraDesc> cameraList) { - ALOGI("Camera list callback received %zu cameras", - cameraList.size()); - cameraInfo.reserve(cameraList.size()); - for (auto&& cam: cameraList) { - ALOGI("Found camera %s", cam.cameraId.c_str()); - cameraInfo.push_back(cam); - } - } + pEnumerator->getCameraList_1_1( + [this](hidl_vec <CameraDesc> cameraList) { + ALOGI("Camera list callback received %zu cameras", + cameraList.size()); + cameraInfo.reserve(cameraList.size()); + for (auto&& cam: cameraList) { + ALOGI("Found camera %s", cam.v1.cameraId.c_str()); + cameraInfo.push_back(cam); + } + } ); // We insist on at least one camera for EVS to pass any camera tests @@ -143,19 +162,23 @@ TEST_F(EvsHidlTest, CameraOpenClean) { // Get the camera list loadCameraList(); + // Using null stream configuration makes EVS uses the default resolution and + // output format. + Stream nullCfg = {}; + // Open and close each camera twice for (auto&& cam: cameraInfo) { for (int pass = 0; pass < 2; pass++) { sp<IEvsCamera_1_1> pCam = - IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId)) + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) .withDefault(nullptr); ASSERT_NE(pCam, nullptr); // Verify that this camera self-identifies correctly - pCam->getCameraInfo([&cam](CameraDesc desc) { - ALOGD("Found camera %s", desc.cameraId.c_str()); - EXPECT_EQ(cam.cameraId, desc.cameraId); - } + pCam->getCameraInfo_1_1([&cam](CameraDesc desc) { + ALOGD("Found camera %s", desc.v1.cameraId.c_str()); + EXPECT_EQ(cam.v1.cameraId, desc.v1.cameraId); + } ); // Explicitly close the camera so resources are released right away @@ -177,22 +200,26 @@ TEST_F(EvsHidlTest, CameraOpenAggressive) { // Get the camera list loadCameraList(); + // Using null stream configuration makes EVS uses the default resolution and + // output format. + Stream nullCfg = {}; + // Open and close each camera twice for (auto&& cam: cameraInfo) { sp<IEvsCamera_1_1> pCam = - IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId)) + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) .withDefault(nullptr); ASSERT_NE(pCam, nullptr); // Verify that this camera self-identifies correctly - pCam->getCameraInfo([&cam](CameraDesc desc) { - ALOGD("Found camera %s", desc.cameraId.c_str()); - EXPECT_EQ(cam.cameraId, desc.cameraId); - } + pCam->getCameraInfo_1_1([&cam](CameraDesc desc) { + ALOGD("Found camera %s", desc.v1.cameraId.c_str()); + EXPECT_EQ(cam.v1.cameraId, desc.v1.cameraId); + } ); sp<IEvsCamera_1_1> pCam2 = - IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId)) + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) .withDefault(nullptr); ASSERT_NE(pCam, pCam2); ASSERT_NE(pCam2, nullptr); @@ -210,10 +237,10 @@ TEST_F(EvsHidlTest, CameraOpenAggressive) { pEnumerator->closeCamera(pCam); // Verify that the second camera instance self-identifies correctly - pCam2->getCameraInfo([&cam](CameraDesc desc) { - ALOGD("Found camera %s", desc.cameraId.c_str()); - EXPECT_EQ(cam.cameraId, desc.cameraId); - } + pCam2->getCameraInfo_1_1([&cam](CameraDesc desc) { + ALOGD("Found camera %s", desc.v1.cameraId.c_str()); + EXPECT_EQ(cam.v1.cameraId, desc.v1.cameraId); + } ); // Close the second camera instance @@ -235,10 +262,14 @@ TEST_F(EvsHidlTest, CameraStreamPerformance) { // Get the camera list loadCameraList(); + // Using null stream configuration makes EVS uses the default resolution and + // output format. + Stream nullCfg = {}; + // Test each reported camera for (auto&& cam: cameraInfo) { sp<IEvsCamera_1_1> pCam = - IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId)) + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) .withDefault(nullptr); ASSERT_NE(pCam, nullptr); @@ -303,11 +334,15 @@ TEST_F(EvsHidlTest, CameraStreamBuffering) { // Get the camera list loadCameraList(); + // Using null stream configuration makes EVS uses the default resolution and + // output format. + Stream nullCfg = {}; + // Test each reported camera for (auto&& cam: cameraInfo) { sp<IEvsCamera_1_1> pCam = - IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId)) + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) .withDefault(nullptr); ASSERT_NE(pCam, nullptr); @@ -371,6 +406,10 @@ TEST_F(EvsHidlTest, CameraToDisplayRoundTrip) { // Get the camera list loadCameraList(); + // Using null stream configuration makes EVS uses the default resolution and + // output format. + Stream nullCfg = {}; + // Request exclusive access to the EVS display sp<IEvsDisplay> pDisplay = pEnumerator->openDisplay(); ASSERT_NE(pDisplay, nullptr); @@ -378,7 +417,7 @@ TEST_F(EvsHidlTest, CameraToDisplayRoundTrip) { // Test each reported camera for (auto&& cam: cameraInfo) { sp<IEvsCamera_1_1> pCam = - IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId)) + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) .withDefault(nullptr); ASSERT_NE(pCam, nullptr); @@ -439,16 +478,20 @@ TEST_F(EvsHidlTest, MultiCameraStream) { // Get the camera list loadCameraList(); + // Using null stream configuration makes EVS uses the default resolution and + // output format. + Stream nullCfg = {}; + // Test each reported camera for (auto&& cam: cameraInfo) { // Create two camera clients. sp<IEvsCamera_1_1> pCam0 = - IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId)) + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) .withDefault(nullptr); ASSERT_NE(pCam0, nullptr); sp<IEvsCamera_1_1> pCam1 = - IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId)) + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) .withDefault(nullptr); ASSERT_NE(pCam1, nullptr); @@ -486,7 +529,6 @@ TEST_F(EvsHidlTest, MultiCameraStream) { nsecs_t runTime = end - firstFrame; float framesPerSecond0 = framesReceived0 / (runTime * kNanoToSeconds); float framesPerSecond1 = framesReceived1 / (runTime * kNanoToSeconds); - printf("Measured camera rate %3.2f fps and %3.2f fps\n", framesPerSecond0, framesPerSecond1); ALOGI("Measured camera rate %3.2f fps and %3.2f fps", framesPerSecond0, framesPerSecond1); EXPECT_GE(framesPerSecond0, kMinimumFramesPerSecond); EXPECT_GE(framesPerSecond1, kMinimumFramesPerSecond); @@ -526,14 +568,33 @@ TEST_F(EvsHidlTest, CameraParameter) { // Get the camera list loadCameraList(); + // Using null stream configuration makes EVS uses the default resolution and + // output format. + Stream nullCfg = {}; + // Test each reported camera + Return<EvsResult> result = EvsResult::OK; for (auto&& cam: cameraInfo) { // Create a camera client sp<IEvsCamera_1_1> pCam = - IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId)) + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) .withDefault(nullptr); ASSERT_NE(pCam, nullptr); + // Get the parameter list + std::vector<CameraParam> cmds; + pCam->getParameterList([&cmds](hidl_vec<CameraParam> cmdList) { + cmds.reserve(cmdList.size()); + for (auto &&cmd : cmdList) { + cmds.push_back(cmd); + } + } + ); + + if (cmds.size() < 1) { + continue; + } + // Set up per-client frame receiver objects which will fire up its own thread sp<FrameHandler> frameHandler = new FrameHandler(pCam, cam, nullptr, @@ -547,83 +608,70 @@ TEST_F(EvsHidlTest, CameraParameter) { // Ensure the stream starts frameHandler->waitForFrameCount(1); - // Try to program few parameters - EvsResult result = EvsResult::OK; - int32_t val0 = 100; - int32_t val1 = 0; - result = pCam->setMaster(); - ASSERT_TRUE(result == EvsResult::OK); - - pCam->setParameter(CameraParam::BRIGHTNESS, val0, - [&result, &val1](auto status, auto effectiveValue) { - result = status; - val1 = effectiveValue; - }); - ASSERT_TRUE(result == EvsResult::OK || - result == EvsResult::INVALID_ARG); + ASSERT_EQ(EvsResult::OK, result); + + for (auto &cmd : cmds) { + // Get a valid parameter value range + int32_t minVal, maxVal, step; + pCam->getIntParameterRange( + cmd, + [&minVal, &maxVal, &step](int32_t val0, int32_t val1, int32_t val2) { + minVal = val0; + maxVal = val1; + step = val2; + } + ); - if (result == EvsResult::OK) { - pCam->getParameter(CameraParam::BRIGHTNESS, - [&result, &val1](auto status, auto value) { + EvsResult result = EvsResult::OK; + if (cmd == CameraParam::ABSOLUTE_FOCUS) { + // Try to turn off auto-focus + int32_t val1 = 0; + pCam->getIntParameter(CameraParam::AUTO_FOCUS, + [&result, &val1](auto status, auto value) { + result = status; + if (status == EvsResult::OK) { + val1 = value; + } + }); + if (val1 != 0) { + pCam->setIntParameter(CameraParam::AUTO_FOCUS, 0, + [&result, &val1](auto status, auto effectiveValue) { + result = status; + val1 = effectiveValue; + }); + ASSERT_EQ(EvsResult::OK, result); + ASSERT_EQ(val1, 0); + } + } + + // Try to program a parameter with a random value [minVal, maxVal] + int32_t val0 = minVal + (std::rand() % (maxVal - minVal)); + int32_t val1 = 0; + + // Rounding down + val0 = val0 - (val0 % step); + pCam->setIntParameter(cmd, val0, + [&result, &val1](auto status, auto effectiveValue) { result = status; - if (status == EvsResult::OK) { - val1 = value; - } + val1 = effectiveValue; }); - ASSERT_TRUE(result == EvsResult::OK || - result == EvsResult::INVALID_ARG); - ASSERT_EQ(val0, val1) << "Values are not matched."; - } - val0 = 80; - val1 = 0; - pCam->setParameter(CameraParam::CONTRAST, val0, - [&result, &val1](auto status, auto effectiveValue) { - result = status; - val1 = effectiveValue; - }); - ASSERT_TRUE(result == EvsResult::OK || - result == EvsResult::INVALID_ARG); - - if (result == EvsResult::OK) { - pCam->getParameter(CameraParam::CONTRAST, - [&result, &val1](auto status, auto value) { - result = status; - if (status == EvsResult::OK) { - val1 = value; - } - }); - ASSERT_TRUE(result == EvsResult::OK || - result == EvsResult::INVALID_ARG); - ASSERT_EQ(val0, val1) << "Values are not matched."; - } + ASSERT_EQ(EvsResult::OK, result); - val0 = 300; - val1 = 0; - pCam->setParameter(CameraParam::ABSOLUTE_ZOOM, val0, - [&result, &val1](auto status, auto effectiveValue) { - result = status; - val1 = effectiveValue; - }); - ASSERT_TRUE(result == EvsResult::OK || - result == EvsResult::INVALID_ARG); - - if (result == EvsResult::OK) { - pCam->getParameter(CameraParam::ABSOLUTE_ZOOM, + pCam->getIntParameter(cmd, [&result, &val1](auto status, auto value) { result = status; if (status == EvsResult::OK) { val1 = value; } }); - ASSERT_TRUE(result == EvsResult::OK || - result == EvsResult::INVALID_ARG); + ASSERT_EQ(EvsResult::OK, result); ASSERT_EQ(val0, val1) << "Values are not matched."; } result = pCam->unsetMaster(); - ASSERT_TRUE(result == EvsResult::OK); + ASSERT_EQ(EvsResult::OK, result); // Shutdown frameHandler->shutdown(); @@ -650,15 +698,19 @@ TEST_F(EvsHidlTest, CameraMasterRelease) { // Get the camera list loadCameraList(); + // Using null stream configuration makes EVS uses the default resolution and + // output format. + Stream nullCfg = {}; + // Test each reported camera for (auto&& cam: cameraInfo) { // Create two camera clients. sp<IEvsCamera_1_1> pCamMaster = - IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId)) + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) .withDefault(nullptr); ASSERT_NE(pCamMaster, nullptr); sp<IEvsCamera_1_1> pCamNonMaster = - IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId)) + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) .withDefault(nullptr); ASSERT_NE(pCamNonMaster, nullptr); @@ -698,15 +750,15 @@ TEST_F(EvsHidlTest, CameraMasterRelease) { // Non-master client expects to receive a master role relesed // notification. - InfoEventDesc aNotification = {}; + EvsEvent aNotification = {}; // Release a master role. pCamMaster->unsetMaster(); // Verify a change notification. - frameHandlerNonMaster->waitForEvent(InfoEventType::MASTER_RELEASED, aNotification); - ASSERT_EQ(InfoEventType::MASTER_RELEASED, - static_cast<InfoEventType>(aNotification.aType)); + frameHandlerNonMaster->waitForEvent(EvsEventType::MASTER_RELEASED, aNotification); + ASSERT_EQ(EvsEventType::MASTER_RELEASED, + static_cast<EvsEventType>(aNotification.aType)); // Non-master becomes a master. result = pCamNonMaster->setMaster(); @@ -720,9 +772,9 @@ TEST_F(EvsHidlTest, CameraMasterRelease) { frameHandlerNonMaster->shutdown(); // Verify a change notification. - frameHandlerMaster->waitForEvent(InfoEventType::MASTER_RELEASED, aNotification); - ASSERT_EQ(InfoEventType::MASTER_RELEASED, - static_cast<InfoEventType>(aNotification.aType)); + frameHandlerMaster->waitForEvent(EvsEventType::MASTER_RELEASED, aNotification); + ASSERT_EQ(EvsEventType::MASTER_RELEASED, + static_cast<EvsEventType>(aNotification.aType)); // Closing another stream. frameHandlerMaster->shutdown(); @@ -752,18 +804,46 @@ TEST_F(EvsHidlTest, MultiCameraParameter) { // Get the camera list loadCameraList(); + // Using null stream configuration makes EVS uses the default resolution and + // output format. + Stream nullCfg = {}; + // Test each reported camera for (auto&& cam: cameraInfo) { // Create two camera clients. sp<IEvsCamera_1_1> pCamMaster = - IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId)) + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) .withDefault(nullptr); ASSERT_NE(pCamMaster, nullptr); sp<IEvsCamera_1_1> pCamNonMaster = - IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId)) + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) .withDefault(nullptr); ASSERT_NE(pCamNonMaster, nullptr); + // Get the parameter list + std::vector<CameraParam> camMasterCmds, camNonMasterCmds; + pCamMaster->getParameterList([&camMasterCmds](hidl_vec<CameraParam> cmdList) { + camMasterCmds.reserve(cmdList.size()); + for (auto &&cmd : cmdList) { + camMasterCmds.push_back(cmd); + } + } + ); + + pCamNonMaster->getParameterList([&camNonMasterCmds](hidl_vec<CameraParam> cmdList) { + camNonMasterCmds.reserve(cmdList.size()); + for (auto &&cmd : cmdList) { + camNonMasterCmds.push_back(cmd); + } + } + ); + + if (camMasterCmds.size() < 1 || + camNonMasterCmds.size() < 1) { + // Skip a camera device if it does not support any parameter. + continue; + } + // Set up per-client frame receiver objects which will fire up its own thread sp<FrameHandler> frameHandlerMaster = new FrameHandler(pCamMaster, cam, @@ -778,11 +858,11 @@ TEST_F(EvsHidlTest, MultiCameraParameter) { // Set one client as the master EvsResult result = pCamMaster->setMaster(); - ASSERT_TRUE(result == EvsResult::OK); + ASSERT_EQ(EvsResult::OK, result); // Try to set another client as the master. result = pCamNonMaster->setMaster(); - ASSERT_TRUE(result == EvsResult::OWNERSHIP_LOST); + ASSERT_EQ(EvsResult::OWNERSHIP_LOST, result); // Start the camera's video stream via a master client. bool startResult = frameHandlerMaster->startStream(); @@ -798,131 +878,168 @@ TEST_F(EvsHidlTest, MultiCameraParameter) { // Ensure the stream starts frameHandlerNonMaster->waitForFrameCount(1); - // Try to program CameraParam::BRIGHTNESS - int32_t val0 = 100; + int32_t val0 = 0; int32_t val1 = 0; + for (auto &cmd : camMasterCmds) { + // Get a valid parameter value range + int32_t minVal, maxVal, step; + pCamMaster->getIntParameterRange( + cmd, + [&minVal, &maxVal, &step](int32_t val0, int32_t val1, int32_t val2) { + minVal = val0; + maxVal = val1; + step = val2; + } + ); - pCamMaster->setParameter(CameraParam::BRIGHTNESS, val0, - [&result, &val1](auto status, auto effectiveValue) { - result = status; - val1 = effectiveValue; - }); - ASSERT_TRUE(result == EvsResult::OK || // Succeeded to program - result == EvsResult::INVALID_ARG); // Camera parameter is not supported - - // Non-master client expects to receive a parameter change notification - // whenever a master client adjusts it. - InfoEventDesc aNotification = {}; - - pCamMaster->getParameter(CameraParam::BRIGHTNESS, - [&result, &val1](auto status, auto value) { - result = status; - if (status == EvsResult::OK) { - val1 = value; - } - }); - ASSERT_TRUE(result == EvsResult::OK || // Succeeded to program - result == EvsResult::INVALID_ARG); // Camera parameter is not supported - if (result == EvsResult::OK) { - ASSERT_EQ(val0, val1) << "Values are not matched."; + EvsResult result = EvsResult::OK; + if (cmd == CameraParam::ABSOLUTE_FOCUS) { + // Try to turn off auto-focus + int32_t val1 = 1; + pCamMaster->setIntParameter(CameraParam::AUTO_FOCUS, 0, + [&result, &val1](auto status, auto effectiveValue) { + result = status; + val1 = effectiveValue; + }); + ASSERT_EQ(EvsResult::OK, result); + ASSERT_EQ(val1, 0); + } + + // Try to program a parameter + val0 = minVal + (std::rand() % (maxVal - minVal)); + + // Rounding down + val0 = val0 - (val0 % step); + pCamMaster->setIntParameter(cmd, val0, + [&result, &val1](auto status, auto effectiveValue) { + result = status; + val1 = effectiveValue; + }); + ASSERT_EQ(EvsResult::OK, result); - // Verify a change notification - frameHandlerNonMaster->waitForEvent(InfoEventType::PARAMETER_CHANGED, aNotification); - ASSERT_EQ(InfoEventType::PARAMETER_CHANGED, - static_cast<InfoEventType>(aNotification.aType)); - ASSERT_EQ(CameraParam::BRIGHTNESS, - static_cast<CameraParam>(aNotification.payload[0])); - ASSERT_EQ(val1, - static_cast<int32_t>(aNotification.payload[1])); - } + // Wait a moment + sleep(1); - // Try to program CameraParam::CONTRAST - val0 = 80; - val1 = 0; - pCamMaster->setParameter(CameraParam::CONTRAST, val0, - [&result, &val1](auto status, auto effectiveValue) { - result = status; - val1 = effectiveValue; - }); - ASSERT_TRUE(result == EvsResult::OK || // Succeeded to program - result == EvsResult::INVALID_ARG); // Camera parameter is not supported + // Non-master client expects to receive a parameter change notification + // whenever a master client adjusts it. + EvsEvent aNotification = {}; - if (result == EvsResult::OK) { - pCamMaster->getParameter(CameraParam::CONTRAST, + pCamMaster->getIntParameter(cmd, [&result, &val1](auto status, auto value) { result = status; if (status == EvsResult::OK) { val1 = value; } }); - ASSERT_TRUE(result == EvsResult::OK); + ASSERT_EQ(EvsResult::OK, result); ASSERT_EQ(val0, val1) << "Values are not matched."; - // Verify a change notification - frameHandlerNonMaster->waitForEvent(InfoEventType::PARAMETER_CHANGED, aNotification); - ASSERT_EQ(InfoEventType::PARAMETER_CHANGED, - static_cast<InfoEventType>(aNotification.aType)); - ASSERT_EQ(CameraParam::CONTRAST, + frameHandlerNonMaster->waitForEvent(EvsEventType::PARAMETER_CHANGED, aNotification); + ASSERT_EQ(EvsEventType::PARAMETER_CHANGED, + static_cast<EvsEventType>(aNotification.aType)); + ASSERT_EQ(cmd, static_cast<CameraParam>(aNotification.payload[0])); ASSERT_EQ(val1, static_cast<int32_t>(aNotification.payload[1])); } // Try to adjust a parameter via non-master client - pCamNonMaster->setParameter(CameraParam::CONTRAST, val0, + pCamNonMaster->setIntParameter(camNonMasterCmds[0], val0, [&result, &val1](auto status, auto effectiveValue) { result = status; val1 = effectiveValue; }); - ASSERT_TRUE(result == EvsResult::INVALID_ARG); + ASSERT_EQ(EvsResult::INVALID_ARG, result); // Non-master client attemps to be a master result = pCamNonMaster->setMaster(); - ASSERT_TRUE(result == EvsResult::OWNERSHIP_LOST); + ASSERT_EQ(EvsResult::OWNERSHIP_LOST, result); // Master client retires from a master role result = pCamMaster->unsetMaster(); - ASSERT_TRUE(result == EvsResult::OK); + ASSERT_EQ(EvsResult::OK, result); // Try to adjust a parameter after being retired - pCamMaster->setParameter(CameraParam::BRIGHTNESS, val0, + pCamMaster->setIntParameter(camMasterCmds[0], val0, [&result, &val1](auto status, auto effectiveValue) { result = status; val1 = effectiveValue; }); - ASSERT_TRUE(result == EvsResult::INVALID_ARG); + ASSERT_EQ(EvsResult::INVALID_ARG, result); // Non-master client becomes a master result = pCamNonMaster->setMaster(); - ASSERT_TRUE(result == EvsResult::OK); + ASSERT_EQ(EvsResult::OK, result); // Try to adjust a parameter via new master client - pCamNonMaster->setParameter(CameraParam::BRIGHTNESS, val0, - [&result, &val1](auto status, auto effectiveValue) { - result = status; - val1 = effectiveValue; - }); - ASSERT_TRUE(result == EvsResult::OK || // Succeeded to program - result == EvsResult::INVALID_ARG); // Camera parameter is not supported + for (auto &cmd : camNonMasterCmds) { + // Get a valid parameter value range + int32_t minVal, maxVal, step; + pCamNonMaster->getIntParameterRange( + cmd, + [&minVal, &maxVal, &step](int32_t val0, int32_t val1, int32_t val2) { + minVal = val0; + maxVal = val1; + step = val2; + } + ); - // Wait a moment - sleep(1); + EvsResult result = EvsResult::OK; + if (cmd == CameraParam::ABSOLUTE_FOCUS) { + // Try to turn off auto-focus + int32_t val1 = 1; + pCamNonMaster->setIntParameter(CameraParam::AUTO_FOCUS, 0, + [&result, &val1](auto status, auto effectiveValue) { + result = status; + val1 = effectiveValue; + }); + ASSERT_EQ(EvsResult::OK, result); + ASSERT_EQ(val1, 0); + } + + // Try to program a parameter + val0 = minVal + (std::rand() % (maxVal - minVal)); + + // Rounding down + val0 = val0 - (val0 % step); + pCamNonMaster->setIntParameter(cmd, val0, + [&result, &val1](auto status, auto effectiveValue) { + result = status; + val1 = effectiveValue; + }); + ASSERT_EQ(EvsResult::OK, result); + + // Wait a moment + sleep(1); + + // Non-master client expects to receive a parameter change notification + // whenever a master client adjusts it. + EvsEvent aNotification = {}; + + pCamNonMaster->getIntParameter(cmd, + [&result, &val1](auto status, auto value) { + result = status; + if (status == EvsResult::OK) { + val1 = value; + } + }); + ASSERT_EQ(EvsResult::OK, result); + ASSERT_EQ(val0, val1) << "Values are not matched."; - // Verify a change notification - if (result == EvsResult::OK) { - frameHandlerMaster->waitForEvent(InfoEventType::PARAMETER_CHANGED, aNotification); - ASSERT_EQ(static_cast<InfoEventType>(aNotification.aType), - InfoEventType::PARAMETER_CHANGED); - ASSERT_EQ(static_cast<CameraParam>(aNotification.payload[0]), - CameraParam::BRIGHTNESS); + // Verify a change notification + frameHandlerMaster->waitForEvent(EvsEventType::PARAMETER_CHANGED, aNotification); + ASSERT_EQ(EvsEventType::PARAMETER_CHANGED, + static_cast<EvsEventType>(aNotification.aType)); + ASSERT_EQ(cmd, + static_cast<CameraParam>(aNotification.payload[0])); ASSERT_EQ(val1, static_cast<int32_t>(aNotification.payload[1])); } // New master retires from a master role result = pCamNonMaster->unsetMaster(); - ASSERT_TRUE(result == EvsResult::OK); + ASSERT_EQ(EvsResult::OK, result); // Shutdown frameHandlerMaster->shutdown(); @@ -943,9 +1060,18 @@ TEST_F(EvsHidlTest, MultiCameraParameter) { TEST_F(EvsHidlTest, HighPriorityCameraClient) { ALOGI("Starting HighPriorityCameraClient test"); + if (mIsHwModule) { + // This test is not for HW module implementation. + return; + } + // Get the camera list loadCameraList(); + // Using null stream configuration makes EVS uses the default resolution and + // output format. + Stream nullCfg = {}; + // Request exclusive access to the EVS display sp<IEvsDisplay> pDisplay = pEnumerator->openDisplay(); ASSERT_NE(pDisplay, nullptr); @@ -954,15 +1080,38 @@ TEST_F(EvsHidlTest, HighPriorityCameraClient) { for (auto&& cam: cameraInfo) { // Create two clients sp<IEvsCamera_1_1> pCam0 = - IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId)) + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) .withDefault(nullptr); ASSERT_NE(pCam0, nullptr); sp<IEvsCamera_1_1> pCam1 = - IEvsCamera_1_1::castFrom(pEnumerator->openCamera(cam.cameraId)) + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) .withDefault(nullptr); ASSERT_NE(pCam1, nullptr); + // Get the parameter list; this test will use the first command in both + // lists. + std::vector<CameraParam> cam0Cmds, cam1Cmds; + pCam0->getParameterList([&cam0Cmds](hidl_vec<CameraParam> cmdList) { + cam0Cmds.reserve(cmdList.size()); + for (auto &&cmd : cmdList) { + cam0Cmds.push_back(cmd); + } + } + ); + + pCam1->getParameterList([&cam1Cmds](hidl_vec<CameraParam> cmdList) { + cam1Cmds.reserve(cmdList.size()); + for (auto &&cmd : cmdList) { + cam1Cmds.push_back(cmd); + } + } + ); + if (cam0Cmds.size() < 1 || cam1Cmds.size() < 1) { + // Cannot execute this test. + return; + } + // Set up a frame receiver object which will fire up its own thread. sp<FrameHandler> frameHandler0 = new FrameHandler(pCam0, cam, pDisplay, @@ -982,67 +1131,121 @@ TEST_F(EvsHidlTest, HighPriorityCameraClient) { frameHandler0->waitForFrameCount(1); frameHandler1->waitForFrameCount(1); - // Client 1 becomes a master and programs a brightness. + // Client 1 becomes a master and programs a parameter. EvsResult result = EvsResult::OK; - int32_t val0 = 100; + // Get a valid parameter value range + int32_t minVal, maxVal, step; + pCam1->getIntParameterRange( + cam1Cmds[0], + [&minVal, &maxVal, &step](int32_t val0, int32_t val1, int32_t val2) { + minVal = val0; + maxVal = val1; + step = val2; + } + ); + + if (cam1Cmds[0] == CameraParam::ABSOLUTE_FOCUS) { + // Try to turn off auto-focus + int32_t val1 = 0; + pCam1->getIntParameter(CameraParam::AUTO_FOCUS, + [&result, &val1](auto status, auto value) { + result = status; + if (status == EvsResult::OK) { + val1 = value; + } + }); + if (val1 != 0) { + pCam1->setIntParameter(CameraParam::AUTO_FOCUS, 0, + [&result, &val1](auto status, auto effectiveValue) { + result = status; + val1 = effectiveValue; + }); + ASSERT_EQ(EvsResult::OK, result); + ASSERT_EQ(val1, 0); + } + } + + // Try to program a parameter with a random value [minVal, maxVal] + int32_t val0 = minVal + (std::rand() % (maxVal - minVal)); int32_t val1 = 0; + // Rounding down + val0 = val0 - (val0 % step); + result = pCam1->setMaster(); - ASSERT_TRUE(result == EvsResult::OK); + ASSERT_EQ(EvsResult::OK, result); - pCam1->setParameter(CameraParam::BRIGHTNESS, val0, + pCam1->setIntParameter(cam1Cmds[0], val0, [&result, &val1](auto status, auto effectiveValue) { result = status; val1 = effectiveValue; }); - ASSERT_TRUE(result == EvsResult::OK || - result == EvsResult::INVALID_ARG); - + ASSERT_EQ(EvsResult::OK, result); // Verify a change notification - InfoEventDesc aNotification = {}; - if (result == EvsResult::OK) { - bool timeout = - frameHandler0->waitForEvent(InfoEventType::PARAMETER_CHANGED, aNotification); - ASSERT_FALSE(timeout) << "Expected event does not arrive"; - ASSERT_EQ(static_cast<InfoEventType>(aNotification.aType), - InfoEventType::PARAMETER_CHANGED); - ASSERT_EQ(static_cast<CameraParam>(aNotification.payload[0]), - CameraParam::BRIGHTNESS); - ASSERT_EQ(val1, - static_cast<int32_t>(aNotification.payload[1])); - } + EvsEvent aNotification = {}; + bool timeout = + frameHandler0->waitForEvent(EvsEventType::PARAMETER_CHANGED, aNotification); + ASSERT_FALSE(timeout) << "Expected event does not arrive"; + ASSERT_EQ(static_cast<EvsEventType>(aNotification.aType), + EvsEventType::PARAMETER_CHANGED); + ASSERT_EQ(static_cast<CameraParam>(aNotification.payload[0]), + cam1Cmds[0]); + ASSERT_EQ(val1, + static_cast<int32_t>(aNotification.payload[1])); // Client 0 steals a master role ASSERT_EQ(EvsResult::OK, pCam0->forceMaster(pDisplay)); - frameHandler1->waitForEvent(InfoEventType::MASTER_RELEASED, aNotification); - ASSERT_EQ(static_cast<InfoEventType>(aNotification.aType), - InfoEventType::MASTER_RELEASED); + frameHandler1->waitForEvent(EvsEventType::MASTER_RELEASED, aNotification); + ASSERT_EQ(static_cast<EvsEventType>(aNotification.aType), + EvsEventType::MASTER_RELEASED); - // Client 0 programs a brightness - val0 = 50; + // Client 0 programs a parameter + val0 = minVal + (std::rand() % (maxVal - minVal)); val1 = 0; - pCam0->setParameter(CameraParam::BRIGHTNESS, val0, + + // Rounding down + val0 = val0 - (val0 % step); + + if (cam0Cmds[0] == CameraParam::ABSOLUTE_FOCUS) { + // Try to turn off auto-focus + int32_t val1 = 0; + pCam0->getIntParameter(CameraParam::AUTO_FOCUS, + [&result, &val1](auto status, auto value) { + result = status; + if (status == EvsResult::OK) { + val1 = value; + } + }); + if (val1 != 0) { + pCam0->setIntParameter(CameraParam::AUTO_FOCUS, 0, + [&result, &val1](auto status, auto effectiveValue) { + result = status; + val1 = effectiveValue; + }); + ASSERT_EQ(EvsResult::OK, result); + ASSERT_EQ(val1, 0); + } + } + + pCam0->setIntParameter(cam0Cmds[0], val0, [&result, &val1](auto status, auto effectiveValue) { result = status; val1 = effectiveValue; }); - ASSERT_TRUE(result == EvsResult::OK || - result == EvsResult::INVALID_ARG); + ASSERT_EQ(EvsResult::OK, result); // Verify a change notification - if (result == EvsResult::OK) { - bool timeout = - frameHandler1->waitForEvent(InfoEventType::PARAMETER_CHANGED, aNotification); - ASSERT_FALSE(timeout) << "Expected event does not arrive"; - ASSERT_EQ(static_cast<InfoEventType>(aNotification.aType), - InfoEventType::PARAMETER_CHANGED); - ASSERT_EQ(static_cast<CameraParam>(aNotification.payload[0]), - CameraParam::BRIGHTNESS); - ASSERT_EQ(val1, - static_cast<int32_t>(aNotification.payload[1])); - } + timeout = + frameHandler1->waitForEvent(EvsEventType::PARAMETER_CHANGED, aNotification); + ASSERT_FALSE(timeout) << "Expected event does not arrive"; + ASSERT_EQ(static_cast<EvsEventType>(aNotification.aType), + EvsEventType::PARAMETER_CHANGED); + ASSERT_EQ(static_cast<CameraParam>(aNotification.payload[0]), + cam0Cmds[0]); + ASSERT_EQ(val1, + static_cast<int32_t>(aNotification.payload[1])); // Turn off the display (yes, before the stream stops -- it should be handled) pDisplay->setDisplayState(DisplayState::NOT_VISIBLE); @@ -1061,6 +1264,248 @@ TEST_F(EvsHidlTest, HighPriorityCameraClient) { } +/* + * CameraUseStreamConfigToDisplay: + * End to end test of data flowing from the camera to the display. Similar to + * CameraToDisplayRoundTrip test case but this case retrieves available stream + * configurations from EVS and uses one of them to start a video stream. + */ +TEST_F(EvsHidlTest, CameraUseStreamConfigToDisplay) { + ALOGI("Starting CameraUseStreamConfigToDisplay test"); + + // Get the camera list + loadCameraList(); + + // Request exclusive access to the EVS display + sp<IEvsDisplay> pDisplay = pEnumerator->openDisplay(); + ASSERT_NE(pDisplay, nullptr); + + // Test each reported camera + for (auto&& cam: cameraInfo) { + // choose a configuration that has a frame rate faster than minReqFps. + Stream targetCfg = {}; + const int32_t minReqFps = 15; + int32_t maxArea = 0; + camera_metadata_entry_t streamCfgs; + bool foundCfg = false; + if (!find_camera_metadata_entry( + reinterpret_cast<camera_metadata_t *>(cam.metadata.data()), + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, + &streamCfgs)) { + // Stream configurations are found in metadata + RawStreamConfig *ptr = reinterpret_cast<RawStreamConfig *>(streamCfgs.data.i32); + for (unsigned idx = 0; idx < streamCfgs.count; idx += kStreamCfgSz) { + if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT && + ptr->format == HAL_PIXEL_FORMAT_RGBA_8888) { + + if (ptr->width * ptr->height > maxArea && + ptr->framerate >= minReqFps) { + targetCfg.width = ptr->width; + targetCfg.height = ptr->height; + + maxArea = ptr->width * ptr->height; + foundCfg = true; + } + } + ++ptr; + } + } + targetCfg.format = + static_cast<PixelFormat>(HAL_PIXEL_FORMAT_RGBA_8888); + + if (!foundCfg) { + // Current EVS camera does not provide stream configurations in the + // metadata. + continue; + } + + sp<IEvsCamera_1_1> pCam = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, targetCfg)) + .withDefault(nullptr); + ASSERT_NE(pCam, nullptr); + + // Set up a frame receiver object which will fire up its own thread. + sp<FrameHandler> frameHandler = new FrameHandler(pCam, cam, + pDisplay, + FrameHandler::eAutoReturn); + + + // Activate the display + pDisplay->setDisplayState(DisplayState::VISIBLE_ON_NEXT_FRAME); + + // Start the camera's video stream + bool startResult = frameHandler->startStream(); + ASSERT_TRUE(startResult); + + // Wait a while to let the data flow + static const int kSecondsToWait = 5; + const int streamTimeMs = kSecondsToWait * kSecondsToMilliseconds - + kMaxStreamStartMilliseconds; + const unsigned minimumFramesExpected = streamTimeMs * kMinimumFramesPerSecond / + kSecondsToMilliseconds; + sleep(kSecondsToWait); + unsigned framesReceived = 0; + unsigned framesDisplayed = 0; + frameHandler->getFramesCounters(&framesReceived, &framesDisplayed); + EXPECT_EQ(framesReceived, framesDisplayed); + EXPECT_GE(framesDisplayed, minimumFramesExpected); + + // Turn off the display (yes, before the stream stops -- it should be handled) + pDisplay->setDisplayState(DisplayState::NOT_VISIBLE); + + // Shut down the streamer + frameHandler->shutdown(); + + // Explicitly release the camera + pEnumerator->closeCamera(pCam); + } + + // Explicitly release the display + pEnumerator->closeDisplay(pDisplay); +} + + +/* + * MultiCameraStreamUseConfig: + * Verify that each client can start and stop video streams on the same + * underlying camera with same configuration. + */ +TEST_F(EvsHidlTest, MultiCameraStreamUseConfig) { + ALOGI("Starting MultiCameraStream test"); + + if (mIsHwModule) { + // This test is not for HW module implementation. + return; + } + + // Get the camera list + loadCameraList(); + + // Test each reported camera + for (auto&& cam: cameraInfo) { + // choose a configuration that has a frame rate faster than minReqFps. + Stream targetCfg = {}; + const int32_t minReqFps = 15; + int32_t maxArea = 0; + camera_metadata_entry_t streamCfgs; + bool foundCfg = false; + if (!find_camera_metadata_entry( + reinterpret_cast<camera_metadata_t *>(cam.metadata.data()), + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, + &streamCfgs)) { + // Stream configurations are found in metadata + RawStreamConfig *ptr = reinterpret_cast<RawStreamConfig *>(streamCfgs.data.i32); + for (unsigned idx = 0; idx < streamCfgs.count; idx += kStreamCfgSz) { + if (ptr->direction == ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS_OUTPUT && + ptr->format == HAL_PIXEL_FORMAT_RGBA_8888) { + + if (ptr->width * ptr->height > maxArea && + ptr->framerate >= minReqFps) { + targetCfg.width = ptr->width; + targetCfg.height = ptr->height; + + maxArea = ptr->width * ptr->height; + foundCfg = true; + } + } + ++ptr; + } + } + targetCfg.format = + static_cast<PixelFormat>(HAL_PIXEL_FORMAT_RGBA_8888); + + if (!foundCfg) { + ALOGI("Device %s does not provide a list of supported stream configurations, skipped", + cam.v1.cameraId.c_str()); + + continue; + } + + // Create the first camera client with a selected stream configuration. + sp<IEvsCamera_1_1> pCam0 = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, targetCfg)) + .withDefault(nullptr); + ASSERT_NE(pCam0, nullptr); + + // Try to create the second camera client with different stream + // configuration. + int32_t id = targetCfg.id; + targetCfg.id += 1; // EVS manager sees only the stream id. + sp<IEvsCamera_1_1> pCam1 = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, targetCfg)) + .withDefault(nullptr); + ASSERT_EQ(pCam1, nullptr); + + // Try again with same stream configuration. + targetCfg.id = id; + pCam1 = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, targetCfg)) + .withDefault(nullptr); + ASSERT_NE(pCam1, nullptr); + + // Set up per-client frame receiver objects which will fire up its own thread + sp<FrameHandler> frameHandler0 = new FrameHandler(pCam0, cam, + nullptr, + FrameHandler::eAutoReturn); + ASSERT_NE(frameHandler0, nullptr); + + sp<FrameHandler> frameHandler1 = new FrameHandler(pCam1, cam, + nullptr, + FrameHandler::eAutoReturn); + ASSERT_NE(frameHandler1, nullptr); + + // Start the camera's video stream via client 0 + bool startResult = false; + startResult = frameHandler0->startStream() && + frameHandler1->startStream(); + ASSERT_TRUE(startResult); + + // Ensure the stream starts + frameHandler0->waitForFrameCount(1); + frameHandler1->waitForFrameCount(1); + + nsecs_t firstFrame = systemTime(SYSTEM_TIME_MONOTONIC); + + // Wait a bit, then ensure both clients get at least the required minimum number of frames + sleep(5); + nsecs_t end = systemTime(SYSTEM_TIME_MONOTONIC); + unsigned framesReceived0 = 0, framesReceived1 = 0; + frameHandler0->getFramesCounters(&framesReceived0, nullptr); + frameHandler1->getFramesCounters(&framesReceived1, nullptr); + framesReceived0 = framesReceived0 - 1; // Back out the first frame we already waited for + framesReceived1 = framesReceived1 - 1; // Back out the first frame we already waited for + nsecs_t runTime = end - firstFrame; + float framesPerSecond0 = framesReceived0 / (runTime * kNanoToSeconds); + float framesPerSecond1 = framesReceived1 / (runTime * kNanoToSeconds); + ALOGI("Measured camera rate %3.2f fps and %3.2f fps", framesPerSecond0, framesPerSecond1); + EXPECT_GE(framesPerSecond0, kMinimumFramesPerSecond); + EXPECT_GE(framesPerSecond1, kMinimumFramesPerSecond); + + // Shutdown one client + frameHandler0->shutdown(); + + // Read frame counters again + frameHandler0->getFramesCounters(&framesReceived0, nullptr); + frameHandler1->getFramesCounters(&framesReceived1, nullptr); + + // Wait a bit again + sleep(5); + unsigned framesReceivedAfterStop0 = 0, framesReceivedAfterStop1 = 0; + frameHandler0->getFramesCounters(&framesReceivedAfterStop0, nullptr); + frameHandler1->getFramesCounters(&framesReceivedAfterStop1, nullptr); + EXPECT_EQ(framesReceived0, framesReceivedAfterStop0); + EXPECT_LT(framesReceived1, framesReceivedAfterStop1); + + // Shutdown another + frameHandler1->shutdown(); + + // Explicitly release the camera + pEnumerator->closeCamera(pCam0); + pEnumerator->closeCamera(pCam1); + } +} + + int main(int argc, char** argv) { ::testing::AddGlobalTestEnvironment(EvsHidlEnvironment::Instance()); ::testing::InitGoogleTest(&argc, argv); diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h index 4a42d79b0b..f41b33c910 100644 --- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/DefaultConfig.h @@ -85,6 +85,28 @@ const int32_t kGenerateFakeDataControllingProperty = 0x0666 | VehiclePropertyGroup::VENDOR | VehicleArea::GLOBAL | VehiclePropertyType::MIXED; /** + * This property is used for test purpose to set properties' value from vehicle. + * For example: Mocking hard button press triggering a HVAC fan speed change. + * Android set kSetPropertyFromVehcileForTest with an array of integer {HVAC_FAN_SPEED, value of + * fan speed} and a long value indicates the timestamp of the events . + * It only works with integer type properties. + */ +const int32_t kSetIntPropertyFromVehcileForTest = + 0x1112 | VehiclePropertyGroup::VENDOR | VehicleArea::GLOBAL | VehiclePropertyType::MIXED; +/** + * This property is used for test purpose to set properties' value from vehicle. + * It only works with float type properties. + */ +const int32_t kSetFloatPropertyFromVehcileForTest = + 0x1113 | VehiclePropertyGroup::VENDOR | VehicleArea::GLOBAL | VehiclePropertyType::MIXED; +/** + * This property is used for test purpose to set properties' value from vehicle. + * It only works with boolean type properties. + */ +const int32_t kSetBooleanPropertyFromVehcileForTest = + 0x1114 | VehiclePropertyGroup::VENDOR | VehicleArea::GLOBAL | VehiclePropertyType::MIXED; + +/** * This property is used for test purpose. End to end tests use this property to test set and get * method for MIXED type properties. */ @@ -348,31 +370,34 @@ const ConfigDeclaration kVehicleProperties[]{ }, .initialValue = {.floatValues = {100.0f}}}, // units in meters - {.config = {.prop = toInt(VehicleProperty::TIRE_PRESSURE), - .access = VehiclePropertyAccess::READ, - .changeMode = VehiclePropertyChangeMode::CONTINUOUS, - .minSampleRate = 1.0f, - .maxSampleRate = 2.0f, - .areaConfigs = {VehicleAreaConfig{ - .areaId = WHEEL_FRONT_LEFT, - .minFloatValue = 100.0f, - .maxFloatValue = 300.0f, - }, - VehicleAreaConfig{ - .areaId = WHEEL_FRONT_RIGHT, - .minFloatValue = 100.0f, - .maxFloatValue = 300.0f, - }, - VehicleAreaConfig{ - .areaId = WHEEL_REAR_LEFT, - .minFloatValue = 100.0f, - .maxFloatValue = 300.0f, - }, - VehicleAreaConfig{ - .areaId = WHEEL_REAR_RIGHT, - .minFloatValue = 100.0f, - .maxFloatValue = 300.0f, - }}}, + {.config = + { + .prop = toInt(VehicleProperty::TIRE_PRESSURE), + .access = VehiclePropertyAccess::READ, + .changeMode = VehiclePropertyChangeMode::CONTINUOUS, + .areaConfigs = {VehicleAreaConfig{ + .areaId = WHEEL_FRONT_LEFT, + .minFloatValue = 100.0f, + .maxFloatValue = 300.0f, + }, + VehicleAreaConfig{ + .areaId = WHEEL_FRONT_RIGHT, + .minFloatValue = 100.0f, + .maxFloatValue = 300.0f, + }, + VehicleAreaConfig{ + .areaId = WHEEL_REAR_LEFT, + .minFloatValue = 100.0f, + .maxFloatValue = 300.0f, + }, + VehicleAreaConfig{ + .areaId = WHEEL_REAR_RIGHT, + .minFloatValue = 100.0f, + .maxFloatValue = 300.0f, + }}, + .minSampleRate = 1.0f, + .maxSampleRate = 2.0f, + }, .initialValue = {.floatValues = {200.0f}}}, // units in kPa {.config = @@ -573,11 +598,14 @@ const ConfigDeclaration kVehicleProperties[]{ .configArray = {(int)VehicleUnit::FAHRENHEIT, (int)VehicleUnit::CELSIUS}}, .initialValue = {.int32Values = {(int)VehicleUnit::FAHRENHEIT}}}, - {.config = {.prop = toInt(VehicleProperty::DISTANCE_DISPLAY_UNITS), - .access = VehiclePropertyAccess::READ_WRITE, - .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .configArray = {(int)VehicleUnit::KILOMETER, (int)VehicleUnit::MILE}, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}}, + {.config = + { + .prop = toInt(VehicleProperty::DISTANCE_DISPLAY_UNITS), + .access = VehiclePropertyAccess::READ_WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, + .configArray = {(int)VehicleUnit::KILOMETER, (int)VehicleUnit::MILE}, + }, .initialValue = {.int32Values = {(int)VehicleUnit::MILE}}}, {.config = @@ -636,16 +664,52 @@ const ConfigDeclaration kVehicleProperties[]{ .prop = kGenerateFakeDataControllingProperty, .access = VehiclePropertyAccess::WRITE, .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + .configArray = {1, 0, 0, 2, 0, 0, 0, 0, 0}, }, }, - {.config = {.prop = kMixedTypePropertyForTest, - .access = VehiclePropertyAccess::READ_WRITE, - .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .configArray = {1, 1, 0, 2, 0, 0, 1, 0, 0}}, - .initialValue = {.stringValue = "MIXED property", - .int32Values = {1 /* indicate TRUE boolean value */, 2, 3}, - .floatValues = {4.5f}}}, + { + .config = + { + .prop = kSetIntPropertyFromVehcileForTest, + .access = VehiclePropertyAccess::WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + .configArray = {0, 0, 0, 2, 1, 0, 0, 0, 0}, + }, + }, + + { + .config = + { + .prop = kSetFloatPropertyFromVehcileForTest, + .access = VehiclePropertyAccess::WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + .configArray = {0, 0, 1, 0, 1, 0, 1, 0, 0}, + }, + }, + + { + .config = + { + .prop = kSetBooleanPropertyFromVehcileForTest, + .access = VehiclePropertyAccess::WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + .configArray = {0, 1, 1, 0, 1, 0, 0, 0, 0}, + }, + }, + + { + .config = {.prop = kMixedTypePropertyForTest, + .access = VehiclePropertyAccess::READ_WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + .configArray = {1, 1, 0, 2, 0, 0, 1, 0, 0}}, + .initialValue = + { + .int32Values = {1 /* indicate TRUE boolean value */, 2, 3}, + .floatValues = {4.5f}, + .stringValue = "MIXED property", + }, + }, {.config = {.prop = toInt(VehicleProperty::DOOR_LOCK), .access = VehiclePropertyAccess::READ_WRITE, @@ -733,7 +797,7 @@ const ConfigDeclaration kVehicleProperties[]{ .initialValue = {.int32Values = {toInt(VehicleApPowerStateReq::ON), 0}}}, {.config = {.prop = toInt(VehicleProperty::AP_POWER_STATE_REPORT), - .access = VehiclePropertyAccess::WRITE, + .access = VehiclePropertyAccess::READ_WRITE, .changeMode = VehiclePropertyChangeMode::ON_CHANGE}, .initialValue = {.int32Values = {toInt(VehicleApPowerStateReport::WAIT_FOR_VHAL), 0}}}, diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleHal.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleHal.cpp index b4f1f07323..dc051d8e53 100644 --- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleHal.cpp +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleHal.cpp @@ -131,6 +131,36 @@ VehicleHal::VehiclePropValuePtr EmulatedVehicleHal::get( StatusCode EmulatedVehicleHal::set(const VehiclePropValue& propValue) { static constexpr bool shouldUpdateStatus = false; + // set the value from vehcile side, used in end to end test. + if (propValue.prop == kSetIntPropertyFromVehcileForTest) { + auto mockValue = createVehiclePropValue(VehiclePropertyType::INT32, 1); + mockValue->prop = propValue.value.int32Values[0]; + mockValue->value.int32Values[0] = propValue.value.int32Values[1]; + mockValue->timestamp = propValue.value.int64Values[0]; + mockValue->areaId = propValue.areaId; + setPropertyFromVehicle(*mockValue); + return StatusCode::OK; + } + + if (propValue.prop == kSetFloatPropertyFromVehcileForTest) { + auto mockValue = createVehiclePropValue(VehiclePropertyType::FLOAT, 1); + mockValue->prop = propValue.value.int32Values[0]; + mockValue->value.floatValues[0] = propValue.value.floatValues[0]; + mockValue->timestamp = propValue.value.int64Values[0]; + mockValue->areaId = propValue.areaId; + setPropertyFromVehicle(*mockValue); + return StatusCode::OK; + } + if (propValue.prop == kSetBooleanPropertyFromVehcileForTest) { + auto mockValue = createVehiclePropValue(VehiclePropertyType::BOOLEAN, 1); + mockValue->prop = propValue.value.int32Values[1]; + mockValue->value.int32Values[0] = propValue.value.int32Values[0]; + mockValue->timestamp = propValue.value.int64Values[0]; + mockValue->areaId = propValue.areaId; + setPropertyFromVehicle(*mockValue); + return StatusCode::OK; + } + if (propValue.prop == kGenerateFakeDataControllingProperty) { StatusCode status = handleGenerateFakeDataRequest(propValue); if (status != StatusCode::OK) { @@ -198,12 +228,19 @@ StatusCode EmulatedVehicleHal::set(const VehiclePropValue& propValue) { return StatusCode::NOT_AVAILABLE; } - if (!mPropStore->writeValue(propValue, shouldUpdateStatus)) { - return StatusCode::INVALID_ARG; + /** + * After checking all conditions, such as the property is available, a real vhal will + * sent the events to Car ECU to take actions. + * Google HAL will just add a timestamp for the value and triggle the callback to android. + */ + VehiclePropValuePtr updatedPropValue = getValuePool()->obtain(propValue); + updatedPropValue->timestamp = elapsedRealtimeNano(); + if (!mPropStore->writeValue(*updatedPropValue, shouldUpdateStatus)) { + return StatusCode::INTERNAL_ERROR; } - getEmulatorOrDie()->doSetValueFromClient(propValue); - doHalEvent(getValuePool()->obtain(propValue)); + getEmulatorOrDie()->doSetValueFromClient(*updatedPropValue); + doHalEvent(std::move(updatedPropValue)); return StatusCode::OK; } @@ -249,8 +286,8 @@ void EmulatedVehicleHal::onCreate() { // Create a separate instance for each individual zone VehiclePropValue prop = { - .prop = cfg.prop, - .areaId = curArea, + .areaId = curArea, + .prop = cfg.prop, }; if (it.initialAreaValues.size() > 0) { diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/JsonFakeValueGenerator.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/JsonFakeValueGenerator.cpp index b8fd2babee..8677f837f5 100644 --- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/JsonFakeValueGenerator.cpp +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/JsonFakeValueGenerator.cpp @@ -101,9 +101,11 @@ std::vector<VehiclePropValue> JsonFakeValueGenerator::parseFakeValueJson(std::is rawEvent.toStyledString().c_str()); continue; } - VehiclePropValue event = {.prop = rawEvent["prop"].asInt(), - .areaId = rawEvent["areaId"].asInt(), - .timestamp = rawEvent["timestamp"].asInt64()}; + VehiclePropValue event = { + .timestamp = rawEvent["timestamp"].asInt64(), + .areaId = rawEvent["areaId"].asInt(), + .prop = rawEvent["prop"].asInt(), + }; Json::Value rawEventValue = rawEvent["value"]; auto& value = event.value; diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleEmulator.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleEmulator.cpp index 356a6b9568..9dc70859c9 100644 --- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleEmulator.cpp +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleEmulator.cpp @@ -120,7 +120,10 @@ void VehicleEmulator::doGetProperty(VehicleEmulator::EmulatorMessage const& rxMs } { - VehiclePropValue request = { .prop = propId, .areaId = areaId }; + VehiclePropValue request = { + .areaId = areaId, + .prop = propId, + }; StatusCode halStatus; auto val = mHal->get(request, &halStatus); if (val != nullptr) { @@ -150,10 +153,10 @@ void VehicleEmulator::doSetProperty(VehicleEmulator::EmulatorMessage const& rxMs VehicleEmulator::EmulatorMessage& respMsg) { emulator::VehiclePropValue protoVal = rxMsg.value(0); VehiclePropValue val = { - .prop = protoVal.prop(), - .areaId = protoVal.area_id(), - .status = (VehiclePropertyStatus)protoVal.status(), - .timestamp = elapsedRealtimeNano(), + .timestamp = elapsedRealtimeNano(), + .areaId = protoVal.area_id(), + .prop = protoVal.prop(), + .status = (VehiclePropertyStatus)protoVal.status(), }; respMsg.set_msg_type(emulator::SET_PROPERTY_RESP); diff --git a/automotive/vehicle/2.0/default/tests/VehicleObjectPool_test.cpp b/automotive/vehicle/2.0/default/tests/VehicleObjectPool_test.cpp index a291351f0b..4e3ade15a3 100644 --- a/automotive/vehicle/2.0/default/tests/VehicleObjectPool_test.cpp +++ b/automotive/vehicle/2.0/default/tests/VehicleObjectPool_test.cpp @@ -57,11 +57,11 @@ public: }; TEST_F(VehicleObjectPoolTest, valuePoolBasicCorrectness) { - void* raw = valuePool->obtain(VehiclePropertyType::INT32).get(); + auto value = valuePool->obtain(VehiclePropertyType::INT32); // At this point, v1 should be recycled and the only object in the pool. - ASSERT_EQ(raw, valuePool->obtain(VehiclePropertyType::INT32).get()); + ASSERT_EQ(value.get(), valuePool->obtain(VehiclePropertyType::INT32).get()); // Obtaining value of another type - should return a new object - ASSERT_NE(raw, valuePool->obtain(VehiclePropertyType::FLOAT).get()); + ASSERT_NE(value.get(), valuePool->obtain(VehiclePropertyType::FLOAT).get()); ASSERT_EQ(3u, stats->Obtained); ASSERT_EQ(2u, stats->Created); diff --git a/automotive/vehicle/2.0/types.hal b/automotive/vehicle/2.0/types.hal index 8c84c0a17e..bc0b4d3d9e 100644 --- a/automotive/vehicle/2.0/types.hal +++ b/automotive/vehicle/2.0/types.hal @@ -1310,7 +1310,7 @@ enum VehicleProperty : int32_t { * * @change_mode VehiclePropertyChangeMode:ON_CHANGE - * @access VehiclePropertyAccess:WRITE + * @access VehiclePropertyAccess:READ_WRITE */ AP_POWER_STATE_REPORT = ( 0x0A01 @@ -2537,7 +2537,7 @@ enum VehicleApPowerStateReq : int32_t { * power controller must change power state to this state to shutdown * system. * - * int32Values[1] : one of enum_vehicle_ap_power_state_shutdown_param_type + * int32Values[1] : one of VehicleApPowerStateShutdownParam * * SHUTDOWN_PRPARE may be requested from either WAIT_FOR_VHAL or ON states. */ @@ -2569,6 +2569,11 @@ enum VehicleApPowerStateShutdownParam : int32_t { /** AP can only shutdown with postponing allowed. */ SHUTDOWN_ONLY = 3, + + /** + * AP may enter deep sleep, but must either sleep or shut down immediately. + * Postponing is not allowed. */ + SLEEP_IMMEDIATELY = 4, }; enum VehicleApPowerStateReport : int32_t { @@ -3581,4 +3586,3 @@ enum VmsAvailabilityStateIntegerValuesIndex : VmsBaseMessageIntegerValuesIndex { enum VmsPublisherInformationIntegerValuesIndex : VmsBaseMessageIntegerValuesIndex { PUBLISHER_ID = 1, }; - diff --git a/biometrics/fingerprint/2.1/vts/functional/VtsHalBiometricsFingerprintV2_1TargetTest.cpp b/biometrics/fingerprint/2.1/vts/functional/VtsHalBiometricsFingerprintV2_1TargetTest.cpp index d577ce4729..bdbf72dea2 100644 --- a/biometrics/fingerprint/2.1/vts/functional/VtsHalBiometricsFingerprintV2_1TargetTest.cpp +++ b/biometrics/fingerprint/2.1/vts/functional/VtsHalBiometricsFingerprintV2_1TargetTest.cpp @@ -16,14 +16,15 @@ #define LOG_TAG "fingerprint_hidl_hal_test" -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> #include <android-base/logging.h> #include <android-base/properties.h> #include <android/hardware/biometrics/fingerprint/2.1/IBiometricsFingerprint.h> #include <android/hardware/biometrics/fingerprint/2.1/IBiometricsFingerprintClientCallback.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> #include <hidl/HidlSupport.h> #include <hidl/HidlTransportSupport.h> +#include <hidl/ServiceManagement.h> #include <utils/Condition.h> #include <cinttypes> @@ -183,315 +184,295 @@ class RemoveCallback : public FingerprintCallbackBase { std::promise<void> promise; }; -// Test environment for Fingerprint HIDL HAL. -class FingerprintHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static FingerprintHidlEnvironment* Instance() { - static FingerprintHidlEnvironment* instance = new FingerprintHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IBiometricsFingerprint>(); } -}; - -class FingerprintHidlTest : public ::testing::VtsHalHidlTargetTestBase { - public: - virtual void SetUp() override { - mService = ::testing::VtsHalHidlTargetTestBase::getService<IBiometricsFingerprint>( - FingerprintHidlEnvironment::Instance()->getServiceName<IBiometricsFingerprint>()); - ASSERT_FALSE(mService == nullptr); - - /* - * Devices shipped from now on will instead store - * fingerprint data under /data/vendor_de/<user-id>/fpdata. - * Support for /data/vendor_de and /data/vendor_ce has been added to vold. - */ - - uint64_t api_level = GetUintProperty<uint64_t>("ro.product.first_api_level", 0); - if (api_level == 0) { - api_level = GetUintProperty<uint64_t>("ro.build.version.sdk", 0); +class FingerprintHidlTest : public ::testing::TestWithParam<std::string> { + public: + virtual void SetUp() override { + mService = IBiometricsFingerprint::getService(GetParam()); + ASSERT_FALSE(mService == nullptr); + + /* + * Devices shipped from now on will instead store + * fingerprint data under /data/vendor_de/<user-id>/fpdata. + * Support for /data/vendor_de and /data/vendor_ce has been added to vold. + */ + + uint64_t api_level = GetUintProperty<uint64_t>("ro.product.first_api_level", 0); + if (api_level == 0) { + api_level = GetUintProperty<uint64_t>("ro.build.version.sdk", 0); + } + ASSERT_TRUE(api_level != 0); + + // 27 is the API number for O-MR1 + if (api_level <= 27) { + kTmpDir = "/data/system/users/0/fpdata/"; + } else { + kTmpDir = "/data/vendor_de/0/fpdata/"; + } + + Return<RequestStatus> res = mService->setActiveGroup(kGroupId, kTmpDir); + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); } - ASSERT_TRUE(api_level != 0); - - // 27 is the API number for O-MR1 - if (api_level <= 27) { - kTmpDir = "/data/system/users/0/fpdata/"; - } else { - kTmpDir = "/data/vendor_de/0/fpdata/"; - } - - Return<RequestStatus> res = mService->setActiveGroup(kGroupId, kTmpDir); - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - } - virtual void TearDown() override {} + virtual void TearDown() override {} - sp<IBiometricsFingerprint> mService; + sp<IBiometricsFingerprint> mService; }; - // The service should be reachable. -TEST_F(FingerprintHidlTest, ConnectTest) { - sp<FingerprintCallbackBase> cb = new FingerprintCallbackBase(); - Return<uint64_t> rc = mService->setNotify(cb); - ASSERT_NE(0UL, static_cast<uint64_t>(rc)); +TEST_P(FingerprintHidlTest, ConnectTest) { + sp<FingerprintCallbackBase> cb = new FingerprintCallbackBase(); + Return<uint64_t> rc = mService->setNotify(cb); + ASSERT_NE(0UL, static_cast<uint64_t>(rc)); } // Starting the service with null callback should succeed. -TEST_F(FingerprintHidlTest, ConnectNullTest) { - Return<uint64_t> rc = mService->setNotify(NULL); - ASSERT_NE(0UL, static_cast<uint64_t>(rc)); +TEST_P(FingerprintHidlTest, ConnectNullTest) { + Return<uint64_t> rc = mService->setNotify(NULL); + ASSERT_NE(0UL, static_cast<uint64_t>(rc)); } // Pre-enroll should always return unique, cryptographically secure, non-zero number -TEST_F(FingerprintHidlTest, PreEnrollTest) { - std::map<uint64_t, uint64_t> m; - - for(unsigned int i = 0; i < kIterations; ++i) { - uint64_t res = static_cast<uint64_t>(mService->preEnroll()); - EXPECT_NE(0UL, res); - m[res]++; - EXPECT_EQ(1UL, m[res]); - } +TEST_P(FingerprintHidlTest, PreEnrollTest) { + std::map<uint64_t, uint64_t> m; + + for (unsigned int i = 0; i < kIterations; ++i) { + uint64_t res = static_cast<uint64_t>(mService->preEnroll()); + EXPECT_NE(0UL, res); + m[res]++; + EXPECT_EQ(1UL, m[res]); + } } // Enroll with an invalid (all zeroes) HAT should fail. -TEST_F(FingerprintHidlTest, EnrollInvalidHatTest) { - sp<ErrorCallback> cb = new ErrorCallback(); - Return<uint64_t> rc = mService->setNotify(cb); - ASSERT_NE(0UL, static_cast<uint64_t>(rc)); - - uint8_t token[69]; - for(int i=0; i<69; i++) { - token[i] = 0; - } +TEST_P(FingerprintHidlTest, EnrollInvalidHatTest) { + sp<ErrorCallback> cb = new ErrorCallback(); + Return<uint64_t> rc = mService->setNotify(cb); + ASSERT_NE(0UL, static_cast<uint64_t>(rc)); + + uint8_t token[69]; + for (int i = 0; i < 69; i++) { + token[i] = 0; + } - Return<RequestStatus> res = mService->enroll(token, kGroupId, kTimeout); - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); + Return<RequestStatus> res = mService->enroll(token, kGroupId, kTimeout); + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - // At least one call to onError should occur - ASSERT_TRUE(waitForCallback(cb->promise.get_future())); - ASSERT_NE(FingerprintError::ERROR_NO_ERROR, cb->error); + // At least one call to onError should occur + ASSERT_TRUE(waitForCallback(cb->promise.get_future())); + ASSERT_NE(FingerprintError::ERROR_NO_ERROR, cb->error); } // Enroll with an invalid (null) HAT should fail. -TEST_F(FingerprintHidlTest, EnrollNullTest) { - sp<ErrorCallback> cb = new ErrorCallback(); - Return<uint64_t> rc = mService->setNotify(cb); - ASSERT_NE(0UL, static_cast<uint64_t>(rc)); - - uint8_t token[69]; - Return<RequestStatus> res = mService->enroll(token, kGroupId, kTimeout); - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - - // At least one call to onError should occur - ASSERT_TRUE(waitForCallback(cb->promise.get_future())); - ASSERT_NE(FingerprintError::ERROR_NO_ERROR, cb->error); +TEST_P(FingerprintHidlTest, EnrollNullTest) { + sp<ErrorCallback> cb = new ErrorCallback(); + Return<uint64_t> rc = mService->setNotify(cb); + ASSERT_NE(0UL, static_cast<uint64_t>(rc)); + + uint8_t token[69]; + Return<RequestStatus> res = mService->enroll(token, kGroupId, kTimeout); + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); + + // At least one call to onError should occur + ASSERT_TRUE(waitForCallback(cb->promise.get_future())); + ASSERT_NE(FingerprintError::ERROR_NO_ERROR, cb->error); } // PostEnroll should always return within 3s -TEST_F(FingerprintHidlTest, PostEnrollTest) { - sp<FingerprintCallbackBase> cb = new FingerprintCallbackBase(); - Return<uint64_t> rc = mService->setNotify(cb); - - auto start = std::chrono::system_clock::now(); - Return<RequestStatus> res = mService->postEnroll(); - auto elapsed = std::chrono::system_clock::now() - start; - ASSERT_GE(kTimeoutInSeconds, elapsed); +TEST_P(FingerprintHidlTest, PostEnrollTest) { + sp<FingerprintCallbackBase> cb = new FingerprintCallbackBase(); + Return<uint64_t> rc = mService->setNotify(cb); + + auto start = std::chrono::system_clock::now(); + Return<RequestStatus> res = mService->postEnroll(); + auto elapsed = std::chrono::system_clock::now() - start; + ASSERT_GE(kTimeoutInSeconds, elapsed); } // getAuthenticatorId should always return non-zero numbers -TEST_F(FingerprintHidlTest, GetAuthenticatorIdTest) { - Return<uint64_t> res = mService->getAuthenticatorId(); - EXPECT_NE(0UL, static_cast<uint64_t>(res)); +TEST_P(FingerprintHidlTest, GetAuthenticatorIdTest) { + Return<uint64_t> res = mService->getAuthenticatorId(); + EXPECT_NE(0UL, static_cast<uint64_t>(res)); } // Enumerate should always trigger onEnumerated(fid=0, rem=0) when there are no fingerprints -TEST_F(FingerprintHidlTest, EnumerateTest) { - sp<EnumerateCallback> cb = new EnumerateCallback(); - Return<uint64_t> rc = mService->setNotify(cb); - ASSERT_NE(0UL, static_cast<uint64_t>(rc)); - - // Callback will return when rem=0 is found - Return<RequestStatus> res = mService->enumerate(); - ASSERT_TRUE(waitForCallback(cb->promise.get_future())); - EXPECT_EQ(0UL, cb->fingerId); - EXPECT_EQ(0UL, cb->remaining); - +TEST_P(FingerprintHidlTest, EnumerateTest) { + sp<EnumerateCallback> cb = new EnumerateCallback(); + Return<uint64_t> rc = mService->setNotify(cb); + ASSERT_NE(0UL, static_cast<uint64_t>(rc)); + + // Callback will return when rem=0 is found + Return<RequestStatus> res = mService->enumerate(); + ASSERT_TRUE(waitForCallback(cb->promise.get_future())); + EXPECT_EQ(0UL, cb->fingerId); + EXPECT_EQ(0UL, cb->remaining); } // Remove should succeed on any inputs // At least one callback with "remaining=0" should occur -TEST_F(FingerprintHidlTest, RemoveFingerprintTest) { - // Register callback - sp<RemoveCallback> cb = new RemoveCallback(kGroupId); - Return<uint64_t> rc = mService->setNotify(cb); - ASSERT_NE(0UL, static_cast<uint64_t>(rc)); - - // Remove a fingerprint - Return<RequestStatus> res = mService->remove(kGroupId, 1); - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - - // At least one call to onRemove with remaining=0 should occur - ASSERT_TRUE(waitForCallback(cb->promise.get_future())); +TEST_P(FingerprintHidlTest, RemoveFingerprintTest) { + // Register callback + sp<RemoveCallback> cb = new RemoveCallback(kGroupId); + Return<uint64_t> rc = mService->setNotify(cb); + ASSERT_NE(0UL, static_cast<uint64_t>(rc)); + + // Remove a fingerprint + Return<RequestStatus> res = mService->remove(kGroupId, 1); + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); + + // At least one call to onRemove with remaining=0 should occur + ASSERT_TRUE(waitForCallback(cb->promise.get_future())); } // Remove should accept 0 to delete all fingerprints // At least one callback with "remaining=0" should occur. -TEST_F(FingerprintHidlTest, RemoveAllFingerprintsTest) { - // Register callback - sp<RemoveCallback> cb = new RemoveCallback(kGroupId); - Return<uint64_t> rc = mService->setNotify(cb); - ASSERT_NE(0UL, static_cast<uint64_t>(rc)); - - // Remove all fingerprints - Return<RequestStatus> res = mService->remove(kGroupId, 0); - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - ASSERT_TRUE(waitForCallback(cb->promise.get_future())); +TEST_P(FingerprintHidlTest, RemoveAllFingerprintsTest) { + // Register callback + sp<RemoveCallback> cb = new RemoveCallback(kGroupId); + Return<uint64_t> rc = mService->setNotify(cb); + ASSERT_NE(0UL, static_cast<uint64_t>(rc)); + + // Remove all fingerprints + Return<RequestStatus> res = mService->remove(kGroupId, 0); + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); + ASSERT_TRUE(waitForCallback(cb->promise.get_future())); } // Active group should successfully set to a writable location. -TEST_F(FingerprintHidlTest, SetActiveGroupTest) { - // Create an active group - Return<RequestStatus> res = mService->setActiveGroup(2, kTmpDir); - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - - // Reset active group - res = mService->setActiveGroup(kGroupId, kTmpDir); - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); +TEST_P(FingerprintHidlTest, SetActiveGroupTest) { + // Create an active group + Return<RequestStatus> res = mService->setActiveGroup(2, kTmpDir); + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); + + // Reset active group + res = mService->setActiveGroup(kGroupId, kTmpDir); + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); } // Active group should fail to set to an unwritable location. -TEST_F(FingerprintHidlTest, SetActiveGroupUnwritableTest) { - // Create an active group to an unwritable location (device root dir) - Return<RequestStatus> res = mService->setActiveGroup(3, "/"); - ASSERT_NE(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - - // Reset active group - res = mService->setActiveGroup(kGroupId, kTmpDir); - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); +TEST_P(FingerprintHidlTest, SetActiveGroupUnwritableTest) { + // Create an active group to an unwritable location (device root dir) + Return<RequestStatus> res = mService->setActiveGroup(3, "/"); + ASSERT_NE(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); + + // Reset active group + res = mService->setActiveGroup(kGroupId, kTmpDir); + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); } // Active group should fail to set to a null location. -TEST_F(FingerprintHidlTest, SetActiveGroupNullTest) { - // Create an active group to a null location. - Return<RequestStatus> res = mService->setActiveGroup(4, nullptr); - ASSERT_NE(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - - // Reset active group - res = mService->setActiveGroup(kGroupId, kTmpDir); - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); +TEST_P(FingerprintHidlTest, SetActiveGroupNullTest) { + // Create an active group to a null location. + Return<RequestStatus> res = mService->setActiveGroup(4, nullptr); + ASSERT_NE(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); + + // Reset active group + res = mService->setActiveGroup(kGroupId, kTmpDir); + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); } // Cancel should always return ERROR_CANCELED from any starting state including // the IDLE state. -TEST_F(FingerprintHidlTest, CancelTest) { - sp<ErrorCallback> cb = new ErrorCallback(true, FingerprintError::ERROR_CANCELED); - Return<uint64_t> rc = mService->setNotify(cb); - ASSERT_NE(0UL, static_cast<uint64_t>(rc)); - - Return<RequestStatus> res = mService->cancel(); - // check that we were able to make an IPC request successfully - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - - // make sure callback was invoked within kTimeoutInSeconds - ASSERT_TRUE(waitForCallback(cb->promise.get_future())); - // check error should be ERROR_CANCELED - ASSERT_EQ(FingerprintError::ERROR_CANCELED, cb->error); +TEST_P(FingerprintHidlTest, CancelTest) { + sp<ErrorCallback> cb = new ErrorCallback(true, FingerprintError::ERROR_CANCELED); + Return<uint64_t> rc = mService->setNotify(cb); + ASSERT_NE(0UL, static_cast<uint64_t>(rc)); + + Return<RequestStatus> res = mService->cancel(); + // check that we were able to make an IPC request successfully + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); + + // make sure callback was invoked within kTimeoutInSeconds + ASSERT_TRUE(waitForCallback(cb->promise.get_future())); + // check error should be ERROR_CANCELED + ASSERT_EQ(FingerprintError::ERROR_CANCELED, cb->error); } // A call to cancel should succeed during enroll. -TEST_F(FingerprintHidlTest, CancelEnrollTest) { - Return<RequestStatus> res = mService->setActiveGroup(kGroupId, kTmpDir); - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); +TEST_P(FingerprintHidlTest, CancelEnrollTest) { + Return<RequestStatus> res = mService->setActiveGroup(kGroupId, kTmpDir); + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - sp<ErrorCallback> cb = new ErrorCallback(true, FingerprintError::ERROR_CANCELED); - Return<uint64_t> rc = mService->setNotify(cb); - ASSERT_NE(0U, static_cast<uint64_t>(rc)); + sp<ErrorCallback> cb = new ErrorCallback(true, FingerprintError::ERROR_CANCELED); + Return<uint64_t> rc = mService->setNotify(cb); + ASSERT_NE(0U, static_cast<uint64_t>(rc)); - uint8_t token[69]; - res = mService->enroll(token, kGroupId, kTimeout); - // check that we were able to make an IPC request successfully - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); + uint8_t token[69]; + res = mService->enroll(token, kGroupId, kTimeout); + // check that we were able to make an IPC request successfully + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - res = mService->cancel(); - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); + res = mService->cancel(); + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - // make sure callback was invoked within kTimeoutInSeconds - ASSERT_TRUE(waitForCallback(cb->promise.get_future())); + // make sure callback was invoked within kTimeoutInSeconds + ASSERT_TRUE(waitForCallback(cb->promise.get_future())); - // check error should be ERROR_CANCELED - ASSERT_EQ(FingerprintError::ERROR_CANCELED, cb->error); + // check error should be ERROR_CANCELED + ASSERT_EQ(FingerprintError::ERROR_CANCELED, cb->error); } // A call to cancel should succeed during authentication. -TEST_F(FingerprintHidlTest, CancelAuthTest) { - sp<ErrorCallback> cb = new ErrorCallback(true, FingerprintError::ERROR_CANCELED); - Return<uint64_t> rc = mService->setNotify(cb); - ASSERT_NE(0U, static_cast<uint64_t>(rc)); +TEST_P(FingerprintHidlTest, CancelAuthTest) { + sp<ErrorCallback> cb = new ErrorCallback(true, FingerprintError::ERROR_CANCELED); + Return<uint64_t> rc = mService->setNotify(cb); + ASSERT_NE(0U, static_cast<uint64_t>(rc)); - Return<RequestStatus> res = mService->authenticate(0, kGroupId); - // check that we were able to make an IPC request successfully - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); + Return<RequestStatus> res = mService->authenticate(0, kGroupId); + // check that we were able to make an IPC request successfully + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - res = mService->cancel(); - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); + res = mService->cancel(); + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - // make sure callback was invoked within kTimeoutInSeconds - ASSERT_TRUE(waitForCallback(cb->promise.get_future())); + // make sure callback was invoked within kTimeoutInSeconds + ASSERT_TRUE(waitForCallback(cb->promise.get_future())); - // check error should be ERROR_CANCELED - ASSERT_EQ(FingerprintError::ERROR_CANCELED, cb->error); + // check error should be ERROR_CANCELED + ASSERT_EQ(FingerprintError::ERROR_CANCELED, cb->error); } // A call to cancel should succeed during authentication. -TEST_F(FingerprintHidlTest, CancelRemoveTest) { - sp<ErrorCallback> cb = new ErrorCallback(true, FingerprintError::ERROR_CANCELED); - Return<uint64_t> rc = mService->setNotify(cb); - ASSERT_NE(0U, static_cast<uint64_t>(rc)); +TEST_P(FingerprintHidlTest, CancelRemoveTest) { + sp<ErrorCallback> cb = new ErrorCallback(true, FingerprintError::ERROR_CANCELED); + Return<uint64_t> rc = mService->setNotify(cb); + ASSERT_NE(0U, static_cast<uint64_t>(rc)); - // Remove a fingerprint - Return<RequestStatus> res = mService->remove(kGroupId, 1); - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); + // Remove a fingerprint + Return<RequestStatus> res = mService->remove(kGroupId, 1); + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - res = mService->cancel(); - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); + res = mService->cancel(); + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - // make sure callback was invoked within kTimeoutInSeconds - ASSERT_TRUE(waitForCallback(cb->promise.get_future())); + // make sure callback was invoked within kTimeoutInSeconds + ASSERT_TRUE(waitForCallback(cb->promise.get_future())); - // check error should be ERROR_CANCELED - ASSERT_EQ(FingerprintError::ERROR_CANCELED, cb->error); + // check error should be ERROR_CANCELED + ASSERT_EQ(FingerprintError::ERROR_CANCELED, cb->error); } // A call to cancel should succeed during authentication. -TEST_F(FingerprintHidlTest, CancelRemoveAllTest) { - sp<ErrorCallback> cb = new ErrorCallback(true, FingerprintError::ERROR_CANCELED); - Return<uint64_t> rc = mService->setNotify(cb); - ASSERT_NE(0U, static_cast<uint64_t>(rc)); +TEST_P(FingerprintHidlTest, CancelRemoveAllTest) { + sp<ErrorCallback> cb = new ErrorCallback(true, FingerprintError::ERROR_CANCELED); + Return<uint64_t> rc = mService->setNotify(cb); + ASSERT_NE(0U, static_cast<uint64_t>(rc)); - // Remove a fingerprint - Return<RequestStatus> res = mService->remove(kGroupId, 0); - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); + // Remove a fingerprint + Return<RequestStatus> res = mService->remove(kGroupId, 0); + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - res = mService->cancel(); - ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); + res = mService->cancel(); + ASSERT_EQ(RequestStatus::SYS_OK, static_cast<RequestStatus>(res)); - // make sure callback was invoked within kTimeoutInSeconds - ASSERT_TRUE(waitForCallback(cb->promise.get_future())); + // make sure callback was invoked within kTimeoutInSeconds + ASSERT_TRUE(waitForCallback(cb->promise.get_future())); - // check error should be ERROR_CANCELED - ASSERT_EQ(FingerprintError::ERROR_CANCELED, cb->error); + // check error should be ERROR_CANCELED + ASSERT_EQ(FingerprintError::ERROR_CANCELED, cb->error); } } // anonymous namespace -int main(int argc, char **argv) { - ::testing::AddGlobalTestEnvironment(FingerprintHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - FingerprintHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - LOG(INFO) << "Test result = " << status; - return status; -} - +INSTANTIATE_TEST_SUITE_P(PerInstance, FingerprintHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames( + IBiometricsFingerprint::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSession.h b/bluetooth/audio/2.0/default/session/BluetoothAudioSession.h index 85e8742d07..838d1ccad0 100644 --- a/bluetooth/audio/2.0/default/session/BluetoothAudioSession.h +++ b/bluetooth/audio/2.0/default/session/BluetoothAudioSession.h @@ -156,8 +156,9 @@ class BluetoothAudioSession { static constexpr PcmParameters kInvalidPcmParameters = { .sampleRate = SampleRate::RATE_UNKNOWN, + .channelMode = ChannelMode::UNKNOWN, .bitsPerSample = BitsPerSample::BITS_UNKNOWN, - .channelMode = ChannelMode::UNKNOWN}; + }; // can't be constexpr because of non-literal type static const CodecConfiguration kInvalidCodecConfiguration; diff --git a/bluetooth/audio/2.0/default/session/BluetoothAudioSupportedCodecsDB.cpp b/bluetooth/audio/2.0/default/session/BluetoothAudioSupportedCodecsDB.cpp index 292e28b3b2..6ea61e1f18 100644 --- a/bluetooth/audio/2.0/default/session/BluetoothAudioSupportedCodecsDB.cpp +++ b/bluetooth/audio/2.0/default/session/BluetoothAudioSupportedCodecsDB.cpp @@ -94,16 +94,18 @@ static const LdacParameters kDefaultOffloadLdacCapability = { static const AptxParameters kDefaultOffloadAptxCapability = { .sampleRate = static_cast<SampleRate>(SampleRate::RATE_44100 | SampleRate::RATE_48000), + .channelMode = ChannelMode::STEREO, .bitsPerSample = BitsPerSample::BITS_16, - .channelMode = ChannelMode::STEREO}; +}; // aptX HD: mSampleRate:(44100|48000), mBitsPerSample:(24), // mChannelMode:(STEREO) static const AptxParameters kDefaultOffloadAptxHdCapability = { .sampleRate = static_cast<SampleRate>(SampleRate::RATE_44100 | SampleRate::RATE_48000), + .channelMode = ChannelMode::STEREO, .bitsPerSample = BitsPerSample::BITS_24, - .channelMode = ChannelMode::STEREO}; +}; const std::vector<CodecCapabilities> kDefaultOffloadA2dpCodecCapabilities = { {.codecType = CodecType::SBC, .capabilities = {}}, diff --git a/boot/1.1/Android.bp b/boot/1.1/Android.bp new file mode 100644 index 0000000000..6a8d57aa8c --- /dev/null +++ b/boot/1.1/Android.bp @@ -0,0 +1,18 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.boot@1.1", + root: "android.hardware", + vndk: { + enabled: true, + }, + srcs: [ + "types.hal", + "IBootControl.hal", + ], + interfaces: [ + "android.hardware.boot@1.0", + "android.hidl.base@1.0", + ], + gen_java: true, +} diff --git a/boot/1.1/IBootControl.hal b/boot/1.1/IBootControl.hal new file mode 100644 index 0000000000..939dfb3d74 --- /dev/null +++ b/boot/1.1/IBootControl.hal @@ -0,0 +1,66 @@ +/* + * Copyright 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.hardware.boot@1.1; + +import @1.0::IBootControl; + +interface IBootControl extends @1.0::IBootControl { + /** + * Sets whether a snapshot-merge of any dynamic partition is in progress. + * + * After the merge status is set to a given value, subsequent calls to + * getSnapshotMergeStatus must return the set value. + * + * The merge status must be persistent across reboots. That is, getSnapshotMergeStatus + * must return the same value after a reboot if the merge status is not altered in any way + * (e.g. set by setSnapshotMergeStatus or set to CANCELLED by bootloader). + * + * Read/write access to the merge status must be atomic. When the HAL is processing a + * setSnapshotMergeStatus call, all subsequent calls to getSnapshotMergeStatus must block until + * setSnapshotMergeStatus has returned. + * + * A MERGING state indicates that dynamic partitions are partially comprised by blocks in the + * userdata partition. + * + * When the merge status is set to MERGING, the following operations must be prohibited from the + * bootloader: + * - Flashing or erasing "userdata" or "metadata". + * + * The following operations may be prohibited when the status is set to MERGING. If not + * prohibited, it is recommended that the user receive a warning. + * - Changing the active slot (e.g. via "fastboot set_active") + * + * @param status Merge status. + * + * @return success True on success, false otherwise. + */ + setSnapshotMergeStatus(MergeStatus status) generates (bool success); + + /** + * Returns whether a snapshot-merge of any dynamic partition is in progress. + * + * This function must return the merge status set by the last setSnapshotMergeStatus call and + * recorded by the bootloader with one exception. If the partitions are being flashed from the + * bootloader such that the pending merge must be canceled (for example, if the super partition + * is being flashed), this function must return CANCELLED. + * + * @return success True if the merge status is read successfully, false otherwise. + * @return status Merge status. + */ + getSnapshotMergeStatus() generates (MergeStatus status); +}; + diff --git a/boot/1.1/default/Android.bp b/boot/1.1/default/Android.bp new file mode 100644 index 0000000000..abf1bf9530 --- /dev/null +++ b/boot/1.1/default/Android.bp @@ -0,0 +1,48 @@ +cc_library_shared { + name: "android.hardware.boot@1.1-impl", + stem: "android.hardware.boot@1.0-impl-1.1", + defaults: [ + "hidl_defaults", + "libboot_control_defaults", + ], + relative_install_path: "hw", + vendor: true, + recovery_available: true, + srcs: ["BootControl.cpp"], + + shared_libs: [ + "liblog", + "libhidlbase", + "libhardware", + "libutils", + "android.hardware.boot@1.0", + "android.hardware.boot@1.1", + ], + static_libs: [ + "libboot_control", + "libfstab", + ], +} + +cc_binary { + name: "android.hardware.boot@1.1-service", + defaults: ["hidl_defaults"], + relative_install_path: "hw", + vendor: true, + init_rc: ["android.hardware.boot@1.1-service.rc"], + srcs: ["service.cpp"], + + vintf_fragments: [ + "android.hardware.boot@1.1.xml", + ], + + shared_libs: [ + "liblog", + "libhardware", + "libhidlbase", + "libutils", + "android.hardware.boot@1.0", + "android.hardware.boot@1.1", + ], + +} diff --git a/boot/1.1/default/BootControl.cpp b/boot/1.1/default/BootControl.cpp new file mode 100644 index 0000000000..c9c62a43d0 --- /dev/null +++ b/boot/1.1/default/BootControl.cpp @@ -0,0 +1,129 @@ +/* + * 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. + */ +#define LOG_TAG "android.hardware.boot@1.1-impl" + +#include <memory> + +#include <log/log.h> + +#include "BootControl.h" + +namespace android { +namespace hardware { +namespace boot { +namespace V1_1 { +namespace implementation { + +using ::android::hardware::boot::V1_0::CommandResult; + +bool BootControl::Init() { + return impl_.Init(); +} + +// Methods from ::android::hardware::boot::V1_0::IBootControl follow. +Return<uint32_t> BootControl::getNumberSlots() { + return impl_.GetNumberSlots(); +} + +Return<uint32_t> BootControl::getCurrentSlot() { + return impl_.GetCurrentSlot(); +} + +Return<void> BootControl::markBootSuccessful(markBootSuccessful_cb _hidl_cb) { + struct CommandResult cr; + if (impl_.MarkBootSuccessful()) { + cr.success = true; + cr.errMsg = "Success"; + } else { + cr.success = false; + cr.errMsg = "Operation failed"; + } + _hidl_cb(cr); + return Void(); +} + +Return<void> BootControl::setActiveBootSlot(uint32_t slot, setActiveBootSlot_cb _hidl_cb) { + struct CommandResult cr; + if (impl_.SetActiveBootSlot(slot)) { + cr.success = true; + cr.errMsg = "Success"; + } else { + cr.success = false; + cr.errMsg = "Operation failed"; + } + _hidl_cb(cr); + return Void(); +} + +Return<void> BootControl::setSlotAsUnbootable(uint32_t slot, setSlotAsUnbootable_cb _hidl_cb) { + struct CommandResult cr; + if (impl_.SetSlotAsUnbootable(slot)) { + cr.success = true; + cr.errMsg = "Success"; + } else { + cr.success = false; + cr.errMsg = "Operation failed"; + } + _hidl_cb(cr); + return Void(); +} + +Return<BoolResult> BootControl::isSlotBootable(uint32_t slot) { + if (!impl_.IsValidSlot(slot)) { + return BoolResult::INVALID_SLOT; + } + return impl_.IsSlotBootable(slot) ? BoolResult::TRUE : BoolResult::FALSE; +} + +Return<BoolResult> BootControl::isSlotMarkedSuccessful(uint32_t slot) { + if (!impl_.IsValidSlot(slot)) { + return BoolResult::INVALID_SLOT; + } + return impl_.IsSlotMarkedSuccessful(slot) ? BoolResult::TRUE : BoolResult::FALSE; +} + +Return<void> BootControl::getSuffix(uint32_t slot, getSuffix_cb _hidl_cb) { + hidl_string ans; + const char* suffix = impl_.GetSuffix(slot); + if (suffix) { + ans = suffix; + } + _hidl_cb(ans); + return Void(); +} + +Return<bool> BootControl::setSnapshotMergeStatus(MergeStatus status) { + return impl_.SetSnapshotMergeStatus(status); +} + +Return<MergeStatus> BootControl::getSnapshotMergeStatus() { + return impl_.GetSnapshotMergeStatus(); +} + +IBootControl* HIDL_FETCH_IBootControl(const char* /* hal */) { + auto module = std::make_unique<BootControl>(); + if (!module->Init()) { + ALOGE("Could not initialize BootControl module"); + return nullptr; + } + return module.release(); +} + +} // namespace implementation +} // namespace V1_1 +} // namespace boot +} // namespace hardware +} // namespace android diff --git a/boot/1.1/default/BootControl.h b/boot/1.1/default/BootControl.h new file mode 100644 index 0000000000..75511b638f --- /dev/null +++ b/boot/1.1/default/BootControl.h @@ -0,0 +1,64 @@ +/* + * 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. + */ + +#pragma once + +#include <android/hardware/boot/1.1/IBootControl.h> +#include <hidl/MQDescriptor.h> +#include <hidl/Status.h> +#include <libboot_control/libboot_control.h> + +namespace android { +namespace hardware { +namespace boot { +namespace V1_1 { +namespace implementation { + +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::boot::V1_0::BoolResult; +using ::android::hardware::boot::V1_1::IBootControl; +using ::android::hardware::boot::V1_1::MergeStatus; + +class BootControl : public IBootControl { + public: + bool Init(); + + // Methods from ::android::hardware::boot::V1_0::IBootControl follow. + Return<uint32_t> getNumberSlots() override; + Return<uint32_t> getCurrentSlot() override; + Return<void> markBootSuccessful(markBootSuccessful_cb _hidl_cb) override; + Return<void> setActiveBootSlot(uint32_t slot, setActiveBootSlot_cb _hidl_cb) override; + Return<void> setSlotAsUnbootable(uint32_t slot, setSlotAsUnbootable_cb _hidl_cb) override; + Return<BoolResult> isSlotBootable(uint32_t slot) override; + Return<BoolResult> isSlotMarkedSuccessful(uint32_t slot) override; + Return<void> getSuffix(uint32_t slot, getSuffix_cb _hidl_cb) override; + + // Methods from ::android::hardware::boot::V1_1::IBootControl follow. + Return<bool> setSnapshotMergeStatus(MergeStatus status) override; + Return<MergeStatus> getSnapshotMergeStatus() override; + + private: + android::bootable::BootControl impl_; +}; + +extern "C" IBootControl* HIDL_FETCH_IBootControl(const char* name); + +} // namespace implementation +} // namespace V1_1 +} // namespace boot +} // namespace hardware +} // namespace android diff --git a/boot/1.1/default/android.hardware.boot@1.1-service.rc b/boot/1.1/default/android.hardware.boot@1.1-service.rc new file mode 100644 index 0000000000..83fa9d0212 --- /dev/null +++ b/boot/1.1/default/android.hardware.boot@1.1-service.rc @@ -0,0 +1,6 @@ +service vendor.boot-hal-1-1 /vendor/bin/hw/android.hardware.boot@1.1-service + interface android.hardware.boot@1.0::IBootControl default + interface android.hardware.boot@1.1::IBootControl default + class early_hal + user root + group root diff --git a/boot/1.1/default/android.hardware.boot@1.1.xml b/boot/1.1/default/android.hardware.boot@1.1.xml new file mode 100644 index 0000000000..83d5d2e8de --- /dev/null +++ b/boot/1.1/default/android.hardware.boot@1.1.xml @@ -0,0 +1,7 @@ +<manifest version="1.0" type="device"> + <hal format="hidl"> + <name>android.hardware.boot</name> + <transport>hwbinder</transport> + <fqname>@1.1::IBootControl/default</fqname> + </hal> +</manifest> diff --git a/boot/1.1/default/service.cpp b/boot/1.1/default/service.cpp new file mode 100644 index 0000000000..93eaedab8d --- /dev/null +++ b/boot/1.1/default/service.cpp @@ -0,0 +1,26 @@ +/* + * 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. + */ +#define LOG_TAG "android.hardware.boot@1.1-service" + +#include <android/hardware/boot/1.0/IBootControl.h> +#include <hidl/LegacySupport.h> + +using android::hardware::defaultPassthroughServiceImplementation; +using ::android::hardware::boot::V1_0::IBootControl; + +int main(int /* argc */, char* /* argv */[]) { + return defaultPassthroughServiceImplementation<IBootControl>(); +} diff --git a/boot/1.1/types.hal b/boot/1.1/types.hal new file mode 100644 index 0000000000..6346078c81 --- /dev/null +++ b/boot/1.1/types.hal @@ -0,0 +1,44 @@ +/* + * Copyright 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.hardware.boot@1.1; + +enum MergeStatus : int32_t { + /** + * No snapshot or merge is in progress. + */ + NONE = 0, + + /** + * The merge status could not be determined. + */ + UNKNOWN, + + /** + * Partitions are being snapshotted, but no merge has been started. + */ + SNAPSHOTTED, + + /** + * At least one partition has merge is in progress. + */ + MERGING, + + /** + * A merge was in progress, but it was canceled by the bootloader. + */ + CANCELLED, +}; diff --git a/boot/1.1/vts/functional/Android.bp b/boot/1.1/vts/functional/Android.bp new file mode 100644 index 0000000000..49ea09a15e --- /dev/null +++ b/boot/1.1/vts/functional/Android.bp @@ -0,0 +1,28 @@ +// +// 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. +// + +cc_test { + name: "VtsHalBootV1_1TargetTest", + defaults: ["VtsHalTargetTestDefaults"], + srcs: ["VtsHalBootV1_1TargetTest.cpp"], + static_libs: [ + "android.hardware.boot@1.0", + "android.hardware.boot@1.1", + "libgmock", + ], + test_suites: ["device-tests"], +} + diff --git a/boot/1.1/vts/functional/VtsHalBootV1_1TargetTest.cpp b/boot/1.1/vts/functional/VtsHalBootV1_1TargetTest.cpp new file mode 100644 index 0000000000..fba9a5ec0c --- /dev/null +++ b/boot/1.1/vts/functional/VtsHalBootV1_1TargetTest.cpp @@ -0,0 +1,86 @@ +/* + * 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. + */ + +#define LOG_TAG "boot_hidl_hal_test" + +#include <vector> + +#include <android-base/logging.h> +#include <android/hardware/boot/1.1/IBootControl.h> +#include <android/hardware/boot/1.1/types.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> + +#include <unistd.h> + +using ::android::sp; +using ::android::hardware::hidl_enum_range; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::boot::V1_1::IBootControl; +using ::android::hardware::boot::V1_1::MergeStatus; +using ::testing::Contains; + +class BootHidlTest : public testing::TestWithParam<std::string> { + public: + virtual void SetUp() override { + boot = IBootControl::getService(GetParam()); + ASSERT_NE(boot, nullptr); + + LOG(INFO) << "Test is remote " << boot->isRemote(); + } + + sp<IBootControl> boot; +}; + +static std::vector<MergeStatus> ValidMergeStatusValues() { + std::vector<MergeStatus> values; + for (const auto value : hidl_enum_range<MergeStatus>()) { + if (value == MergeStatus::UNKNOWN) { + continue; + } + values.push_back(value); + } + return values; +} + +/** + * Ensure merge status can be retrieved. + */ +TEST_P(BootHidlTest, GetSnapshotMergeStatus) { + auto values = ValidMergeStatusValues(); + auto status = (MergeStatus)boot->getSnapshotMergeStatus(); + EXPECT_THAT(values, Contains(status)); +} + +/** + * Ensure merge status can be set to arbitrary value. + */ +TEST_P(BootHidlTest, SetSnapshotMergeStatus) { + for (const auto value : ValidMergeStatusValues()) { + EXPECT_TRUE(boot->setSnapshotMergeStatus(value).withDefault(false)); + auto status = boot->getSnapshotMergeStatus(); + EXPECT_EQ(status, value); + } +} + +INSTANTIATE_TEST_SUITE_P( + , BootHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IBootControl::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/camera/device/3.4/default/ExternalCameraDevice.cpp b/camera/device/3.4/default/ExternalCameraDevice.cpp index 9a2fddff88..f518a155a7 100644 --- a/camera/device/3.4/default/ExternalCameraDevice.cpp +++ b/camera/device/3.4/default/ExternalCameraDevice.cpp @@ -754,11 +754,11 @@ void ExternalCameraDevice::getFrameRateList( int fd, double fpsUpperBound, SupportedV4L2Format* format) { format->frameRates.clear(); - v4l2_frmivalenum frameInterval { - .pixel_format = format->fourcc, - .width = format->width, - .height = format->height, - .index = 0 + v4l2_frmivalenum frameInterval{ + .index = 0, + .pixel_format = format->fourcc, + .width = format->width, + .height = format->height, }; for (frameInterval.index = 0; diff --git a/camera/device/3.4/default/ExternalCameraDeviceSession.cpp b/camera/device/3.4/default/ExternalCameraDeviceSession.cpp index dc5579ac67..9ff0d74687 100644 --- a/camera/device/3.4/default/ExternalCameraDeviceSession.cpp +++ b/camera/device/3.4/default/ExternalCameraDeviceSession.cpp @@ -2412,9 +2412,7 @@ int ExternalCameraDeviceSession::configureV4l2StreamLocked( mV4L2BufferCount = req_buffers.count; for (uint32_t i = 0; i < req_buffers.count; i++) { v4l2_buffer buffer = { - .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, - .index = i, - .memory = V4L2_MEMORY_MMAP}; + .index = i, .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, .memory = V4L2_MEMORY_MMAP}; if (TEMP_FAILURE_RETRY(ioctl(mV4l2Fd.get(), VIDIOC_QUERYBUF, &buffer)) < 0) { ALOGE("%s: QUERYBUF %d failed: %s", __FUNCTION__, i, strerror(errno)); diff --git a/cas/1.0/vts/functional/Android.bp b/cas/1.0/vts/functional/Android.bp index 622baa5988..ab39c0e93b 100644 --- a/cas/1.0/vts/functional/Android.bp +++ b/cas/1.0/vts/functional/Android.bp @@ -29,6 +29,6 @@ cc_test { shared_libs: [ "libbinder", ], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/cas/1.0/vts/functional/VtsHalCasV1_0TargetTest.cpp b/cas/1.0/vts/functional/VtsHalCasV1_0TargetTest.cpp index f0bba57137..0f16de5e90 100644 --- a/cas/1.0/vts/functional/VtsHalCasV1_0TargetTest.cpp +++ b/cas/1.0/vts/functional/VtsHalCasV1_0TargetTest.cpp @@ -16,8 +16,6 @@ #define LOG_TAG "mediacas_hidl_hal_test" -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> #include <android-base/logging.h> #include <android/hardware/cas/1.0/ICas.h> #include <android/hardware/cas/1.0/ICasListener.h> @@ -27,8 +25,11 @@ #include <android/hardware/cas/native/1.0/IDescrambler.h> #include <android/hardware/cas/native/1.0/types.h> #include <binder/MemoryDealer.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> #include <hidl/HidlSupport.h> #include <hidl/HidlTransportSupport.h> +#include <hidl/ServiceManagement.h> #include <hidl/Status.h> #include <hidlmemory/FrameworkUtils.h> #include <utils/Condition.h> @@ -208,29 +209,16 @@ void MediaCasListener::testEventEcho(sp<ICas>& mediaCas, int32_t& event, int32_t EXPECT_TRUE(mEventData == eventData); } -// Test environment for Cas HIDL HAL. -class CasHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static CasHidlEnvironment* Instance() { - static CasHidlEnvironment* instance = new CasHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IMediaCasService>(); } -}; - -class MediaCasHidlTest : public ::testing::VtsHalHidlTargetTestBase { - public: +class MediaCasHidlTest : public testing::TestWithParam<std::string> { + public: virtual void SetUp() override { - mService = ::testing::VtsHalHidlTargetTestBase::getService<IMediaCasService>( - CasHidlEnvironment::Instance()->getServiceName<IMediaCasService>()); + mService = IMediaCasService::getService(GetParam()); ASSERT_NE(mService, nullptr); } - sp<IMediaCasService> mService; + sp<IMediaCasService> mService = nullptr; - protected: + protected: static void description(const std::string& description) { RecordProperty("description", description); } @@ -419,7 +407,7 @@ class MediaCasHidlTest : public ::testing::VtsHalHidlTargetTestBase { return ::testing::AssertionResult(returnVoid.isOk()); } -TEST_F(MediaCasHidlTest, EnumeratePlugins) { +TEST_P(MediaCasHidlTest, EnumeratePlugins) { description("Test enumerate plugins"); hidl_vec<HidlCasPluginDescriptor> descriptors; EXPECT_TRUE(mService @@ -440,7 +428,7 @@ TEST_F(MediaCasHidlTest, EnumeratePlugins) { } } -TEST_F(MediaCasHidlTest, TestInvalidSystemIdFails) { +TEST_P(MediaCasHidlTest, TestInvalidSystemIdFails) { description("Test failure for invalid system ID"); sp<MediaCasListener> casListener = new MediaCasListener(); @@ -458,7 +446,7 @@ TEST_F(MediaCasHidlTest, TestInvalidSystemIdFails) { EXPECT_EQ(descramblerBase, nullptr); } -TEST_F(MediaCasHidlTest, TestClearKeyPluginInstalled) { +TEST_P(MediaCasHidlTest, TestClearKeyPluginInstalled) { description("Test if ClearKey plugin is installed"); hidl_vec<HidlCasPluginDescriptor> descriptors; EXPECT_TRUE(mService @@ -480,7 +468,7 @@ TEST_F(MediaCasHidlTest, TestClearKeyPluginInstalled) { ASSERT_TRUE(false) << "ClearKey plugin not installed"; } -TEST_F(MediaCasHidlTest, TestClearKeyApis) { +TEST_P(MediaCasHidlTest, TestClearKeyApis) { description("Test that valid call sequences succeed"); ASSERT_TRUE(createCasPlugin(CLEAR_KEY_SYSTEM_ID)); @@ -584,7 +572,7 @@ TEST_F(MediaCasHidlTest, TestClearKeyApis) { EXPECT_EQ(Status::OK, returnStatus); } -TEST_F(MediaCasHidlTest, TestClearKeySessionClosedAfterRelease) { +TEST_P(MediaCasHidlTest, TestClearKeySessionClosedAfterRelease) { description("Test that all sessions are closed after a MediaCas object is released"); ASSERT_TRUE(createCasPlugin(CLEAR_KEY_SYSTEM_ID)); @@ -611,7 +599,7 @@ TEST_F(MediaCasHidlTest, TestClearKeySessionClosedAfterRelease) { EXPECT_EQ(Status::ERROR_CAS_SESSION_NOT_OPENED, returnStatus); } -TEST_F(MediaCasHidlTest, TestClearKeyErrors) { +TEST_P(MediaCasHidlTest, TestClearKeyErrors) { description("Test that invalid call sequences fail with expected error codes"); ASSERT_TRUE(createCasPlugin(CLEAR_KEY_SYSTEM_ID)); @@ -700,7 +688,7 @@ TEST_F(MediaCasHidlTest, TestClearKeyErrors) { EXPECT_FALSE(mDescramblerBase->requiresSecureDecoderComponent("bad")); } -TEST_F(MediaCasHidlTest, TestClearKeyOobFails) { +TEST_P(MediaCasHidlTest, TestClearKeyOobFails) { description("Test that oob descramble request fails with expected error"); ASSERT_TRUE(createCasPlugin(CLEAR_KEY_SYSTEM_ID)); @@ -849,11 +837,7 @@ TEST_F(MediaCasHidlTest, TestClearKeyOobFails) { } // anonymous namespace -int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(CasHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - CasHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - LOG(INFO) << "Test result = " << status; - return status; -} +INSTANTIATE_TEST_SUITE_P( + PerInstance, MediaCasHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IMediaCasService::descriptor)), + android::hardware::PrintInstanceNameToString);
\ No newline at end of file diff --git a/cas/1.1/vts/functional/Android.bp b/cas/1.1/vts/functional/Android.bp index 8afd19abda..9e8eb52efe 100644 --- a/cas/1.1/vts/functional/Android.bp +++ b/cas/1.1/vts/functional/Android.bp @@ -30,6 +30,6 @@ cc_test { shared_libs: [ "libbinder", ], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp b/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp index 0264bddedd..7e5a61a6ff 100644 --- a/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp +++ b/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp @@ -27,8 +27,11 @@ #include <android/hardware/cas/native/1.0/IDescrambler.h> #include <android/hardware/cas/native/1.0/types.h> #include <binder/MemoryDealer.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> #include <hidl/HidlSupport.h> #include <hidl/HidlTransportSupport.h> +#include <hidl/ServiceManagement.h> #include <hidl/Status.h> #include <hidlmemory/FrameworkUtils.h> #include <utils/Condition.h> @@ -251,27 +254,14 @@ void MediaCasListener::testSessionEventEcho(sp<ICas>& mediaCas, const hidl_vec<u EXPECT_TRUE(mEventData == eventData); } -// Test environment for Cas HIDL HAL. -class CasHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static CasHidlEnvironment* Instance() { - static CasHidlEnvironment* instance = new CasHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IMediaCasService>(); } -}; - -class MediaCasHidlTest : public ::testing::VtsHalHidlTargetTestBase { +class MediaCasHidlTest : public testing::TestWithParam<std::string> { public: virtual void SetUp() override { - mService = ::testing::VtsHalHidlTargetTestBase::getService<IMediaCasService>( - CasHidlEnvironment::Instance()->getServiceName<IMediaCasService>()); + mService = IMediaCasService::getService(GetParam()); ASSERT_NE(mService, nullptr); } - sp<IMediaCasService> mService; + sp<IMediaCasService> mService = nullptr; protected: static void description(const std::string& description) { @@ -453,7 +443,7 @@ class MediaCasHidlTest : public ::testing::VtsHalHidlTargetTestBase { return ::testing::AssertionResult(returnVoid.isOk()); } -TEST_F(MediaCasHidlTest, TestClearKeyApisWithSession) { +TEST_P(MediaCasHidlTest, TestClearKeyApisWithSession) { description("Test that valid call sequences with SessionEvent send and receive"); ASSERT_TRUE(createCasPlugin(CLEAR_KEY_SYSTEM_ID)); @@ -561,11 +551,7 @@ TEST_F(MediaCasHidlTest, TestClearKeyApisWithSession) { } // anonymous namespace -int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(CasHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - CasHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - LOG(INFO) << "Test result = " << status; - return status; -} +INSTANTIATE_TEST_SUITE_P( + PerInstance, MediaCasHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IMediaCasService::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml index ef1cd75cd1..34cbe26d72 100644 --- a/compatibility_matrices/compatibility_matrix.current.xml +++ b/compatibility_matrices/compatibility_matrix.current.xml @@ -10,6 +10,7 @@ <hal format="hidl" optional="false"> <name>android.hardware.audio</name> <version>5.0</version> + <version>6.0</version> <interface> <name>IDevicesFactory</name> <instance>default</instance> @@ -18,6 +19,7 @@ <hal format="hidl" optional="false"> <name>android.hardware.audio.effect</name> <version>5.0</version> + <version>6.0</version> <interface> <name>IEffectsFactory</name> <instance>default</instance> @@ -89,7 +91,7 @@ </hal> <hal format="hidl" optional="true"> <name>android.hardware.boot</name> - <version>1.0</version> + <version>1.1</version> <interface> <name>IBootControl</name> <instance>default</instance> @@ -204,7 +206,7 @@ </hal> <hal format="hidl" optional="false"> <name>android.hardware.graphics.composer</name> - <version>2.1-3</version> + <version>2.1-4</version> <interface> <name>IComposer</name> <instance>default</instance> @@ -307,7 +309,7 @@ </hal> <hal format="hidl" optional="true"> <name>android.hardware.neuralnetworks</name> - <version>1.0-2</version> + <version>1.0-3</version> <interface> <name>IDevice</name> <regex-instance>.*</regex-instance> @@ -467,7 +469,7 @@ </hal> <hal format="hidl" optional="true"> <name>android.hardware.vibrator</name> - <version>1.0-3</version> + <version>1.0-4</version> <interface> <name>IVibrator</name> <instance>default</instance> @@ -507,7 +509,7 @@ </hal> <hal format="hidl" optional="true"> <name>android.hardware.wifi.supplicant</name> - <version>1.0-2</version> + <version>1.0-3</version> <interface> <name>ISupplicant</name> <instance>default</instance> diff --git a/confirmationui/1.0/vts/functional/Android.bp b/confirmationui/1.0/vts/functional/Android.bp index d19d7021a2..fd088cd360 100644 --- a/confirmationui/1.0/vts/functional/Android.bp +++ b/confirmationui/1.0/vts/functional/Android.bp @@ -23,7 +23,7 @@ cc_test { static_libs: [ "android.hardware.confirmationui@1.0", "android.hardware.keymaster@4.0", - "libcrypto", + "libcrypto_static", "libcn-cbor", "android.hardware.confirmationui-support-lib", ], diff --git a/contexthub/1.0/default/OWNERS b/contexthub/1.0/default/OWNERS index 5373073cb8..90c233030e 100644 --- a/contexthub/1.0/default/OWNERS +++ b/contexthub/1.0/default/OWNERS @@ -1,4 +1,3 @@ -aarossig@google.com arthuri@google.com bduddie@google.com -bstack@google.com +stange@google.com diff --git a/contexthub/1.0/vts/functional/OWNERS b/contexthub/1.0/vts/functional/OWNERS index ee01441e4f..045cc4e938 100644 --- a/contexthub/1.0/vts/functional/OWNERS +++ b/contexthub/1.0/vts/functional/OWNERS @@ -1,8 +1,7 @@ #Context Hub team -aarossig@google.com arthuri@google.com bduddie@google.com -bstack@google.com +stange@google.com #VTS team yim@google.com diff --git a/current.txt b/current.txt index 83657b2d47..9ab1967f66 100644 --- a/current.txt +++ b/current.txt @@ -575,7 +575,26 @@ cfa81f229b69f9011c58f48264fcb552447430fe68610eac514e811e65bc306a android.hardwar 2410dd02d67786a732d36e80b0f8ccf55086604ef37f9838e2013ff2c571e404 android.hardware.camera.device@3.5::types b69a7615c508acf5c5201efd1bfa3262167874fc3594e2db5a3ff93addd8ac75 android.hardware.keymaster@4.0::IKeymasterDevice eb2fa0c883c2185d514be0b84c179b283753ef0c1b77b45b4f359bd23bba8b75 android.hardware.neuralnetworks@1.0::IPreparedModel +f1109cbb10297b7429a11fab42afa912710b303c9bf20bd5cdb8bd57b9c84186 android.hardware.neuralnetworks@1.0::types +9d8ee57c490ffeaa28f702eaea8d198cb510e4bbfb99e6cb5f63e73341057c7c android.hardware.neuralnetworks@1.1::types fb382e986c10b8fbb797a8546e8f9ea6d1107bfe6f3fb7e57f6bbbf1f807a906 android.hardware.neuralnetworks@1.2::IDevice 40e71cd693de5b832325c5d8f081f2ff20a7ba2b89d401cee5b4b3eb0e241681 android.hardware.neuralnetworks@1.2::IPreparedModel +71c0f7127335e5b74d1615d5e7f129831b43ffbae5318ad0924d7d8d8910a859 android.hardware.neuralnetworks@1.2::types +a785a57447a81e9c130eef6904c3a5c256076c6a04588c40620ebd6fa2660d77 android.hardware.radio@1.2::types 1a6e2bd289f22931c526b21916910f1d4c436b7acb9556e4243de4ce8e6cc2e4 android.hardware.soundtrigger@2.0::ISoundTriggerHwCallback fd65298e1e09e0e3c781ab18305920d757dbe55a3b459ce17814ec5cf6dfee99 android.hardware.wifi@1.0::IWifiP2pIface + +# HALs released in Android R +07d0a252b2d8fa35887908a996ba395cf392968395fc30afab791f46e0c22a52 android.hardware.boot@1.1::IBootControl +74049a402be913963edfdd80828a53736570e9d8124a1bf18166b6ed46a6b0ab android.hardware.boot@1.1::types +34515afa2bb792d3c6d8495a5f5d907d179c8507ca5e55c10050d02ae1d516ef android.hardware.neuralnetworks@1.3::IDevice +b74fe72cfe438f50e772e6a307657ff449d5bde83c15dd1f140ff2edbe73499c android.hardware.neuralnetworks@1.3::types +544049dcda3f943ad67d83d5277f06681a3782982a9af5a78b5d4e8d295d061a android.hardware.vibrator@1.4::IVibrator +5e1c12efbbba89c9143d10b1b90eceff8bc79aa079f5106215b528e104fef101 android.hardware.vibrator@1.4::IVibratorCallback +033eae03c09ebc75e82db37bc39995dfaa9086745577b44d9e14e9ccb48bd8cc android.hardware.vibrator@1.4::types +3e01d4446cd69fd1c48f8572efd97487bc179564b32bd795800b97bbe10be37b android.hardware.wifi@1.4::IWifi +a64467bae843569f0d465c5be7f0c7a5b987985b55a3ef4794dd5afc68538650 android.hardware.wifi.supplicant@1.3::ISupplicant +0a7ff83fd0326b82232e1609da98f34960be11335df72fc407ad238d7bd0e081 android.hardware.wifi.supplicant@1.3::ISupplicantStaIface +619fc9839ec6e369cfa9b28e3e9412e6885720ff8f9b5750c1b6ffb905120391 android.hardware.wifi.supplicant@1.3::ISupplicantStaIfaceCallback +c9273429fcf98d797d3bb07fdba6f1be95bf960f9255cde169fd1ca4db85f856 android.hardware.wifi.supplicant@1.3::ISupplicantStaNetwork +b0f8c9cd61a45a8c1b4a8e40913ecaea0921011cbe2305a6fa5a2feaa0d36c30 android.hardware.wifi.supplicant@1.3::types diff --git a/drm/1.0/vts/functional/Android.bp b/drm/1.0/vts/functional/Android.bp index d6ebfdddfd..61d4d583a9 100644 --- a/drm/1.0/vts/functional/Android.bp +++ b/drm/1.0/vts/functional/Android.bp @@ -30,7 +30,7 @@ cc_test { "libhidlmemory", "libnativehelper", "libssl", - "libcrypto", + "libcrypto_static", ], test_suites: ["general-tests"], } diff --git a/drm/1.2/vts/functional/Android.bp b/drm/1.2/vts/functional/Android.bp index 6b4a4c0adf..95883bf728 100644 --- a/drm/1.2/vts/functional/Android.bp +++ b/drm/1.2/vts/functional/Android.bp @@ -34,7 +34,7 @@ cc_test { "libhidlmemory", "libnativehelper", "libssl", - "libcrypto", + "libcrypto_static", ], test_suites: ["general-tests"], } diff --git a/drm/TEST_MAPPING b/drm/TEST_MAPPING new file mode 100644 index 0000000000..cff681917f --- /dev/null +++ b/drm/TEST_MAPPING @@ -0,0 +1,8 @@ +{ + "imports": [ + // gts and cts filters + { + "path": "frameworks/av/drm/libmediadrm" + } + ] +} diff --git a/dumpstate/1.0/default/DumpstateDevice.cpp b/dumpstate/1.0/default/DumpstateDevice.cpp index 25d92b0acd..c57bf43dbf 100644 --- a/dumpstate/1.0/default/DumpstateDevice.cpp +++ b/dumpstate/1.0/default/DumpstateDevice.cpp @@ -37,11 +37,6 @@ Return<void> DumpstateDevice::dumpstateBoard(const hidl_handle& handle) { // NOTE: this is just an example on how to use the DumpstateUtil.h functions to implement // this interface. - // Exit when dump is completed since this is a lazy HAL. - addPostCommandTask([]() { - exit(0); - }); - if (handle == nullptr || handle->numFds < 1) { ALOGE("no FDs\n"); return Void(); diff --git a/dumpstate/1.0/default/service.cpp b/dumpstate/1.0/default/service.cpp index 4f276b7d40..76c72b5b25 100644 --- a/dumpstate/1.0/default/service.cpp +++ b/dumpstate/1.0/default/service.cpp @@ -15,22 +15,26 @@ */ #define LOG_TAG "android.hardware.dumpstate@1.0-service" +#include <hidl/HidlLazyUtils.h> #include <hidl/HidlSupport.h> #include <hidl/HidlTransportSupport.h> #include "DumpstateDevice.h" +using ::android::OK; +using ::android::sp; using ::android::hardware::configureRpcThreadpool; +using ::android::hardware::joinRpcThreadpool; +using ::android::hardware::LazyServiceRegistrar; using ::android::hardware::dumpstate::V1_0::IDumpstateDevice; using ::android::hardware::dumpstate::V1_0::implementation::DumpstateDevice; -using ::android::hardware::joinRpcThreadpool; -using ::android::OK; -using ::android::sp; int main(int /* argc */, char* /* argv */ []) { sp<IDumpstateDevice> dumpstate = new DumpstateDevice; configureRpcThreadpool(1, true /* will join */); - if (dumpstate->registerAsService() != OK) { + + auto registrar = LazyServiceRegistrar::getInstance(); + if (registrar.registerService(dumpstate) != OK) { ALOGE("Could not register service."); return 1; } diff --git a/dumpstate/1.0/vts/functional/Android.bp b/dumpstate/1.0/vts/functional/Android.bp index fc64d056b0..3bac281598 100644 --- a/dumpstate/1.0/vts/functional/Android.bp +++ b/dumpstate/1.0/vts/functional/Android.bp @@ -18,5 +18,5 @@ cc_test { defaults: ["VtsHalTargetTestDefaults"], srcs: ["VtsHalDumpstateV1_0TargetTest.cpp"], static_libs: ["android.hardware.dumpstate@1.0"], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/dumpstate/1.0/vts/functional/VtsHalDumpstateV1_0TargetTest.cpp b/dumpstate/1.0/vts/functional/VtsHalDumpstateV1_0TargetTest.cpp index 57ebf2a888..96b13c5c38 100644 --- a/dumpstate/1.0/vts/functional/VtsHalDumpstateV1_0TargetTest.cpp +++ b/dumpstate/1.0/vts/functional/VtsHalDumpstateV1_0TargetTest.cpp @@ -21,32 +21,19 @@ #include <android/hardware/dumpstate/1.0/IDumpstateDevice.h> #include <cutils/native_handle.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> #include <log/log.h> -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> - using ::android::hardware::dumpstate::V1_0::IDumpstateDevice; using ::android::hardware::Return; using ::android::sp; -// Test environment for Dumpstate HIDL HAL. -class DumpstateHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static DumpstateHidlEnvironment* Instance() { - static DumpstateHidlEnvironment* instance = new DumpstateHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IDumpstateDevice>(); } -}; - -class DumpstateHidlTest : public ::testing::VtsHalHidlTargetTestBase { - public: +class DumpstateHidlTest : public ::testing::TestWithParam<std::string> { + public: virtual void SetUp() override { - dumpstate = ::testing::VtsHalHidlTargetTestBase::getService<IDumpstateDevice>( - DumpstateHidlEnvironment::Instance()->getServiceName<IDumpstateDevice>()); + dumpstate = IDumpstateDevice::getService(GetParam()); ASSERT_NE(dumpstate, nullptr) << "Could not get HIDL instance"; } @@ -54,14 +41,14 @@ class DumpstateHidlTest : public ::testing::VtsHalHidlTargetTestBase { }; // Negative test: make sure dumpstateBoard() doesn't crash when passed a null pointer. -TEST_F(DumpstateHidlTest, TestNullHandle) { +TEST_P(DumpstateHidlTest, TestNullHandle) { Return<void> status = dumpstate->dumpstateBoard(nullptr); ASSERT_TRUE(status.isOk()) << "Status should be ok: " << status.description(); } // Negative test: make sure dumpstateBoard() ignores a handle with no FD. -TEST_F(DumpstateHidlTest, TestHandleWithNoFd) { +TEST_P(DumpstateHidlTest, TestHandleWithNoFd) { native_handle_t* handle = native_handle_create(0, 0); ASSERT_NE(handle, nullptr) << "Could not create native_handle"; @@ -74,7 +61,7 @@ TEST_F(DumpstateHidlTest, TestHandleWithNoFd) { } // Positive test: make sure dumpstateBoard() writes something to the FD. -TEST_F(DumpstateHidlTest, TestOk) { +TEST_P(DumpstateHidlTest, TestOk) { // Index 0 corresponds to the read end of the pipe; 1 to the write end. int fds[2]; ASSERT_EQ(0, pipe2(fds, O_NONBLOCK)) << errno; @@ -94,7 +81,7 @@ TEST_F(DumpstateHidlTest, TestOk) { } // Positive test: make sure dumpstateBoard() doesn't crash with two FDs. -TEST_F(DumpstateHidlTest, TestHandleWithTwoFds) { +TEST_P(DumpstateHidlTest, TestHandleWithTwoFds) { int fds1[2]; int fds2[2]; ASSERT_EQ(0, pipe2(fds1, O_NONBLOCK)) << errno; @@ -111,11 +98,7 @@ TEST_F(DumpstateHidlTest, TestHandleWithTwoFds) { native_handle_close(handle); } -int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(DumpstateHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - DumpstateHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - ALOGI("Test result = %d", status); - return status; -} +INSTANTIATE_TEST_SUITE_P( + PerInstance, DumpstateHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IDumpstateDevice::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/gnss/1.0/default/Gnss.cpp b/gnss/1.0/default/Gnss.cpp index 32c131c83d..7d1cacfcd8 100644 --- a/gnss/1.0/default/Gnss.cpp +++ b/gnss/1.0/default/Gnss.cpp @@ -128,20 +128,20 @@ void Gnss::gnssSvStatusCb(GnssSvStatus* status) { for (size_t i = 0; i < svStatus.numSvs; i++) { auto svInfo = status->gnss_sv_list[i]; IGnssCallback::GnssSvInfo gnssSvInfo = { - .svid = svInfo.svid, - .constellation = static_cast< - android::hardware::gnss::V1_0::GnssConstellationType>( - svInfo.constellation), - .cN0Dbhz = svInfo.c_n0_dbhz, - .elevationDegrees = svInfo.elevation, - .azimuthDegrees = svInfo.azimuth, - // Older chipsets do not provide carrier frequency, hence - // HAS_CARRIER_FREQUENCY flag and the carrierFrequencyHz fields - // are not set. So we are resetting both fields here. - .svFlag = static_cast<uint8_t>( - svInfo.flags &= ~(static_cast<uint8_t>( - IGnssCallback::GnssSvFlags::HAS_CARRIER_FREQUENCY))), - .carrierFrequencyHz = 0}; + .svid = svInfo.svid, + .constellation = static_cast<android::hardware::gnss::V1_0::GnssConstellationType>( + svInfo.constellation), + .cN0Dbhz = svInfo.c_n0_dbhz, + .elevationDegrees = svInfo.elevation, + .azimuthDegrees = svInfo.azimuth, + .carrierFrequencyHz = 0, + // Older chipsets do not provide carrier frequency, hence + // HAS_CARRIER_FREQUENCY flag and the carrierFrequencyHz fields + // are not set. So we are resetting both fields here. + .svFlag = static_cast<uint8_t>( + svInfo.flags &= + ~(static_cast<uint8_t>(IGnssCallback::GnssSvFlags::HAS_CARRIER_FREQUENCY))), + }; svStatus.gnssSvList[i] = gnssSvInfo; } diff --git a/gnss/1.0/vts/functional/Android.bp b/gnss/1.0/vts/functional/Android.bp index 505cb41a23..d73b32ea8b 100644 --- a/gnss/1.0/vts/functional/Android.bp +++ b/gnss/1.0/vts/functional/Android.bp @@ -19,5 +19,5 @@ cc_test { defaults: ["VtsHalTargetTestDefaults"], srcs: ["VtsHalGnssV1_0TargetTest.cpp"], static_libs: ["android.hardware.gnss@1.0"], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/gnss/1.0/vts/functional/VtsHalGnssV1_0TargetTest.cpp b/gnss/1.0/vts/functional/VtsHalGnssV1_0TargetTest.cpp index c26f60a703..1a80ecf44c 100644 --- a/gnss/1.0/vts/functional/VtsHalGnssV1_0TargetTest.cpp +++ b/gnss/1.0/vts/functional/VtsHalGnssV1_0TargetTest.cpp @@ -16,11 +16,11 @@ #define LOG_TAG "VtsHalGnssV1_0TargetTest" #include <android/hardware/gnss/1.0/IGnss.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> #include <log/log.h> -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> - #include <chrono> #include <condition_variable> #include <mutex> @@ -42,23 +42,8 @@ using android::sp; bool sAgpsIsPresent = false; // if SUPL or XTRA assistance available bool sSignalIsWeak = false; // if GNSS signals are weak (e.g. light indoor) -// Test environment for GNSS HIDL HAL. -class GnssHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static GnssHidlEnvironment* Instance() { - static GnssHidlEnvironment* instance = new GnssHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IGnss>(); } - - private: - GnssHidlEnvironment() {} -}; - // The main test class for GNSS HAL. -class GnssHalTest : public ::testing::VtsHalHidlTargetTestBase { +class GnssHalTest : public testing::TestWithParam<std::string> { public: virtual void SetUp() override { // Clean between tests @@ -67,8 +52,7 @@ class GnssHalTest : public ::testing::VtsHalHidlTargetTestBase { info_called_count_ = 0; notify_count_ = 0; - gnss_hal_ = ::testing::VtsHalHidlTargetTestBase::getService<IGnss>( - GnssHidlEnvironment::Instance()->getServiceName<IGnss>()); + gnss_hal_ = IGnss::getService(GetParam()); ASSERT_NE(gnss_hal_, nullptr); gnss_cb_ = new GnssCallback(*this); @@ -344,14 +328,14 @@ class GnssHalTest : public ::testing::VtsHalHidlTargetTestBase { * Since this is just the basic operation of SetUp() and TearDown(), * the function definition is intentionally empty */ -TEST_F(GnssHalTest, SetCallbackCapabilitiesCleanup) {} +TEST_P(GnssHalTest, SetCallbackCapabilitiesCleanup) {} /* * GetLocation: * Turns on location, waits 45 second for at least 5 locations, * and checks them for reasonable validity. */ -TEST_F(GnssHalTest, GetLocation) { +TEST_P(GnssHalTest, GetLocation) { #define MIN_INTERVAL_MSEC 500 #define PREFERRED_ACCURACY 0 // Ideally perfect (matches GnssLocationProvider) #define PREFERRED_TIME_MSEC 0 // Ideally immediate @@ -391,7 +375,7 @@ TEST_F(GnssHalTest, GetLocation) { * InjectDelete: * Ensures that calls to inject and/or delete information state are handled. */ -TEST_F(GnssHalTest, InjectDelete) { +TEST_P(GnssHalTest, InjectDelete) { // confidently, well north of Alaska auto result = gnss_hal_->injectLocation(80.0, -170.0, 1000.0); @@ -424,7 +408,7 @@ TEST_F(GnssHalTest, InjectDelete) { * null or actual extension, no crash. * Confirms year-based required extensions (Measurement & Debug) are present */ -TEST_F(GnssHalTest, GetAllExtensions) { +TEST_P(GnssHalTest, GetAllExtensions) { // Basic call-is-handled checks auto gnssXtra = gnss_hal_->getExtensionXtra(); ASSERT_TRUE(gnssXtra.isOk()); @@ -470,7 +454,7 @@ TEST_F(GnssHalTest, GetAllExtensions) { * MeasurementCapabilities: * Verifies that modern hardware supports measurement capabilities. */ -TEST_F(GnssHalTest, MeasurementCapabilites) { +TEST_P(GnssHalTest, MeasurementCapabilites) { if (info_called_count_ > 0 && last_info_.yearOfHw >= 2016) { EXPECT_TRUE(last_capabilities_ & IGnssCallback::Capabilities::MEASUREMENTS); } @@ -480,16 +464,19 @@ TEST_F(GnssHalTest, MeasurementCapabilites) { * SchedulingCapabilities: * Verifies that 2018+ hardware supports Scheduling capabilities. */ -TEST_F(GnssHalTest, SchedulingCapabilities) { +TEST_P(GnssHalTest, SchedulingCapabilities) { if (info_called_count_ > 0 && last_info_.yearOfHw >= 2018) { EXPECT_TRUE(last_capabilities_ & IGnssCallback::Capabilities::SCHEDULING); } } +INSTANTIATE_TEST_SUITE_P( + PerInstance, GnssHalTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IGnss::descriptor)), + android::hardware::PrintInstanceNameToString); + int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(GnssHidlEnvironment::Instance()); ::testing::InitGoogleTest(&argc, argv); - GnssHidlEnvironment::Instance()->init(&argc, argv); /* * These arguments not used by automated VTS testing. * Only for use in manual testing, when wanting to run @@ -502,7 +489,6 @@ int main(int argc, char** argv) { sSignalIsWeak = true; } } - int status = RUN_ALL_TESTS(); - ALOGI("Test result = %d", status); - return status; -} + + return RUN_ALL_TESTS(); +}
\ No newline at end of file diff --git a/gnss/1.1/vts/functional/Android.bp b/gnss/1.1/vts/functional/Android.bp index cc34290fa8..bdd02d2e6e 100644 --- a/gnss/1.1/vts/functional/Android.bp +++ b/gnss/1.1/vts/functional/Android.bp @@ -30,5 +30,5 @@ cc_test { shared_libs: [ "android.hardware.gnss.measurement_corrections@1.0", ], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/gnss/1.1/vts/functional/VtsHalGnssV1_1TargetTest.cpp b/gnss/1.1/vts/functional/VtsHalGnssV1_1TargetTest.cpp index ca9eef4822..4a0a7f9ffa 100644 --- a/gnss/1.1/vts/functional/VtsHalGnssV1_1TargetTest.cpp +++ b/gnss/1.1/vts/functional/VtsHalGnssV1_1TargetTest.cpp @@ -15,15 +15,15 @@ */ #define LOG_TAG "VtsHalGnssV1_1TargetTest" -#include <VtsHalHidlTargetTestBase.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> #include "gnss_hal_test.h" -int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(GnssHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - GnssHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - ALOGI("Test result = %d", status); - return status; -} +using android::hardware::gnss::V1_1::IGnss; + +INSTANTIATE_TEST_SUITE_P( + PerInstance, GnssHalTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IGnss::descriptor)), + android::hardware::PrintInstanceNameToString);
\ No newline at end of file diff --git a/gnss/1.1/vts/functional/gnss_hal_test.cpp b/gnss/1.1/vts/functional/gnss_hal_test.cpp index 61a2ce434d..2c8a7b1399 100644 --- a/gnss/1.1/vts/functional/gnss_hal_test.cpp +++ b/gnss/1.1/vts/functional/gnss_hal_test.cpp @@ -17,6 +17,8 @@ #define LOG_TAG "GnssHalTest" #include <android/hidl/manager/1.2/IServiceManager.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> #include <hidl/ServiceManagement.h> #include <gnss_hal_test.h> @@ -29,8 +31,7 @@ using ::android::hardware::hidl_vec; using ::android::hardware::gnss::common::Utils; void GnssHalTest::SetUp() { - gnss_hal_ = ::testing::VtsHalHidlTargetTestBase::getService<IGnss>( - GnssHidlEnvironment::Instance()->getServiceName<IGnss>()); + gnss_hal_ = IGnss::getService(GetParam()); ASSERT_NE(gnss_hal_, nullptr); SetUpGnssCallback(); diff --git a/gnss/1.1/vts/functional/gnss_hal_test.h b/gnss/1.1/vts/functional/gnss_hal_test.h index e4325bf359..169cd62c59 100644 --- a/gnss/1.1/vts/functional/gnss_hal_test.h +++ b/gnss/1.1/vts/functional/gnss_hal_test.h @@ -19,8 +19,7 @@ #include <android/hardware/gnss/1.1/IGnss.h> -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> +#include <gtest/gtest.h> #include "GnssCallbackEventQueue.h" using android::hardware::Return; @@ -37,24 +36,9 @@ using android::sp; #define TIMEOUT_SEC 2 // for basic commands/responses -// Test environment for GNSS HIDL HAL. -class GnssHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static GnssHidlEnvironment* Instance() { - static GnssHidlEnvironment* instance = new GnssHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IGnss>(); } - - private: - GnssHidlEnvironment() {} -}; - // The main test class for GNSS HAL. -class GnssHalTest : public ::testing::VtsHalHidlTargetTestBase { - public: +class GnssHalTest : public testing::TestWithParam<std::string> { + public: virtual void SetUp() override; virtual void TearDown() override; diff --git a/gnss/1.1/vts/functional/gnss_hal_test_cases.cpp b/gnss/1.1/vts/functional/gnss_hal_test_cases.cpp index 3294bcd1ad..79da84ac10 100644 --- a/gnss/1.1/vts/functional/gnss_hal_test_cases.cpp +++ b/gnss/1.1/vts/functional/gnss_hal_test_cases.cpp @@ -18,9 +18,8 @@ #include <gnss_hal_test.h> -#include <VtsHalHidlTargetTestBase.h> - #include <android/hardware/gnss/1.1/IGnssConfiguration.h> +#include <gtest/gtest.h> using android::hardware::hidl_vec; @@ -39,13 +38,13 @@ using android::hardware::gnss::V1_1::IGnssMeasurement; * * Empty test fixture to verify basic Setup & Teardown */ -TEST_F(GnssHalTest, SetupTeardownCreateCleanup) {} +TEST_P(GnssHalTest, SetupTeardownCreateCleanup) {} /* * TestGnssMeasurementCallback: * Gets the GnssMeasurementExtension and verify that it returns an actual extension. */ -TEST_F(GnssHalTest, TestGnssMeasurementCallback) { +TEST_P(GnssHalTest, TestGnssMeasurementCallback) { auto gnssMeasurement_1_1 = gnss_hal_->getExtensionGnssMeasurement_1_1(); ASSERT_TRUE(gnssMeasurement_1_1.isOk()); auto gnssMeasurement_1_0 = gnss_hal_->getExtensionGnssMeasurement(); @@ -65,7 +64,7 @@ TEST_F(GnssHalTest, TestGnssMeasurementCallback) { * NO_LOCATION_PERIOD_SEC and verfiy that no location is received. Also perform validity checks on * each received location. */ -TEST_F(GnssHalTest, GetLocationLowPower) { +TEST_P(GnssHalTest, GetLocationLowPower) { if (!IsGnssHalVersion_1_1()) { ALOGI("Test GetLocationLowPower skipped. GNSS HAL version is greater than 1.1."); return; @@ -97,7 +96,6 @@ TEST_F(GnssHalTest, GetLocationLowPower) { gnss_cb_->location_cbq_.retrieve(gnss_cb_->last_location_, kNoLocationPeriodSec); const int location_called_count = gnss_cb_->location_cbq_.calledCount(); - // Tolerate (ignore) one extra location right after the first one // to handle startup edge case scheduling limitations in some implementations if ((i == 1) && (location_called_count == 2)) { @@ -132,7 +130,8 @@ TEST_F(GnssHalTest, GetLocationLowPower) { */ IGnssConfiguration::BlacklistedSource FindStrongFrequentNonGpsSource( - const list<IGnssCallback::GnssSvStatus> list_gnss_sv_status, const int min_observations) { + const std::list<IGnssCallback::GnssSvStatus> list_gnss_sv_status, + const int min_observations) { struct ComparableBlacklistedSource { IGnssConfiguration::BlacklistedSource id; @@ -218,7 +217,7 @@ IGnssConfiguration::BlacklistedSource FindStrongFrequentNonGpsSource( * 5b) Retry a few times, in case GNSS search strategy takes a while to reacquire even the * formerly strongest satellite */ -TEST_F(GnssHalTest, BlacklistIndividualSatellites) { +TEST_P(GnssHalTest, BlacklistIndividualSatellites) { if (!IsGnssHalVersion_1_1()) { ALOGI("Test BlacklistIndividualSatellites skipped. GNSS HAL version is greater than 1.1."); return; @@ -244,7 +243,7 @@ TEST_F(GnssHalTest, BlacklistIndividualSatellites) { */ const int kGnssSvStatusTimeout = 2; - list<IGnssCallback::GnssSvStatus> sv_status_list; + std::list<IGnssCallback::GnssSvStatus> sv_status_list; int count = gnss_cb_->sv_status_cbq_.retrieve(sv_status_list, sv_status_cbq_size, kGnssSvStatusTimeout); ASSERT_EQ(count, sv_status_cbq_size); @@ -362,7 +361,7 @@ TEST_F(GnssHalTest, BlacklistIndividualSatellites) { * GnssStatus does not use any constellation but GPS. * 4a & b) Clean up by turning off location, and send in empty blacklist. */ -TEST_F(GnssHalTest, BlacklistConstellation) { +TEST_P(GnssHalTest, BlacklistConstellation) { if (!IsGnssHalVersion_1_1()) { ALOGI("Test BlacklistConstellation skipped. GNSS HAL version is greater than 1.1."); return; @@ -457,7 +456,7 @@ TEST_F(GnssHalTest, BlacklistConstellation) { * * Ensure successfully injecting a location. */ -TEST_F(GnssHalTest, InjectBestLocation) { +TEST_P(GnssHalTest, InjectBestLocation) { StartAndCheckLocations(1); GnssLocation gnssLocation = gnss_cb_->last_location_; CheckLocation(gnssLocation, true); @@ -476,7 +475,7 @@ TEST_F(GnssHalTest, InjectBestLocation) { * GnssDebugValuesSanityTest: * Ensures that GnssDebug values make sense. */ -TEST_F(GnssHalTest, GnssDebugValuesSanityTest) { +TEST_P(GnssHalTest, GnssDebugValuesSanityTest) { auto gnssDebug = gnss_hal_->getExtensionGnssDebug(); ASSERT_TRUE(gnssDebug.isOk()); if (gnss_cb_->info_cbq_.calledCount() > 0 && gnss_cb_->last_info_.yearOfHw >= 2017) { diff --git a/gnss/2.0/default/GnssMeasurement.cpp b/gnss/2.0/default/GnssMeasurement.cpp index 93de89cdfb..1f95ff95dc 100644 --- a/gnss/2.0/default/GnssMeasurement.cpp +++ b/gnss/2.0/default/GnssMeasurement.cpp @@ -119,12 +119,13 @@ GnssData GnssMeasurement::getMockMeasurement() { V2_0::IGnssMeasurementCallback::GnssMeasurement measurement_2_0 = { .v1_1 = measurement_1_1, .codeType = "C", - .constellation = GnssConstellationType::GLONASS, .state = GnssMeasurementState::STATE_CODE_LOCK | GnssMeasurementState::STATE_BIT_SYNC | GnssMeasurementState::STATE_SUBFRAME_SYNC | GnssMeasurementState::STATE_TOW_DECODED | GnssMeasurementState::STATE_GLO_STRING_SYNC | - GnssMeasurementState::STATE_GLO_TOD_DECODED}; + GnssMeasurementState::STATE_GLO_TOD_DECODED, + .constellation = GnssConstellationType::GLONASS, + }; hidl_vec<IGnssMeasurementCallback::GnssMeasurement> measurements(1); measurements[0] = measurement_2_0; diff --git a/gnss/2.0/vts/functional/Android.bp b/gnss/2.0/vts/functional/Android.bp index 278d87b117..9aa13349b0 100644 --- a/gnss/2.0/vts/functional/Android.bp +++ b/gnss/2.0/vts/functional/Android.bp @@ -30,4 +30,5 @@ cc_test { "android.hardware.gnss@2.0", "android.hardware.gnss@common-vts-lib", ], + test_suites: ["general-tests", "vts-core"], } diff --git a/gnss/2.0/vts/functional/VtsHalGnssV2_0TargetTest.cpp b/gnss/2.0/vts/functional/VtsHalGnssV2_0TargetTest.cpp index ae36c50689..2c74fa37be 100644 --- a/gnss/2.0/vts/functional/VtsHalGnssV2_0TargetTest.cpp +++ b/gnss/2.0/vts/functional/VtsHalGnssV2_0TargetTest.cpp @@ -15,15 +15,15 @@ */ #define LOG_TAG "VtsHalGnssV2_0TargetTest" -#include <VtsHalHidlTargetTestBase.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> #include "gnss_hal_test.h" -int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(GnssHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - GnssHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - ALOGI("Test result = %d", status); - return status; -} +using android::hardware::gnss::V2_0::IGnss; + +INSTANTIATE_TEST_SUITE_P( + PerInstance, GnssHalTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IGnss::descriptor)), + android::hardware::PrintInstanceNameToString);
\ No newline at end of file diff --git a/gnss/2.0/vts/functional/gnss_hal_test.cpp b/gnss/2.0/vts/functional/gnss_hal_test.cpp index 14ae43ce26..8ca3f684a3 100644 --- a/gnss/2.0/vts/functional/gnss_hal_test.cpp +++ b/gnss/2.0/vts/functional/gnss_hal_test.cpp @@ -20,12 +20,13 @@ #include <chrono> #include "Utils.h" +#include <gtest/gtest.h> + using ::android::hardware::gnss::common::Utils; // Implementations for the main test class for GNSS HAL void GnssHalTest::SetUp() { - gnss_hal_ = ::testing::VtsHalHidlTargetTestBase::getService<IGnss>( - GnssHidlEnvironment::Instance()->getServiceName<IGnss>()); + gnss_hal_ = IGnss::getService(GetParam()); ASSERT_NE(gnss_hal_, nullptr); SetUpGnssCallback(); diff --git a/gnss/2.0/vts/functional/gnss_hal_test.h b/gnss/2.0/vts/functional/gnss_hal_test.h index 7d07c0f423..4f7b87a485 100644 --- a/gnss/2.0/vts/functional/gnss_hal_test.h +++ b/gnss/2.0/vts/functional/gnss_hal_test.h @@ -17,11 +17,11 @@ #ifndef GNSS_HAL_TEST_H_ #define GNSS_HAL_TEST_H_ -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> #include <android/hardware/gnss/2.0/IGnss.h> #include "GnssCallbackEventQueue.h" +#include <gtest/gtest.h> + using android::hardware::hidl_vec; using android::hardware::Return; using android::hardware::Void; @@ -45,24 +45,9 @@ using android::sp; #define TIMEOUT_SEC 2 // for basic commands/responses -// Test environment for GNSS HIDL HAL. -class GnssHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static GnssHidlEnvironment* Instance() { - static GnssHidlEnvironment* instance = new GnssHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IGnss>(); } - - private: - GnssHidlEnvironment() {} -}; - // The main test class for GNSS HAL. -class GnssHalTest : public ::testing::VtsHalHidlTargetTestBase { - public: +class GnssHalTest : public testing::TestWithParam<std::string> { + public: virtual void SetUp() override; virtual void TearDown() override; diff --git a/gnss/2.0/vts/functional/gnss_hal_test_cases.cpp b/gnss/2.0/vts/functional/gnss_hal_test_cases.cpp index ca3edc57d4..c442cc6bf8 100644 --- a/gnss/2.0/vts/functional/gnss_hal_test_cases.cpp +++ b/gnss/2.0/vts/functional/gnss_hal_test_cases.cpp @@ -16,10 +16,11 @@ #define LOG_TAG "GnssHalTestCases" -#include <VtsHalHidlTargetTestBase.h> #include <gnss_hal_test.h> #include "Utils.h" +#include <gtest/gtest.h> + using android::hardware::hidl_string; using android::hardware::hidl_vec; @@ -51,13 +52,13 @@ using android::hardware::gnss::visibility_control::V1_0::IGnssVisibilityControl; * * Empty test fixture to verify basic Setup & Teardown */ -TEST_F(GnssHalTest, SetupTeardownCreateCleanup) {} +TEST_P(GnssHalTest, SetupTeardownCreateCleanup) {} /* * TestGnssMeasurementExtension: * Gets the GnssMeasurementExtension and verifies that it returns an actual extension. */ -TEST_F(GnssHalTest, TestGnssMeasurementExtension) { +TEST_P(GnssHalTest, TestGnssMeasurementExtension) { auto gnssMeasurement_2_0 = gnss_hal_->getExtensionGnssMeasurement_2_0(); auto gnssMeasurement_1_1 = gnss_hal_->getExtensionGnssMeasurement_1_1(); auto gnssMeasurement_1_0 = gnss_hal_->getExtensionGnssMeasurement(); @@ -80,7 +81,7 @@ TEST_F(GnssHalTest, TestGnssMeasurementExtension) { * The GNSS HAL 2.0 implementation must support @2.0::IGnssConfiguration interface due to * the deprecation of some methods in @1.0::IGnssConfiguration interface. */ -TEST_F(GnssHalTest, TestGnssConfigurationExtension) { +TEST_P(GnssHalTest, TestGnssConfigurationExtension) { auto gnssConfiguration = gnss_hal_->getExtensionGnssConfiguration_2_0(); ASSERT_TRUE(gnssConfiguration.isOk()); sp<IGnssConfiguration_2_0> iGnssConfiguration = gnssConfiguration; @@ -96,7 +97,7 @@ TEST_F(GnssHalTest, TestGnssConfigurationExtension) { * TestGnssConfiguration_setSuplEs_Deprecation: * Calls setSuplEs and verifies that it returns false. */ -TEST_F(GnssHalTest, TestGnssConfiguration_setSuplEs_Deprecation) { +TEST_P(GnssHalTest, TestGnssConfiguration_setSuplEs_Deprecation) { auto gnssConfiguration = gnss_hal_->getExtensionGnssConfiguration_2_0(); ASSERT_TRUE(gnssConfiguration.isOk()); sp<IGnssConfiguration_2_0> iGnssConfiguration = gnssConfiguration; @@ -111,7 +112,7 @@ TEST_F(GnssHalTest, TestGnssConfiguration_setSuplEs_Deprecation) { * TestGnssConfiguration_setGpsLock_Deprecation: * Calls setGpsLock and verifies that it returns false. */ -TEST_F(GnssHalTest, TestGnssConfiguration_setGpsLock_Deprecation) { +TEST_P(GnssHalTest, TestGnssConfiguration_setGpsLock_Deprecation) { auto gnssConfiguration = gnss_hal_->getExtensionGnssConfiguration_2_0(); ASSERT_TRUE(gnssConfiguration.isOk()); sp<IGnssConfiguration_2_0> iGnssConfiguration = gnssConfiguration; @@ -130,7 +131,7 @@ TEST_F(GnssHalTest, TestGnssConfiguration_setGpsLock_Deprecation) { * @2.0::IAGnssRil interface due to the deprecation of framework network API methods needed * to support the @1.0::IAGnssRil interface. */ -TEST_F(GnssHalTest, TestAGnssRilExtension) { +TEST_P(GnssHalTest, TestAGnssRilExtension) { auto agnssRil_2_0 = gnss_hal_->getExtensionAGnssRil_2_0(); ASSERT_TRUE(agnssRil_2_0.isOk()); sp<IAGnssRil_2_0> iAGnssRil_2_0 = agnssRil_2_0; @@ -148,7 +149,7 @@ TEST_F(GnssHalTest, TestAGnssRilExtension) { * 1. Updates GNSS HAL that a network has connected. * 2. Updates GNSS HAL that network has disconnected. */ -TEST_F(GnssHalTest, TestAGnssRil_UpdateNetworkState_2_0) { +TEST_P(GnssHalTest, TestAGnssRil_UpdateNetworkState_2_0) { auto agnssRil = gnss_hal_->getExtensionAGnssRil_2_0(); ASSERT_TRUE(agnssRil.isOk()); sp<IAGnssRil_2_0> iAGnssRil = agnssRil; @@ -180,7 +181,7 @@ TEST_F(GnssHalTest, TestAGnssRil_UpdateNetworkState_2_0) { * 2. constellation is valid. * 3. state is valid. */ -TEST_F(GnssHalTest, TestGnssMeasurementFields) { +TEST_P(GnssHalTest, TestGnssMeasurementFields) { const int kFirstGnssMeasurementTimeoutSeconds = 10; auto gnssMeasurement = gnss_hal_->getExtensionGnssMeasurement_2_0(); @@ -234,7 +235,7 @@ TEST_F(GnssHalTest, TestGnssMeasurementFields) { * @2.0::IAGnss interface due to the deprecation of framework network API methods needed * to support the @1.0::IAGnss interface. */ -TEST_F(GnssHalTest, TestAGnssExtension) { +TEST_P(GnssHalTest, TestAGnssExtension) { auto agnss_2_0 = gnss_hal_->getExtensionAGnss_2_0(); ASSERT_TRUE(agnss_2_0.isOk()); sp<IAGnss_2_0> iAGnss_2_0 = agnss_2_0; @@ -258,7 +259,7 @@ TEST_F(GnssHalTest, TestAGnssExtension) { * TestGnssNiExtension_Deprecation: * Gets the @1.0::IGnssNi extension and verifies that it is a nullptr. */ -TEST_F(GnssHalTest, TestGnssNiExtension_Deprecation) { +TEST_P(GnssHalTest, TestGnssNiExtension_Deprecation) { // Verify IGnssNi 1.0 is not supported. auto gnssNi = gnss_hal_->getExtensionGnssNi(); ASSERT_TRUE(!gnssNi.isOk() || ((sp<IGnssNi>)gnssNi) == nullptr); @@ -269,7 +270,7 @@ TEST_F(GnssHalTest, TestGnssNiExtension_Deprecation) { * Gets the GnssVisibilityControlExtension and if it is not null, verifies that it supports * the gnss.visibility_control@1.0::IGnssVisibilityControl interface by invoking a method. */ -TEST_F(GnssHalTest, TestGnssVisibilityControlExtension) { +TEST_P(GnssHalTest, TestGnssVisibilityControlExtension) { auto gnssVisibilityControl = gnss_hal_->getExtensionVisibilityControl(); ASSERT_TRUE(gnssVisibilityControl.isOk()); sp<IGnssVisibilityControl> iGnssVisibilityControl = gnssVisibilityControl; @@ -290,7 +291,7 @@ TEST_F(GnssHalTest, TestGnssVisibilityControlExtension) { * capabilities are reported and the mandatory LOS_SATS or the EXCESS_PATH_LENGTH * capability flag is set. */ -TEST_F(GnssHalTest, TestGnssMeasurementCorrectionsCapabilities) { +TEST_P(GnssHalTest, TestGnssMeasurementCorrectionsCapabilities) { if (!(gnss_cb_->last_capabilities_ & IGnssCallback::Capabilities::MEASUREMENT_CORRECTIONS)) { return; } @@ -318,7 +319,7 @@ TEST_F(GnssHalTest, TestGnssMeasurementCorrectionsCapabilities) { * If measurement corrections capability is supported, verifies that it supports the * gnss.measurement_corrections@1.0::IMeasurementCorrections interface by invoking a method. */ -TEST_F(GnssHalTest, TestGnssMeasurementCorrections) { +TEST_P(GnssHalTest, TestGnssMeasurementCorrections) { if (!(gnss_cb_->last_capabilities_ & IGnssCallback::Capabilities::MEASUREMENT_CORRECTIONS)) { return; } @@ -348,7 +349,7 @@ TEST_F(GnssHalTest, TestGnssMeasurementCorrections) { * Sets a GnssMeasurementCallback, waits for a GnssData object, and verifies the flags in member * elapsedRealitme are valid. */ -TEST_F(GnssHalTest, TestGnssDataElapsedRealtimeFlags) { +TEST_P(GnssHalTest, TestGnssDataElapsedRealtimeFlags) { const int kFirstGnssMeasurementTimeoutSeconds = 10; auto gnssMeasurement = gnss_hal_->getExtensionGnssMeasurement_2_0(); @@ -383,7 +384,7 @@ TEST_F(GnssHalTest, TestGnssDataElapsedRealtimeFlags) { iGnssMeasurement->close(); } -TEST_F(GnssHalTest, TestGnssLocationElapsedRealtime) { +TEST_P(GnssHalTest, TestGnssLocationElapsedRealtime) { StartAndCheckFirstLocation(); ASSERT_TRUE((int)gnss_cb_->last_location_.elapsedRealtime.flags <= @@ -399,7 +400,7 @@ TEST_F(GnssHalTest, TestGnssLocationElapsedRealtime) { } // This test only verify that injectBestLocation_2_0 does not crash. -TEST_F(GnssHalTest, TestInjectBestLocation_2_0) { +TEST_P(GnssHalTest, TestInjectBestLocation_2_0) { StartAndCheckFirstLocation(); gnss_hal_->injectBestLocation_2_0(gnss_cb_->last_location_); StopAndClearLocations(); @@ -410,7 +411,7 @@ TEST_F(GnssHalTest, TestInjectBestLocation_2_0) { * Gets the @2.0::IGnssBatching extension and verifies that it doesn't return an error. Support * for this interface is optional. */ -TEST_F(GnssHalTest, TestGnssBatchingExtension) { +TEST_P(GnssHalTest, TestGnssBatchingExtension) { auto gnssBatching_2_0 = gnss_hal_->getExtensionGnssBatching_2_0(); ASSERT_TRUE(gnssBatching_2_0.isOk()); } @@ -422,7 +423,7 @@ TEST_F(GnssHalTest, TestGnssBatchingExtension) { * NO_LOCATION_PERIOD_SEC and verfiy that no location is received. Also perform validity checks on * each received location. */ -TEST_F(GnssHalTest, GetLocationLowPower) { +TEST_P(GnssHalTest, GetLocationLowPower) { if (!(gnss_cb_->last_capabilities_ & IGnssCallback::Capabilities::LOW_POWER_MODE)) { ALOGI("Test GetLocationLowPower skipped. LOW_POWER_MODE capability not supported."); return; @@ -513,7 +514,7 @@ GnssConstellationType_1_0 MapConstellationType(GnssConstellationType_2_0 constel * or a source with constellation == UNKNOWN if none are found sufficient times */ IGnssConfiguration_1_1::BlacklistedSource FindStrongFrequentNonGpsSource( - const list<hidl_vec<IGnssCallback_2_0::GnssSvInfo>>& sv_info_lists, + const std::list<hidl_vec<IGnssCallback_2_0::GnssSvInfo>>& sv_info_lists, const int min_observations) { struct ComparableBlacklistedSource { IGnssConfiguration_1_1::BlacklistedSource id; @@ -599,7 +600,7 @@ IGnssConfiguration_1_1::BlacklistedSource FindStrongFrequentNonGpsSource( * 5b) Retry a few times, in case GNSS search strategy takes a while to reacquire even the * formerly strongest satellite */ -TEST_F(GnssHalTest, BlacklistIndividualSatellites) { +TEST_P(GnssHalTest, BlacklistIndividualSatellites) { if (!(gnss_cb_->last_capabilities_ & IGnssCallback::Capabilities::SATELLITE_BLACKLIST)) { ALOGI("Test BlacklistIndividualSatellites skipped. SATELLITE_BLACKLIST capability" " not supported."); @@ -626,7 +627,7 @@ TEST_F(GnssHalTest, BlacklistIndividualSatellites) { */ const int kGnssSvStatusTimeout = 2; - list<hidl_vec<IGnssCallback_2_0::GnssSvInfo>> sv_info_lists; + std::list<hidl_vec<IGnssCallback_2_0::GnssSvInfo>> sv_info_lists; int count = gnss_cb_->sv_info_list_cbq_.retrieve(sv_info_lists, sv_info_list_cbq_size, kGnssSvStatusTimeout); ASSERT_EQ(count, sv_info_list_cbq_size); @@ -744,7 +745,7 @@ TEST_F(GnssHalTest, BlacklistIndividualSatellites) { * GnssStatus does not use any constellation but GPS. * 4a & b) Clean up by turning off location, and send in empty blacklist. */ -TEST_F(GnssHalTest, BlacklistConstellation) { +TEST_P(GnssHalTest, BlacklistConstellation) { if (!(gnss_cb_->last_capabilities_ & IGnssCallback::Capabilities::SATELLITE_BLACKLIST)) { ALOGI("Test BlacklistConstellation skipped. SATELLITE_BLACKLIST capability not supported."); return; diff --git a/gnss/common/utils/vts/include/GnssCallbackEventQueue.h b/gnss/common/utils/vts/include/GnssCallbackEventQueue.h index b1d8ed4e36..3dc429b05e 100644 --- a/gnss/common/utils/vts/include/GnssCallbackEventQueue.h +++ b/gnss/common/utils/vts/include/GnssCallbackEventQueue.h @@ -53,7 +53,7 @@ class GnssCallbackEventQueue { * timeout_seconds to retrieve each event. If timeout occurs, it returns the number of * items retrieved which will be less than count. */ - int retrieve(list<T>& event_list, int count, int timeout_seconds); + int retrieve(std::list<T>& event_list, int count, int timeout_seconds); /* Returns the number of events pending to be retrieved from the callback event queue. */ int size() const; @@ -97,7 +97,7 @@ bool GnssCallbackEventQueue<T>::retrieve(T& event, int timeout_seconds) { } template <class T> -int GnssCallbackEventQueue<T>::retrieve(list<T>& event_list, int count, int timeout_seconds) { +int GnssCallbackEventQueue<T>::retrieve(std::list<T>& event_list, int count, int timeout_seconds) { for (int i = 0; i < count; ++i) { T event; if (!retrieve(event, timeout_seconds)) { diff --git a/graphics/composer/2.2/vts/functional/Android.bp b/graphics/composer/2.2/vts/functional/Android.bp index 28728803e8..21ba9f3d7d 100644 --- a/graphics/composer/2.2/vts/functional/Android.bp +++ b/graphics/composer/2.2/vts/functional/Android.bp @@ -30,8 +30,6 @@ cc_test { "libfmq", "libgui", "libhidlbase", - "libhidltransport", - "libhwbinder", "libprocessgroup", "libsync", "libui", diff --git a/graphics/composer/2.4/IComposer.hal b/graphics/composer/2.4/IComposer.hal index 34801daa27..d3b3cb60a7 100644 --- a/graphics/composer/2.4/IComposer.hal +++ b/graphics/composer/2.4/IComposer.hal @@ -17,12 +17,10 @@ package android.hardware.graphics.composer@2.4; import IComposerClient; - import @2.1::Error; import @2.3::IComposer; interface IComposer extends @2.3::IComposer { - /** * Creates a v2.4 client of the composer. Supersedes @2.3::createClient. * diff --git a/graphics/composer/2.4/IComposerClient.hal b/graphics/composer/2.4/IComposerClient.hal index 8fe0976781..60445f5327 100644 --- a/graphics/composer/2.4/IComposerClient.hal +++ b/graphics/composer/2.4/IComposerClient.hal @@ -21,13 +21,12 @@ import @2.1::Error; import @2.3::IComposerClient; interface IComposerClient extends @2.3::IComposerClient { - /** * Required capabilities which are supported by the display. The * particular set of supported capabilities for a given display may be * retrieved using getDisplayCapabilities. */ - enum DisplayCapability : uint32_t { + enum DisplayCapability : @2.3::IComposerClient.DisplayCapability { /** * Indicates that the display supports protected contents. * When returned, hardware composer must be able to accept client target @@ -37,6 +36,20 @@ interface IComposerClient extends @2.3::IComposerClient { }; /** + * Supersedes {@link @2.1::IComposerClient.DisplayType}. + */ + enum DisplayConnectionType : uint32_t { + /** + * Display is connected through internal port, e.g. DSI, eDP. + */ + INTERNAL = 0, + /** + * Display is connected through external port, e.g. HDMI, DisplayPort. + */ + EXTERNAL = 1, + }; + + /** * Provides a list of supported capabilities (as described in the * definition of DisplayCapability above). This list must not change after * initialization. @@ -46,6 +59,14 @@ interface IComposerClient extends @2.3::IComposerClient { * @return capabilities is a list of supported capabilities. */ getDisplayCapabilities_2_4(Display display) - generates (Error error, - vec<DisplayCapability> capabilities); + generates (Error error, vec<DisplayCapability> capabilities); + + /** + * Returns whether the given physical display is internal or external. + * + * @return error is NONE upon success. Otherwise, + * BAD_DISPLAY when the given display is invalid or virtual. + * @return type is the connection type of the display. + */ + getDisplayConnectionType(Display display) generates (Error error, DisplayConnectionType type); }; diff --git a/graphics/composer/2.4/default/Android.bp b/graphics/composer/2.4/default/Android.bp index a44e68794e..a30609b1a2 100644 --- a/graphics/composer/2.4/default/Android.bp +++ b/graphics/composer/2.4/default/Android.bp @@ -37,7 +37,6 @@ cc_binary { "libfmq", "libhardware", "libhidlbase", - "libhidltransport", "libhwc2on1adapter", "libhwc2onfbadapter", "liblog", diff --git a/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerClient.h b/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerClient.h index 7110c80db0..c810186665 100644 --- a/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerClient.h +++ b/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerClient.h @@ -46,6 +46,14 @@ class ComposerClientImpl : public V2_3::hal::detail::ComposerClientImpl<Interfac return Void(); } + Return<void> getDisplayConnectionType( + Display display, IComposerClient::getDisplayConnectionType_cb hidl_cb) override { + IComposerClient::DisplayConnectionType type; + Error error = mHal->getDisplayConnectionType(display, &type); + hidl_cb(error, type); + return Void(); + } + static std::unique_ptr<ComposerClientImpl> create(Hal* hal) { auto client = std::make_unique<ComposerClientImpl>(hal); return client->init() ? std::move(client) : nullptr; diff --git a/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerHal.h b/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerHal.h index 0074808309..c3bb535360 100644 --- a/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerHal.h +++ b/graphics/composer/2.4/utils/hal/include/composer-hal/2.4/ComposerHal.h @@ -38,6 +38,8 @@ class ComposerHal : public V2_3::hal::ComposerHal { public: virtual Error getDisplayCapabilities_2_4( Display display, std::vector<IComposerClient::DisplayCapability>* outCapabilities) = 0; + virtual Error getDisplayConnectionType(Display display, + IComposerClient::DisplayConnectionType* outType) = 0; }; } // namespace hal diff --git a/graphics/composer/2.4/utils/passthrough/include/composer-passthrough/2.4/HwcHal.h b/graphics/composer/2.4/utils/passthrough/include/composer-passthrough/2.4/HwcHal.h index 65d47d781d..fd05f6630b 100644 --- a/graphics/composer/2.4/utils/passthrough/include/composer-passthrough/2.4/HwcHal.h +++ b/graphics/composer/2.4/utils/passthrough/include/composer-passthrough/2.4/HwcHal.h @@ -62,15 +62,34 @@ class HwcHalImpl : public V2_3::passthrough::detail::HwcHalImpl<Hal> { return Error::NONE; } + Error getDisplayConnectionType(Display display, + IComposerClient::DisplayConnectionType* outType) override { + if (!mDispatch.getDisplayConnectionType) { + return Error::UNSUPPORTED; + } + + uint32_t type = HWC2_DISPLAY_CONNECTION_TYPE_INTERNAL; + int32_t error = mDispatch.getDisplayConnectionType(mDevice, display, &type); + *outType = static_cast<IComposerClient::DisplayConnectionType>(type); + return static_cast<Error>(error); + } + protected: bool initDispatch() override { if (!BaseType2_3::initDispatch()) { return false; } + + this->initOptionalDispatch(HWC2_FUNCTION_GET_DISPLAY_CONNECTION_TYPE, + &mDispatch.getDisplayConnectionType); return true; } private: + struct { + HWC2_PFN_GET_DISPLAY_CONNECTION_TYPE getDisplayConnectionType; + } mDispatch = {}; + using BaseType2_1 = V2_1::passthrough::detail::HwcHalImpl<Hal>; using BaseType2_3 = V2_3::passthrough::detail::HwcHalImpl<Hal>; using BaseType2_1::mDevice; diff --git a/graphics/composer/2.4/utils/vts/ComposerVts.cpp b/graphics/composer/2.4/utils/vts/ComposerVts.cpp index ee4f3a3959..937b50efb4 100644 --- a/graphics/composer/2.4/utils/vts/ComposerVts.cpp +++ b/graphics/composer/2.4/utils/vts/ComposerVts.cpp @@ -51,7 +51,6 @@ sp<IComposerClient> ComposerClient::getRaw() const { Error ComposerClient::getDisplayCapabilities( Display display, std::vector<IComposerClient::DisplayCapability>* outCapabilities) { - std::vector<IComposerClient::DisplayCapability> capabilities; Error error = Error::NONE; mClient->getDisplayCapabilities_2_4(display, [&](const auto& tmpError, const auto& tmpCapabilities) { @@ -61,6 +60,16 @@ Error ComposerClient::getDisplayCapabilities( return error; } +Error ComposerClient::getDisplayConnectionType(Display display, + IComposerClient::DisplayConnectionType* outType) { + Error error = Error::NONE; + mClient->getDisplayConnectionType(display, [&](const auto& tmpError, const auto& tmpType) { + error = tmpError; + *outType = tmpType; + }); + return error; +} + } // namespace vts } // namespace V2_4 } // namespace composer diff --git a/graphics/composer/2.4/utils/vts/include/composer-vts/2.4/ComposerVts.h b/graphics/composer/2.4/utils/vts/include/composer-vts/2.4/ComposerVts.h index 0a301c6ca7..a7d7f8644f 100644 --- a/graphics/composer/2.4/utils/vts/include/composer-vts/2.4/ComposerVts.h +++ b/graphics/composer/2.4/utils/vts/include/composer-vts/2.4/ComposerVts.h @@ -71,6 +71,9 @@ class ComposerClient : public V2_3::vts::ComposerClient { Display display, std::vector<IComposerClient::DisplayCapability>* outDisplayCapabilities); + Error getDisplayConnectionType(Display display, + IComposerClient::DisplayConnectionType* outType); + private: const sp<IComposerClient> mClient; }; diff --git a/graphics/composer/2.4/vts/functional/Android.bp b/graphics/composer/2.4/vts/functional/Android.bp index 6ee78738cc..921c42155f 100644 --- a/graphics/composer/2.4/vts/functional/Android.bp +++ b/graphics/composer/2.4/vts/functional/Android.bp @@ -22,7 +22,6 @@ cc_test { // TODO(b/64437680): Assume these libs are always available on the device. shared_libs: [ "libfmq", - "libhidltransport", "libsync", ], static_libs: [ diff --git a/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp b/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp index 0fccc58637..76c0039cf2 100644 --- a/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp +++ b/graphics/composer/2.4/vts/functional/VtsHalGraphicsComposerV2_4TargetTest.cpp @@ -179,6 +179,16 @@ TEST_F(GraphicsComposerHidlTest, getDisplayCapabilitiesBadDisplay) { EXPECT_EQ(Error::BAD_DISPLAY, error); } +TEST_F(GraphicsComposerHidlTest, getDisplayConnectionType) { + IComposerClient::DisplayConnectionType type; + EXPECT_EQ(Error::BAD_DISPLAY, + mComposerClient->getDisplayConnectionType(mInvalidDisplayId, &type)); + + for (Display display : mComposerCallback->getDisplays()) { + EXPECT_EQ(Error::NONE, mComposerClient->getDisplayConnectionType(display, &type)); + } +} + } // namespace } // namespace vts } // namespace V2_4 diff --git a/graphics/mapper/2.0/utils/hal/include/mapper-hal/2.0/Mapper.h b/graphics/mapper/2.0/utils/hal/include/mapper-hal/2.0/Mapper.h index 5ad2a6564c..81341742c7 100644 --- a/graphics/mapper/2.0/utils/hal/include/mapper-hal/2.0/Mapper.h +++ b/graphics/mapper/2.0/utils/hal/include/mapper-hal/2.0/Mapper.h @@ -80,17 +80,21 @@ class MapperImpl : public Interface { } Return<Error> freeBuffer(void* buffer) override { - native_handle_t* bufferHandle = removeImportedBuffer(buffer); + native_handle_t* bufferHandle = getImportedBuffer(buffer); if (!bufferHandle) { return Error::BAD_BUFFER; } - return mHal->freeBuffer(bufferHandle); + Error error = mHal->freeBuffer(bufferHandle); + if (error == Error::NONE) { + removeImportedBuffer(buffer); + } + return error; } Return<void> lock(void* buffer, uint64_t cpuUsage, const V2_0::IMapper::Rect& accessRegion, const hidl_handle& acquireFence, IMapper::lock_cb hidl_cb) override { - const native_handle_t* bufferHandle = getImportedBuffer(buffer); + const native_handle_t* bufferHandle = getConstImportedBuffer(buffer); if (!bufferHandle) { hidl_cb(Error::BAD_BUFFER, nullptr); return Void(); @@ -112,7 +116,7 @@ class MapperImpl : public Interface { Return<void> lockYCbCr(void* buffer, uint64_t cpuUsage, const V2_0::IMapper::Rect& accessRegion, const hidl_handle& acquireFence, IMapper::lockYCbCr_cb hidl_cb) override { - const native_handle_t* bufferHandle = getImportedBuffer(buffer); + const native_handle_t* bufferHandle = getConstImportedBuffer(buffer); if (!bufferHandle) { hidl_cb(Error::BAD_BUFFER, YCbCrLayout{}); return Void(); @@ -132,7 +136,7 @@ class MapperImpl : public Interface { } Return<void> unlock(void* buffer, IMapper::unlock_cb hidl_cb) override { - const native_handle_t* bufferHandle = getImportedBuffer(buffer); + const native_handle_t* bufferHandle = getConstImportedBuffer(buffer); if (!bufferHandle) { hidl_cb(Error::BAD_BUFFER, nullptr); return Void(); @@ -160,7 +164,11 @@ class MapperImpl : public Interface { return static_cast<native_handle_t*>(buffer); } - virtual const native_handle_t* getImportedBuffer(void* buffer) const { + virtual native_handle_t* getImportedBuffer(void* buffer) const { + return static_cast<native_handle_t*>(buffer); + } + + virtual const native_handle_t* getConstImportedBuffer(void* buffer) const { return static_cast<const native_handle_t*>(buffer); } diff --git a/graphics/mapper/2.0/utils/passthrough/include/mapper-passthrough/2.0/Gralloc1Hal.h b/graphics/mapper/2.0/utils/passthrough/include/mapper-passthrough/2.0/Gralloc1Hal.h index d9beb4f678..db7e67dd9c 100644 --- a/graphics/mapper/2.0/utils/passthrough/include/mapper-passthrough/2.0/Gralloc1Hal.h +++ b/graphics/mapper/2.0/utils/passthrough/include/mapper-passthrough/2.0/Gralloc1Hal.h @@ -282,14 +282,16 @@ class Gralloc1HalImpl : public Hal { } if (flex.planes[0].component != FLEX_COMPONENT_Y || - flex.planes[1].component != FLEX_COMPONENT_Cb || - flex.planes[2].component != FLEX_COMPONENT_Cr) { + ((flex.planes[1].component != FLEX_COMPONENT_Cb || flex.planes[2].component != FLEX_COMPONENT_Cr) && + (flex.planes[2].component != FLEX_COMPONENT_Cb || flex.planes[1].component != FLEX_COMPONENT_Cr))) { return false; } const auto& y = flex.planes[0]; - const auto& cb = flex.planes[1]; - const auto& cr = flex.planes[2]; + const auto& cb = (flex.planes[1].component == FLEX_COMPONENT_Cb)? + flex.planes[1] : flex.planes[2]; + const auto& cr = (flex.planes[2].component == FLEX_COMPONENT_Cr)? + flex.planes[2] : flex.planes[1]; if (cb.h_increment != cr.h_increment || cb.v_increment != cr.v_increment) { return false; diff --git a/graphics/mapper/2.0/utils/passthrough/include/mapper-passthrough/2.0/GrallocLoader.h b/graphics/mapper/2.0/utils/passthrough/include/mapper-passthrough/2.0/GrallocLoader.h index e8b1b4b837..85a91c3ada 100644 --- a/graphics/mapper/2.0/utils/passthrough/include/mapper-passthrough/2.0/GrallocLoader.h +++ b/graphics/mapper/2.0/utils/passthrough/include/mapper-passthrough/2.0/GrallocLoader.h @@ -68,7 +68,14 @@ class GrallocImportedBufferPool { return mBufferHandles.erase(bufferHandle) == 1 ? bufferHandle : nullptr; } - const native_handle_t* get(void* buffer) { + native_handle_t* get(void* buffer) { + auto bufferHandle = static_cast<native_handle_t*>(buffer); + + std::lock_guard<std::mutex> lock(mMutex); + return mBufferHandles.count(bufferHandle) == 1 ? bufferHandle : nullptr; + } + + const native_handle_t* getConst(void* buffer) { auto bufferHandle = static_cast<const native_handle_t*>(buffer); std::lock_guard<std::mutex> lock(mMutex); @@ -92,9 +99,13 @@ class GrallocMapper : public T { return GrallocImportedBufferPool::getInstance().remove(buffer); } - const native_handle_t* getImportedBuffer(void* buffer) const override { + native_handle_t* getImportedBuffer(void* buffer) const override { return GrallocImportedBufferPool::getInstance().get(buffer); } + + const native_handle_t* getConstImportedBuffer(void* buffer) const override { + return GrallocImportedBufferPool::getInstance().getConst(buffer); + } }; class GrallocLoader { diff --git a/graphics/mapper/2.1/utils/passthrough/include/mapper-passthrough/2.1/Gralloc0Hal.h b/graphics/mapper/2.1/utils/passthrough/include/mapper-passthrough/2.1/Gralloc0Hal.h index 18fbb6d035..8540068a48 100644 --- a/graphics/mapper/2.1/utils/passthrough/include/mapper-passthrough/2.1/Gralloc0Hal.h +++ b/graphics/mapper/2.1/utils/passthrough/include/mapper-passthrough/2.1/Gralloc0Hal.h @@ -37,6 +37,10 @@ class Gralloc0HalImpl : public V2_0::passthrough::detail::Gralloc0HalImpl<Hal> { Error validateBufferSize(const native_handle_t* bufferHandle, const IMapper::BufferDescriptorInfo& descriptorInfo, uint32_t stride) override { + if (descriptorInfo.layerCount != 1) { + return Error::BAD_VALUE; + } + if (!mModule->validateBufferSize) { return Error::NONE; } diff --git a/graphics/mapper/4.0/IMapper.hal b/graphics/mapper/4.0/IMapper.hal index f5df04b853..85c7c81e85 100644 --- a/graphics/mapper/4.0/IMapper.hal +++ b/graphics/mapper/4.0/IMapper.hal @@ -202,6 +202,9 @@ interface IMapper { * the buffer. If the bytesPerStride is unknown or variable, a value of -1 * should be returned. * + * The locked buffer must adhere to the format requested at allocation time + * in the BufferDescriptorInfo. + * * @param buffer Buffer to lock. * @param cpuUsage CPU usage flags to request. See +ndk * libnativewindow#AHardwareBuffer_UsageFlags for possible values. diff --git a/health/2.0/default/Android.bp b/health/2.0/default/Android.bp index 1c455d30ae..3c5877e1da 100644 --- a/health/2.0/default/Android.bp +++ b/health/2.0/default/Android.bp @@ -31,7 +31,11 @@ cc_library_static { vendor_available: true, srcs: [ "Health.cpp", - "healthd_common.cpp", + "healthd_common_adapter.cpp", + ], + + whole_static_libs: [ + "libhealthloop", ], export_include_dirs: ["include"], diff --git a/health/2.0/default/Health.cpp b/health/2.0/default/Health.cpp index a2b81d1ba1..c0c5a404c9 100644 --- a/health/2.0/default/Health.cpp +++ b/health/2.0/default/Health.cpp @@ -148,7 +148,14 @@ Return<Result> Health::update() { // Retrieve all information and call healthd_mode_ops->battery_update, which calls // notifyListeners. - bool chargerOnline = battery_monitor_->update(); + battery_monitor_->updateValues(); + struct BatteryProperties props = getBatteryProperties(battery_monitor_.get()); + bool log = healthd_board_battery_update(&props); + if (log) { + battery_monitor_->logValues(); + } + healthd_mode_ops->battery_update(&props); + bool chargerOnline = battery_monitor_->isChargerOnline(); // adjust uevent / wakealarm periods healthd_battery_update_internal(chargerOnline); diff --git a/health/2.0/default/healthd_common.cpp b/health/2.0/default/healthd_common.cpp deleted file mode 100644 index b5fdc8ecb8..0000000000 --- a/health/2.0/default/healthd_common.cpp +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright (C) 2013 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. - */ - -#define LOG_TAG "android.hardware.health@2.0-impl" -#define KLOG_LEVEL 6 - -#include <healthd/BatteryMonitor.h> -#include <healthd/healthd.h> - -#include <batteryservice/BatteryService.h> -#include <cutils/klog.h> -#include <cutils/uevent.h> -#include <errno.h> -#include <libgen.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/epoll.h> -#include <sys/timerfd.h> -#include <unistd.h> -#include <utils/Errors.h> - -#include <health2/Health.h> - -using namespace android; - -// Periodic chores fast interval in seconds -#define DEFAULT_PERIODIC_CHORES_INTERVAL_FAST (60 * 1) -// Periodic chores fast interval in seconds -#define DEFAULT_PERIODIC_CHORES_INTERVAL_SLOW (60 * 10) - -static struct healthd_config healthd_config = { - .periodic_chores_interval_fast = DEFAULT_PERIODIC_CHORES_INTERVAL_FAST, - .periodic_chores_interval_slow = DEFAULT_PERIODIC_CHORES_INTERVAL_SLOW, - .batteryStatusPath = String8(String8::kEmptyString), - .batteryHealthPath = String8(String8::kEmptyString), - .batteryPresentPath = String8(String8::kEmptyString), - .batteryCapacityPath = String8(String8::kEmptyString), - .batteryVoltagePath = String8(String8::kEmptyString), - .batteryTemperaturePath = String8(String8::kEmptyString), - .batteryTechnologyPath = String8(String8::kEmptyString), - .batteryCurrentNowPath = String8(String8::kEmptyString), - .batteryCurrentAvgPath = String8(String8::kEmptyString), - .batteryChargeCounterPath = String8(String8::kEmptyString), - .batteryFullChargePath = String8(String8::kEmptyString), - .batteryCycleCountPath = String8(String8::kEmptyString), - .energyCounter = NULL, - .boot_min_cap = 0, - .screen_on = NULL, -}; - -static int eventct; -static int epollfd; - -#define POWER_SUPPLY_SUBSYSTEM "power_supply" - -static int uevent_fd; -static int wakealarm_fd; - -// -1 for no epoll timeout -static int awake_poll_interval = -1; - -static int wakealarm_wake_interval = DEFAULT_PERIODIC_CHORES_INTERVAL_FAST; - -using ::android::hardware::health::V2_0::implementation::Health; - -struct healthd_mode_ops* healthd_mode_ops = nullptr; - -int healthd_register_event(int fd, void (*handler)(uint32_t), EventWakeup wakeup) { - struct epoll_event ev; - - ev.events = EPOLLIN; - - if (wakeup == EVENT_WAKEUP_FD) ev.events |= EPOLLWAKEUP; - - ev.data.ptr = (void*)handler; - if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) { - KLOG_ERROR(LOG_TAG, "epoll_ctl failed; errno=%d\n", errno); - return -1; - } - - eventct++; - return 0; -} - -static void wakealarm_set_interval(int interval) { - struct itimerspec itval; - - if (wakealarm_fd == -1) return; - - wakealarm_wake_interval = interval; - - if (interval == -1) interval = 0; - - itval.it_interval.tv_sec = interval; - itval.it_interval.tv_nsec = 0; - itval.it_value.tv_sec = interval; - itval.it_value.tv_nsec = 0; - - if (timerfd_settime(wakealarm_fd, 0, &itval, NULL) == -1) - KLOG_ERROR(LOG_TAG, "wakealarm_set_interval: timerfd_settime failed\n"); -} - -void healthd_battery_update_internal(bool charger_online) { - // Fast wake interval when on charger (watch for overheat); - // slow wake interval when on battery (watch for drained battery). - - int new_wake_interval = charger_online ? healthd_config.periodic_chores_interval_fast - : healthd_config.periodic_chores_interval_slow; - - if (new_wake_interval != wakealarm_wake_interval) wakealarm_set_interval(new_wake_interval); - - // During awake periods poll at fast rate. If wake alarm is set at fast - // rate then just use the alarm; if wake alarm is set at slow rate then - // poll at fast rate while awake and let alarm wake up at slow rate when - // asleep. - - if (healthd_config.periodic_chores_interval_fast == -1) - awake_poll_interval = -1; - else - awake_poll_interval = new_wake_interval == healthd_config.periodic_chores_interval_fast - ? -1 - : healthd_config.periodic_chores_interval_fast * 1000; -} - -static void healthd_battery_update(void) { - Health::getImplementation()->update(); -} - -static void periodic_chores() { - healthd_battery_update(); -} - -#define UEVENT_MSG_LEN 2048 -static void uevent_event(uint32_t /*epevents*/) { - char msg[UEVENT_MSG_LEN + 2]; - char* cp; - int n; - - n = uevent_kernel_multicast_recv(uevent_fd, msg, UEVENT_MSG_LEN); - if (n <= 0) return; - if (n >= UEVENT_MSG_LEN) /* overflow -- discard */ - return; - - msg[n] = '\0'; - msg[n + 1] = '\0'; - cp = msg; - - while (*cp) { - if (!strcmp(cp, "SUBSYSTEM=" POWER_SUPPLY_SUBSYSTEM)) { - healthd_battery_update(); - break; - } - - /* advance to after the next \0 */ - while (*cp++) - ; - } -} - -static void uevent_init(void) { - uevent_fd = uevent_open_socket(64 * 1024, true); - - if (uevent_fd < 0) { - KLOG_ERROR(LOG_TAG, "uevent_init: uevent_open_socket failed\n"); - return; - } - - fcntl(uevent_fd, F_SETFL, O_NONBLOCK); - if (healthd_register_event(uevent_fd, uevent_event, EVENT_WAKEUP_FD)) - KLOG_ERROR(LOG_TAG, "register for uevent events failed\n"); -} - -static void wakealarm_event(uint32_t /*epevents*/) { - unsigned long long wakeups; - - if (read(wakealarm_fd, &wakeups, sizeof(wakeups)) == -1) { - KLOG_ERROR(LOG_TAG, "wakealarm_event: read wakealarm fd failed\n"); - return; - } - - periodic_chores(); -} - -static void wakealarm_init(void) { - wakealarm_fd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK); - if (wakealarm_fd == -1) { - KLOG_ERROR(LOG_TAG, "wakealarm_init: timerfd_create failed\n"); - return; - } - - if (healthd_register_event(wakealarm_fd, wakealarm_event, EVENT_WAKEUP_FD)) - KLOG_ERROR(LOG_TAG, "Registration of wakealarm event failed\n"); - - wakealarm_set_interval(healthd_config.periodic_chores_interval_fast); -} - -static void healthd_mainloop(void) { - int nevents = 0; - while (1) { - struct epoll_event events[eventct]; - int timeout = awake_poll_interval; - int mode_timeout; - - /* Don't wait for first timer timeout to run periodic chores */ - if (!nevents) periodic_chores(); - - healthd_mode_ops->heartbeat(); - - mode_timeout = healthd_mode_ops->preparetowait(); - if (timeout < 0 || (mode_timeout > 0 && mode_timeout < timeout)) timeout = mode_timeout; - nevents = epoll_wait(epollfd, events, eventct, timeout); - if (nevents == -1) { - if (errno == EINTR) continue; - KLOG_ERROR(LOG_TAG, "healthd_mainloop: epoll_wait failed\n"); - break; - } - - for (int n = 0; n < nevents; ++n) { - if (events[n].data.ptr) (*(void (*)(int))events[n].data.ptr)(events[n].events); - } - } - - return; -} - -static int healthd_init() { - epollfd = epoll_create1(EPOLL_CLOEXEC); - if (epollfd == -1) { - KLOG_ERROR(LOG_TAG, "epoll_create1 failed; errno=%d\n", errno); - return -1; - } - - healthd_mode_ops->init(&healthd_config); - wakealarm_init(); - uevent_init(); - - return 0; -} - -int healthd_main() { - int ret; - - klog_set_level(KLOG_LEVEL); - - if (!healthd_mode_ops) { - KLOG_ERROR("healthd ops not set, exiting\n"); - exit(1); - } - - ret = healthd_init(); - if (ret) { - KLOG_ERROR("Initialization failed, exiting\n"); - exit(2); - } - - healthd_mainloop(); - KLOG_ERROR("Main loop terminated, exiting\n"); - return 3; -} diff --git a/health/2.0/default/healthd_common_adapter.cpp b/health/2.0/default/healthd_common_adapter.cpp new file mode 100644 index 0000000000..8fc689d50c --- /dev/null +++ b/health/2.0/default/healthd_common_adapter.cpp @@ -0,0 +1,74 @@ +/* + * 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. + */ + +// Support legacy functions in healthd/healthd.h using healthd_mode_ops. +// New code should use HealthLoop directly instead. + +#include <memory> + +#include <cutils/klog.h> +#include <health/HealthLoop.h> +#include <health2/Health.h> +#include <healthd/healthd.h> + +using android::hardware::health::HealthLoop; +using android::hardware::health::V2_0::implementation::Health; + +struct healthd_mode_ops* healthd_mode_ops = nullptr; + +// Adapter of HealthLoop to use legacy healthd_mode_ops. +class HealthLoopAdapter : public HealthLoop { + public: + // Expose internal functions, assuming clients calls them in the same thread + // where StartLoop is called. + int RegisterEvent(int fd, BoundFunction func, EventWakeup wakeup) { + return HealthLoop::RegisterEvent(fd, func, wakeup); + } + void AdjustWakealarmPeriods(bool charger_online) { + return HealthLoop::AdjustWakealarmPeriods(charger_online); + } + protected: + void Init(healthd_config* config) override { healthd_mode_ops->init(config); } + void Heartbeat() override { healthd_mode_ops->heartbeat(); } + int PrepareToWait() override { return healthd_mode_ops->preparetowait(); } + void ScheduleBatteryUpdate() override { Health::getImplementation()->update(); } +}; +static std::unique_ptr<HealthLoopAdapter> health_loop; + +int healthd_register_event(int fd, void (*handler)(uint32_t), EventWakeup wakeup) { + auto wrapped_handler = [handler](auto*, uint32_t epevents) { handler(epevents); }; + return health_loop->RegisterEvent(fd, wrapped_handler, wakeup); +} + +void healthd_battery_update_internal(bool charger_online) { + health_loop->AdjustWakealarmPeriods(charger_online); +} + +int healthd_main() { + if (!healthd_mode_ops) { + KLOG_ERROR("healthd ops not set, exiting\n"); + exit(1); + } + + health_loop = std::make_unique<HealthLoopAdapter>(); + + int ret = health_loop->StartLoop(); + + // Should not reach here. The following will exit(). + health_loop.reset(); + + return ret; +} diff --git a/health/2.0/vts/functional/VtsHalHealthV2_0TargetTest.cpp b/health/2.0/vts/functional/VtsHalHealthV2_0TargetTest.cpp index 74fe4fbd83..6e13a9896d 100644 --- a/health/2.0/vts/functional/VtsHalHealthV2_0TargetTest.cpp +++ b/health/2.0/vts/functional/VtsHalHealthV2_0TargetTest.cpp @@ -257,15 +257,21 @@ bool verifyHealthInfo(const HealthInfo& health_info) { using V1_0::BatteryStatus; using V1_0::BatteryHealth; - if (!((health_info.legacy.batteryChargeCounter > 0) && - (health_info.legacy.batteryCurrent != INT32_MIN) && + if (!((health_info.legacy.batteryCurrent != INT32_MIN) && (0 <= health_info.legacy.batteryLevel && health_info.legacy.batteryLevel <= 100) && verifyEnum<BatteryHealth>(health_info.legacy.batteryHealth) && - (health_info.legacy.batteryStatus != BatteryStatus::UNKNOWN) && verifyEnum<BatteryStatus>(health_info.legacy.batteryStatus))) { return false; } + if (health_info.legacy.batteryPresent) { + // If a battery is present, the battery status must be known. + if (!((health_info.legacy.batteryChargeCounter > 0) && + (health_info.legacy.batteryStatus != BatteryStatus::UNKNOWN))) { + return false; + } + } + return true; } diff --git a/health/utils/libhealthloop/Android.bp b/health/utils/libhealthloop/Android.bp new file mode 100644 index 0000000000..de0f24fcbb --- /dev/null +++ b/health/utils/libhealthloop/Android.bp @@ -0,0 +1,35 @@ +// 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. + +cc_library_static { + name: "libhealthloop", + vendor_available: true, + recovery_available: true, + srcs: [ + "HealthLoop.cpp", + "utils.cpp", + ], + shared_libs: [ + "libcutils", + "libbase", + ], + header_libs: [ + "libbatteryservice_headers", + "libhealthd_headers", + "libutils_headers", + ], + export_include_dirs: [ + "include", + ], +} diff --git a/health/utils/libhealthloop/HealthLoop.cpp b/health/utils/libhealthloop/HealthLoop.cpp new file mode 100644 index 0000000000..3f4b5bc596 --- /dev/null +++ b/health/utils/libhealthloop/HealthLoop.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2013 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. + */ + +#define LOG_TAG "HealthLoop" +#define KLOG_LEVEL 6 + +#include <health/HealthLoop.h> + +#include <errno.h> +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/epoll.h> +#include <sys/timerfd.h> +#include <unistd.h> + +#include <android-base/logging.h> +#include <batteryservice/BatteryService.h> +#include <cutils/klog.h> +#include <cutils/uevent.h> +#include <healthd/healthd.h> +#include <utils/Errors.h> + +#include <health/utils.h> + +using namespace android; +using namespace std::chrono_literals; + +#define POWER_SUPPLY_SUBSYSTEM "power_supply" + +namespace android { +namespace hardware { +namespace health { + +HealthLoop::HealthLoop() { + InitHealthdConfig(&healthd_config_); + awake_poll_interval_ = -1; + wakealarm_wake_interval_ = healthd_config_.periodic_chores_interval_fast; +} + +HealthLoop::~HealthLoop() { + LOG(FATAL) << "HealthLoop cannot be destroyed"; +} + +int HealthLoop::RegisterEvent(int fd, BoundFunction func, EventWakeup wakeup) { + CHECK(!reject_event_register_); + + auto* event_handler = + event_handlers_ + .emplace_back(std::make_unique<EventHandler>(EventHandler{this, fd, func})) + .get(); + + struct epoll_event ev; + + ev.events = EPOLLIN; + + if (wakeup == EVENT_WAKEUP_FD) ev.events |= EPOLLWAKEUP; + + ev.data.ptr = reinterpret_cast<void*>(event_handler); + + if (epoll_ctl(epollfd_, EPOLL_CTL_ADD, fd, &ev) == -1) { + KLOG_ERROR(LOG_TAG, "epoll_ctl failed; errno=%d\n", errno); + return -1; + } + + return 0; +} + +void HealthLoop::WakeAlarmSetInterval(int interval) { + struct itimerspec itval; + + if (wakealarm_fd_ == -1) return; + + wakealarm_wake_interval_ = interval; + + if (interval == -1) interval = 0; + + itval.it_interval.tv_sec = interval; + itval.it_interval.tv_nsec = 0; + itval.it_value.tv_sec = interval; + itval.it_value.tv_nsec = 0; + + if (timerfd_settime(wakealarm_fd_, 0, &itval, NULL) == -1) + KLOG_ERROR(LOG_TAG, "wakealarm_set_interval: timerfd_settime failed\n"); +} + +void HealthLoop::AdjustWakealarmPeriods(bool charger_online) { + // Fast wake interval when on charger (watch for overheat); + // slow wake interval when on battery (watch for drained battery). + + int new_wake_interval = charger_online ? healthd_config_.periodic_chores_interval_fast + : healthd_config_.periodic_chores_interval_slow; + + if (new_wake_interval != wakealarm_wake_interval_) WakeAlarmSetInterval(new_wake_interval); + + // During awake periods poll at fast rate. If wake alarm is set at fast + // rate then just use the alarm; if wake alarm is set at slow rate then + // poll at fast rate while awake and let alarm wake up at slow rate when + // asleep. + + if (healthd_config_.periodic_chores_interval_fast == -1) + awake_poll_interval_ = -1; + else + awake_poll_interval_ = new_wake_interval == healthd_config_.periodic_chores_interval_fast + ? -1 + : healthd_config_.periodic_chores_interval_fast * 1000; +} + +void HealthLoop::PeriodicChores() { + ScheduleBatteryUpdate(); +} + +// TODO(b/140330870): Use BPF instead. +#define UEVENT_MSG_LEN 2048 +void HealthLoop::UeventEvent(uint32_t /*epevents*/) { + // No need to lock because uevent_fd_ is guaranteed to be initialized. + + char msg[UEVENT_MSG_LEN + 2]; + char* cp; + int n; + + n = uevent_kernel_multicast_recv(uevent_fd_, msg, UEVENT_MSG_LEN); + if (n <= 0) return; + if (n >= UEVENT_MSG_LEN) /* overflow -- discard */ + return; + + msg[n] = '\0'; + msg[n + 1] = '\0'; + cp = msg; + + while (*cp) { + if (!strcmp(cp, "SUBSYSTEM=" POWER_SUPPLY_SUBSYSTEM)) { + ScheduleBatteryUpdate(); + break; + } + + /* advance to after the next \0 */ + while (*cp++) + ; + } +} + +void HealthLoop::UeventInit(void) { + uevent_fd_.reset(uevent_open_socket(64 * 1024, true)); + + if (uevent_fd_ < 0) { + KLOG_ERROR(LOG_TAG, "uevent_init: uevent_open_socket failed\n"); + return; + } + + fcntl(uevent_fd_, F_SETFL, O_NONBLOCK); + if (RegisterEvent(uevent_fd_, &HealthLoop::UeventEvent, EVENT_WAKEUP_FD)) + KLOG_ERROR(LOG_TAG, "register for uevent events failed\n"); +} + +void HealthLoop::WakeAlarmEvent(uint32_t /*epevents*/) { + // No need to lock because wakealarm_fd_ is guaranteed to be initialized. + + unsigned long long wakeups; + + if (read(wakealarm_fd_, &wakeups, sizeof(wakeups)) == -1) { + KLOG_ERROR(LOG_TAG, "wakealarm_event: read wakealarm fd failed\n"); + return; + } + + PeriodicChores(); +} + +void HealthLoop::WakeAlarmInit(void) { + wakealarm_fd_.reset(timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK)); + if (wakealarm_fd_ == -1) { + KLOG_ERROR(LOG_TAG, "wakealarm_init: timerfd_create failed\n"); + return; + } + + if (RegisterEvent(wakealarm_fd_, &HealthLoop::WakeAlarmEvent, EVENT_WAKEUP_FD)) + KLOG_ERROR(LOG_TAG, "Registration of wakealarm event failed\n"); + + WakeAlarmSetInterval(healthd_config_.periodic_chores_interval_fast); +} + +void HealthLoop::MainLoop(void) { + int nevents = 0; + while (1) { + reject_event_register_ = true; + size_t eventct = event_handlers_.size(); + struct epoll_event events[eventct]; + int timeout = awake_poll_interval_; + + int mode_timeout; + + /* Don't wait for first timer timeout to run periodic chores */ + if (!nevents) PeriodicChores(); + + Heartbeat(); + + mode_timeout = PrepareToWait(); + if (timeout < 0 || (mode_timeout > 0 && mode_timeout < timeout)) timeout = mode_timeout; + nevents = epoll_wait(epollfd_, events, eventct, timeout); + if (nevents == -1) { + if (errno == EINTR) continue; + KLOG_ERROR(LOG_TAG, "healthd_mainloop: epoll_wait failed\n"); + break; + } + + for (int n = 0; n < nevents; ++n) { + if (events[n].data.ptr) { + auto* event_handler = reinterpret_cast<EventHandler*>(events[n].data.ptr); + event_handler->func(event_handler->object, events[n].events); + } + } + } + + return; +} + +int HealthLoop::InitInternal() { + epollfd_.reset(epoll_create1(EPOLL_CLOEXEC)); + if (epollfd_ == -1) { + KLOG_ERROR(LOG_TAG, "epoll_create1 failed; errno=%d\n", errno); + return -1; + } + + // Call subclass's init for any additional init steps. + // Note that healthd_config_ is initialized before wakealarm_fd_; see + // AdjustUeventWakealarmPeriods(). + Init(&healthd_config_); + + WakeAlarmInit(); + UeventInit(); + + return 0; +} + +int HealthLoop::StartLoop() { + int ret; + + klog_set_level(KLOG_LEVEL); + + ret = InitInternal(); + if (ret) { + KLOG_ERROR(LOG_TAG, "Initialization failed, exiting\n"); + return 2; + } + + MainLoop(); + KLOG_ERROR(LOG_TAG, "Main loop terminated, exiting\n"); + return 3; +} + +} // namespace health +} // namespace hardware +} // namespace android diff --git a/health/utils/libhealthloop/include/health/HealthLoop.h b/health/utils/libhealthloop/include/health/HealthLoop.h new file mode 100644 index 0000000000..693e6cbb9a --- /dev/null +++ b/health/utils/libhealthloop/include/health/HealthLoop.h @@ -0,0 +1,108 @@ +/* + * 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. + */ +#pragma once + +#include <memory> +#include <mutex> +#include <vector> + +#include <android-base/unique_fd.h> +#include <healthd/healthd.h> + +namespace android { +namespace hardware { +namespace health { + +class HealthLoop { + public: + HealthLoop(); + + // Client is responsible for holding this forever. Process will exit + // when this is destroyed. + virtual ~HealthLoop(); + + // Initialize and start the main loop. This function does not exit unless + // the process is interrupted. + // Once the loop is started, event handlers are no longer allowed to be + // registered. + int StartLoop(); + + protected: + // healthd_mode_ops overrides. Note that healthd_mode_ops->battery_update + // is missing because it is only used by BatteryMonitor. + // Init is called right after epollfd_ is initialized (so RegisterEvent + // is allowed) but before other things are initialized (so SetChargerOnline + // is not allowed.) + virtual void Init(healthd_config* config) = 0; + virtual void Heartbeat() = 0; + virtual int PrepareToWait() = 0; + + // Note that this is NOT healthd_mode_ops->battery_update(BatteryProperties*), + // which is called by BatteryMonitor after values are fetched. This is the + // implementation of healthd_battery_update(), which calls + // the correct IHealth::update(), + // which calls BatteryMonitor::update(), which calls + // healthd_mode_ops->battery_update(BatteryProperties*). + virtual void ScheduleBatteryUpdate() = 0; + + // Register an epoll event. When there is an event, |func| will be + // called with |this| as the first argument and |epevents| as the second. + // This may be called in a different thread from where StartLoop is called + // (for obvious reasons; StartLoop never ends). + // Once the loop is started, event handlers are no longer allowed to be + // registered. + using BoundFunction = std::function<void(HealthLoop*, uint32_t /* epevents */)>; + int RegisterEvent(int fd, BoundFunction func, EventWakeup wakeup); + + // Helper for implementing ScheduleBatteryUpdate(). An implementation of + // ScheduleBatteryUpdate should get charger_online from BatteryMonitor::update(), + // then reset wake alarm interval by calling AdjustWakealarmPeriods. + void AdjustWakealarmPeriods(bool charger_online); + + private: + struct EventHandler { + HealthLoop* object = nullptr; + int fd; + BoundFunction func; + }; + + int InitInternal(); + void MainLoop(); + void WakeAlarmInit(); + void WakeAlarmEvent(uint32_t); + void UeventInit(); + void UeventEvent(uint32_t); + void WakeAlarmSetInterval(int interval); + void PeriodicChores(); + + // These are fixed after InitInternal() is called. + struct healthd_config healthd_config_; + android::base::unique_fd wakealarm_fd_; + android::base::unique_fd uevent_fd_; + + android::base::unique_fd epollfd_; + std::vector<std::unique_ptr<EventHandler>> event_handlers_; + int awake_poll_interval_; // -1 for no epoll timeout + int wakealarm_wake_interval_; + + // If set to true, future RegisterEvent() will be rejected. This is to ensure all + // events are registered before StartLoop(). + bool reject_event_register_ = false; +}; + +} // namespace health +} // namespace hardware +} // namespace android diff --git a/health/utils/libhealthloop/include/health/utils.h b/health/utils/libhealthloop/include/health/utils.h new file mode 100644 index 0000000000..e46771c7e1 --- /dev/null +++ b/health/utils/libhealthloop/include/health/utils.h @@ -0,0 +1,28 @@ +/* + * 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. + */ +#pragma once + +#include <healthd/healthd.h> + +namespace android { +namespace hardware { +namespace health { + +void InitHealthdConfig(struct healthd_config* healthd_config); + +} // namespace health +} // namespace hardware +} // namespace android diff --git a/health/utils/libhealthloop/utils.cpp b/health/utils/libhealthloop/utils.cpp new file mode 100644 index 0000000000..b0d153f583 --- /dev/null +++ b/health/utils/libhealthloop/utils.cpp @@ -0,0 +1,51 @@ +/* + * 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. + */ + +#include <health/utils.h> +namespace android { +namespace hardware { +namespace health { + +// Periodic chores fast interval in seconds +#define DEFAULT_PERIODIC_CHORES_INTERVAL_FAST (60 * 1) +// Periodic chores fast interval in seconds +#define DEFAULT_PERIODIC_CHORES_INTERVAL_SLOW (60 * 10) + +void InitHealthdConfig(struct healthd_config* healthd_config) { + *healthd_config = { + .periodic_chores_interval_fast = DEFAULT_PERIODIC_CHORES_INTERVAL_FAST, + .periodic_chores_interval_slow = DEFAULT_PERIODIC_CHORES_INTERVAL_SLOW, + .batteryStatusPath = String8(String8::kEmptyString), + .batteryHealthPath = String8(String8::kEmptyString), + .batteryPresentPath = String8(String8::kEmptyString), + .batteryCapacityPath = String8(String8::kEmptyString), + .batteryVoltagePath = String8(String8::kEmptyString), + .batteryTemperaturePath = String8(String8::kEmptyString), + .batteryTechnologyPath = String8(String8::kEmptyString), + .batteryCurrentNowPath = String8(String8::kEmptyString), + .batteryCurrentAvgPath = String8(String8::kEmptyString), + .batteryChargeCounterPath = String8(String8::kEmptyString), + .batteryFullChargePath = String8(String8::kEmptyString), + .batteryCycleCountPath = String8(String8::kEmptyString), + .energyCounter = NULL, + .boot_min_cap = 0, + .screen_on = NULL, + }; +} + +} // namespace health +} // namespace hardware +} // namespace android diff --git a/keymaster/3.0/vts/functional/Android.bp b/keymaster/3.0/vts/functional/Android.bp index b0371c79ef..69aa56d306 100644 --- a/keymaster/3.0/vts/functional/Android.bp +++ b/keymaster/3.0/vts/functional/Android.bp @@ -26,7 +26,7 @@ cc_test { ], static_libs: [ "android.hardware.keymaster@3.0", - "libcrypto", + "libcrypto_static", "libsoftkeymasterdevice", ], test_suites: ["general-tests"], diff --git a/keymaster/4.0/support/include/keymasterV4_0/keymaster_tags.h b/keymaster/4.0/support/include/keymasterV4_0/keymaster_tags.h index 6b02a7fffd..7d29459d58 100644 --- a/keymaster/4.0/support/include/keymasterV4_0/keymaster_tags.h +++ b/keymaster/4.0/support/include/keymasterV4_0/keymaster_tags.h @@ -283,39 +283,50 @@ inline KeyParameter Authorization(TypedTag<tag_type, tag> ttag, Args&&... args) */ template <typename ValueT> class NullOr { - template <typename T> - struct reference_initializer { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wnull-dereference" - static T&& init() { return *static_cast<std::remove_reference_t<T>*>(nullptr); } -#pragma GCC diagnostic pop - }; - template <typename T> + using internal_t = std::conditional_t<std::is_lvalue_reference<ValueT>::value, + std::remove_reference_t<ValueT>*, ValueT>; + struct pointer_initializer { - static T init() { return nullptr; } + static std::nullptr_t init() { return nullptr; } }; - template <typename T> struct value_initializer { - static T init() { return T(); } + static ValueT init() { return ValueT(); } }; + struct value_pointer_deref_t { + static ValueT& deref(ValueT& v) { return v; } + }; + struct reference_deref_t { + static auto& deref(internal_t v) { return *v; } + }; + using initializer_t = std::conditional_t<std::is_lvalue_reference<ValueT>::value || + std::is_pointer<ValueT>::value, + pointer_initializer, value_initializer>; + using deref_t = std::conditional_t<std::is_lvalue_reference<ValueT>::value, reference_deref_t, + value_pointer_deref_t>; + + public: + NullOr() : value_(initializer_t::init()), null_(true) {} template <typename T> - using initializer_t = - std::conditional_t<std::is_lvalue_reference<T>::value, reference_initializer<T>, - std::conditional_t<std::is_pointer<T>::value, pointer_initializer<T>, - value_initializer<T>>>; - - public: - NullOr() : value_(initializer_t<ValueT>::init()), null_(true) {} - NullOr(ValueT&& value) : value_(std::forward<ValueT>(value)), null_(false) {} + NullOr(T&& value, typename std::enable_if< + !std::is_lvalue_reference<ValueT>::value && + std::is_same<std::decay_t<ValueT>, std::decay_t<T>>::value, + int>::type = 0) + : value_(std::forward<ValueT>(value)), null_(false) {} + template <typename T> + NullOr(T& value, typename std::enable_if< + std::is_lvalue_reference<ValueT>::value && + std::is_same<std::decay_t<ValueT>, std::decay_t<T>>::value, + int>::type = 0) + : value_(&value), null_(false) {} bool isOk() const { return !null_; } - const ValueT& value() const & { return value_; } - ValueT& value() & { return value_; } - ValueT&& value() && { return std::move(value_); } + const ValueT& value() const& { return deref_t::deref(value_); } + ValueT& value() & { return deref_t::deref(value_); } + ValueT&& value() && { return std::move(deref_t::deref(value_)); } - private: - ValueT value_; + private: + internal_t value_; bool null_; }; diff --git a/keymaster/4.0/vts/functional/Android.bp b/keymaster/4.0/vts/functional/Android.bp index 333e408b28..0401362e96 100644 --- a/keymaster/4.0/vts/functional/Android.bp +++ b/keymaster/4.0/vts/functional/Android.bp @@ -25,7 +25,7 @@ cc_test { ], static_libs: [ "android.hardware.keymaster@4.0", - "libcrypto", + "libcrypto_static", "libkeymaster4support", "libsoftkeymasterdevice", ], diff --git a/keymaster/4.0/vts/functional/KeymasterHidlTest.cpp b/keymaster/4.0/vts/functional/KeymasterHidlTest.cpp index 3af1df32d3..4838e7e575 100644 --- a/keymaster/4.0/vts/functional/KeymasterHidlTest.cpp +++ b/keymaster/4.0/vts/functional/KeymasterHidlTest.cpp @@ -48,10 +48,11 @@ uint32_t KeymasterHidlTest::os_patch_level_; SecurityLevel KeymasterHidlTest::securityLevel_; hidl_string KeymasterHidlTest::name_; hidl_string KeymasterHidlTest::author_; +string KeymasterHidlTest::service_name_; -void KeymasterHidlTest::SetUpTestCase() { - string service_name = KeymasterHidlEnvironment::Instance()->getServiceName<IKeymasterDevice>(); - keymaster_ = ::testing::VtsHalHidlTargetTestBase::getService<IKeymasterDevice>(service_name); +void KeymasterHidlTest::InitializeKeymaster() { + service_name_ = KeymasterHidlEnvironment::Instance()->getServiceName<IKeymasterDevice>(); + keymaster_ = ::testing::VtsHalHidlTargetTestBase::getService<IKeymasterDevice>(service_name_); ASSERT_NE(keymaster_, nullptr); ASSERT_TRUE(keymaster_ @@ -62,18 +63,22 @@ void KeymasterHidlTest::SetUpTestCase() { author_ = author; }) .isOk()); +} + +void KeymasterHidlTest::SetUpTestCase() { + + InitializeKeymaster(); os_version_ = ::keymaster::GetOsVersion(); os_patch_level_ = ::keymaster::GetOsPatchlevel(); auto service_manager = android::hidl::manager::V1_0::IServiceManager::getService(); ASSERT_NE(nullptr, service_manager.get()); - all_keymasters_.push_back(keymaster_); service_manager->listByInterface( IKeymasterDevice::descriptor, [&](const hidl_vec<hidl_string>& names) { for (auto& name : names) { - if (name == service_name) continue; + if (name == service_name_) continue; auto keymaster = ::testing::VtsHalHidlTargetTestBase::getService<IKeymasterDevice>(name); ASSERT_NE(keymaster, nullptr); @@ -269,6 +274,13 @@ ErrorCode KeymasterHidlTest::GetCharacteristics(const HidlBuf& key_blob, return GetCharacteristics(key_blob, client_id, app_data, key_characteristics); } +ErrorCode KeymasterHidlTest::GetDebugInfo(DebugInfo* debug_info) { + EXPECT_TRUE(keymaster_->getDebugInfo([&](const DebugInfo& hidl_debug_info) { + *debug_info = hidl_debug_info; + }).isOk()); + return ErrorCode::OK; +} + ErrorCode KeymasterHidlTest::Begin(KeyPurpose purpose, const HidlBuf& key_blob, const AuthorizationSet& in_params, AuthorizationSet* out_params, OperationHandle* op_handle) { @@ -611,6 +623,20 @@ string KeymasterHidlTest::EncryptMessage(const string& message, BlockMode block_ return ciphertext; } +string KeymasterHidlTest::EncryptMessage(const string& message, BlockMode block_mode, + PaddingMode padding, uint8_t mac_length_bits, + const HidlBuf& iv_in) { + SCOPED_TRACE("EncryptMessage"); + auto params = AuthorizationSetBuilder() + .BlockMode(block_mode) + .Padding(padding) + .Authorization(TAG_MAC_LENGTH, mac_length_bits) + .Authorization(TAG_NONCE, iv_in); + AuthorizationSet out_params; + string ciphertext = EncryptMessage(message, params, &out_params); + return ciphertext; +} + string KeymasterHidlTest::DecryptMessage(const HidlBuf& key_blob, const string& ciphertext, const AuthorizationSet& params) { SCOPED_TRACE("DecryptMessage"); diff --git a/keymaster/4.0/vts/functional/KeymasterHidlTest.h b/keymaster/4.0/vts/functional/KeymasterHidlTest.h index 015fc43752..b09da45cf2 100644 --- a/keymaster/4.0/vts/functional/KeymasterHidlTest.h +++ b/keymaster/4.0/vts/functional/KeymasterHidlTest.h @@ -37,6 +37,7 @@ namespace test { using ::android::sp; using ::std::string; +using hidl::base::V1_0::DebugInfo; class HidlBuf : public hidl_vec<uint8_t> { typedef hidl_vec<uint8_t> super; @@ -95,6 +96,7 @@ class KeymasterHidlTest : public ::testing::VtsHalHidlTargetTestBase { // SetUpTestCase runs only once per test case, not once per test. static void SetUpTestCase(); + static void InitializeKeymaster(); static void TearDownTestCase() { keymaster_.clear(); all_keymasters_.clear(); @@ -140,6 +142,8 @@ class KeymasterHidlTest : public ::testing::VtsHalHidlTargetTestBase { const HidlBuf& app_data, KeyCharacteristics* key_characteristics); ErrorCode GetCharacteristics(const HidlBuf& key_blob, KeyCharacteristics* key_characteristics); + ErrorCode GetDebugInfo(DebugInfo* debug_info); + ErrorCode Begin(KeyPurpose purpose, const HidlBuf& key_blob, const AuthorizationSet& in_params, AuthorizationSet* out_params, OperationHandle* op_handle); ErrorCode Begin(KeyPurpose purpose, const AuthorizationSet& in_params, @@ -201,6 +205,8 @@ class KeymasterHidlTest : public ::testing::VtsHalHidlTargetTestBase { HidlBuf* iv_out); string EncryptMessage(const string& message, BlockMode block_mode, PaddingMode padding, const HidlBuf& iv_in); + string EncryptMessage(const string& message, BlockMode block_mode, PaddingMode padding, + uint8_t mac_length_bits, const HidlBuf& iv_in); string DecryptMessage(const HidlBuf& key_blob, const string& ciphertext, const AuthorizationSet& params); @@ -235,6 +241,7 @@ class KeymasterHidlTest : public ::testing::VtsHalHidlTargetTestBase { static SecurityLevel securityLevel_; static hidl_string name_; static hidl_string author_; + static string service_name_; }; } // namespace test diff --git a/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp b/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp index 9e6cce7e97..0ac7e481ae 100644 --- a/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp +++ b/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp @@ -18,6 +18,7 @@ #include <cutils/log.h> #include <iostream> +#include <signal.h> #include <openssl/evp.h> #include <openssl/mem.h> @@ -2706,6 +2707,40 @@ TEST_F(EncryptionOperationsTest, AesWrongMode) { } /* + * EncryptionOperationsTest.AesWrongPurpose + * + * Verifies that AES encryption fails in the correct way when an unauthorized purpose is specified. + */ +TEST_F(EncryptionOperationsTest, AesWrongPurpose) { + auto err = GenerateKey(AuthorizationSetBuilder() + .Authorization(TAG_NO_AUTH_REQUIRED) + .AesKey(128) + .Authorization(TAG_PURPOSE, KeyPurpose::ENCRYPT) + .Authorization(TAG_BLOCK_MODE, BlockMode::GCM) + .Authorization(TAG_MIN_MAC_LENGTH, 128) + .Padding(PaddingMode::NONE)); + ASSERT_EQ(ErrorCode::OK, err) << "Got " << err; + + err = Begin(KeyPurpose::DECRYPT, + AuthorizationSetBuilder().BlockMode(BlockMode::GCM).Padding(PaddingMode::NONE)); + EXPECT_EQ(ErrorCode::INCOMPATIBLE_PURPOSE, err) << "Got " << err; + + CheckedDeleteKey(); + + ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() + .Authorization(TAG_NO_AUTH_REQUIRED) + .AesKey(128) + .Authorization(TAG_PURPOSE, KeyPurpose::DECRYPT) + .Authorization(TAG_BLOCK_MODE, BlockMode::GCM) + .Authorization(TAG_MIN_MAC_LENGTH, 128) + .Padding(PaddingMode::NONE))); + + err = Begin(KeyPurpose::ENCRYPT, + AuthorizationSetBuilder().BlockMode(BlockMode::GCM).Padding(PaddingMode::NONE)); + EXPECT_EQ(ErrorCode::INCOMPATIBLE_PURPOSE, err) << "Got " << err; +} + +/* * EncryptionOperationsTest.AesEcbNoPaddingWrongInputSize * * Verifies that AES encryption fails in the correct way when provided an input that is not a @@ -3225,6 +3260,92 @@ TEST_F(EncryptionOperationsTest, AesGcmRoundTripSuccess) { } /* + * EncryptionOperationsTest.AesGcmRoundTripWithDelaySuccess + * + * Verifies that AES GCM mode works, even when there's a long delay + * between operations. + */ +TEST_F(EncryptionOperationsTest, AesGcmRoundTripWithDelaySuccess) { + ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() + .Authorization(TAG_NO_AUTH_REQUIRED) + .AesEncryptionKey(128) + .Authorization(TAG_BLOCK_MODE, BlockMode::GCM) + .Padding(PaddingMode::NONE) + .Authorization(TAG_MIN_MAC_LENGTH, 128))); + + string aad = "foobar"; + string message = "123456789012345678901234567890123456"; + + auto begin_params = AuthorizationSetBuilder() + .BlockMode(BlockMode::GCM) + .Padding(PaddingMode::NONE) + .Authorization(TAG_MAC_LENGTH, 128); + + auto update_params = + AuthorizationSetBuilder().Authorization(TAG_ASSOCIATED_DATA, aad.data(), aad.size()); + + // Encrypt + AuthorizationSet begin_out_params; + ASSERT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, begin_params, &begin_out_params)) + << "Begin encrypt"; + string ciphertext; + AuthorizationSet update_out_params; + sleep(5); + ASSERT_EQ(ErrorCode::OK, + Finish(op_handle_, update_params, message, "", &update_out_params, &ciphertext)); + + ASSERT_EQ(ciphertext.length(), message.length() + 16); + + // Grab nonce + begin_params.push_back(begin_out_params); + + // Decrypt. + ASSERT_EQ(ErrorCode::OK, Begin(KeyPurpose::DECRYPT, begin_params)) << "Begin decrypt"; + string plaintext; + size_t input_consumed; + sleep(5); + ASSERT_EQ(ErrorCode::OK, Update(op_handle_, update_params, ciphertext, &update_out_params, + &plaintext, &input_consumed)); + EXPECT_EQ(ciphertext.size(), input_consumed); + sleep(5); + EXPECT_EQ(ErrorCode::OK, Finish("", &plaintext)); + EXPECT_EQ(message.length(), plaintext.length()); + EXPECT_EQ(message, plaintext); +} + +/* + * EncryptionOperationsTest.AesGcmDifferentNonces + * + * Verifies that encrypting the same data with different nonces produces different outputs. + */ +TEST_F(EncryptionOperationsTest, AesGcmDifferentNonces) { + ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() + .Authorization(TAG_NO_AUTH_REQUIRED) + .AesEncryptionKey(128) + .Authorization(TAG_BLOCK_MODE, BlockMode::GCM) + .Padding(PaddingMode::NONE) + .Authorization(TAG_MIN_MAC_LENGTH, 128) + .Authorization(TAG_CALLER_NONCE))); + + string aad = "foobar"; + string message = "123456789012345678901234567890123456"; + string nonce1 = "000000000000"; + string nonce2 = "111111111111"; + string nonce3 = "222222222222"; + + string ciphertext1 = + EncryptMessage(message, BlockMode::GCM, PaddingMode::NONE, 128, HidlBuf(nonce1)); + string ciphertext2 = + EncryptMessage(message, BlockMode::GCM, PaddingMode::NONE, 128, HidlBuf(nonce2)); + string ciphertext3 = + EncryptMessage(message, BlockMode::GCM, PaddingMode::NONE, 128, HidlBuf(nonce3)); + + ASSERT_NE(ciphertext1, ciphertext2); + ASSERT_NE(ciphertext1, ciphertext3); + ASSERT_NE(ciphertext2, ciphertext3); +} + +/* * EncryptionOperationsTest.AesGcmTooShortTag * * Verifies that AES GCM mode fails correctly when a too-short tag length is specified. @@ -4456,6 +4577,84 @@ TEST_F(UpgradeKeyTest, UpgradeKey) { EXPECT_EQ(result, std::make_pair(ErrorCode::OK, HidlBuf())); } + +using ClearOperationsTest = KeymasterHidlTest; + +/* + * ClearSlotsTest.TooManyOperations + * + * Verifies that TOO_MANY_OPERATIONS is returned after the max number of + * operations are started without being finished or aborted. Also verifies + * that aborting the operations clears the operations. + * + */ +TEST_F(ClearOperationsTest, TooManyOperations) { + ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() + .Authorization(TAG_NO_AUTH_REQUIRED) + .RsaEncryptionKey(2048, 65537) + .Padding(PaddingMode::NONE))); + + auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE); + int max_operations = SecLevel() == SecurityLevel::STRONGBOX ? 4 : 16; + OperationHandle op_handles[max_operations]; + AuthorizationSet out_params; + for(int i=0; i<max_operations; i++) { + EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params, &(op_handles[i]))); + } + EXPECT_EQ(ErrorCode::TOO_MANY_OPERATIONS, + Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params, &op_handle_)); + // Try again just in case there's a weird overflow bug + EXPECT_EQ(ErrorCode::TOO_MANY_OPERATIONS, + Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params, &op_handle_)); + for(int i=0; i<max_operations; i++) { + EXPECT_EQ(ErrorCode::OK, Abort(op_handles[i])); + } + EXPECT_EQ(ErrorCode::OK, + Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params, &op_handle_)); + AbortIfNeeded(); +} + +/* + * ClearSlotsTest.ServiceDeath + * + * Verifies that the service is restarted after death and the ongoing + * operations are cleared. + */ +TEST_F(ClearOperationsTest, ServiceDeath) { + + ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() + .Authorization(TAG_NO_AUTH_REQUIRED) + .RsaEncryptionKey(2048, 65537) + .Padding(PaddingMode::NONE))); + + auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE); + int max_operations = SecLevel() == SecurityLevel::STRONGBOX ? 4 : 16; + OperationHandle op_handles[max_operations]; + AuthorizationSet out_params; + for(int i=0; i<max_operations; i++) { + EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params, &(op_handles[i]))); + } + EXPECT_EQ(ErrorCode::TOO_MANY_OPERATIONS, + Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params, &op_handle_)); + + DebugInfo debug_info; + GetDebugInfo(&debug_info); + kill(debug_info.pid, SIGKILL); + // wait 1 second for keymaster to restart + sleep(1); + InitializeKeymaster(); + + for(int i=0; i<max_operations; i++) { + EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params, &(op_handles[i]))); + } + EXPECT_EQ(ErrorCode::TOO_MANY_OPERATIONS, + Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params, &op_handle_)); + for(int i=0; i<max_operations; i++) { + EXPECT_EQ(ErrorCode::OK, Abort(op_handles[i])); + } +} + + } // namespace test } // namespace V4_0 } // namespace keymaster diff --git a/light/2.0/vts/functional/Android.bp b/light/2.0/vts/functional/Android.bp index 9f03d271fa..2c0a08fab4 100644 --- a/light/2.0/vts/functional/Android.bp +++ b/light/2.0/vts/functional/Android.bp @@ -19,6 +19,6 @@ cc_test { defaults: ["VtsHalTargetTestDefaults"], srcs: ["VtsHalLightV2_0TargetTest.cpp"], static_libs: ["android.hardware.light@2.0"], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/light/utils/main.cpp b/light/utils/main.cpp index d07e799ab9..b83413246b 100644 --- a/light/utils/main.cpp +++ b/light/utils/main.cpp @@ -25,7 +25,7 @@ void error(const std::string& msg) { std::cerr << msg << std::endl; } -int main() { +int main(int argc, char* argv[]) { using ::android::hardware::hidl_vec; using ::android::hardware::light::V2_0::Brightness; using ::android::hardware::light::V2_0::Flash; @@ -41,10 +41,29 @@ int main() { return -1; } - const static LightState off = { - .color = 0u, .flashMode = Flash::NONE, .brightnessMode = Brightness::USER, + static LightState off = { + .color = 0u, + .flashMode = Flash::NONE, + .brightnessMode = Brightness::USER, }; + if (argc > 2) { + error("Usage: blank_screen [color]"); + return -1; + } + + if (argc > 1) { + char* col_ptr; + unsigned int col_new; + + col_new = strtoul(argv[1], &col_ptr, 0); + if (*col_ptr != '\0') { + error("Failed to convert " + std::string(argv[1]) + " to number"); + return -1; + } + off.color = col_new; + } + service->getSupportedTypes([&](const hidl_vec<Type>& types) { for (Type type : types) { Status ret = service->setLight(type, off); diff --git a/neuralnetworks/1.0/types.hal b/neuralnetworks/1.0/types.hal index 02db063e18..ba9d068e34 100644 --- a/neuralnetworks/1.0/types.hal +++ b/neuralnetworks/1.0/types.hal @@ -25,25 +25,24 @@ package android.hardware.neuralnetworks@1.0; * with at least one dimension). Types not prefaced by TENSOR_* represent * scalar values and must have no dimensions. * - * Although many types are defined, most operators accept just a few + * Although we define many types, most operators accept just a few * types. Most used are {@link OperandType::TENSOR_FLOAT32}, * {@link OperandType::TENSOR_QUANT8_ASYMM}, * and {@link OperandType::INT32}. */ enum OperandType : int32_t { /** A 32 bit floating point scalar value. */ - FLOAT32 = 0, + FLOAT32 = 0, /** A signed 32 bit integer scalar value. */ - INT32 = 1, + INT32 = 1, /** An unsigned 32 bit integer scalar value. */ - UINT32 = 2, - + UINT32 = 2, /** A tensor of 32 bit floating point values. */ - TENSOR_FLOAT32 = 3, + TENSOR_FLOAT32 = 3, /** A tensor of 32 bit integer values. */ - TENSOR_INT32 = 4, + TENSOR_INT32 = 4, /** - * A tensor of 8 bit integers that represent real numbers. + * A tensor of 8 bit unsigned integers that represent real numbers. * * Attached to this tensor are two numbers that can be used to convert the * 8 bit integer to the real value and vice versa. These two numbers are: @@ -51,21 +50,21 @@ enum OperandType : int32_t { * - zeroPoint: a 32 bit integer, in range [0, 255]. * * The formula is: - * real_value = (integer_value - zeroPoint) * scale. + * real_value = (integer_value - zeroPoint) * scale. */ TENSOR_QUANT8_ASYMM = 5, /** - * DEPRECATED. Since NNAPI 1.2, extensions are the preferred alternative to - * OEM operation and data types. + * DEPRECATED. Since HAL version 1.2, extensions are the preferred + * alternative to OEM operation and data types. * * OEM specific scalar value. */ OEM = 10000, /** - * DEPRECATED. Since NNAPI 1.2, extensions are the preferred alternative to - * OEM operation and data types. + * DEPRECATED. Since HAL version 1.2, extensions are the preferred + * alternative to OEM operation and data types. * * A tensor of OEM specific values. */ @@ -78,7 +77,6 @@ enum OperandType : int32_t { * The type of an operation in a model. */ enum OperationType : int32_t { - /** * Adds two tensors, element-wise. * @@ -110,14 +108,16 @@ enum OperationType : int32_t { * * 0: A tensor. * * 1: A tensor of the same {@link OperandType}, and compatible dimensions * as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scales and zeroPoint can be different from input0 scale and zeroPoint. * * 2: An {@link OperandType::INT32} scalar, and has to be one of the * {@link FusedActivationFunc} values. Specifies the activation to * invoke on the result. * * Outputs: * * 0: The sum, a tensor of the same {@link OperandType} as input0. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint can be different from inputs' scale and zeroPoint. */ ADD = 0, @@ -187,8 +187,8 @@ enum OperationType : int32_t { * Outputs: * * 0: The output 4-D tensor, of shape * [batches, out_height, out_width, depth]. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ AVERAGE_POOL_2D = 1, @@ -206,22 +206,23 @@ enum OperationType : int32_t { * * Inputs: * * 0 ~ n-1: The list of n input tensors, of shape - * [D0, D1, ..., Daxis(i), ..., Dm]. For inputs of - * {@link OperandType::TENSOR_QUANT8_ASYMM}, all input tensors - * must have the same scale and zeroPoint. + * [D0, D1, ..., Daxis(i), ..., Dm]. + * All input tensors of + * {@link OperandType::TENSOR_QUANT8_ASYMM} + * must have the same scale and zeroPoint as the output tensor. * * n: An {@link OperandType::INT32} scalar, specifying the * concatenation axis. * * Outputs: * * 0: The output, a tensor of the same {@link OperandType} as the input * tensors. The output shape is [D0, D1, ..., sum(Daxis(i)), ..., Dm]. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, the scale and zeroPoint + * values must be the same as the input tensors'. */ CONCATENATION = 2, /** - * Performs an 2-D convolution operation. + * Performs a 2-D convolution operation. * * The CONV_2D op sweeps a 2-D filter that can mix channels together over a * batch of images, applying the filter to each window of each image of the @@ -238,11 +239,17 @@ enum OperationType : int32_t { * filter[channel, di, dj, k] * ) + bias[channel] * - * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT32} - * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * Supported tensor {@link OperandType} configurations: + * * 32 bit floating point: + * * * {@link OperandType::TENSOR_FLOAT32} for input, filter, output, and bias. * - * Supported tensor rank: 4, with "NHWC" data layout. + * * Quantized: + * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, filter, and output. + * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to + * * * input.scale * filter.scale). + * + * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width, + * and Channels) data layout. * * Both explicit padding and implicit padding are supported. * @@ -252,12 +259,12 @@ enum OperationType : int32_t { * * 1: A 4-D tensor, of shape * [depth_out, filter_height, filter_width, depth_in], specifying the * filter. - * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. - * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the bias - * should also be of {@link OperandType::TENSOR_FLOAT32}. For input - * tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, the bias - * should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of - * 0 and bias_scale == input_scale * filter_scale. + * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input + * tensor of type {@link OperandType::TENSOR_FLOAT32} + * the bias must be of the same + * type. For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint + * of 0 and bias_scale == input_scale * filter_scale. * * 3: An {@link OperandType::INT32} scalar, specifying the padding on * the left, in the ‘width’ dimension. * * 4: An {@link OperandType::INT32} scalar, specifying the padding on @@ -281,11 +288,11 @@ enum OperationType : int32_t { * [depth_out, filter_height, filter_width, depth_in], specifying the * filter. * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input - * tensor of {@link OperandType::TENSOR_FLOAT32}, the bias should - * also be of {@link OperandType::TENSOR_FLOAT32}. For input tensor - * of {@link OperandType::TENSOR_QUANT8_ASYMM}, the bias should be - * of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0 and - * bias_scale == input_scale * filter_scale. + * tensor of type {@link OperandType::TENSOR_FLOAT32} + * the bias must be of the same + * type. For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint + * of 0 and bias_scale == input_scale * filter_scale. * * 3: An {@link OperandType::INT32} scalar, specifying the implicit * padding scheme, has to be one of the * following values: {0 (NONE), 1 (SAME), 2 (VALID)}. @@ -299,11 +306,9 @@ enum OperationType : int32_t { * * Outputs: * * 0: The output 4-D tensor, of shape - * [batches, out_height, out_width, depth_out]. For output tensor of - * {@link OperandType::TENSOR_QUANT8_ASYMM}, the following condition - * must be satisfied: output_scale > input_scale * filter_scale. - * - * Available since API level 27. + * [batches, out_height, out_width, depth_out]. + * For output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, + * the following condition must be satisfied: output_scale > input_scale * filter_scale */ CONV_2D = 3, @@ -329,11 +334,17 @@ enum OperationType : int32_t { * filter[1, di, dj, k * channel_multiplier + q] * ) + bias[k * channel_multiplier + q] * - * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT32} - * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * Supported tensor {@link OperandType} configurations: + * * 32 bit floating point: + * * * {@link OperandType::TENSOR_FLOAT32} for input, filter, output, and bias. * - * Supported tensor rank: 4, with "NHWC" data layout. + * * Quantized: + * * * {@link OperandType::TENSOR_QUANT8_ASYMM} for input, filter, and output. + * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to + * * * input.scale * filter.scale). + * + * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width, + * and Channels) data layout. * * Both explicit padding and implicit padding are supported. * @@ -343,11 +354,11 @@ enum OperationType : int32_t { * * 1: A 4-D tensor, of shape [1, filter_height, filter_width, depth_out], * specifying the filter. * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input - * tensor of {@link OperandType::TENSOR_FLOAT32}, the bias should - * also be of {@link OperandType::TENSOR_FLOAT32}. For input tensor - * of {@link OperandType::TENSOR_QUANT8_ASYMM}, the bias should be - * of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0 and - * bias_scale == input_scale * filter_scale. + * tensor of type {@link OperandType::TENSOR_FLOAT32} + * the bias must be of the same + * type. For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint + * of 0 and bias_scale == input_scale * filter_scale. * * 3: An {@link OperandType::INT32} scalar, specifying the padding on * the left, in the ‘width’ dimension. * * 4: An {@link OperandType::INT32} scalar, specifying the padding on @@ -372,11 +383,11 @@ enum OperationType : int32_t { * * 1: A 4-D tensor, of shape [1, filter_height, filter_width, depth_out], * specifying the filter. * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input - * tensor of {@link OperandType::TENSOR_FLOAT32}, the bias should - * also be of {@link OperandType::TENSOR_FLOAT32}. For input tensor - * of {@link OperandType::TENSOR_QUANT8_ASYMM}, the bias should be - * of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0 and - * bias_scale == input_scale * filter_scale. + * tensor of type {@link OperandType::TENSOR_FLOAT32} + * the bias must be of the same + * type. For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint + * of 0 and bias_scale == input_scale * filter_scale. * * 3: An {@link OperandType::INT32} scalar, specifying the implicit * padding scheme, has to be one of the * following values: {0 (NONE), 1 (SAME), 2 (VALID)}. @@ -392,11 +403,10 @@ enum OperationType : int32_t { * * Outputs: * * 0: The output 4-D tensor, of shape - * [batches, out_height, out_width, depth_out]. For output tensor of - * {@link OperandType::TENSOR_QUANT8_ASYMM}, the following condition - * must be satisfied: output_scale > input_scale * filter_scale. - * - * Available since API level 27. + * [batches, out_height, out_width, depth_out]. For + * output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, + * the following condition must be satisfied: + * output_scale > input_scale * filter_scale */ DEPTHWISE_CONV_2D = 4, @@ -419,7 +429,8 @@ enum OperationType : int32_t { * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * - * Supported tensor rank: 4, with "NHWC" data layout. + * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width, + * and Channels) data layout. * * Inputs: * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], @@ -431,8 +442,8 @@ enum OperationType : int32_t { * Outputs: * * 0: The output 4-D tensor, of shape [batch, height*block_size, * width*block_size, depth/(block_size*block_size)]. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ DEPTH_TO_SPACE = 5, @@ -443,19 +454,19 @@ enum OperationType : int32_t { * * output = (input - zeroPoint) * scale. * - * Supported tensor {@link OperandType}: + * Supported input tensor {@link OperandType}: * * {@link OperandType::TENSOR_QUANT8_ASYMM} * + * Supported output tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT32}. + * * Supported tensor rank: up to 4 * * Inputs: - * * 0: A tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}. + * * 0: A tensor. * * Outputs: - * * 0: The output tensor of same shape as input0, but with - * {@link OperandType::TENSOR_FLOAT32}. - * - * Available since API level 27. + * * 0: A tensor with the same shape as input0. */ DEQUANTIZE = 6, @@ -479,6 +490,13 @@ enum OperationType : int32_t { * If a value in Lookups is out of bounds, the operation must fail * and an error must be reported. * + * Supported value tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * + * Supported value tensor rank: from 2 + * * Inputs: * * 0: Lookups. A 1-D tensor of {@link OperandType::TENSOR_INT32}. * The values are indices into the first dimension of Values. @@ -489,8 +507,8 @@ enum OperationType : int32_t { * * 0: A n-D tensor with the same rank and shape as the Values * tensor, except for the first dimension which has the same size * as Lookups' only dimension. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input1. */ EMBEDDING_LOOKUP = 7, @@ -508,8 +526,6 @@ enum OperationType : int32_t { * Outputs: * * 0: The output tensor, of the same {@link OperandType} and dimensions as * the input tensor. - * - * Available since API level 27. */ FLOOR = 8, @@ -549,12 +565,9 @@ enum OperationType : int32_t { * invoke on the result. * * Outputs: - * * 0: The output tensor, of shape [batch_size, num_units]. For output - * tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, the following - * condition must be satisfied: - * output_scale > input_scale * filter_scale. - * - * Available since API level 27. + * * 0: The output tensor, of shape [batch_size, num_units]. For + * output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, the following + * condition must be satisfied: output_scale > input_scale * filter_scale. */ FULLY_CONNECTED = 9, @@ -585,6 +598,13 @@ enum OperationType : int32_t { * must be selected. If no entry in Keys has 123456, a slice of zeroes * must be concatenated. * + * Supported value tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * + * Supported value tensor rank: from 2 + * * Inputs: * * 0: Lookups. A 1-D {@link OperandType::TENSOR_INT32} tensor with * shape [ k ]. @@ -598,13 +618,13 @@ enum OperationType : int32_t { * * Outputs: * * 0: Output. A tensor with shape [ k …]. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input2. * * 1: Hits. A boolean tensor with shape [ k ] indicates whether the lookup * hits (True) or not (False). * Stored as {@link OperandType::TENSOR_QUANT8_ASYMM} with offset 0 * and scale 1.0f. * A non-zero byte represents True, a hit. A zero indicates otherwise. - * - * Available since API level 27. */ HASHTABLE_LOOKUP = 10, @@ -617,9 +637,6 @@ enum OperationType : int32_t { * input[batch, row, col, channel] / * sqrt(sum_{c} pow(input[batch, row, col, c], 2)) * - * For input tensor with more dimensions, independently normalizes each 1-D - * slice along dimension dim. - * * Supported tensor {@link OperandType}: * * {@link OperandType::TENSOR_FLOAT32} * @@ -627,13 +644,10 @@ enum OperationType : int32_t { * Height, Width, and Channels). * * Inputs: - * * 0: A 4-D tensor, of shape [batches, height, width, depth]. + * * 0: A 4-D tensor, specifying the tensor to be normalized. * * Outputs: - * * 0: The output 4-D tensor, of the same shape as input - * [batches, height, width, depth]. - * - * Available since API level 27. + * * 0: A tensor of the same {@link OperandType} and same shape as input0. */ L2_NORMALIZATION = 11, @@ -652,7 +666,8 @@ enum OperationType : int32_t { * Supported tensor {@link OperandType}: * * {@link OperandType::TENSOR_FLOAT32} * - * Supported tensor rank: 4, with "NHWC" data layout. + * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width, + * and Channels) data layout. * * Both explicit padding and implicit padding are supported. * @@ -700,8 +715,6 @@ enum OperationType : int32_t { * Outputs: * * 0: The output 4-D tensor, of shape * [batches, out_height, out_width, depth]. - * - * Available since API level 27. */ L2_POOL_2D = 12, @@ -729,17 +742,18 @@ enum OperationType : int32_t { * the input. * * 1: An {@link OperandType::INT32} scalar, specifying the radius of * the normalization window. - * * 2: An {@link OperandType::FLOAT32} scalar, specifying the bias, must - * not be zero. - * * 3: An {@link OperandType::FLOAT32} scalar, specifying the scale - * factor, alpha. - * * 4: An {@link OperandType::FLOAT32} scalar, specifying the exponent, - * beta. + * * 2: A scalar, specifying the bias, must not be zero. + * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the bias + * value must be of {@link OperandType::FLOAT32}. + * * 3: A scalar, specifying the scale factor, alpha. + * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the + * alpha value must be of {@link OperandType::FLOAT32}. + * * 4: A scalar, specifying the exponent, beta. + * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the beta + * value must be of {@link OperandType::FLOAT32}. * * Outputs: * * 0: The output tensor of same shape as input0. - * - * Available since API level 27. */ LOCAL_RESPONSE_NORMALIZATION = 13, @@ -763,45 +777,53 @@ enum OperationType : int32_t { * * 0: The output tensor of same shape as input0. * For {@link OperandType::TENSOR_QUANT8_ASYMM}, * the scale must be 1.f / 256 and the zeroPoint must be 0. - * - * Available since API level 27. */ LOGISTIC = 14, /** * Projects an input to a bit vector via locality senstive hashing. * + * Supported input tensor {@link OperandType}: + * * {@link OperandType::TENSOR_FLOAT32} + * * {@link OperandType::TENSOR_INT32} + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * + * Supported input tensor rank: from 1 + * * Inputs: * * 0: Hash functions. Dim.size == 2, DataType: Float. - * Tensor[0].Dim[0]: Number of hash functions. - * Tensor[0].Dim[1]: Number of seeds per hash functions. - * Tensor[0].Dim[1] <= 32 in sparse case. + * Tensor[0].Dim[0]: Number of hash functions. + * Tensor[0].Dim[1]: Number of projected output bits generated by each + * hash function. + * If the projection type is Sparse: + * Tensor[0].Dim[1] + ceil(log2(Tensor[0].Dim[0])) <= 32 * * * 1: Input. Dim.size >= 1, no restriction on DataType. * * 2: Weight. Optional. Dim.size == 1, DataType: Float. - * If not set, each input element is considered to have the same weight - * of 1.0. - * Tensor[1].Dim[0] == Tensor[2].Dim[0] + * If not set, each input element is considered to have the same weight + * of 1.0. + * Tensor[1].Dim[0] == Tensor[2].Dim[0] * * 3: Type: - * Sparse: Value LSHProjectionType_SPARSE(=1). + * Sparse: + * Value LSHProjectionType_SPARSE(=1). * Computed bit vector is considered to be sparse. * Each output element is an int32 made up of multiple bits * computed from hash functions. * - * Dense: Value LSHProjectionType_DENSE(=2). + * Dense: + * Value LSHProjectionType_DENSE(=2). * Computed bit vector is considered to be dense. Each output * element represents a bit and can take the value of either * 0 or 1. * * Outputs: - * * 0: If the projection type is sparse: - * Output.Dim == { Tensor[0].Dim[0] } - * A tensor of int32 that represents hash signatures. - * If the projection type is Dense: - * Output.Dim == { Tensor[0].Dim[0] * Tensor[0].Dim[1] } - * A flattened tensor that represents projected bit vectors. + * * 0: If the projection type is Sparse: + * Output.Dim == { Tensor[0].Dim[0] } + * A tensor of int32 that represents hash signatures. * - * Available since API level 27. + * If the projection type is Dense: + * Output.Dim == { Tensor[0].Dim[0] * Tensor[0].Dim[1] } + * A flattened tensor that represents projected bit vectors. */ LSH_PROJECTION = 15, @@ -901,71 +923,54 @@ enum OperationType : int32_t { * Supported tensor {@link OperandType}: * * {@link OperandType::TENSOR_FLOAT32} * + * All input and output tensors must be of the same type. + * * Inputs: * * 0: The input (\f$x_t\f$). - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [batch_size, input_size], where “batch_size” corresponds to the - * batching dimension, and “input_size” is the size of the input. + * A 2-D tensor of shape [batch_size, input_size], where “batch_size” + * corresponds to the batching dimension, and “input_size” is the size + * of the input. * * 1: The input-to-input weights (\f$W_{xi}\f$). Optional. - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units, input_size], where “num_units” corresponds to the - * number of cell units. + * A 2-D tensor of shape [num_units, input_size], where “num_units” + * corresponds to the number of cell units. * * 2: The input-to-forget weights (\f$W_{xf}\f$). - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units, input_size]. + * A 2-D tensor of shape [num_units, input_size]. * * 3: The input-to-cell weights (\f$W_{xc}\f$). - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units, input_size]. + * A 2-D tensor of shape [num_units, input_size]. * * 4: The input-to-output weights (\f$W_{xo}\f$). - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units, input_size]. + * A 2-D tensor of shape [num_units, input_size]. * * 5: The recurrent-to-input weights (\f$W_{hi}\f$). Optional. - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units, output_size], where “output_size” corresponds to either - * the number of cell units (i.e., “num_units”), or the second - * dimension of the “projection_weights”, if defined. + * A 2-D tensor of shape [num_units, output_size], where “output_size” + * corresponds to either the number of cell units (i.e., “num_units”), + * or the second dimension of the “projection_weights”, if defined. * * 6: The recurrent-to-forget weights (\f$W_{hf}\f$). - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units, output_size]. + * A 2-D tensor of shape [num_units, output_size]. * * 7: The recurrent-to-cell weights (\f$W_{hc}\f$). - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units, output_size]. + * A 2-D tensor of shape [num_units, output_size]. * * 8: The recurrent-to-output weights (\f$W_{ho}\f$). - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units, output_size]. + * A 2-D tensor of shape [num_units, output_size]. * * 9: The cell-to-input weights (\f$W_{ci}\f$). Optional. - * A 1-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units]. + * A 1-D tensor of shape [num_units]. * * 10:The cell-to-forget weights (\f$W_{cf}\f$). Optional. - * A 1-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units]. + * A 1-D tensor of shape [num_units]. * * 11:The cell-to-output weights (\f$W_{co}\f$). Optional. - * A 1-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units]. + * A 1-D tensor of shape [num_units]. * * 12:The input gate bias (\f$b_i\f$). Optional. - * A 1-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units]. + * A 1-D tensor of shape [num_units]. * * 13:The forget gate bias (\f$b_f\f$). - * A 1-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units]. + * A 1-D tensor of shape [num_units]. * * 14:The cell bias (\f$b_c\f$). - * A 1-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units]. + * A 1-D tensor of shape [num_units]. * * 15:The output gate bias (\f$b_o\f$). - * A 1-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units]. + * A 1-D tensor of shape [num_units]. * * 16:The projection weights (\f$W_{proj}\f$). Optional. - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [output_size, num_units]. + * A 2-D tensor of shape [output_size, num_units]. * * 17:The projection bias (\f$b_{proj}\f$). Optional. - * A 1-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [output_size]. + * A 1-D tensor of shape [output_size]. * * 18:The output state (in) (\f$h_{t-1}\f$). - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [batch_size, output_size]. + * A 2-D tensor of shape [batch_size, output_size]. * * 19:The cell state (in) (\f$C_{t-1}\f$). - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [batch_size, num_units]. + * A 2-D tensor of shape [batch_size, num_units]. * * 20:The activation function (\f$g\f$). * A value indicating the activation function: * <ul> @@ -984,21 +989,15 @@ enum OperationType : int32_t { * * Outputs: * * 0: The scratch buffer. - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [batch_size, num_units * 3] with CIFG, or + * A 2-D tensor of shape [batch_size, num_units * 3] with CIFG, or * [batch_size, num_units * 4] without CIFG. * * 1: The output state (out) (\f$h_t\f$). - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [batch_size, output_size]. + * A 2-D tensor of shape [batch_size, output_size]. * * 2: The cell state (out) (\f$C_t\f$). - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [batch_size, num_units]. + * A 2-D tensor of shape [batch_size, num_units]. * * 3: The output (\f$o_t\f$). - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [batch_size, output_size]. This is effectively the same as the - * current “output state (out)” value. - * - * Available since API level 27. + * A 2-D tensor of shape [batch_size, output_size]. This is effectively + * the same as the current “output state (out)” value. */ LSTM = 16, @@ -1019,7 +1018,8 @@ enum OperationType : int32_t { * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * - * Supported tensor rank: 4, with "NHWC" data layout. + * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width, + * and Channels) data layout. * * Both explicit padding and implicit padding are supported. * @@ -1067,8 +1067,8 @@ enum OperationType : int32_t { * Outputs: * * 0: The output 4-D tensor, of shape * [batches, out_height, out_width, depth]. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ MAX_POOL_2D = 17, @@ -1106,8 +1106,6 @@ enum OperationType : int32_t { * For output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, * the following condition must be satisfied: * output_scale > input1_scale * input2_scale. - * - * Available since API level 27. */ MUL = 18, @@ -1129,8 +1127,8 @@ enum OperationType : int32_t { * * Outputs: * * 0: The output tensor of same shape as input0. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ RELU = 19, @@ -1151,9 +1149,9 @@ enum OperationType : int32_t { * * 0: A tensor, specifying the input. * * Outputs: - * * 0: The output tensor of same shape as input0. - * - * Available since API level 27. + * * 0: The output tensor of the same shape as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ RELU1 = 20, @@ -1175,8 +1173,8 @@ enum OperationType : int32_t { * * Outputs: * * 0: The output tensor of same shape as input0. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ RELU6 = 21, @@ -1205,8 +1203,8 @@ enum OperationType : int32_t { * * Outputs: * * 0: The output tensor, of shape specified by the input shape. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ RESHAPE = 22, @@ -1220,9 +1218,10 @@ enum OperationType : int32_t { * Supported tensor {@link OperandType}: * * {@link OperandType::TENSOR_FLOAT32} * - * Supported tensor rank: 4, with "NHWC" data layout. + * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width, + * and Channels) data layout. * - * Inputs: + * Inputs (resizing by shape): * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying * the input. * * 1: An {@link OperandType::INT32} scalar, specifying the output @@ -1233,8 +1232,6 @@ enum OperationType : int32_t { * Outputs: * * 0: The output 4-D tensor, of shape * [batches, new_height, new_width, depth]. - * - * Available since API level 27. */ RESIZE_BILINEAR = 23, @@ -1257,25 +1254,23 @@ enum OperationType : int32_t { * Supported tensor {@link OperandType}: * * {@link OperandType::TENSOR_FLOAT32} * + * The input tensors must all be the same type. + * * Inputs: * * 0: input. - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32} of shape - * [batch_size, input_size], where “batch_size” corresponds to the - * batching dimension, and “input_size” is the size of the input. + * A 2-D tensor of shape [batch_size, input_size], where “batch_size” + * corresponds to the batching dimension, and “input_size” is the size + * of the input. * * 1: weights. - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units, input_size], where “num_units” corresponds to the - * number of units. + * A 2-D tensor of shape [num_units, input_size], where “num_units” + * corresponds to the number of units. * * 2: recurrent_weights. - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units, num_units], with columns corresponding to the weights - * from each unit. + * A 2-D tensor of shape [num_units, num_units], with columns + * corresponding to the weights from each unit. * * 3: bias. - * A 1-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units]. + * A 1-D tensor of shape [num_units]. * * 4: hidden state (in). - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [batch_size, num_units]. + * A 2-D tensor of shape [batch_size, num_units]. * * 5: fused_activation_function. * An optional {@link FusedActivationFunc} value indicating the * activation function. If “NONE” is specified then it results in a @@ -1283,15 +1278,11 @@ enum OperationType : int32_t { * * Outputs: * * 0: hidden state (out). - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [batch_size, num_units]. + * A 2-D tensor of shape [batch_size, num_units]. * * * 1: output. - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [batch_size, num_units]. This is effectively the same as the - * current state value. - * - * Available since API level 27. + * A 2-D tensor of shape [batch_size, num_units]. This is effectively + * the same as the current state value. */ RNN = 24, @@ -1306,6 +1297,9 @@ enum OperationType : int32_t { * exp((input[batch, i] - max(input[batch, :])) * beta) / * sum_{k}{exp((input[batch, k] - max(input[batch, :])) * beta)} * + * For input tensor with rank other than 2, the activation will be applied + * independently on each 1-D slice along specified dimension. + * * Supported tensor {@link OperandType}: * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} @@ -1314,15 +1308,15 @@ enum OperationType : int32_t { * * Inputs: * * 0: A 2-D or 4-D tensor, specifying the tensor to be reshaped. - * * 1: An {@link OperandType::FLOAT32} scalar, specifying the positive - * scaling factor for the exponent, beta. + * * 1: A scalar, specifying the positive scaling factor for the exponent, + * beta. If input0 is of {@link OperandType::TENSOR_FLOAT32} or + * {@link OperandType::TENSOR_QUANT8_ASYMM}, the scalar must be of + * {@link OperandType::FLOAT32}. * * Outputs: * * 0: The output tensor of same shape as input0. * For {@link OperandType::TENSOR_QUANT8_ASYMM}, * the scale must be 1.f / 256 and the zeroPoint must be 0. - * - * Available since API level 27. */ SOFTMAX = 25, @@ -1344,7 +1338,8 @@ enum OperationType : int32_t { * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * - * Supported tensor rank: 4, with "NHWC" data layout. + * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width, + * and Channels) data layout. * * Inputs: * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], @@ -1356,8 +1351,8 @@ enum OperationType : int32_t { * Outputs: * * 0: The output 4-D tensor, of shape [batches, height/block_size, * width/block_size, depth_in*block_size*block_size]. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ SPACE_TO_DEPTH = 26, @@ -1403,25 +1398,23 @@ enum OperationType : int32_t { * Supported tensor {@link OperandType}: * * {@link OperandType::TENSOR_FLOAT32} * + * All input tensors must be the same type. + * * Inputs: * * 0: input. - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [batch_size, input_size], where “batch_size” corresponds to the - * batching dimension, and “input_size” is the size of the input. + * A 2-D tensor of shape [batch_size, input_size], where “batch_size” + * corresponds to the batching dimension, and “input_size” is the size + * of the input. * * 1: weights_feature. - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units, input_size], where “num_units” corresponds to the - * number of units. + * A 2-D tensor of shape [num_units, input_size], where “num_units” + * corresponds to the number of units. * * 2: weights_time. - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [num_units, memory_size], where “memory_size” corresponds to the - * fixed-size of the memory. + * A 2-D tensor of shape [num_units, memory_size], where “memory_size” + * corresponds to the fixed-size of the memory. * * 3: bias. - * An optional 1-D tensor of {@link OperandType::TENSOR_FLOAT32}, - * of shape [num_units]. + * An optional 1-D tensor of shape [num_units]. * * 4: state (in). - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape - * [batch_size, (memory_size - 1) * num_units * rank]. + * A 2-D tensor of shape [batch_size, (memory_size - 1) * num_units * rank]. * * 5: rank. * The rank of the SVD approximation. * * 6: fused_activation_function. @@ -1431,13 +1424,11 @@ enum OperationType : int32_t { * * Outputs: * * 0: state (out). - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape + * A 2-D tensor of the same {@link OperandType} as the inputs, with shape * [batch_size, (memory_size - 1) * num_units * rank]. * * 1: output. - * A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape + * A 2-D tensor of the same {@link OperandType} as the inputs, with shape * [batch_size, num_units]. - * - * Available since API level 27. */ SVDF = 27, @@ -1458,8 +1449,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: The output tensor of same shape as input0. - * - * Available since API level 27. */ TANH = 28, diff --git a/neuralnetworks/1.0/types.t b/neuralnetworks/1.0/types.t new file mode 100644 index 0000000000..d7b26aab35 --- /dev/null +++ b/neuralnetworks/1.0/types.t @@ -0,0 +1,431 @@ +%% template file for generating types.hal. +%% see frameworks/ml/nn/tools/api/README.md. +/* + * Copyright (C) 2017 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.hardware.neuralnetworks@1.0; + +%insert Operand_1.0_Comment +enum OperandType : int32_t { +%insert Operand_1.0 + + /** + * DEPRECATED. Since HAL version 1.2, extensions are the preferred + * alternative to OEM operation and data types. + * + * OEM specific scalar value. + */ + OEM = 10000, + + /** + * DEPRECATED. Since HAL version 1.2, extensions are the preferred + * alternative to OEM operation and data types. + * + * A tensor of OEM specific values. + */ + TENSOR_OEM_BYTE = 10001, +}; + +%insert Operation_1.0_Comment +enum OperationType : int32_t { +%insert Operation_1.0 + + /** + * DEPRECATED. Since NNAPI 1.2, extensions are the preferred alternative to + * OEM operation and data types. + * + * This operation is OEM specific. It should only be used for OEM + * applications. + */ + OEM_OPERATION = 10000, +}; + +/** + * Fused activation function types. + */ +enum FusedActivationFunc : int32_t { + NONE = 0, + RELU = 1, + RELU1 = 2, + RELU6 = 3, +}; + +/** + * How an operand is used. + */ +enum OperandLifeTime : int32_t { + /** + * The operand is internal to the model. It's created by an operation and + * consumed by other operations. It must be an output operand of + * exactly one operation. + */ + TEMPORARY_VARIABLE, + + /** + * The operand is an input of the model. It must not be an output + * operand of any operation. + * + * An operand can't be both input and output of a model. + */ + MODEL_INPUT, + + /** + * The operand is an output of the model. It must be an output + * operand of exactly one operation. + * + * An operand can't be both input and output of a model. + */ + MODEL_OUTPUT, + + /** + * The operand is a constant found in Model.operandValues. It must + * not be an output operand of any operation. + */ + CONSTANT_COPY, + + /** + * The operand is a constant that was specified via a Memory + * object. It must not be an output operand of any operation. + */ + CONSTANT_REFERENCE, + + /** + * The operand does not have a value. This is valid only for optional + * arguments of operations. + */ + NO_VALUE, +}; + +/** + * Status of a device. + */ +enum DeviceStatus : int32_t { + AVAILABLE, + BUSY, + OFFLINE, + UNKNOWN, +}; + +/** + * Performance information for the reference workload. + * + * Used by a driver to report its performance characteristics. + */ +struct PerformanceInfo { + /** + * Ratio of the time taken by the driver to execute the + * workload compared to the time the CPU would take for the + * same workload. A lower number is better. + */ + float execTime; + + /** + * Ratio of the energy used by the driver compared to what + * the CPU would use for doing the same workload. A lower number + * is better. + */ + float powerUsage; +}; + +/** + * The capabilities of a driver. + */ +struct Capabilities { + /** + * Driver performance when operating on float32 data. + */ + PerformanceInfo float32Performance; + + /** + * Driver performance when operating on asymmetric 8-bit quantized data. + */ + PerformanceInfo quantized8Performance; +}; + +/** + * Describes the location of a data object. + */ +struct DataLocation { + /** + * The index of the memory pool where this location is found. + */ + uint32_t poolIndex; + + /** + * Offset in bytes from the start of the pool. + */ + uint32_t offset; + + /** + * The length of the data in bytes. + */ + uint32_t length; +}; + +/** + * Describes one operand of the model's graph. + */ +struct Operand { + /** + * Data type of the operand. + */ + OperandType type; + + /** + * Dimensions of the operand. + * + * For a scalar operand, dimensions.size() must be 0. + * + * For a tensor operand, dimensions.size() must be at least 1; + * however, any of the dimensions may be unspecified. + * + * A tensor operand with all dimensions specified has "fully + * specified" dimensions. Whenever possible (i.e., whenever the + * dimensions are known at model construction time), a tensor + * operand should have (but is not required to have) fully + * specified dimensions, in order to enable the best possible + * performance. + * + * If a tensor operand's dimensions are not fully specified, the + * dimensions of the operand are deduced from the operand + * dimensions and values of the operation for which that operand + * is an output. + * + * In the following situations, a tensor operand's dimensions must + * be fully specified: + * + * . The operand has lifetime CONSTANT_COPY or + * CONSTANT_REFERENCE. + * + * . The operand has lifetime MODEL_INPUT or MODEL_OUTPUT. Fully + * specified dimensions must either be present in the + * Operand or they must be provided in the corresponding + * RequestArgument. + * EXCEPTION: If the input or output is optional and omitted + * (by setting the hasNoValue field of the corresponding + * RequestArgument to true) then it need not have fully + * specified dimensions. + * + * A tensor operand with some number of unspecified dimensions is + * represented by setting each unspecified dimension to 0. + */ + vec<uint32_t> dimensions; + + /** + * The number of times this operand appears as an operation input. + * + * (For example, if this operand appears once in one operation's + * input list, and three times in another operation's input list, + * then numberOfConsumers = 4.) + */ + uint32_t numberOfConsumers; + + /** + * Quantized scale of the operand. + * + * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM or + * TENSOR_INT32. + */ + float scale; + + /** + * Quantized zero-point offset of the operand. + * + * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM. + */ + int32_t zeroPoint; + + /** + * How the operand is used. + */ + OperandLifeTime lifetime; + + /** + * Where to find the data for this operand. + * If the lifetime is TEMPORARY_VARIABLE, MODEL_INPUT, MODEL_OUTPUT, or + * NO_VALUE: + * - All the fields must be 0. + * If the lifetime is CONSTANT_COPY: + * - location.poolIndex is 0. + * - location.offset is the offset in bytes into Model.operandValues. + * - location.length is set. + * If the lifetime is CONSTANT_REFERENCE: + * - location.poolIndex is set. + * - location.offset is the offset in bytes into the specified pool. + * - location.length is set. + */ + DataLocation location; +}; + +/** + * Describes one operation of the model's graph. + */ +struct Operation { + /** + * The operation type. + */ + OperationType type; + + /** + * Describes the table that contains the indexes of the inputs of the + * operation. The offset is the index in the operandIndexes table. + */ + vec<uint32_t> inputs; + + /** + * Describes the table that contains the indexes of the outputs of the + * operation. The offset is the index in the operandIndexes table. + */ + vec<uint32_t> outputs; +}; + +/** + * A Neural Network Model. + * + * This includes not only the execution graph, but also constant data such as + * weights or scalars added at construction time. The only information that + * might not be known is the shape of the input tensors. + */ +struct Model { + /** + * All operands included in the model. + */ + vec<Operand> operands; + + /** + * All operations included in the model. + * + * The operations are sorted into execution order. Every operand + * with lifetime MODEL_OUTPUT or TEMPORARY_VARIABLE must be + * written before it is read. + */ + vec<Operation> operations; + + /** + * Input indexes of the model. There must be at least one. + * + * Each value corresponds to the index of the operand in "operands". + */ + vec<uint32_t> inputIndexes; + + /** + * Output indexes of the model. There must be at least one. + * + * Each value corresponds to the index of the operand in "operands". + */ + vec<uint32_t> outputIndexes; + + /** + * A byte buffer containing operand data that were copied into the model. + * + * An operand's value must be located here if and only if Operand::lifetime + * equals OperandLifeTime::CONSTANT_COPY. + */ + vec<uint8_t> operandValues; + + /** + * A collection of shared memory pools containing operand values. + * + * An operand's value must be located here if and only if Operand::lifetime + * equals OperandLifeTime::CONSTANT_REFERENCE. + */ + vec<memory> pools; +}; + +/** + * Metadata information specifying the location of the input or output data and + * any updates to the input or output operand. + */ +struct RequestArgument { + /** + * If true, the argument does not have a value. This can be used for + * operations that take optional arguments. If true, the fields of location + * are set to 0 and the dimensions vector is left empty. + */ + bool hasNoValue; + + /** + * The location within one of the memory pools passed in the Request. + */ + DataLocation location; + + /** + * Updated dimension information. + * + * If dimensions.size() > 0, dimension information was provided + * along with the argument. This can be the case for models that + * accept inputs of varying size. This can't change the rank, just + * the value of the dimensions that were unspecified in the + * model. If dimensions.size() > 0, then all dimensions must be + * specified here; and any dimension that was specified in the + * model must have the same value here. + * + * If the dimensions in the model are not fully specified, then + * they must be fully specified here, unless hasNoValue is set to + * true. If the dimensions in the model are fully specified, then + * either dimensions.size() may be 0, or the dimensions in the + * model must be identical to the dimensions here. + */ + vec<uint32_t> dimensions; +}; + +/** + * Inputs to be sent to and outputs to be retrieved from a prepared model. + * + * A Request serves two primary tasks: + * 1) Provides the input and output data to be used when executing the model. + * 2) Specifies any updates to the input operand metadata that were left + * unspecified at model preparation time. + * + * An output must not overlap with any other output, with an input, or + * with an operand of lifetime CONSTANT_REFERENCE. + */ +struct Request { + /** + * Input data and information to be used in the execution of a prepared + * model. + * + * The index of the input corresponds to the index in Model.inputIndexes. + * E.g., input[i] corresponds to Model.inputIndexes[i]. + */ + vec<RequestArgument> inputs; + + /** + * Output data and information to be used in the execution of a prepared + * model. + * + * The index of the output corresponds to the index in Model.outputIndexes. + * E.g., output[i] corresponds to Model.outputIndexes[i]. + */ + vec<RequestArgument> outputs; + + /** + * A collection of shared memory pools containing operand data for both the + * inputs and the outputs to a model. + */ + vec<memory> pools; +}; + +/** + * Return status of a function. + */ +enum ErrorStatus : int32_t { + NONE, + DEVICE_UNAVAILABLE, + GENERAL_FAILURE, + OUTPUT_INSUFFICIENT_SIZE, + INVALID_ARGUMENT, +}; diff --git a/neuralnetworks/1.0/vts/functional/Android.bp b/neuralnetworks/1.0/vts/functional/Android.bp index 3e9d5f7e83..ba9fb4584e 100644 --- a/neuralnetworks/1.0/vts/functional/Android.bp +++ b/neuralnetworks/1.0/vts/functional/Android.bp @@ -71,5 +71,5 @@ cc_test { header_libs: [ "libneuralnetworks_headers", ], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/neuralnetworks/1.1/types.hal b/neuralnetworks/1.1/types.hal index 73705bb56d..3d78fb654d 100644 --- a/neuralnetworks/1.1/types.hal +++ b/neuralnetworks/1.1/types.hal @@ -26,7 +26,6 @@ import @1.0::PerformanceInfo; * The type of an operation in a model. */ enum OperationType : @1.0::OperationType { - /** * BatchToSpace for N-dimensional tensors. * @@ -41,7 +40,8 @@ enum OperationType : @1.0::OperationType { * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * - * Supported tensor rank: 4 + * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width, + * and Channels) data layout. * * Inputs: * * 0: An n-D tensor, specifying the tensor to be reshaped @@ -51,8 +51,8 @@ enum OperationType : @1.0::OperationType { * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 28. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ BATCH_TO_SPACE_ND = 29, @@ -91,8 +91,6 @@ enum OperationType : @1.0::OperationType { * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 28. */ DIV = 30, @@ -126,8 +124,8 @@ enum OperationType : @1.0::OperationType { * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 28. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be same as input0. */ MEAN = 31, @@ -138,7 +136,8 @@ enum OperationType : @1.0::OperationType { * * Supported tensor {@link OperandType}: * * {@link OperandType::TENSOR_FLOAT32} - * * {@link OperandType::TENSOR_QUANT8_ASYMM} (the pad value is undefined) + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * (the pad value is undefined) * * Supported tensor rank: up to 4 * @@ -160,11 +159,8 @@ enum OperationType : @1.0::OperationType { * of the padding: * output0.dimension[i] = * padding[i, 0] + input0.dimension[i] + padding[i, 1] - * - * NOTE: The pad value for {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} - * is undefined. - * - * Available since API level 28. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ PAD = 32, @@ -182,8 +178,10 @@ enum OperationType : @1.0::OperationType { * Supported tensor {@link OperandType}: * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * (the pad value is undefined) * - * Supported tensor rank: 4 + * Supported tensor rank: 4, with "NHWC" (i.e., Num_samples, Height, Width, + * and Channels) data layout. * * Inputs: * * 0: An n-D tensor, specifying the input. @@ -201,8 +199,8 @@ enum OperationType : @1.0::OperationType { * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 28. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ SPACE_TO_BATCH_ND = 33, @@ -232,8 +230,8 @@ enum OperationType : @1.0::OperationType { * * 0: A tensor of the same {@link OperandType} as input0. Contains the * same data as input, but has one or more dimensions of size 1 * removed. - * - * Available since API level 28. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ SQUEEZE = 34, @@ -278,8 +276,8 @@ enum OperationType : @1.0::OperationType { * Outputs: * * 0: A tensor of the same {@link OperandType} as input0 and rank (n - k), * where k is the number of bits set in shrink_axis_mask. - * - * Available since API level 28. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ STRIDED_SLICE = 35, @@ -318,8 +316,6 @@ enum OperationType : @1.0::OperationType { * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 28. */ SUB = 36, @@ -345,11 +341,10 @@ enum OperationType : @1.0::OperationType { * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 28. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ TRANSPOSE = 37, - }; /** diff --git a/neuralnetworks/1.1/types.t b/neuralnetworks/1.1/types.t new file mode 100644 index 0000000000..75ac2e7be8 --- /dev/null +++ b/neuralnetworks/1.1/types.t @@ -0,0 +1,158 @@ +%% template file for generating types.hal. +%% see frameworks/ml/nn/tools/api/README.md. +/* + * Copyright (C) 2018 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.hardware.neuralnetworks@1.1; + +import @1.0::Operand; +import @1.0::OperationType; +import @1.0::PerformanceInfo; + +/** + * Operation types. + * + * The type of an operation in a model. + */ +enum OperationType : @1.0::OperationType { +%insert Operation_1.1 +}; + +/** + * The capabilities of a driver. + */ +struct Capabilities { + /** + * Driver performance when operating on float32 data. + */ + PerformanceInfo float32Performance; + + /** + * Driver performance when operating on asymmetric 8-bit quantized data. + */ + PerformanceInfo quantized8Performance; + + /** + * Driver performance when operating on float32 data but performing + * calculations with range and/or precision as low as that of the IEEE + * 754 16-bit floating-point format. + */ + PerformanceInfo relaxedFloat32toFloat16Performance; +}; + +/** + * Describes one operation of the model's graph. + */ +struct Operation { + /** + * The operation type. + */ + OperationType type; + + /** + * Describes the table that contains the indexes of the inputs of the + * operation. The offset is the index in the operandIndexes table. + */ + vec<uint32_t> inputs; + + /** + * Describes the table that contains the indexes of the outputs of the + * operation. The offset is the index in the operandIndexes table. + */ + vec<uint32_t> outputs; +}; + +/** + * A Neural Network Model. + * + * This includes not only the execution graph, but also constant data such as + * weights or scalars added at construction time. The only information that + * may not be known is the shape of the input tensors. + */ +struct Model { + /** + * All operands included in the model. + */ + vec<Operand> operands; + + /** + * All operations included in the model. + * + * The operations are sorted into execution order. Every operand + * with lifetime MODEL_OUTPUT or TEMPORARY_VARIABLE must be + * written before it is read. + */ + vec<Operation> operations; + + /** + * Input indexes of the model. There must be at least one. + * + * Each value corresponds to the index of the operand in "operands". + */ + vec<uint32_t> inputIndexes; + + /** + * Output indexes of the model. There must be at least one. + * + * Each value corresponds to the index of the operand in "operands". + */ + vec<uint32_t> outputIndexes; + + /** + * A byte buffer containing operand data that were copied into the model. + * + * An operand's value must be located here if and only if Operand::lifetime + * equals OperandLifeTime::CONSTANT_COPY. + */ + vec<uint8_t> operandValues; + + /** + * A collection of shared memory pools containing operand values. + * + * An operand's value must be located here if and only if Operand::lifetime + * equals OperandLifeTime::CONSTANT_REFERENCE. + */ + vec<memory> pools; + + /** + * 'true' indicates TENSOR_FLOAT32 may be calculated with range and/or + * precision as low as that of the IEEE 754 16-bit floating-point format. + * 'false' indicates TENSOR_FLOAT32 must be calculated using at least the + * range and precision of the IEEE 754 32-bit floating-point format. + */ + bool relaxComputationFloat32toFloat16; +}; + +/** + * Execution preferences. + */ +enum ExecutionPreference : int32_t { + /** + * Prefer executing in a way that minimizes battery drain. + * This is desirable for compilations that will be executed often. + */ + LOW_POWER = 0, + /** + * Prefer returning a single answer as fast as possible, even if this causes + * more power consumption. + */ + FAST_SINGLE_ANSWER = 1, + /** + * Prefer maximizing the throughput of successive frames, for example when + * processing successive frames coming from the camera. + */ + SUSTAINED_SPEED = 2, +}; diff --git a/neuralnetworks/1.1/vts/functional/Android.bp b/neuralnetworks/1.1/vts/functional/Android.bp index 4e85355838..69e1761ec6 100644 --- a/neuralnetworks/1.1/vts/functional/Android.bp +++ b/neuralnetworks/1.1/vts/functional/Android.bp @@ -47,5 +47,5 @@ cc_test { header_libs: [ "libneuralnetworks_headers", ], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/neuralnetworks/1.2/types.hal b/neuralnetworks/1.2/types.hal index f368ce2cea..837ced5b48 100644 --- a/neuralnetworks/1.2/types.hal +++ b/neuralnetworks/1.2/types.hal @@ -43,8 +43,6 @@ enum OperandType : @1.0::OperandType { * * Values of this operand type are either true or false. A zero value * represents false; any other value represents true. - * - * Available since API level 29. */ BOOL = 6, /** @@ -55,14 +53,10 @@ enum OperandType : @1.0::OperandType { * realValue = integerValue * scale. * * scale is a 32 bit floating point with value greater than zero. - * - * Available since API level 29. */ TENSOR_QUANT16_SYMM = 7, /** * A tensor of IEEE 754 16 bit floating point values. - * - * Available since API level 29. */ TENSOR_FLOAT16 = 8, /** @@ -70,14 +64,10 @@ enum OperandType : @1.0::OperandType { * * Values of this operand type are either true or false. A zero value * represents false; any other value represents true. - * - * Available since API level 29. */ TENSOR_BOOL8 = 9, /** * An IEEE 754 16 bit floating point scalar value. - * - * Available since API level 29. */ FLOAT16 = 10, /** @@ -90,14 +80,13 @@ enum OperandType : @1.0::OperandType { * - scales: an array of positive 32 bit floating point values. * The size of the scales array must be equal to dimensions[channelDim]. * + *{@link SymmPerChannelQuantParams} must hold the parameters for an Operand of this type. * The channel dimension of this tensor must not be unknown (dimensions[channelDim] != 0). * * The formula is: * realValue[..., C, ...] = * integerValue[..., C, ...] * scales[C] * where C is an index in the Channel dimension. - * - * Available since API level 29. */ TENSOR_QUANT8_SYMM_PER_CHANNEL = 11, /** @@ -110,8 +99,6 @@ enum OperandType : @1.0::OperandType { * * The formula is: * real_value = (integer_value - zeroPoint) * scale. - * - * Available since API level 29. */ TENSOR_QUANT16_ASYMM = 12, /** @@ -122,20 +109,19 @@ enum OperandType : @1.0::OperandType { * realValue = integerValue * scale. * * scale is a 32 bit floating point with value greater than zero. - * - * Available since API level 29. */ TENSOR_QUANT8_SYMM = 13, + /* - * DEPRECATED. Since NNAPI 1.2, extensions are the preferred alternative to - * OEM operation and data types. + * DEPRECATED. Since HAL version 1.2, extensions are the preferred + * alternative to OEM operation and data types. * * OEM specific scalar value. * OEM = 10000, */ /* - * DEPRECATED. Since NNAPI 1.2, extensions are the preferred alternative to - * OEM operation and data types. + * DEPRECATED. Since HAL version 1.2, extensions are the preferred + * alternative to OEM operation and data types. * * A tensor of OEM specific values. * TENSOR_OEM_BYTE = 10001, @@ -166,6 +152,7 @@ enum OperandTypeRange : uint32_t { * The type of an operation in a model. */ enum OperationType : int32_t { + /** * Adds two tensors, element-wise. * @@ -187,12 +174,12 @@ enum OperationType : int32_t { * input2.dimension = {5, 4, 3, 1} * output.dimension = {5, 4, 3, 2} * - * Since API level 29, generic zero-sized input tensor is supported. Zero + * Since HAL version 1.2, generic zero-sized input tensor is supported. Zero * dimension is only compatible with 0 or 1. The size of the output * dimension is zero if either of corresponding input dimension is zero. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * @@ -202,14 +189,16 @@ enum OperationType : int32_t { * * 0: A tensor. * * 1: A tensor of the same {@link OperandType}, and compatible dimensions * as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scales and zeroPoint can be different from input0 scale and zeroPoint. * * 2: An {@link OperandType::INT32} scalar, and has to be one of the * {@link FusedActivationFunc} values. Specifies the activation to * invoke on the result. * * Outputs: * * 0: The sum, a tensor of the same {@link OperandType} as input0. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint can be different from inputs' scale and zeroPoint. */ ADD = @1.1::OperationType:ADD, @@ -227,7 +216,7 @@ enum OperationType : int32_t { * ) / sum(1) * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * @@ -235,13 +224,14 @@ enum OperationType : int32_t { * With the default data layout NHWC, the data is stored in the order of: * [batch, height, width, channels]. Alternatively, the data layout could * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. * * Both explicit padding and implicit padding are supported. * * Inputs (explicit padding): * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying - * the input. Since API level 29, zero batches is supported for this - * tensor. + * the input. + * Since HAL version 1.2, zero batches is supported for this tensor. * * 1: An {@link OperandType::INT32} scalar, specifying the padding on * the left, in the ‘width’ dimension. * * 2: An {@link OperandType::INT32} scalar, specifying the padding on @@ -263,12 +253,12 @@ enum OperationType : int32_t { * invoke on the result. * * 10: An optional {@link OperandType::BOOL} scalar, default to false. * Set to true to specify NCHW data layout for input0 and output0. - * Available since API level 29. + * Available since HAL version 1.2. * * Inputs (implicit padding): * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying - * the input. Since API level 29, zero batches is supported for this - * tensor. + * the input. + * Since HAL version 1.2, zero batches is supported for this tensor. * * 1: An {@link OperandType::INT32} scalar, specifying the implicit * padding scheme, has to be one of the * following values: {0 (NONE), 1 (SAME), 2 (VALID)}. @@ -285,13 +275,13 @@ enum OperationType : int32_t { * invoke on the result. * * 7: An optional {@link OperandType::BOOL} scalar, default to false. * Set to true to specify NCHW data layout for input0 and output0. - * Available since API level 29. + * Available since HAL version 1.2. * * Outputs: * * 0: The output 4-D tensor, of shape * [batches, out_height, out_width, depth]. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ AVERAGE_POOL_2D = @1.1::OperationType:AVERAGE_POOL_2D, @@ -302,33 +292,34 @@ enum OperationType : int32_t { * dimensions except the dimension along the concatenation axis. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} - * * {@link OperandType::TENSOR_QUANT8_ASYMM} (full support since API - * level 29, see the input section) + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * (full support since HAL version 1.2, see the input section) * * Supported tensor rank: up to 4 * * Inputs: * * 0 ~ n-1: The list of n input tensors, of shape * [D0, D1, ..., Daxis(i), ..., Dm]. - * Before API level 29, all input tensors of + * Before HAL version 1.2, all input tensors of * {@link OperandType::TENSOR_QUANT8_ASYMM} * must have the same scale and zeroPoint as the output tensor. - * Since API level 29, zero-sized tensors are supported. + * Since HAL version 1.2, zero-sized tensors are supported. * * n: An {@link OperandType::INT32} scalar, specifying the * concatenation axis. * * Outputs: * * 0: The output, a tensor of the same {@link OperandType} as the input * tensors. The output shape is [D0, D1, ..., sum(Daxis(i)), ..., Dm]. - * - * Available since API level 27. + * Since HAL version 1.2, for a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint values can be different from + * input tensors. Before HAL version 1.2 they have to be the same as for the input tensors. */ CONCATENATION = @1.1::OperationType:CONCATENATION, /** - * Performs an 2-D convolution operation. + * Performs a 2-D convolution operation. * * The CONV_2D op sweeps a 2-D filter that can mix channels together over a * batch of images, applying the filter to each window of each image of the @@ -354,7 +345,7 @@ enum OperationType : int32_t { * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to * * * input.scale * filter.scale). * - * Available since API level 29: + * Available since HAL version 1.2: * * 16 bit floating point: * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias. * @@ -368,27 +359,29 @@ enum OperationType : int32_t { * With the default data layout NHWC, the data is stored in the order of: * [batch, height, width, channels]. Alternatively, the data layout could * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. * * Both explicit padding and implicit padding are supported. * * Inputs (explicit padding): * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], - * specifying the input. Since API level 29, zero batches is supported - * for this tensor. + * specifying the input. + * Since HAL version 1.2, zero batches is supported for this tensor. * * 1: A 4-D tensor, of shape * [depth_out, filter_height, filter_width, depth_in], specifying the - * filter. For tensor of type - * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} the channel - * dimension (extraParams.channelQuant.channelDim) must be set to 0. + * filter. + * For tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} + * the channel dimension (SymmPerChannelQuantParams::channelDim) + * must be set to 0. * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input - * tensor of type {@link OperandType::TENSOR_FLOAT32} or - * {@link OperandType::TENSOR_FLOAT16}, the bias must be of the same + * tensor of type {@link OperandType::TENSOR_FLOAT32} + * or {@link OperandType::TENSOR_FLOAT16} the bias must be of the same * type. For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint - * of 0 and bias_scale == input_scale * filter_scale. For filter tensor - * of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, the bias - * should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of - * 0 and bias_scale of 0. The actual scale of each value 'i' is equal to + * of 0 and bias_scale == input_scale * filter_scale. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0 + * and bias_scale of 0. The actual scale of each value 'i' is equal to * bias_scale[i] = input_scale * filter_scale[i]. * * 3: An {@link OperandType::INT32} scalar, specifying the padding on * the left, in the ‘width’ dimension. @@ -407,36 +400,37 @@ enum OperationType : int32_t { * invoke on the result. * * 10: An optional {@link OperandType::BOOL} scalar, default to false. * Set to true to specify NCHW data layout for input0 and output0. - * Available since API level 29. + * Available since HAL version 1.2. * * 11: An optional {@link OperandType::INT32} scalar, specifying the dilation * factor for width. Defaults to 1. If set to k > 1, there will be k-1 skipped * cells between each filter element on width dimension. If this input is set, * input 12 (dilation factor for height) must be specified as well. - * Available since API level 29. + * Available since HAL version 1.2. * * 12: An optional {@link OperandType::INT32} scalar, specifying the dilation * factor for height. Defaults to 1. If set to k > 1, there will be k-1 skipped * cells between each filter element on height dimension. If this input is set, * input 11 (dilation factor for width) must be specified as well. - * Available since API level 29. + * Available since HAL version 1.2. * * Inputs (implicit padding): * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], - * specifying the input. Since API level 29, zero batches is supported - * for this tensor. + * specifying the input. + * Since HAL version 1.2, zero batches is supported for this tensor. * * 1: A 4-D tensor, of shape * [depth_out, filter_height, filter_width, depth_in], specifying the - * filter. For tensor of type - * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} the channel - * dimension (extraParams.channelQuant.channelDim) must be set to 0. + * filter. + * For tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} + * the channel dimension (SymmPerChannelQuantParams::channelDim) + * must be set to 0. * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input - * tensor of type {@link OperandType::TENSOR_FLOAT32} or - * {@link OperandType::TENSOR_FLOAT16}, the bias must be of the same + * tensor of type {@link OperandType::TENSOR_FLOAT32} + * or {@link OperandType::TENSOR_FLOAT16} the bias must be of the same * type. For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint - * of 0 and bias_scale == input_scale * filter_scale. For filter tensor - * of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, the bias - * should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of - * 0 and bias_scale of 0. The actual scale of each value 'i' is equal to + * of 0 and bias_scale == input_scale * filter_scale. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0 + * and bias_scale of 0. The actual scale of each value 'i' is equal to * bias_scale[i] = input_scale * filter_scale[i]. * * 3: An {@link OperandType::INT32} scalar, specifying the implicit * padding scheme, has to be one of the @@ -450,26 +444,23 @@ enum OperationType : int32_t { * invoke on the result. * * 7: An optional {@link OperandType::BOOL} scalar, default to false. * Set to true to specify NCHW data layout for input0 and output0. - * Available since API level 29. + * Available since HAL version 1.2. * * 8: An optional {@link OperandType::INT32} scalar, specifying the dilation * factor for width. Defaults to 1. If set to k > 1, there will be k-1 skipped * cells between each filter element on width dimension. If this input is set, * input 9 (dilation factor for height) must be specified as well. - * Available since API level 29. + * Available since HAL version 1.2. * * 9: An optional {@link OperandType::INT32} scalar, specifying the dilation * factor for height. Defaults to 1. If set to k > 1, there will be k-1 skipped * cells between each filter element on height dimension. If this input is set, * input 8 (dilation factor for width) must be specified as well. - * Available since API level 29. + * Available since HAL version 1.2. * * Outputs: * * 0: The output 4-D tensor, of shape - * [batches, out_height, out_width, depth_out]. Before API level 29, - * for output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, the - * following condition must be satisfied: - * output_scale > input_scale * filter_scale - * - * Available since API level 27. + * [batches, out_height, out_width, depth_out]. + * Before HAL version 1.2, for output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, + * the following condition must be satisfied: output_scale > input_scale * filter_scale */ CONV_2D = @1.1::OperationType:CONV_2D, @@ -504,7 +495,7 @@ enum OperationType : int32_t { * * * {@link OperandType::TENSOR_INT32} for bias (with scale set to * * * input.scale * filter.scale). * - * Available since API level 29: + * Available since HAL version 1.2: * * 16 bit floating point: * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias. * @@ -518,6 +509,7 @@ enum OperationType : int32_t { * With the default data layout NHWC, the data is stored in the order of: * [batch, height, width, channels]. Alternatively, the data layout could * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. * * Both explicit padding and implicit padding are supported. * @@ -525,18 +517,19 @@ enum OperationType : int32_t { * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], * specifying the input. * * 1: A 4-D tensor, of shape [1, filter_height, filter_width, depth_out], - * specifying the filter. For tensor of type - * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} the channel - * dimension (extraParams.channelQuant.channelDim) must be set to 3. + * specifying the filter. + * For tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} + * the channel dimension (SymmPerChannelQuantParams::channelDim) + * must be set to 3. * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input - * tensor of type {@link OperandType::TENSOR_FLOAT32} or - * {@link OperandType::TENSOR_FLOAT16}, the bias must be of the same + * tensor of type {@link OperandType::TENSOR_FLOAT32} + * or {@link OperandType::TENSOR_FLOAT16} the bias must be of the same * type. For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint - * of 0 and bias_scale == input_scale * filter_scale. For filter tensor - * of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, the bias - * should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of - * 0 and bias_scale of 0. The actual scale of each value 'i' is equal to + * of 0 and bias_scale == input_scale * filter_scale. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0 + * and bias_scale of 0. The actual scale of each value 'i' is equal to * bias_scale[i] = input_scale * filter_scale[i]. * * 3: An {@link OperandType::INT32} scalar, specifying the padding on * the left, in the ‘width’ dimension. @@ -557,17 +550,17 @@ enum OperationType : int32_t { * invoke on the result. * * 11: An optional {@link OperandType::BOOL} scalar, default to false. * Set to true to specify NCHW data layout for input0 and output0. - * Available since API level 29. + * Available since HAL version 1.2. * * 12: An optional {@link OperandType::INT32} scalar, specifying the dilation * factor for width. Defaults to 1. If set to k > 1, there will be k-1 skipped * cells between each filter element on width dimension. If this input is set, * input 13 (dilation factor for height) must be specified as well. - * Available since API level 29. + * Available since HAL version 1.2. * * 13: An optional {@link OperandType::INT32} scalar, specifying the dilation * factor for height. Defaults to 1. If set to k > 1, there will be k-1 skipped * cells between each filter element on height dimension. If this input is set, * input 12 (dilation factor for width) must be specified as well. - * Available since API level 29. + * Available since HAL version 1.2. * * Inputs (implicit padding): * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], @@ -575,14 +568,14 @@ enum OperationType : int32_t { * * 1: A 4-D tensor, of shape [1, filter_height, filter_width, depth_out], * specifying the filter. * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input - * tensor of type {@link OperandType::TENSOR_FLOAT32} or - * {@link OperandType::TENSOR_FLOAT16}, the bias must be of the same + * tensor of type {@link OperandType::TENSOR_FLOAT32} + * or {@link OperandType::TENSOR_FLOAT16} the bias must be of the same * type. For filter tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint - * of 0 and bias_scale == input_scale * filter_scale. For filter tensor - * of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, the bias - * should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of - * 0 and bias_scale of 0. The actual scale of each value 'i' is equal to + * of 0 and bias_scale == input_scale * filter_scale. + * For filter tensor of {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL}, + * the bias should be of {@link OperandType::TENSOR_INT32}, with zeroPoint of 0 + * and bias_scale of 0. The actual scale of each value 'i' is equal to * bias_scale[i] = input_scale * filter_scale[i]. * * 3: An {@link OperandType::INT32} scalar, specifying the implicit * padding scheme, has to be one of the @@ -598,27 +591,24 @@ enum OperationType : int32_t { * invoke on the result. * * 8: An optional {@link OperandType::BOOL} scalar, default to false. * Set to true to specify NCHW data layout for input0 and output0. - * Available since API level 29. + * Available since HAL version 1.2. * * 9: An optional {@link OperandType::INT32} scalar, specifying the dilation * factor for width. Defaults to 1. If set to k > 1, there will be k-1 skipped * cells between each filter element on width dimension. If this input is set, * input 10 (dilation factor for height) must be specified as well. - * Available since API level 29. + * Available since HAL version 1.2. * * 10: An optional {@link OperandType::INT32} scalar, specifying the dilation * factor for height. Defaults to 1. If set to k > 1, there will be k-1 skipped * cells between each filter element on height dimension. If this input is set, * input 9 (dilation factor for width) must be specified as well. - * Available since API level 29. - + * Available since HAL version 1.2. * * Outputs: * * 0: The output 4-D tensor, of shape - * [batches, out_height, out_width, depth_out]. Before API level 29, - * for output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, the - * following condition must be satisfied: + * [batches, out_height, out_width, depth_out]. Before HAL version 1.2, for + * output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, + * the following condition must be satisfied: * output_scale > input_scale * filter_scale - * - * Available since API level 27. */ DEPTHWISE_CONV_2D = @1.1::OperationType:DEPTHWISE_CONV_2D, @@ -638,7 +628,7 @@ enum OperationType : int32_t { * be divisible by block_size * block_size * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * @@ -646,6 +636,7 @@ enum OperationType : int32_t { * With the default data layout NHWC, the data is stored in the order of: * [batch, height, width, channels]. Alternatively, the data layout could * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. * * Inputs: * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], @@ -655,13 +646,13 @@ enum OperationType : int32_t { * of the input depth. * * 2: An optional {@link OperandType::BOOL} scalar, default to false. * Set to true to specify NCHW data layout for input0 and output0. - * Available since API level 29. + * Available since HAL version 1.2. * * Outputs: * * 0: The output 4-D tensor, of shape [batch, height*block_size, * width*block_size, depth/(block_size*block_size)]. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ DEPTH_TO_SPACE = @1.1::OperationType:DEPTH_TO_SPACE, @@ -674,22 +665,21 @@ enum OperationType : int32_t { * * Supported input tensor {@link OperandType}: * * {@link OperandType::TENSOR_QUANT8_ASYMM} - * * {@link OperandType::TENSOR_QUANT8_SYMM} (since API level 29) - * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} (since API level 29) + * * {@link OperandType::TENSOR_QUANT8_SYMM} (since HAL version 1.2) + * * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} (since HAL version 1.2) * * Supported output tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32}. * * Supported tensor rank: up to 4 * * Inputs: - * * 0: A tensor. Since API level 29, this tensor may be zero-sized. + * * 0: A tensor. + * Since HAL version 1.2, this tensor may be zero-sized. * * Outputs: * * 0: A tensor with the same shape as input0. - * - * Available since API level 27. */ DEQUANTIZE = @1.1::OperationType:DEQUANTIZE, @@ -730,8 +720,8 @@ enum OperationType : int32_t { * * 0: A n-D tensor with the same rank and shape as the Values * tensor, except for the first dimension which has the same size * as Lookups' only dimension. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input1. */ EMBEDDING_LOOKUP = @1.1::OperationType:EMBEDDING_LOOKUP, @@ -739,7 +729,7 @@ enum OperationType : int32_t { * Computes element-wise floor() on the input tensor. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * Supported tensor rank: up to 4 @@ -750,8 +740,6 @@ enum OperationType : int32_t { * Outputs: * * 0: The output tensor, of the same {@link OperandType} and dimensions as * the input tensor. - * - * Available since API level 27. */ FLOOR = @1.1::OperationType:FLOOR, @@ -764,7 +752,7 @@ enum OperationType : int32_t { * outputs = activation(inputs * weights’ + bias) * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * @@ -777,8 +765,8 @@ enum OperationType : int32_t { * [batch_size, input_size], where "input_size" corresponds to the * number of inputs to the layer, matching the second dimension of * weights, and "batch_size" is calculated by dividing the number of - * elements by "input_size". Since API level 29, zero batch_size is - * supported for this tensor. + * elements by "input_size". + * Since HAL version 1.2, zero batch_size is supported for this tensor. * * 1: A 2-D tensor, specifying the weights, of shape * [num_units, input_size], where "num_units" corresponds to the number * of output nodes. @@ -793,12 +781,9 @@ enum OperationType : int32_t { * invoke on the result. * * Outputs: - * * 0: The output tensor, of shape [batch_size, num_units]. Before API - * level 29, For output tensor of {@link - * OperandType::TENSOR_QUANT8_ASYMM}, the following condition must be - * satisfied: output_scale > input_scale * filter_scale. - * - * Available since API level 27. + * * 0: The output tensor, of shape [batch_size, num_units]. Before HAL version 1.2, for + * output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, the following + * condition must be satisfied: output_scale > input_scale * filter_scale. */ FULLY_CONNECTED = @1.1::OperationType:FULLY_CONNECTED, @@ -849,13 +834,13 @@ enum OperationType : int32_t { * * Outputs: * * 0: Output. A tensor with shape [ k …]. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input2. * * 1: Hits. A boolean tensor with shape [ k ] indicates whether the lookup * hits (True) or not (False). * Stored as {@link OperandType::TENSOR_QUANT8_ASYMM} with offset 0 * and scale 1.0f. * A non-zero byte represents True, a hit. A zero indicates otherwise. - * - * Available since API level 27. */ HASHTABLE_LOOKUP = @1.1::OperationType:HASHTABLE_LOOKUP, @@ -872,12 +857,12 @@ enum OperationType : int32_t { * 1-D slice along dimension dim. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} - * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since API level 29) + * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2) * * Supported tensor rank: up to 4 - * Tensors with rank less than 4 are only supported since API level 29. + * Tensors with rank less than 4 are only supported since HAL version 1.2. * * Inputs: * * 0: An n-D tensor, specifying the tensor to be normalized. @@ -885,14 +870,12 @@ enum OperationType : int32_t { * specifying the dimension normalization would be performed on. * Negative index is used to specify axis from the end (e.g. -1 for * the last axis). Must be in the range [-n, n). - * Available since API level 29. + * Available since HAL version 1.2. * * Outputs: * * 0: A tensor of the same {@link OperandType} and same shape as input0. * For {@link OperandType::TENSOR_QUANT8_ASYMM}, * the scale must be 1.f / 128 and the zeroPoint must be 128. - * - * Available since API level 27. */ L2_NORMALIZATION = @1.1::OperationType:L2_NORMALIZATION, @@ -909,20 +892,21 @@ enum OperationType : int32_t { * sum(1)) * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. * With the default data layout NHWC, the data is stored in the order of: * [batch, height, width, channels]. Alternatively, the data layout could * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. * * Both explicit padding and implicit padding are supported. * * Inputs (explicit padding): * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying - * the input. Since API level 29, zero batches is supported for this - * tensor. + * the input. + * Since HAL version 1.2, zero batches is supported for this tensor. * * 1: An {@link OperandType::INT32} scalar, specifying the padding on * the left, in the ‘width’ dimension. * * 2: An {@link OperandType::INT32} scalar, specifying the padding on @@ -944,12 +928,12 @@ enum OperationType : int32_t { * invoke on the result. * * 10: An optional {@link OperandType::BOOL} scalar, default to false. * Set to true to specify NCHW data layout for input0 and output0. - * Available since API level 29. + * Available since HAL version 1.2. * * Inputs (implicit padding): * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying - * the input. Since API level 29, zero batches is supported for this - * tensor. + * the input. + * Since HAL version 1.2, zero batches is supported for this tensor. * * 1: An {@link OperandType::INT32} scalar, specifying the implicit * padding scheme, has to be one of the * following values: {0 (NONE), 1 (SAME), 2 (VALID)}. @@ -966,13 +950,11 @@ enum OperationType : int32_t { * invoke on the result. * * 7: An optional {@link OperandType::BOOL} scalar, default to false. * Set to true to specify NCHW data layout for input0 and output0. - * Available since API level 29. + * Available since HAL version 1.2. * * Outputs: * * 0: The output 4-D tensor, of shape * [batches, out_height, out_width, depth]. - * - * Available since API level 27. */ L2_POOL_2D = @1.1::OperationType:L2_POOL_2D, @@ -994,11 +976,11 @@ enum OperationType : int32_t { * 1-D slice along specified dimension. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * Supported tensor rank: up to 4 - * Tensors with rank less than 4 are only supported since API level 29. + * Tensors with rank less than 4 are only supported since HAL version 1.2. * * Inputs: * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying @@ -1011,10 +993,10 @@ enum OperationType : int32_t { * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the bias * value must be of {@link OperandType::FLOAT32}. * * 3: A scalar, specifying the scale factor, alpha. - * For input tensor of {@link OperandType::TENSOR_FLOAT16}, the alpha - * value must be of {@link OperandType::FLOAT16}. - * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the alpha - * value must be of {@link OperandType::FLOAT32}. + * For input tensor of {@link OperandType::TENSOR_FLOAT16}, the + * alpha value must be of {@link OperandType::FLOAT16}. + * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the + * alpha value must be of {@link OperandType::FLOAT32}. * * 4: A scalar, specifying the exponent, beta. * For input tensor of {@link OperandType::TENSOR_FLOAT16}, the beta * value must be of {@link OperandType::FLOAT16}. @@ -1024,12 +1006,10 @@ enum OperationType : int32_t { * specifying the dimension normalization would be performed on. * Negative index is used to specify axis from the end (e.g. -1 for * the last axis). Must be in the range [-n, n). - * Available since API level 29. + * Available since HAL version 1.2. * * Outputs: * * 0: The output tensor of same shape as input0. - * - * Available since API level 27. */ LOCAL_RESPONSE_NORMALIZATION = @1.1::OperationType:LOCAL_RESPONSE_NORMALIZATION, @@ -1041,22 +1021,20 @@ enum OperationType : int32_t { * output = 1 / (1 + exp(-input)) * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * * Supported tensor rank: up to 4. * * Inputs: - * * 0: A tensor, specifying the input. Since API level 29, this tensor may - * be zero-sized. + * * 0: A tensor, specifying the input. + * Since HAL version 1.2, this tensor may be zero-sized. * * Outputs: * * 0: The output tensor of same shape as input0. * For {@link OperandType::TENSOR_QUANT8_ASYMM}, * the scale must be 1.f / 256 and the zeroPoint must be 0. - * - * Available since API level 27. */ LOGISTIC = @1.1::OperationType:LOGISTIC, @@ -1064,7 +1042,7 @@ enum OperationType : int32_t { * Projects an input to a bit vector via locality senstive hashing. * * Supported input tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_INT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} @@ -1086,7 +1064,7 @@ enum OperationType : int32_t { * Tensor[1].Dim[0] == Tensor[2].Dim[0] * * 3: Type: * Sparse: - * Value LSHProjectionType_SPARSE(=3) (since API level 29). + * Value LSHProjectionType_SPARSE(=3) (since HAL version 1.2). * Computed bit vector is considered to be sparse. * Each output element is an int32 made up of multiple bits * computed from hash functions. @@ -1107,14 +1085,12 @@ enum OperationType : int32_t { * Outputs: * * 0: If the projection type is Sparse: * Output.Dim == { Tensor[0].Dim[0] } - * A tensor of int32 that represents hash signatures, + * A tensor of int32 that represents hash signatures. * * If the projection type is Dense: * Output.Dim == { Tensor[0].Dim[0] * Tensor[0].Dim[1] } * A flattened tensor that represents projected bit vectors. - * - * Available since API level 27. - * The offset value for sparse projections was added in API level 29. + * The offset value for sparse projections was added in HAL version 1.2. */ LSH_PROJECTION = @1.1::OperationType:LSH_PROJECTION, @@ -1170,7 +1146,7 @@ enum OperationType : int32_t { * matrix, each element of which is the product of the corresponding * elements of the input matrices. * - * Since API level 29 LSTM supports layer normalization. + * Since HAL version 1.2 LSTM supports layer normalization. * In case layer normalization is used, the inputs to internal activation * functions (sigmoid and \f$g\f$) are normalized, rescaled and recentered * following an approach from section 3.1 from @@ -1197,7 +1173,7 @@ enum OperationType : int32_t { * * The projection bias (\f$b_{proj}\f$) may (but not required to) have a * value if the recurrent projection layer exists, and should otherwise * have no value. - * * (API level >= 29) The four layer normalization weights either all have + * * (HAL version 1.2 or later) The four layer normalization weights either all have * values or none of them have values. Additionally, if CIFG is used, * input layer normalization weights tensor is omitted and the other layer * normalization weights either all have values or none of them have @@ -1228,7 +1204,7 @@ enum OperationType : int32_t { * Jimmy Ba et al. "Layer Normalization" * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * All input and output tensors must be of the same type. @@ -1291,24 +1267,24 @@ enum OperationType : int32_t { * * 21:The clipping threshold (\f$t_{cell}\f$) for the cell state, such * that values are bound within [-cell_clip, cell_clip]. If set to 0.0 * then clipping is disabled. - * Until API level 29 this scalar must be of type {@link - * FLOAT32}. Since API level 29, if all the input + * Until HAL version 1.2 this scalar must be of type {@link + * OperandType::FLOAT32}. Since HAL version 1.2, if all the input * tensors have type {@link OperandType::TENSOR_FLOAT32}, this * scalar must be of the type {@link OperandType::FLOAT32}, * otherwise if all the input tensors have the type {@link - * TENSOR_FLOAT16}, this scalar must be of type {@link - * FLOAT16}. + * OperandType::TENSOR_FLOAT16}, this scalar must be of type {@link + * OperandType::FLOAT16}. * * 22:The clipping threshold (\f$t_{proj}\f$) for the output from the * projection layer, such that values are bound within * [-proj_clip, proj_clip]. If set to 0.0 then clipping is disabled. - * Until API level 29 this scalar must be of type {@link - * FLOAT32}. Since API level 29, if all the input + * Until HAL version 1.2 this scalar must be of type {@link + * OperandType::FLOAT32}. Since HAL version 1.2, if all the input * tensors have type {@link OperandType::TENSOR_FLOAT32}, this * scalar must be of the type {@link OperandType::FLOAT32}, * otherwise if all the input tensors have the type {@link - * TENSOR_FLOAT16}, this scalar must be of type {@link - * FLOAT16}. - * Since API level 29 there are additional inputs to this op: + * OperandType::TENSOR_FLOAT16}, this scalar must be of type {@link + * OperandType::FLOAT16}. + * Since HAL version 1.2 there are additional inputs to this op: * * 23:The input layer normalization weights. * A 1-D tensor of shape [num_units]. Used to rescale normalized inputs * to activation at input gate. @@ -1333,8 +1309,6 @@ enum OperationType : int32_t { * * 3: The output (\f$o_t\f$). * A 2-D tensor of shape [batch_size, output_size]. This is effectively * the same as the current “output state (out)” value. - * - * Available since API level 27. */ LSTM = @1.1::OperationType:LSTM, @@ -1352,7 +1326,7 @@ enum OperationType : int32_t { * ) * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * @@ -1360,13 +1334,14 @@ enum OperationType : int32_t { * With the default data layout NHWC, the data is stored in the order of: * [batch, height, width, channels]. Alternatively, the data layout could * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. * * Both explicit padding and implicit padding are supported. * * Inputs (explicit padding): * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying - * the input. Since API level 29, zero batches is supported for this - * tensor. + * the input. + * Since HAL version 1.2, zero batches is supported for this tensor. * * 1: An {@link OperandType::INT32} scalar, specifying the padding on * the left, in the ‘width’ dimension. * * 2: An {@link OperandType::INT32} scalar, specifying the padding on @@ -1388,12 +1363,12 @@ enum OperationType : int32_t { * invoke on the result. * * 10: An optional {@link OperandType::BOOL} scalar, default to false. * Set to true to specify NCHW data layout for input0 and output0. - * Available since API level 29. + * Available since HAL version 1.2. * * Inputs (implicit padding): * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying - * the input. Since API level 29, zero batches is supported for this - * tensor. + * the input. + * Since HAL version 1.2, zero batches is supported for this tensor. * * 1: An {@link OperandType::INT32} scalar, specifying the implicit * padding scheme, has to be one of the * following values: {0 (NONE), 1 (SAME), 2 (VALID)}. @@ -1410,13 +1385,13 @@ enum OperationType : int32_t { * invoke on the result. * * 7: An optional {@link OperandType::BOOL} scalar, default to false. * Set to true to specify NCHW data layout for input0 and output0. - * Available since API level 29. + * Available since HAL version 1.2. * * Outputs: * * 0: The output 4-D tensor, of shape * [batches, out_height, out_width, depth]. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ MAX_POOL_2D = @1.1::OperationType:MAX_POOL_2D, @@ -1435,15 +1410,15 @@ enum OperationType : int32_t { * of the input operands. It starts with the trailing dimensions, and works * its way forward. * + * Since HAL version 1.2, generic zero-sized input tensor is supported. Zero + * dimension is only compatible with 0 or 1. The size of the output + * dimension is zero if either of corresponding input dimension is zero. + * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * - * Since API level 29, generic zero-sized input tensor is supported. Zero - * dimension is only compatible with 0 or 1. The size of the output - * dimension is zero if either of corresponding input dimension is zero. - * * Supported tensor rank: up to 4 * * Inputs: @@ -1459,8 +1434,6 @@ enum OperationType : int32_t { * For output tensor of {@link OperandType::TENSOR_QUANT8_ASYMM}, * the following condition must be satisfied: * output_scale > input1_scale * input2_scale. - * - * Available since API level 27. */ MUL = @1.1::OperationType:MUL, @@ -1472,20 +1445,20 @@ enum OperationType : int32_t { * output = max(0, input) * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * * Supported tensor rank: up to 4. * * Inputs: - * * 0: A tensor, specifying the input. Since API level 29, this tensor may - * be zero-sized. + * * 0: A tensor, specifying the input. + * Since HAL version 1.2, this tensor may be zero-sized. * * Outputs: * * 0: The output tensor of same shape as input0. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ RELU = @1.1::OperationType:RELU, @@ -1497,20 +1470,20 @@ enum OperationType : int32_t { * output = min(1.f, max(-1.f, input)) * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * * Supported tensor rank: up to 4. * * Inputs: - * * 0: A tensor, specifying the input. Since API level 29, this tensor may - * be zero-sized. + * * 0: A tensor, specifying the input. + * Since HAL version 1.2, this tensor may be zero-sized. * * Outputs: - * * 0: The output tensor of same shape as input0. - * - * Available since API level 27. + * * 0: The output tensor of the same shape as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ RELU1 = @1.1::OperationType:RELU1, @@ -1522,20 +1495,20 @@ enum OperationType : int32_t { * output = min(6, max(0, input)) * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * * Supported tensor rank: up to 4. * * Inputs: - * * 0: A tensor, specifying the input. Since API level 29, this tensor may - * be zero-sized. + * * 0: A tensor, specifying the input. + * Since HAL version 1.2, this tensor may be zero-sized. * * Outputs: * * 0: The output tensor of same shape as input0. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ RELU6 = @1.1::OperationType:RELU6, @@ -1546,7 +1519,7 @@ enum OperationType : int32_t { * tensor, but with a newly specified shape. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * @@ -1565,8 +1538,8 @@ enum OperationType : int32_t { * * Outputs: * * 0: The output tensor, of shape specified by the input shape. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ RESHAPE = @1.1::OperationType:RESHAPE, @@ -1578,30 +1551,31 @@ enum OperationType : int32_t { * same as corner pixels of input. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} - * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since API level 29) + * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2) * * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. * With the default data layout NHWC, the data is stored in the order of: * [batch, height, width, channels]. Alternatively, the data layout could * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. * * Both resizing by shape and resizing by scale are supported. * * Inputs (resizing by shape): * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying - * the input. Since API level 29, zero batches is supported for this - * tensor. + * the input. + * Since HAL version 1.2, zero batches is supported for this tensor. * * 1: An {@link OperandType::INT32} scalar, specifying the output * width of the output tensor. * * 2: An {@link OperandType::INT32} scalar, specifying the output * height of the output tensor. * * 3: An optional {@link OperandType::BOOL} scalar, default to false. * Set to true to specify NCHW data layout for input0 and output0. - * Available since API level 29. + * Available since HAL version 1.2. * - * Inputs (resizing by scale, since API level 29): + * Inputs (resizing by scale, since HAL version 1.2): * * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying * the input. Zero batches is supported for this tensor. * * 1: A scalar, specifying width_scale, the scaling factor of the width @@ -1622,8 +1596,8 @@ enum OperationType : int32_t { * Outputs: * * 0: The output 4-D tensor, of shape * [batches, new_height, new_width, depth]. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ RESIZE_BILINEAR = @1.1::OperationType:RESIZE_BILINEAR, @@ -1644,7 +1618,7 @@ enum OperationType : int32_t { * argument (if not “NONE”). * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * The input tensors must all be the same type. @@ -1676,8 +1650,6 @@ enum OperationType : int32_t { * * 1: output. * A 2-D tensor of shape [batch_size, num_units]. This is effectively * the same as the current state value. - * - * Available since API level 27. */ RNN = @1.1::OperationType:RNN, @@ -1696,34 +1668,32 @@ enum OperationType : int32_t { * independently on each 1-D slice along specified dimension. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * * Supported tensor rank: up to 4. - * Tensors with rank other than 2 or 4 are only supported since API level 29. + * Tensors with rank other than 2 or 4 are only supported since HAL version 1.2. * * Inputs: - * * 0: A 2-D or 4-D tensor, specifying the tensor to be reshaped. Since - * API level 29, this tensor may be zero-sized. + * * 0: A 2-D or 4-D tensor, specifying the tensor to be reshaped. + * Since HAL version 1.2, this tensor may be zero-sized. * * 1: A scalar, specifying the positive scaling factor for the exponent, * beta. If input0 is of {@link OperandType::TENSOR_FLOAT32} or * {@link OperandType::TENSOR_QUANT8_ASYMM}, the scalar must be of - * {@link OperandType::FLOAT32}. If input0 is of {@link - * OperandType::TENSOR_FLOAT16}, then the scalar must be of {@link - * OperandType::FLOAT16}. + * {@link OperandType::FLOAT32}. + * If input0 is of {@link OperandType::TENSOR_FLOAT16}, then the + * scalar must be of {@link OperandType::FLOAT16}. * * 2: An optional {@link OperandType::INT32} scalar, default to -1, * specifying the dimension the activation would be performed on. * Negative index is used to specify axis from the end (e.g. -1 for * the last axis). Must be in the range [-n, n). - * Available since API level 29. + * Available since HAL version 1.2. * * Outputs: * * 0: The output tensor of same shape as input0. * For {@link OperandType::TENSOR_QUANT8_ASYMM}, * the scale must be 1.f / 256 and the zeroPoint must be 0. - * - * Available since API level 27. */ SOFTMAX = @1.1::OperationType:SOFTMAX, @@ -1742,7 +1712,7 @@ enum OperationType : int32_t { * The input tensor's height and width must be divisible by block_size. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * @@ -1750,6 +1720,7 @@ enum OperationType : int32_t { * With the default data layout NHWC, the data is stored in the order of: * [batch, height, width, channels]. Alternatively, the data layout could * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. * * Inputs: * * 0: A 4-D tensor, of shape [batches, height, width, depth_in], @@ -1759,13 +1730,13 @@ enum OperationType : int32_t { * input height and width. * * 2: An optional {@link OperandType::BOOL} scalar, default to false. * Set to true to specify NCHW data layout for input0 and output0. - * Available since API level 29. + * Available since HAL version 1.2. * * Outputs: * * 0: The output 4-D tensor, of shape [batches, height/block_size, * width/block_size, depth_in*block_size*block_size]. - * - * Available since API level 27. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ SPACE_TO_DEPTH = @1.1::OperationType:SPACE_TO_DEPTH, @@ -1809,7 +1780,7 @@ enum OperationType : int32_t { * the filters. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * All input tensors must be the same type. @@ -1843,8 +1814,6 @@ enum OperationType : int32_t { * * 1: output. * A 2-D tensor of the same {@link OperandType} as the inputs, with shape * [batch_size, num_units]. - * - * Available since API level 27. */ SVDF = @1.1::OperationType:SVDF, @@ -1856,22 +1825,20 @@ enum OperationType : int32_t { * output = tanh(input) * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} - * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since API level 29) + * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2) * * Supported tensor rank: up to 4. * * Inputs: - * * 0: A tensor, specifying the input. Since API level 29, this tensor may - * be zero-sized. + * * 0: A tensor, specifying the input. + * Since HAL version 1.2, this tensor may be zero-sized. * * Outputs: * * 0: The output tensor of same shape as input0. * For {@link OperandType::TENSOR_QUANT8_ASYMM}, * the scale must be 1.f / 128 and the zeroPoint must be 128. - * - * Available since API level 27. */ TANH = @1.1::OperationType:TANH, @@ -1886,7 +1853,7 @@ enum OperationType : int32_t { * This is the reverse of SpaceToBatch. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * @@ -1894,6 +1861,7 @@ enum OperationType : int32_t { * With the default data layout NHWC, the data is stored in the order of: * [batch, height, width, channels]. Alternatively, the data layout could * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. * * Inputs: * * 0: An n-D tensor, specifying the tensor to be reshaped @@ -1906,8 +1874,8 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 28. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ BATCH_TO_SPACE_ND = @1.1::OperationType:BATCH_TO_SPACE_ND, @@ -1931,12 +1899,12 @@ enum OperationType : int32_t { * input2.dimension = {5, 4, 3, 1} * output.dimension = {5, 4, 3, 2} * - * Since API level 29, generic zero-sized input tensor is supported. Zero + * Since HAL version 1.2, generic zero-sized input tensor is supported. Zero * dimension is only compatible with 0 or 1. The size of the output * dimension is zero if either of corresponding input dimension is zero. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * Supported tensor rank: up to 4 @@ -1951,8 +1919,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 28. */ DIV = @1.1::OperationType:DIV, @@ -1965,7 +1931,7 @@ enum OperationType : int32_t { * length 1. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * @@ -1987,21 +1953,21 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 28. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be same as input0. */ MEAN = @1.1::OperationType:MEAN, /** - * Pads a tensor with zeros. + * Pads a tensor. * * This operation pads a tensor according to the specified paddings. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} - * * {@link OperandType::TENSOR_QUANT8_ASYMM} (full support since API - * level 29, see the output section) + * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * (full support since HAL version 1.2, see the output section) * * Supported tensor rank: up to 4 * @@ -2023,12 +1989,12 @@ enum OperationType : int32_t { * of the padding: * output0.dimension[i] = * padding[i, 0] + input0.dimension[i] + padding[i, 1] + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. * - * NOTE: Before API level 29, the pad value for - * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} is undefined. - * Since API level 29, the pad value is always the logical zero. - * - * Available since API level 28. + * NOTE: Before HAL version 1.2, the pad value for + * {@link OperandType::TENSOR_QUANT8_ASYMM} is undefined. + * Since HAL version 1.2, the pad value is always the logical zero. */ PAD = @1.1::OperationType:PAD, @@ -2044,14 +2010,16 @@ enum OperationType : int32_t { * dimensions of the input are optionally zero padded according to paddings. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} + * (full support since HAL version 1.2, see the output section) * * Supported tensor rank: 4, with "NHWC" or "NCHW" data layout. * With the default data layout NHWC, the data is stored in the order of: * [batch, height, width, channels]. Alternatively, the data layout could * be NCHW, the data storage order of: [batch, channels, height, width]. + * NCHW is supported since HAL version 1.2. * * Inputs: * * 0: An n-D tensor, specifying the input. @@ -2068,12 +2036,16 @@ enum OperationType : int32_t { * end of dimension i. * * 3: An optional {@link OperandType::BOOL} scalar, default to false. * Set to true to specify NCHW data layout for input0 and output0. - * Available since API level 29. + * Available since HAL version 1.2. * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. * - * Available since API level 28. + * NOTE: Before HAL version 1.2, the pad value for + * {@link OperandType::TENSOR_QUANT8_ASYMM} is undefined. + * Since HAL version 1.2, the pad value is always the logical zero. */ SPACE_TO_BATCH_ND = @1.1::OperationType:SPACE_TO_BATCH_ND, @@ -2086,7 +2058,7 @@ enum OperationType : int32_t { * dimensions by specifying the axes (input1). * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * @@ -2104,8 +2076,8 @@ enum OperationType : int32_t { * * 0: A tensor of the same {@link OperandType} as input0. Contains the * same data as input, but has one or more dimensions of size 1 * removed. - * - * Available since API level 28. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ SQUEEZE = @1.1::OperationType:SQUEEZE, @@ -2119,7 +2091,7 @@ enum OperationType : int32_t { * reverse slice. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * @@ -2151,8 +2123,8 @@ enum OperationType : int32_t { * Outputs: * * 0: A tensor of the same {@link OperandType} as input0 and rank (n - k), * where k is the number of bits set in shrink_axis_mask. - * - * Available since API level 28. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ STRIDED_SLICE = @1.1::OperationType:STRIDED_SLICE, @@ -2176,14 +2148,14 @@ enum OperationType : int32_t { * input2.dimension = {5, 4, 3, 1} * output.dimension = {5, 4, 3, 2} * - * Since API level 29, generic zero-sized input tensor is supported. Zero + * Since HAL version 1.2, generic zero-sized input tensor is supported. Zero * dimension is only compatible with 0 or 1. The size of the output * dimension is zero if either of corresponding input dimension is zero. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} - * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since API level 29) + * * {@link OperandType::TENSOR_QUANT8_ASYMM} (since HAL version 1.2) * * Supported tensor rank: up to 4 * @@ -2197,8 +2169,8 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 28. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint can be different from inputs' scale and zeroPoint. */ SUB = @1.1::OperationType:SUB, @@ -2212,7 +2184,7 @@ enum OperationType : int32_t { * regular matrix transpose on 2-D input Tensors. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} (since HAL version 1.2) * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * @@ -2220,14 +2192,14 @@ enum OperationType : int32_t { * * Inputs: * * 0: An n-D tensor, specifying the tensor to be transposed. - * Since API level 29, this tensor may be zero-sized. + * Since HAL version 1.2, this tensor may be zero-sized. * * 1: An optional 1-D Tensor of {@link OperandType::TENSOR_INT32}, * the permutation of the dimensions of the input tensor. * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 28. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ TRANSPOSE = @1.1::OperationType:TRANSPOSE, @@ -2245,8 +2217,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: The output tensor of same shape as input0. - * - * Available since API level 29. */ ABS = 38, @@ -2269,8 +2239,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: An (n - 1)-D {@link OperandType::TENSOR_INT32} tensor. - * - * Available since API level 29. */ // There is no underscore in ARG_MAX to avoid name conflict with // the macro defined in libc/kernel/uapi/linux/limits.h. @@ -2295,8 +2263,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: An (n - 1)-D {@link OperandType::TENSOR_INT32} tensor. - * - * Available since API level 29. */ ARGMIN = 40, // See ARGMAX for naming discussion. @@ -2341,8 +2307,8 @@ enum OperationType : int32_t { * * 0: A tensor of the same {@link OperandType} as input0, with shape * [num_rois, num_classes * 4], specifying the coordinates of each * output bounding box for each class, with format [x1, y1, x2, y2]. - * - * Available since API level 29. + * For type of {@link OperandType::TENSOR_QUANT16_ASYMM}, the + * scale must be 0.125 and the zero point must be 0. */ AXIS_ALIGNED_BBOX_TRANSFORM = 41, @@ -2482,17 +2448,15 @@ enum OperationType : int32_t { * then clipping is disabled. * If all the input tensors have type {@link OperandType::TENSOR_FLOAT32}, * this scalar must be of the type {@link OperandType::FLOAT32}, - * otherwise if all the input tensors have the type {@link - * TENSOR_FLOAT16}, this scalar must be of type {@link - * FLOAT16}. + * otherwise if all the input tensors have the type {@link OperandType::TENSOR_FLOAT16}, + * this scalar must be of type {@link OperandType::FLOAT16}. * * 50: The clipping threshold for the output from the * projection layer, such that values are bound within * [-proj_clip, proj_clip]. If set to 0.0 then clipping is disabled. * If all the input tensors have type {@link OperandType::TENSOR_FLOAT32}, * this scalar must be of the type {@link OperandType::FLOAT32}, - * otherwise if all the input tensors have the type {@link - * TENSOR_FLOAT16}, this scalar must be of type {@link - * FLOAT16}. + * otherwise if all the input tensors have the type {@link OperandType::TENSOR_FLOAT16}, + * this scalar must be of type {@link OperandType::FLOAT16}. * * 51: merge_outputs * An {@link OperandType::BOOL} scalar specifying if the outputs * from forward and backward cells should be merged. @@ -2539,8 +2503,6 @@ enum OperationType : int32_t { * A 3-D tensor of shape: * If time-major: [max_time, batch_size, bw_output_size] * If batch-major: [batch_size, max_time, bw_output_size] - * - * Available since API level 29. */ BIDIRECTIONAL_SEQUENCE_LSTM = 42, @@ -2658,8 +2620,6 @@ enum OperationType : int32_t { * (timeMajor). If it is set to true, then the shape is set to * [maxTime, batchSize, bwNumUnits], otherwise the shape is set to * [batchSize, maxTime, bwNumUnits]. - * - * Available since API level 29. */ BIDIRECTIONAL_SEQUENCE_RNN = 43, @@ -2737,8 +2697,6 @@ enum OperationType : int32_t { * * 3: A 1-D {@link OperandType::TENSOR_INT32} tensor, of shape * [num_output_rois], specifying the batch index of each box. Boxes * with the same batch index are grouped together. - * - * Available since API level 29. */ BOX_WITH_NMS_LIMIT = 44, @@ -2762,8 +2720,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor with the same shape as input0. - * - * Available since API level 29. */ CAST = 45, @@ -2800,8 +2756,8 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of the same {@link OperandType} and same shape as input0. - * - * Available since API level 29. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ CHANNEL_SHUFFLE = 46, @@ -2856,14 +2812,14 @@ enum OperationType : int32_t { * * 11: A scalar, score_threshold. Boxes with scores lower than the * threshold are filtered before sending to the NMS algorithm. The * scalar must be of {@link OperandType::FLOAT16} if input0 is of - * {@link OperandType::TENSOR_FLOAT16} and of {@link - * OperandType::FLOAT32} if input0 is of {@link - * OperandType::TENSOR_FLOAT32}. + * {@link OperandType::TENSOR_FLOAT16} and of + * {@link OperandType::FLOAT32} if input0 is of + * {@link OperandType::TENSOR_FLOAT32}. * * 12: A scalar, specifying the IoU threshold for hard NMS. The scalar - * must be of {@link OperandType::FLOAT16} if input0 is of {@link - * OperandType::TENSOR_FLOAT16} and of {@link - * OperandType::FLOAT32} if input0 is of {@link - * OperandType::TENSOR_FLOAT32}. + * must be of {@link OperandType::FLOAT16} if input0 is of + * {@link OperandType::TENSOR_FLOAT16} and of + * {@link OperandType::FLOAT32} if input0 is of + * {@link OperandType::TENSOR_FLOAT32}. * * 13: An {@link OperandType::BOOL} scalar, set to true to include * background class in the list of label map for the output, set * to false to not include the background. When the background @@ -2882,8 +2838,6 @@ enum OperationType : int32_t { * output detection. * * 3: An 1-D {@link OperandType::TENSOR_INT32} tensor, of shape [batches], * specifying the number of valid output detections for each batch. - * - * Available since API level 29. */ DETECTION_POSTPROCESSING = 47, @@ -2908,8 +2862,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. - * - * Available since API level 29. */ EQUAL = 48, @@ -2927,8 +2879,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: The output tensor of same shape as input0. - * - * Available since API level 29. */ EXP = 49, @@ -2956,8 +2906,8 @@ enum OperationType : int32_t { * Outputs: * * 0: An (n + 1)-D tensor with the same {@link OperandType} and data as * input0. - * - * Available since API level 29. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ EXPAND_DIMS = 50, @@ -2994,8 +2944,8 @@ enum OperationType : int32_t { * * Outputs: * * 0: An (n + k - 1)-D tensor with the same {@link OperandType} as input0. - * - * Available since API level 29. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ GATHER = 51, @@ -3074,8 +3024,6 @@ enum OperationType : int32_t { * * 2: A 1-D {@link OperandType::TENSOR_INT32} tensor, of shape * [num_output_rois], specifying the batch index of each box. Boxes * with the same batch index are grouped together. - * - * Available since API level 29. */ GENERATE_PROPOSALS = 52, @@ -3100,8 +3048,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. - * - * Available since API level 29. */ GREATER = 53, /** @@ -3125,8 +3071,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. - * - * Available since API level 29. */ GREATER_EQUAL = 54, @@ -3191,7 +3135,8 @@ enum OperationType : int32_t { * [depth_out, filter_height, filter_width, depth_group], specifying * the filter, where depth_out must be divisible by num_groups. For * tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} - * the channel dimension must be set to 0. + * the channel dimension (channelDim at + * {@link SymmPerChannelQuantParams}) must be set to 0. * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input * tensor of type {@link OperandType::TENSOR_FLOAT32} or * {@link OperandType::TENSOR_FLOAT16}, the bias must be of the same @@ -3229,7 +3174,8 @@ enum OperationType : int32_t { * [depth_out, filter_height, filter_width, depth_group], specifying * the filter, where depth_out must be divisible by num_groups. For * tensor of type {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} - * the channel dimension must be set to 0. + * the channel dimension (SymmPerChannelQuantParams::channelDim) + * must be set to 0. * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input * tensor of type {@link OperandType::TENSOR_FLOAT32} or * {@link OperandType::TENSOR_FLOAT16}, the bias must be of the same @@ -3258,8 +3204,8 @@ enum OperationType : int32_t { * Outputs: * * 0: The output 4-D tensor, of shape * [batches, out_height, out_width, depth_out]. - * - * Available since API level 29. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint can be different from inputs' scale and zeroPoint. */ GROUPED_CONV_2D = 55, @@ -3300,12 +3246,14 @@ enum OperationType : int32_t { * Outputs: * * 0: A tensor of the same {@link OperandType} as input0, with shape * [num_boxes, num_keypoints], specifying score of the keypoints. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint can be different from input0 scale and zeroPoint. * * 1: A tensor of the same {@link OperandType} as input1, with shape * [num_boxes, num_keypoints, 2], specifying the location of * the keypoints, the second dimension is organized as * [keypoint_x, keypoint_y]. - * - * Available since API level 29. + * For type of {@link OperandType::TENSOR_QUANT16_ASYMM}, the + * scale must be 0.125 and the zero point must be 0. */ HEATMAP_MAX_KEYPOINT = 56, @@ -3339,26 +3287,24 @@ enum OperationType : int32_t { * * 0: An n-D tensor, specifying the tensor to be normalized. * * 1: A scalar, specifying gamma, the scale applied to the normalized * tensor. The scalar must be of {@link OperandType::FLOAT16} if - * input0 is of {@link OperandType::TENSOR_FLOAT16} and of {@link - * OperandType::FLOAT32} if input0 is of {@link - * OperandType::TENSOR_FLOAT32}. + * input0 is of {@link OperandType::TENSOR_FLOAT16} and of + * {@link OperandType::FLOAT32} if input0 is of + * {@link OperandType::TENSOR_FLOAT32}. * * 2: A scalar, specifying beta, the offset applied to the normalized * tensor. The scalar must be of {@link OperandType::FLOAT16} if - * input0 is of {@link OperandType::TENSOR_FLOAT16} and of {@link - * OperandType::FLOAT32} if input0 is of {@link - * OperandType::TENSOR_FLOAT32}. + * input0 is of {@link OperandType::TENSOR_FLOAT16} and of + * {@link OperandType::FLOAT32} if input0 is of + * {@link OperandType::TENSOR_FLOAT32}. * * 3: A scalar, specifying epsilon, the small value added to variance to * avoid dividing by zero. The scalar must be of {@link OperandType::FLOAT16} if - * input0 is of {@link OperandType::TENSOR_FLOAT16} and of {@link - * OperandType::FLOAT32} if input0 is of {@link - * OperandType::TENSOR_FLOAT32}. + * input0 is of {@link OperandType::TENSOR_FLOAT16} and of + * {@link OperandType::FLOAT32} if input0 is of + * {@link OperandType::TENSOR_FLOAT32}. * * 4: An {@link OperandType::BOOL} scalar, set to true to specify * NCHW data layout for input0 and output0. Set to false for NHWC. * * Outputs: * * 0: A tensor of the same {@link OperandType} and same shape as input0. - * - * Available since API level 29. */ INSTANCE_NORMALIZATION = 57, @@ -3383,8 +3329,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. - * - * Available since API level 29. */ LESS = 58, @@ -3409,8 +3353,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. - * - * Available since API level 29. */ LESS_EQUAL = 59, @@ -3428,8 +3370,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: The output tensor of same shape as input0. - * - * Available since API level 29. */ LOG = 60, @@ -3450,8 +3390,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. - * - * Available since API level 29. */ LOGICAL_AND = 61, @@ -3468,8 +3406,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: The output tensor of same shape as input0. - * - * Available since API level 29. */ LOGICAL_NOT = 62, @@ -3490,8 +3426,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. - * - * Available since API level 29. */ LOGICAL_OR = 63, @@ -3523,8 +3457,6 @@ enum OperationType : int32_t { * Outputs: * * 0: The output tensor of the same {@link OperandType} and shape as * input0. - * - * Available since API level 29. */ LOG_SOFTMAX = 64, @@ -3543,11 +3475,13 @@ enum OperationType : int32_t { * * 0: A tensor. * * 1: A tensor of the same {@link OperandType} and compatible dimensions * with input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scales and zeroPoint can be different from input0 scale and zeroPoint. * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 29. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint can be different from inputs' scale and zeroPoint. */ MAXIMUM = 65, @@ -3566,11 +3500,13 @@ enum OperationType : int32_t { * * 0: A tensor. * * 1: A tensor of the same {@link OperandType} and compatible dimensions * with input0. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scales and zeroPoint can be different from input0 scale and zeroPoint. * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 29. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint can be different from inputs' scale and zeroPoint. */ MINIMUM = 66, @@ -3589,8 +3525,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: The output tensor of same shape as input0. - * - * Available since API level 29. */ NEG = 67, @@ -3615,8 +3549,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of {@link OperandType::TENSOR_BOOL8}. - * - * Available since API level 29. */ NOT_EQUAL = 68, @@ -3657,8 +3589,8 @@ enum OperationType : int32_t { * of the padding: * output0.dimension[i] = * padding[i, 0] + input0.dimension[i] + padding[i, 1] - * - * Available since API level 29. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ PAD_V2 = 69, @@ -3689,8 +3621,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: An output tensor. - * - * Available since API level 29. */ POW = 70, @@ -3728,8 +3658,8 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 29. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint can be diffent from the input0 scale and zeroPoint. */ PRELU = 71, @@ -3752,8 +3682,6 @@ enum OperationType : int32_t { * Outputs: * * 0: The output tensor of same shape as input0, but with * {@link OperandType::TENSOR_QUANT8_ASYMM}. - * - * Available since API level 29. */ QUANTIZE = 72, @@ -3879,8 +3807,6 @@ enum OperationType : int32_t { * Outputs: * * 0: A 2-D {@link OperandType::TENSOR_INT32} tensor with shape * [batches, samples], containing the drawn samples. - * - * Available since API level 29. */ RANDOM_MULTINOMIAL = 74, @@ -3906,8 +3832,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 29. */ REDUCE_ALL = 75, @@ -3933,8 +3857,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 29. */ REDUCE_ANY = 76, @@ -3962,8 +3884,8 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 29. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ REDUCE_MAX = 77, @@ -3991,8 +3913,8 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 29. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ REDUCE_MIN = 78, @@ -4018,8 +3940,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 29. */ REDUCE_PROD = 79, @@ -4045,8 +3965,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. - * - * Available since API level 29. */ REDUCE_SUM = 80, @@ -4064,7 +3982,7 @@ enum OperationType : int32_t { * interpolation. * * Supported tensor {@link OperandType}: - * * {@link OperandType::TENSOR_FLOAT16} (since API level 29) + * * {@link OperandType::TENSOR_FLOAT16} * * {@link OperandType::TENSOR_FLOAT32} * * {@link OperandType::TENSOR_QUANT8_ASYMM} * @@ -4105,8 +4023,8 @@ enum OperationType : int32_t { * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. The output * shape is [num_rois, out_height, out_width, depth]. - * - * Available since API level 29. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint can be different from the input0 scale and zeroPoint. */ ROI_ALIGN = 81, @@ -4156,8 +4074,8 @@ enum OperationType : int32_t { * Outputs: * * 0: A tensor of the same {@link OperandType} as input0. The output * shape is [num_rois, out_height, out_width, depth]. - * - * Available since API level 29. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ ROI_POOLING = 82, @@ -4175,8 +4093,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: The output tensor of same shape as input0. - * - * Available since API level 29. */ RSQRT = 83, @@ -4201,9 +4117,13 @@ enum OperationType : int32_t { * true) or input2 (if false). * * 1: An input tensor of the same shape as input0. * * 2: An input tensor of the same shape and type as input1. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scales and zeroPoint can be different from input1 scale and zeroPoint. * * Outputs: * * 0: A tensor of the same type and shape as input1 and input2. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint can be different from inputs' scale and zeroPoint. * */ SELECT = 84, @@ -4222,8 +4142,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: The output tensor of same shape as input0. - * - * Available since API level 29. */ SIN = 85, @@ -4235,7 +4153,6 @@ enum OperationType : int32_t { * for each dimension. The size is specified as a 1-D tensor containing * either size of a slice along corresponding dimension or -1. In the latter * case, all the remaining elements in dimension are included in the slice. - * Slice size in each dimension cannot be zero. * * A sum of begin offset and a size of a slice must not exceed size of a * corresponding dimension. @@ -4257,8 +4174,8 @@ enum OperationType : int32_t { * * Outputs: * * 0: An n-D tensor of the same type as the input containing the slice. - * - * Available since API level 29. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * its scale and zeroPoint has to be same as the input0 scale and zeroPoint. */ SLICE = 86, @@ -4282,8 +4199,8 @@ enum OperationType : int32_t { * * Outputs: * * 0 ~ (num_splits - 1): Resulting subtensors. - * - * Available since API level 29. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ SPLIT = 87, @@ -4301,8 +4218,6 @@ enum OperationType : int32_t { * * Outputs: * * 0: The output tensor of same shape as input0. - * - * Available since API level 29. */ SQRT = 88, @@ -4330,8 +4245,8 @@ enum OperationType : int32_t { * * Outputs: * * 0: A tiled tensor of the same {@link OperandType} and rank as `input`. - * - * Available since API level 29. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ TILE = 89, @@ -4357,10 +4272,10 @@ enum OperationType : int32_t { * Outputs: * * 0: An n-D tensor of the same type as the input, containing the k * largest elements along each last dimensional slice. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. * * 1: An n-D tensor of type {@link OperandType::TENSOR_INT32} * containing the indices of values within the last dimension of input. - * - * Available since API level 29. */ TOPK_V2 = 90, @@ -4374,7 +4289,7 @@ enum OperationType : int32_t { * The output dimensions are functions of the filter dimensions, stride, and * padding. * - * Supported tensor {@link OperandCode} configurations: + * Supported tensor {@link OperandType} configurations: * * 16 bit floating point: * * * {@link OperandType::TENSOR_FLOAT16} for input, filter, output, and bias. * @@ -4406,7 +4321,7 @@ enum OperationType : int32_t { * [depth_out, filter_height, filter_width, depth_in], specifying the * filter. For tensor of type * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} the channel - * dimension (extraParams.channelQuant.channelDim) must be set to 0. + * dimension (SymmPerChannelQuantParams::channelDim) must be set to 0. * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input * tensor of type {@link OperandType::TENSOR_FLOAT32} or * {@link OperandType::TENSOR_FLOAT16}, the bias should be of the @@ -4443,7 +4358,7 @@ enum OperationType : int32_t { * [depth_out, filter_height, filter_width, depth_in], specifying the * filter. For tensor of type * {@link OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL} the channel - * dimension (extraParams.channelQuant.channelDim) must be set to 0. + * dimension (SymmPerChannelQuantParams::channelDim) must be set to 0. * * 2: A 1-D tensor, of shape [depth_out], specifying the bias. For input * tensor of type {@link OperandType::TENSOR_FLOAT32} or * {@link OperandType::TENSOR_FLOAT16}, the bias should be of the @@ -4473,8 +4388,8 @@ enum OperationType : int32_t { * Outputs: * * 0: The output 4-D tensor, of shape * [batches, out_height, out_width, depth_out]. - * - * Available since API level 29. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint can be different from inputs' scale and zeroPoint. */ TRANSPOSE_CONV_2D = 91, @@ -4584,8 +4499,6 @@ enum OperationType : int32_t { * A 3-D tensor of shape: * If time-major: [max_time, batch_size, output_size] * If batch-major: [batch_size, max_time, output_size] - * - * Available since API level 29. */ UNIDIRECTIONAL_SEQUENCE_LSTM = 92, @@ -4641,8 +4554,6 @@ enum OperationType : int32_t { * it is set to 1, then the output has a shape [maxTime, batchSize, * numUnits], otherwise the output has a shape [batchSize, maxTime, * numUnits]. - * - * Available since API level 29. */ UNIDIRECTIONAL_SEQUENCE_RNN = 93, @@ -4696,8 +4607,8 @@ enum OperationType : int32_t { * Outputs: * * 0: The output 4-D tensor, of shape * [batches, new_height, new_width, depth]. - * - * Available since API level 29. + * For a {@link OperandType::TENSOR_QUANT8_ASYMM} tensor, + * the scale and zeroPoint must be the same as input0. */ RESIZE_NEAREST_NEIGHBOR = 94, diff --git a/neuralnetworks/1.2/types.t b/neuralnetworks/1.2/types.t new file mode 100644 index 0000000000..d197f6b541 --- /dev/null +++ b/neuralnetworks/1.2/types.t @@ -0,0 +1,725 @@ +%% template file for generating types.hal. +%% see frameworks/ml/nn/tools/api/README.md. +/* + * Copyright (C) 2018 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.hardware.neuralnetworks@1.2; + +import @1.0::DataLocation; +import @1.0::ErrorStatus; +import @1.0::OperandLifeTime; +import @1.0::OperandType; +import @1.0::PerformanceInfo; +import @1.1::OperationType; + +import android.hidl.safe_union@1.0::Monostate; + +enum Constant : uint32_t { + /** + * The byte size of the cache token. + */ + BYTE_SIZE_OF_CACHE_TOKEN = 32, + + /** + * The maximum number of files for each type of cache in compilation caching. + */ + MAX_NUMBER_OF_CACHE_FILES = 32, +}; + +enum OperandType : @1.0::OperandType { +%insert Operand_1.2 +%insert OEMDeprecationAndOperandTypeRangeMaxComment +}; + +/** + * The range of operand values in the OperandType enum. + */ +enum OperandTypeRange : uint32_t { + BASE_MIN = 0, + FUNDAMENTAL_MIN = 0, +%insert Operand_1.2_MAX + OEM_MIN = 10000, + OEM_MAX = 10001, + BASE_MAX = 0xFFFF, +}; + +/** + * Operation types. + * + * The type of an operation in a model. + */ +enum OperationType : int32_t { + +%insert Operation_1.0 + +%insert Operation_1.1 + +%insert Operation_1.2 + + /** + * DEPRECATED. Since NNAPI 1.2, extensions are the preferred alternative to + * OEM operation and data types. + * + * This operation is OEM specific. It should only be used for OEM + * applications. + */ + OEM_OPERATION = @1.1::OperationType:OEM_OPERATION, + /* ADDING A NEW FUNDAMENTAL OPERATION REQUIRES UPDATING THE VALUE OF + * OperationTypeRange::FUNDAMENTAL_MAX. + */ + /* ADDING A NEW OEM OPERATION REQUIRES UPDATING THE VALUE OF + * OperationTypeRange::OEM_MAX. + */ +}; + +/** + * The range of values in the OperationType enum. + */ +enum OperationTypeRange : uint32_t { + BASE_MIN = 0, + FUNDAMENTAL_MIN = 0, +%insert Operation_1.2_MAX + OEM_MIN = 10000, + OEM_MAX = 10000, + BASE_MAX = 0xFFFF, +}; + +/** + * Device types. + * + * The type of NNAPI device. + */ +enum DeviceType : int32_t { + // Leaving 0 unused as it means unknown type in NDK NNAPI. There is no + // HAL equivalent of unknown type and a 1.2 HAL implementation must belong + // to one of the categories below. + /** The device does not fall into any category below. */ + OTHER = 1, + /** The device runs NNAPI models on single or multi-core CPU. */ + CPU = 2, + /** The device can run NNAPI models and also accelerate graphics APIs such + * as OpenGL ES and Vulkan. */ + GPU = 3, + /** Dedicated accelerator for Machine Learning workloads. */ + ACCELERATOR = 4, +}; + +/** + * The capabilities of a driver. + * + * Performance of an operation comes from the type of its first operand. + * This represents performance for non extension operand types. + */ +struct Capabilities { + /** + * Driver performance when operating on float32 data but performing + * calculations with range and/or precision as low as that of the IEEE + * 754 16-bit floating-point format. + */ + PerformanceInfo relaxedFloat32toFloat16PerformanceScalar; + PerformanceInfo relaxedFloat32toFloat16PerformanceTensor; + + /** + * Driver performance when operating on a particular data type. + * In the case of float32 data, this is used when the calculations + * are not relaxed. + */ + struct OperandPerformance { + OperandType type; + PerformanceInfo info; + }; + + /** + * Performance by operand type. Must be sorted by OperandType. + * If a particular OperandType is not present in operandPerformance, + * its performance is treated as { .execTime = FLT_MAX, .powerUsage = FLT_MAX }. + */ + vec<OperandPerformance> operandPerformance; +}; + +/** + * Describes one operation of the model's graph. + */ +struct Operation { + /** + * The operation type. + * + * Besides the values listed in {@link OperationType}, any value above + * {@link OperationTypeRange::BASE_MAX} is possible and should be interpreted + * as an extension type according to {@link Model::extensionNameToPrefix}. + */ + OperationType type; + + /** + * Describes the table that contains the indexes of the inputs of the + * operation. The offset is the index in the operandIndexes table. + */ + vec<uint32_t> inputs; + + /** + * Describes the table that contains the indexes of the outputs of the + * operation. The offset is the index in the operandIndexes table. + */ + vec<uint32_t> outputs; +}; + +/** + * Parameters for TENSOR_QUANT8_SYMM_PER_CHANNEL operand. + */ +struct SymmPerChannelQuantParams { + /** Array of scaling values for each channel. Each value must be greater than zero. */ + vec<float> scales; + /** Index of the channel dimension */ + uint32_t channelDim; +}; + +/** + * Describes one operand of the model's graph. + */ +struct Operand { + /** + * The data type. + * + * Besides the values listed in {@link OperandType}, any value above + * {@link OperandTypeRange::BASE_MAX} is possible and should be interpreted + * as an extension type according to {@link Model::extensionNameToPrefix}. + */ + OperandType type; + + /** + * Dimensions of the operand. + * + * For a scalar operand, dimensions.size() must be 0. + * + * A tensor operand with all dimensions specified has "fully + * specified" dimensions. Whenever possible (i.e., whenever the + * dimensions are known at model construction time), a tensor + * operand should have (but is not required to have) fully + * specified dimensions, in order to enable the best possible + * performance. + * + * If a tensor operand's dimensions are not fully specified, the + * dimensions of the operand are deduced from the operand + * dimensions and values of the operation for which that operand + * is an output. + * + * In the following situations, a tensor operand's dimensions must + * be fully specified: + * + * . The operand has lifetime CONSTANT_COPY or + * CONSTANT_REFERENCE. + * + * . The operand has lifetime MODEL_INPUT. Fully + * specified dimensions must either be present in the + * Operand or they must be provided in the corresponding + * RequestArgument. + * EXCEPTION: If the input is optional and omitted + * (by setting the hasNoValue field of the corresponding + * RequestArgument to true) then it need not have fully + * specified dimensions. + * + * A tensor operand with some number of unspecified dimensions is + * represented by setting each unspecified dimension to 0. + * + * A tensor operand with unspecified rank is represented by providing + * an empty dimensions vector. + */ + vec<uint32_t> dimensions; + + /** + * The number of times this operand appears as an operation input. + * + * (For example, if this operand appears once in one operation's + * input list, and three times in another operation's input list, + * then numberOfConsumers = 4.) + */ + uint32_t numberOfConsumers; + + /** + * Quantized scale of the operand. + * + * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM or + * TENSOR_INT32. + */ + float scale; + + /** + * Quantized zero-point offset of the operand. + * + * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM. + */ + int32_t zeroPoint; + + /** + * How the operand is used. + */ + OperandLifeTime lifetime; + + /** + * Where to find the data for this operand. + * If the lifetime is TEMPORARY_VARIABLE, MODEL_INPUT, MODEL_OUTPUT, or + * NO_VALUE: + * - All the fields must be 0. + * If the lifetime is CONSTANT_COPY: + * - location.poolIndex is 0. + * - location.offset is the offset in bytes into Model.operandValues. + * - location.length is set. + * If the lifetime is CONSTANT_REFERENCE: + * - location.poolIndex is set. + * - location.offset is the offset in bytes into the specified pool. + * - location.length is set. + */ + DataLocation location; + + /** + * Additional parameters specific to a particular operand type. + */ + safe_union ExtraParams { + /** + * No additional parameters. + */ + Monostate none; + + /** + * Symmetric per-channel quantization parameters. + * + * Only applicable to operands of type TENSOR_QUANT8_SYMM_PER_CHANNEL. + */ + SymmPerChannelQuantParams channelQuant; + + /** + * Extension operand parameters. + * + * The framework treats this as an opaque data blob. + * The format is up to individual extensions. + */ + vec<uint8_t> extension; + } extraParams; +}; + +/** + * A Neural Network Model. + * + * This includes not only the execution graph, but also constant data such as + * weights or scalars added at construction time. The only information that + * may not be known is the shape of the input tensors. + */ +struct Model { + /** + * All operands included in the model. + */ + vec<Operand> operands; + + /** + * All operations included in the model. + * + * The operations are sorted into execution order. Every operand + * with lifetime MODEL_OUTPUT or TEMPORARY_VARIABLE must be + * written before it is read. + */ + vec<Operation> operations; + + /** + * Input indexes of the model. There must be at least one. + * + * Each value corresponds to the index of the operand in "operands". + */ + vec<uint32_t> inputIndexes; + + /** + * Output indexes of the model. There must be at least one. + * + * Each value corresponds to the index of the operand in "operands". + */ + vec<uint32_t> outputIndexes; + + /** + * A byte buffer containing operand data that were copied into the model. + * + * An operand's value must be located here if and only if Operand::lifetime + * equals OperandLifeTime::CONSTANT_COPY. + */ + vec<uint8_t> operandValues; + + /** + * A collection of shared memory pools containing operand values. + * + * An operand's value must be located here if and only if Operand::lifetime + * equals OperandLifeTime::CONSTANT_REFERENCE. + */ + vec<memory> pools; + + /** + * 'true' indicates TENSOR_FLOAT32 may be calculated with range and/or + * precision as low as that of the IEEE 754 16-bit floating-point format. + * 'false' indicates TENSOR_FLOAT32 must be calculated using at least the + * range and precision of the IEEE 754 32-bit floating-point format. + */ + bool relaxComputationFloat32toFloat16; + + /** + * The mapping between extension names and prefixes of operand and + * operation type values. + * + * An operand or operation whose numeric type value is above + * {@link OperandTypeRange::BASE_MAX} or + * {@link OperationTypeRange::BASE_MAX} respectively should be interpreted + * as an extension operand. The low + * {@link Model::ExtensionTypeEncoding::LOW_BITS_TYPE} bits of the value + * correspond to the type ID within the extension and the high + * {@link Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX} bits encode + * the "prefix", which maps uniquely to the extension name. + * + * For example, if a model contains an operation whose value is + * 0xAAAABBBB and extensionNameToPrefix contains an entry with + * prefix=0xAAAA and name="vendor.test.test_extension", then + * the operation should be interpreted as the operation 0xBBBB + * of the extension named vendor.test.test_extension. + * + * This is a one-to-one correspondence. That is, there must be at most one + * prefix corresponding to each extension name and at most one extension + * name corresponding to each prefix. + */ + vec<ExtensionNameAndPrefix> extensionNameToPrefix; + + /** + * A correspondence between an extension name and a prefix of operand and + * operation type values. + */ + struct ExtensionNameAndPrefix { + /** + * The extension name. + * + * See {@link Extension::name} for the format specification. + */ + string name; + + /** + * The unique extension identifier within the model. + * + * See {@link Model::extensionNameToPrefix}. + */ + uint16_t prefix; + }; + + /** + * Numeric values of extension operand and operation types have the + * following structure: + * - 16 high bits represent the "prefix", which corresponds uniquely to the + * extension name. + * - 16 low bits represent the type ID within the extension. + */ + enum ExtensionTypeEncoding : uint8_t { + HIGH_BITS_PREFIX = 16, + LOW_BITS_TYPE = 16, + }; +}; + +/** + * Describes the shape information of an output operand after execution. + */ +struct OutputShape { + /** + * Dimensions of the operand. + */ + vec<uint32_t> dimensions; + + /** + * Whether the provided buffer size is sufficient for the output. + */ + bool isSufficient; +}; + +/** + * Specifies whether or not to measure timing information during execution. + */ +enum MeasureTiming : int32_t { + NO = 0, + YES = 1, +}; + +/** + + * Timing information measured during execution. Each time is a duration from + * the beginning of some task to the end of that task, including time when that + * task is not active (for example, preempted by some other task, or + * waiting for some resource to become available). + * + * Times are measured in microseconds. + * When a time is not available, it must be reported as UINT64_MAX. + */ +struct Timing { + /** Execution time on device (not driver, which runs on host processor). */ + uint64_t timeOnDevice; + /** Execution time in driver (including time on device). */ + uint64_t timeInDriver; +}; + +/** + * FmqRequestDatum is a single element of a serialized representation of an + * execution request (a {@link @1.0::Request} object and a {@link MeasureTiming} + * value) which is sent across FastMessageQueue. + * + * The serialized representation for a particular execution is referred to later + * in these descriptions as a 'packet'. + * + * FastMessageQueue can only pass HIDL-defined types that do not involve nested + * buffers, handles, or interfaces. + * + * The request is serialized as follows: + * 1) 'packetInformation' + * 2) For each input operand: + * 2.1) 'inputOperandInformation' + * 2.2) For each dimension element of the operand: + * 2.2.1) 'inputOperandDimensionValue' + * 3) For each output operand: + * 3.1) 'outputOperandInformation' + * 3.2) For each dimension element of the operand: + * 3.2.1) 'outputOperandDimensionValue' + * 4) For each pool: + * 4.1) 'poolIdentifier' + * 5) 'measureTiming' + */ +safe_union FmqRequestDatum { + /** + * Type to describe the high-level layout of the packet. + */ + struct PacketInformation { + /** + * How many elements the packet contains, including the + * "packetInformation" datum. + */ + uint32_t packetSize; + + /** + * Number of input operands. + */ + uint32_t numberOfInputOperands; + + /** + * Number of output operands. + */ + uint32_t numberOfOutputOperands; + + /** + * Number of pool identifiers. + */ + uint32_t numberOfPools; + }; + + /** + * Type representing the information for each operand. + */ + struct OperandInformation { + /** + * If true, the argument does not have a value. This can be used for + * operations that take optional arguments. If true, the fields of + * 'location' are set to 0, 'numberOfDimensions' is set to 0, and the + * dimensions information is omitted from the serialization. + */ + bool hasNoValue; + + /** + * The location within one of the memory pools passed in the Request. + */ + DataLocation location; + + /** + * Number of subsequent elements that belong to the dimensions vector. + */ + uint32_t numberOfDimensions; + }; + + /** + * packetInformation is the first element of the packet and describes the + * remainder of the packet. + */ + PacketInformation packetInformation; + + /** + * Information for each input operand. + */ + OperandInformation inputOperandInformation; + + /** + * Element of the dimensions vector. + */ + uint32_t inputOperandDimensionValue; + + /** + * Information for each output operand. + */ + OperandInformation outputOperandInformation; + + /** + * Element of the dimensions vector. + */ + uint32_t outputOperandDimensionValue; + + /** + * Unique identifier for a pool. + * + * A {@link @1.0::Request} passes across one or more pools of shared memory + * for the inputs and outputs of an execution. However, these memory pools + * are not able to be sent across FastMessageQueue directly. Instead, the + * producing side of the FMQ represents each different pool with a unique + * identifier, and sends this identifier across the FMQ. Whenever the + * consuming side of the FMQ needs the memory corresponding to this unique + * identifier, it can pass the identifier to + * {@link IBurstCallback::getMemories} to retreive the memory. Although this + * HIDL Binder call is expensive compared to communication across FMQ, it is + * only needed in the cases when the consumer does not recognize the unique + * identifier. + */ + int32_t poolIdentifier; + + /** + * Specifies whether or not to measure duration of the execution. The + * duration runs from the time the driver dequeues the request from a + * FastMessageQueue to the time the driver enqueues results to a + * FastMessageQueue. + */ + MeasureTiming measureTiming; +}; + +/** + * FmqResultDatum is a single element of a serialized representation of the + * values returned from an execution ({@link @1.0::ErrorStatus}, + * vec<{@link OutputShape}>, and {@link Timing}) which is returned via + * FastMessageQueue. + * + * The serialized representation for a particular execution is referred to later + * in these descriptions as a 'packet'. + * + * FastMessageQueue can only pass HIDL-defined types that do not involve nested + * buffers, handles, or interfaces. + * + * The execution return values ({@link @1.0::ErrorStatus} and + * vec<{@link OutputShape}>) are serialized as follows: + * 1) 'packetInformation' + * 2) For each returned operand: + * 2.1) 'operandInformation' + * 2.2) For each dimension element of the operand: + * 2.2.1) 'operandDimensionValue' + * 3) 'executionTiming' + */ +safe_union FmqResultDatum { + /** + * Type to describe the high-level layout of the packet. + */ + struct PacketInformation { + /** + * How many elements the packet contains, including the + * "packetInformation" datum. + */ + uint32_t packetSize; + + /** + * Status of the execution. + */ + ErrorStatus errorStatus; + + /** + * Number of returned operands. + */ + uint32_t numberOfOperands; + }; + + /** + * Type representing the information for each operand. + */ + struct OperandInformation { + /** + * Indicates whether the operand's output buffer is large enough to + * store the operand's result data. + */ + bool isSufficient; + + /** + * Number of subsequent elements that belong to the dimensions vector. + */ + uint32_t numberOfDimensions; + }; + + /** + * packetInformation is the first element of the packet and describes the + * remainder of the packet. It additionally includes the status of the + * execution. + */ + PacketInformation packetInformation; + + /** + * Information for each returned operand. + */ + OperandInformation operandInformation; + + /** + * Element of the dimensions vector. + */ + uint32_t operandDimensionValue; + + /** + * Duration of execution. Unless measurement was requested and execution + * succeeds, all times must be reported as UINT64_MAX. A driver may choose + * to report any time as UINT64_MAX, indicating that measurement is not + * available. + */ + Timing executionTiming; +}; + +/** + * Information about an extension. + */ +struct Extension { + /** + * The extension name. + * + * The name must consist of lowercase latin letters, numbers, periods, and + * underscore signs. The name must contain at least one period. + * + * The name must start with the reverse domain name of the vendor. + * + * Example: com.google.test_extension + */ + string name; + + /** + * Information about an extension operand type. + */ + struct OperandTypeInformation { + /** + * The extension operand type. + */ + uint16_t type; + + /** + * Indicates whether the extension operand type represents a tensor or + * a scalar. + */ + bool isTensor; + + /** + * The byte size of the operand (if scalar) or of a single element (if + * tensor). + */ + uint32_t byteSize; + }; + + /** + * Information about operand types defined by the extension. + */ + vec<OperandTypeInformation> operandTypes; +}; diff --git a/neuralnetworks/1.2/vts/functional/Android.bp b/neuralnetworks/1.2/vts/functional/Android.bp index 3ba8879ae9..bdca0e95e0 100644 --- a/neuralnetworks/1.2/vts/functional/Android.bp +++ b/neuralnetworks/1.2/vts/functional/Android.bp @@ -14,12 +14,28 @@ // limitations under the License. // +cc_library_static { + name: "VtsHalNeuralNetworksV1_2Callbacks", + defaults: ["VtsHalTargetTestDefaults"], + export_include_dirs: ["include"], + srcs: [ + "Callbacks.cpp", + ], + static_libs: [ + "android.hardware.neuralnetworks@1.0", + "android.hardware.neuralnetworks@1.1", + "android.hardware.neuralnetworks@1.2", + ], + header_libs: [ + "libbase_headers", + ] +} + cc_test { name: "VtsHalNeuralnetworksV1_2TargetTest", defaults: ["VtsHalTargetTestDefaults"], srcs: [ "BasicTests.cpp", - "Callbacks.cpp", "CompilationCachingTests.cpp", "GeneratedTestHarness.cpp", "TestAssertions.cpp", @@ -37,6 +53,7 @@ cc_test { "android.hardware.neuralnetworks@1.0", "android.hardware.neuralnetworks@1.1", "android.hardware.neuralnetworks@1.2", + "android.hardware.neuralnetworks@1.3", "android.hidl.allocator@1.0", "android.hidl.memory@1.0", "libgmock", @@ -44,6 +61,7 @@ cc_test { "libneuralnetworks_generated_test_harness", "libneuralnetworks_utils", "VtsHalNeuralNetworksV1_0_utils", + "VtsHalNeuralNetworksV1_2Callbacks", ], whole_static_libs: [ "neuralnetworks_generated_V1_0_example", @@ -53,5 +71,5 @@ cc_test { header_libs: [ "libneuralnetworks_headers", ], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/neuralnetworks/1.2/vts/functional/GeneratedTestHarness.cpp b/neuralnetworks/1.2/vts/functional/GeneratedTestHarness.cpp index 2beec983e0..aacb38500b 100644 --- a/neuralnetworks/1.2/vts/functional/GeneratedTestHarness.cpp +++ b/neuralnetworks/1.2/vts/functional/GeneratedTestHarness.cpp @@ -33,6 +33,7 @@ #include <gtest/gtest.h> #include <algorithm> +#include <chrono> #include <iostream> #include <numeric> @@ -190,7 +191,8 @@ static Return<ErrorStatus> ExecutePreparedModel(const sp<IPreparedModel>& prepar } static std::shared_ptr<::android::nn::ExecutionBurstController> CreateBurst( const sp<IPreparedModel>& preparedModel) { - return android::nn::ExecutionBurstController::create(preparedModel, /*blocking=*/true); + return android::nn::ExecutionBurstController::create(preparedModel, + std::chrono::microseconds{0}); } enum class Executor { ASYNC, SYNC, BURST }; @@ -254,8 +256,10 @@ void EvaluatePreparedModel(const sp<IPreparedModel>& preparedModel, const TestMo } // execute burst - std::tie(executionStatus, outputShapes, timing) = + int n; + std::tie(n, outputShapes, timing, std::ignore) = controller->compute(request, measure, keys); + executionStatus = nn::convertResultCodeToErrorStatus(n); break; } diff --git a/neuralnetworks/1.2/vts/functional/ValidateBurst.cpp b/neuralnetworks/1.2/vts/functional/ValidateBurst.cpp index 1d4493d208..416744f902 100644 --- a/neuralnetworks/1.2/vts/functional/ValidateBurst.cpp +++ b/neuralnetworks/1.2/vts/functional/ValidateBurst.cpp @@ -26,6 +26,7 @@ #include "Utils.h" #include <android-base/logging.h> +#include <chrono> #include <cstring> namespace android::hardware::neuralnetworks::V1_2::vts::functional { @@ -64,9 +65,9 @@ static void createBurst(const sp<IPreparedModel>& preparedModel, const sp<IBurst // create FMQ objects auto [fmqRequestChannel, fmqRequestDescriptor] = - RequestChannelSender::create(kExecutionBurstChannelLength, /*blocking=*/true); + RequestChannelSender::create(kExecutionBurstChannelLength); auto [fmqResultChannel, fmqResultDescriptor] = - ResultChannelReceiver::create(resultChannelLength, /*blocking=*/true); + ResultChannelReceiver::create(resultChannelLength, std::chrono::microseconds{0}); ASSERT_NE(nullptr, fmqRequestChannel.get()); ASSERT_NE(nullptr, fmqResultChannel.get()); ASSERT_NE(nullptr, fmqRequestDescriptor); @@ -293,8 +294,10 @@ static void validateBurstFmqLength(const sp<IPreparedModel>& preparedModel, } // collect serialized result by running regular burst - const auto [statusRegular, outputShapesRegular, timingRegular] = + const auto [nRegular, outputShapesRegular, timingRegular, fallbackRegular] = controllerRegular->compute(request, MeasureTiming::NO, keys); + const ErrorStatus statusRegular = nn::convertResultCodeToErrorStatus(nRegular); + EXPECT_FALSE(fallbackRegular); // skip test if regular burst output isn't useful for testing a failure // caused by having too small of a length for the result FMQ @@ -307,11 +310,13 @@ static void validateBurstFmqLength(const sp<IPreparedModel>& preparedModel, // by this point, execution should fail because the result channel isn't // large enough to return the serialized result - const auto [statusSmall, outputShapesSmall, timingSmall] = + const auto [nSmall, outputShapesSmall, timingSmall, fallbackSmall] = controllerSmall->compute(request, MeasureTiming::NO, keys); + const ErrorStatus statusSmall = nn::convertResultCodeToErrorStatus(nSmall); EXPECT_NE(ErrorStatus::NONE, statusSmall); EXPECT_EQ(0u, outputShapesSmall.size()); EXPECT_TRUE(badTiming(timingSmall)); + EXPECT_FALSE(fallbackSmall); } static bool isSanitized(const FmqResultDatum& datum) { diff --git a/neuralnetworks/1.2/vts/functional/ValidateRequest.cpp b/neuralnetworks/1.2/vts/functional/ValidateRequest.cpp index f25ee62617..2d83b8186c 100644 --- a/neuralnetworks/1.2/vts/functional/ValidateRequest.cpp +++ b/neuralnetworks/1.2/vts/functional/ValidateRequest.cpp @@ -16,6 +16,7 @@ #define LOG_TAG "neuralnetworks_hidl_hal_test" +#include <chrono> #include "1.0/Utils.h" #include "1.2/Callbacks.h" #include "ExecutionBurstController.h" @@ -94,7 +95,8 @@ static void validate(const sp<IPreparedModel>& preparedModel, const std::string& // create burst std::shared_ptr<::android::nn::ExecutionBurstController> burst = - android::nn::ExecutionBurstController::create(preparedModel, /*blocking=*/true); + android::nn::ExecutionBurstController::create(preparedModel, + std::chrono::microseconds{0}); ASSERT_NE(nullptr, burst.get()); // create memory keys @@ -104,13 +106,12 @@ static void validate(const sp<IPreparedModel>& preparedModel, const std::string& } // execute and verify - ErrorStatus error; - std::vector<OutputShape> outputShapes; - Timing timing; - std::tie(error, outputShapes, timing) = burst->compute(request, measure, keys); - EXPECT_EQ(ErrorStatus::INVALID_ARGUMENT, error); + const auto [n, outputShapes, timing, fallback] = burst->compute(request, measure, keys); + const ErrorStatus status = nn::convertResultCodeToErrorStatus(n); + EXPECT_EQ(ErrorStatus::INVALID_ARGUMENT, status); EXPECT_EQ(outputShapes.size(), 0); EXPECT_TRUE(badTiming(timing)); + EXPECT_FALSE(fallback); // additional burst testing if (request.pools.size() > 0) { diff --git a/neuralnetworks/1.3/Android.bp b/neuralnetworks/1.3/Android.bp new file mode 100644 index 0000000000..0615ec67dd --- /dev/null +++ b/neuralnetworks/1.3/Android.bp @@ -0,0 +1,21 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.neuralnetworks@1.3", + root: "android.hardware", + vndk: { + enabled: true, + }, + srcs: [ + "types.hal", + "IDevice.hal", + ], + interfaces: [ + "android.hardware.neuralnetworks@1.0", + "android.hardware.neuralnetworks@1.1", + "android.hardware.neuralnetworks@1.2", + "android.hidl.base@1.0", + "android.hidl.safe_union@1.0", + ], + gen_java: false, +} diff --git a/neuralnetworks/1.3/IDevice.hal b/neuralnetworks/1.3/IDevice.hal new file mode 100644 index 0000000000..ee36fb4e51 --- /dev/null +++ b/neuralnetworks/1.3/IDevice.hal @@ -0,0 +1,171 @@ +/* + * 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.hardware.neuralnetworks@1.3; + +import @1.0::ErrorStatus; +import @1.1::ExecutionPreference; +import @1.2::Constant; +import @1.2::DeviceType; +import @1.2::Extension; +import @1.2::IDevice; +import @1.2::IPreparedModelCallback; + +/** + * This interface represents a device driver. + */ +interface IDevice extends @1.2::IDevice { + /** + * Gets the capabilities of a driver. + * + * @return status Error status of the call, must be: + * - NONE if successful + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + * @return capabilities Capabilities of the driver. + */ + getCapabilities_1_3() generates (ErrorStatus status, Capabilities capabilities); + + /** + * Gets the supported operations in a model. + * + * getSupportedOperations indicates which operations of a model are fully + * supported by the vendor driver. If an operation may not be supported for + * any reason, getSupportedOperations must return false for that operation. + * + * @param model A model whose operations--and their corresponding operands-- + * are to be verified by the driver. + * @return status Error status of the call, must be: + * - NONE if successful + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + * - INVALID_ARGUMENT if provided model is invalid + * @return supportedOperations A list of supported operations, where true + * indicates the operation is supported and false indicates the + * operation is not supported. The index of "supported" corresponds with + * the index of the operation it is describing. + */ + getSupportedOperations_1_3(Model model) + generates (ErrorStatus status, vec<bool> supportedOperations); + + /** + * Asynchronously creates a prepared model for execution and optionally + * saves it into cache files. + * + * prepareModel is used to make any necessary transformations to or + * alternative representations to a model for execution, possibly including + * transformations on the constant data, optimization on the model's graph, + * or compilation into the device's native binary format. The model itself + * is not changed. + * + * Optionally, caching information may be provided for the driver to save + * the prepared model to cache files for faster model compilation time when + * the same model preparation is requested in the future. There are two + * types of cache file handles provided to the driver: model cache and data + * cache. For more information on the two types of cache handles, refer to + * getNumberOfCacheFilesNeeded. + * + * The file descriptors must be opened with read and write permission. A + * file may have any size, and the corresponding file descriptor may have + * any offset. The driver must truncate a file to zero size before writing + * to that file. The file descriptors may be closed by the client once the + * asynchronous preparation has finished. The driver must dup a file + * descriptor if it wants to get access to the cache file later. + * + * The model is prepared asynchronously with respect to the caller. The + * prepareModel function must verify the inputs to the preparedModel + * function related to preparing the model (as opposed to saving the + * prepared model to cache) are correct. If there is an error, prepareModel + * must immediately invoke the callback with the appropriate ErrorStatus + * value and nullptr for the IPreparedModel, then return with the same + * ErrorStatus. If the inputs to the prepareModel function that are related + * to preparing the model are valid and there is no error, prepareModel must + * launch an asynchronous task to prepare the model in the background, and + * immediately return from prepareModel with ErrorStatus::NONE. If the + * asynchronous task fails to launch, prepareModel must immediately invoke + * the callback with ErrorStatus::GENERAL_FAILURE and nullptr for the + * IPreparedModel, then return with ErrorStatus::GENERAL_FAILURE. + * + * When the asynchronous task has finished preparing the model, it must + * immediately invoke the callback function provided as an input to + * prepareModel. If the model was prepared successfully, the callback object + * must be invoked with an error status of ErrorStatus::NONE and the + * produced IPreparedModel object. If an error occurred preparing the model, + * the callback object must be invoked with the appropriate ErrorStatus + * value and nullptr for the IPreparedModel. + * + * Optionally, the driver may save the prepared model to cache during the + * asynchronous preparation. Any error that occurs when saving to cache must + * not affect the status of preparing the model. Even if the input arguments + * related to the cache may be invalid, or the driver may fail to save to + * cache, the prepareModel function must finish preparing the model. The + * driver may choose not to save to cache even if the caching information is + * provided and valid. + * + * The only information that may be unknown to the model at this stage is + * the shape of the tensors, which may only be known at execution time. As + * such, some driver services may return partially prepared models, where + * the prepared model may only be finished when it is paired with a set of + * inputs to the model. Note that the same prepared model object may be used + * with different shapes of inputs on different (possibly concurrent) + * executions. + * + * Multiple threads may call prepareModel on the same model concurrently. + * + * @param model The model to be prepared for execution. + * @param preference Indicates the intended execution behavior of a prepared + * model. + * @param modelCache A vector of handles with each entry holding exactly one + * cache file descriptor for the security-sensitive cache. The length of + * the vector must either be 0 indicating that caching information is + * not provided, or match the numModelCache returned from + * getNumberOfCacheFilesNeeded. The cache handles will be provided in + * the same order when retrieving the preparedModel from cache files + * with prepareModelFromCache. + * @param dataCache A vector of handles with each entry holding exactly one + * cache file descriptor for the constants' cache. The length of the + * vector must either be 0 indicating that caching information is not + * provided, or match the numDataCache returned from + * getNumberOfCacheFilesNeeded. The cache handles will be provided in + * the same order when retrieving the preparedModel from cache files + * with prepareModelFromCache. + * @param token A caching token of length Constant::BYTE_SIZE_OF_CACHE_TOKEN + * identifying the prepared model. The same token will be provided when + * retrieving the prepared model from the cache files with + * prepareModelFromCache. Tokens should be chosen to have a low rate of + * collision for a particular application. The driver cannot detect a + * collision; a collision will result in a failed execution or in a + * successful execution that produces incorrect output values. If both + * modelCache and dataCache are empty indicating that caching + * information is not provided, this token must be ignored. + * @param callback A callback object used to return the error status of + * preparing the model for execution and the prepared model if + * successful, nullptr otherwise. The callback object's notify function + * must be called exactly once, even if the model could not be prepared. + * @return status Error status of launching a task which prepares the model + * in the background; must be: + * - NONE if preparation task is successfully launched + * - DEVICE_UNAVAILABLE if driver is offline or busy + * - GENERAL_FAILURE if there is an unspecified error + * - INVALID_ARGUMENT if one of the input arguments related to preparing + * the model is invalid + */ + prepareModel_1_3(Model model, ExecutionPreference preference, + vec<handle> modelCache, vec<handle> dataCache, + uint8_t[Constant:BYTE_SIZE_OF_CACHE_TOKEN] token, + IPreparedModelCallback callback) + generates (ErrorStatus status); +}; diff --git a/neuralnetworks/1.3/types.hal b/neuralnetworks/1.3/types.hal new file mode 100644 index 0000000000..86ab287067 --- /dev/null +++ b/neuralnetworks/1.3/types.hal @@ -0,0 +1,373 @@ +/* + * 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.hardware.neuralnetworks@1.3; + +import @1.0::DataLocation; +import @1.0::OperandLifeTime; +import @1.0::PerformanceInfo; +import @1.2::OperandType; +import @1.2::OperationType; +import @1.2::SymmPerChannelQuantParams; + +import android.hidl.safe_union@1.0::Monostate; + +enum OperandType : @1.2::OperandType { + /** + * A tensor of 8 bit signed integers that represent real numbers. + * + * Attached to this tensor are two numbers that can be used to convert the + * 8 bit integer to the real value and vice versa. These two numbers are: + * - scale: a 32 bit floating point value greater than zero. + * - zeroPoint: a 32 bit integer, in range [-128, 127]. + * + * The formula is: + * real_value = (integer_value - zeroPoint) * scale. + */ + TENSOR_QUANT8_ASYMM_SIGNED = 14, + + /* + * DEPRECATED. Since HAL version 1.2, extensions are the preferred + * alternative to OEM operation and data types. + * + * OEM specific scalar value. + * OEM = 10000, + */ + /* + * DEPRECATED. Since HAL version 1.2, extensions are the preferred + * alternative to OEM operation and data types. + * + * A tensor of OEM specific values. + * TENSOR_OEM_BYTE = 10001, + */ + /* ADDING A NEW FUNDAMENTAL TYPE REQUIRES UPDATING THE VALUE OF + * OperandTypeRange::FUNDAMENTAL_MAX. + */ + /* ADDING A NEW OEM TYPE REQUIRES UPDATING THE VALUE OF + * OperandTypeRange::OEM_MAX. + */ +}; + +/** + * The range of operand values in the OperandType enum. + */ +enum OperandTypeRange : uint32_t { + BASE_MIN = 0, + FUNDAMENTAL_MIN = 0, + FUNDAMENTAL_MAX = 14, + OEM_MIN = 10000, + OEM_MAX = 10001, + BASE_MAX = 0xFFFF, +}; + + +/** + * The capabilities of a driver. + * + * Performance of an operation comes from the type of its first operand. + * This represents performance for non extension operand types. + */ +struct Capabilities { + /** + * Driver performance when operating on float32 data but performing + * calculations with range and/or precision as low as that of the IEEE + * 754 16-bit floating-point format. + */ + PerformanceInfo relaxedFloat32toFloat16PerformanceScalar; + PerformanceInfo relaxedFloat32toFloat16PerformanceTensor; + + /** + * Driver performance when operating on a particular data type. + * In the case of float32 data, this is used when the calculations + * are not relaxed. + */ + struct OperandPerformance { + OperandType type; + PerformanceInfo info; + }; + + /** + * Performance by operand type. Must be sorted by OperandType. + * If a particular OperandType is not present in operandPerformance, + * its performance is treated as + * { .execTime = FLT_MAX, .powerUsage = FLT_MAX }. + */ + vec<OperandPerformance> operandPerformance; +}; + +/** + * Describes one operand of the model's graph. + */ +struct Operand { + /** + * The data type. + * + * Besides the values listed in {@link OperandType}, any value above + * {@link OperandTypeRange::BASE_MAX} is possible and should be interpreted + * as an extension type according to {@link Model::extensionNameToPrefix}. + */ + OperandType type; + + /** + * Dimensions of the operand. + * + * For a scalar operand, dimensions.size() must be 0. + * + * A tensor operand with all dimensions specified has "fully + * specified" dimensions. Whenever possible (i.e., whenever the + * dimensions are known at model construction time), a tensor + * operand should have (but is not required to have) fully + * specified dimensions, in order to enable the best possible + * performance. + * + * If a tensor operand's dimensions are not fully specified, the + * dimensions of the operand are deduced from the operand + * dimensions and values of the operation for which that operand + * is an output. + * + * In the following situations, a tensor operand's dimensions must + * be fully specified: + * + * . The operand has lifetime CONSTANT_COPY or + * CONSTANT_REFERENCE. + * + * . The operand has lifetime MODEL_INPUT. Fully + * specified dimensions must either be present in the + * Operand or they must be provided in the corresponding + * RequestArgument. + * EXCEPTION: If the input is optional and omitted + * (by setting the hasNoValue field of the corresponding + * RequestArgument to true) then it need not have fully + * specified dimensions. + * + * A tensor operand with some number of unspecified dimensions is + * represented by setting each unspecified dimension to 0. + * + * A tensor operand with unspecified rank is represented by providing + * an empty dimensions vector. + */ + vec<uint32_t> dimensions; + + /** + * The number of times this operand appears as an operation input. + * + * (For example, if this operand appears once in one operation's + * input list, and three times in another operation's input list, + * then numberOfConsumers = 4.) + */ + uint32_t numberOfConsumers; + + /** + * Quantized scale of the operand. + * + * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM or + * TENSOR_INT32. + */ + float scale; + + /** + * Quantized zero-point offset of the operand. + * + * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM. + */ + int32_t zeroPoint; + + /** + * How the operand is used. + */ + OperandLifeTime lifetime; + + /** + * Where to find the data for this operand. + * If the lifetime is TEMPORARY_VARIABLE, MODEL_INPUT, MODEL_OUTPUT, or + * NO_VALUE: + * - All the fields must be 0. + * If the lifetime is CONSTANT_COPY: + * - location.poolIndex is 0. + * - location.offset is the offset in bytes into Model.operandValues. + * - location.length is set. + * If the lifetime is CONSTANT_REFERENCE: + * - location.poolIndex is set. + * - location.offset is the offset in bytes into the specified pool. + * - location.length is set. + */ + DataLocation location; + + /** + * Additional parameters specific to a particular operand type. + */ + safe_union ExtraParams { + /** + * No additional parameters. + */ + Monostate none; + + /** + * Symmetric per-channel quantization parameters. + * + * Only applicable to operands of type TENSOR_QUANT8_SYMM_PER_CHANNEL. + */ + SymmPerChannelQuantParams channelQuant; + + /** + * Extension operand parameters. + * + * The framework treats this as an opaque data blob. + * The format is up to individual extensions. + */ + vec<uint8_t> extension; + } extraParams; +}; + +/** + * Describes one operation of the model's graph. + */ +struct Operation { + /** + * The operation type. + */ + OperationType type; + + /** + * Describes the table that contains the indexes of the inputs of the + * operation. The offset is the index in the operandIndexes table. + */ + vec<uint32_t> inputs; + + /** + * Describes the table that contains the indexes of the outputs of the + * operation. The offset is the index in the operandIndexes table. + */ + vec<uint32_t> outputs; +}; + +/** + * A Neural Network Model. + * + * This includes not only the execution graph, but also constant data such as + * weights or scalars added at construction time. The only information that + * may not be known is the shape of the input tensors. + */ +struct Model { + /** + * All operands included in the model. + */ + vec<Operand> operands; + + /** + * All operations included in the model. + * + * The operations are sorted into execution order. Every operand + * with lifetime MODEL_OUTPUT or TEMPORARY_VARIABLE must be + * written before it is read. + */ + vec<Operation> operations; + + /** + * Input indexes of the model. There must be at least one. + * + * Each value corresponds to the index of the operand in "operands". + */ + vec<uint32_t> inputIndexes; + + /** + * Output indexes of the model. There must be at least one. + * + * Each value corresponds to the index of the operand in "operands". + */ + vec<uint32_t> outputIndexes; + + /** + * A byte buffer containing operand data that were copied into the model. + * + * An operand's value must be located here if and only if Operand::lifetime + * equals OperandLifeTime::CONSTANT_COPY. + */ + vec<uint8_t> operandValues; + + /** + * A collection of shared memory pools containing operand values. + * + * An operand's value must be located here if and only if Operand::lifetime + * equals OperandLifeTime::CONSTANT_REFERENCE. + */ + vec<memory> pools; + + /** + * 'true' indicates TENSOR_FLOAT32 may be calculated with range and/or + * precision as low as that of the IEEE 754 16-bit floating-point format. + * 'false' indicates TENSOR_FLOAT32 must be calculated using at least the + * range and precision of the IEEE 754 32-bit floating-point format. + */ + bool relaxComputationFloat32toFloat16; + + /** + * The mapping between extension names and prefixes of operand and + * operation type values. + * + * An operand or operation whose numeric type value is above + * {@link OperandTypeRange::BASE_MAX} or + * {@link OperationTypeRange::BASE_MAX} respectively should be interpreted + * as an extension operand. The low + * {@link Model::ExtensionTypeEncoding::LOW_BITS_TYPE} bits of the value + * correspond to the type ID within the extension and the high + * {@link Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX} bits encode + * the "prefix", which maps uniquely to the extension name. + * + * For example, if a model contains an operation whose value is + * 0xAAAABBBB and extensionNameToPrefix contains an entry with + * prefix=0xAAAA and name="vendor.test.test_extension", then + * the operation should be interpreted as the operation 0xBBBB + * of the extension named vendor.test.test_extension. + * + * This is a one-to-one correspondence. That is, there must be at most one + * prefix corresponding to each extension name and at most one extension + * name corresponding to each prefix. + */ + vec<ExtensionNameAndPrefix> extensionNameToPrefix; + + /** + * A correspondence between an extension name and a prefix of operand and + * operation type values. + */ + struct ExtensionNameAndPrefix { + /** + * The extension name. + * + * See {@link Extension::name} for the format specification. + */ + string name; + + /** + * The unique extension identifier within the model. + * + * See {@link Model::extensionNameToPrefix}. + */ + uint16_t prefix; + }; + + /** + * Numeric values of extension operand and operation types have the + * following structure: + * - 16 high bits represent the "prefix", which corresponds uniquely to the + * extension name. + * - 16 low bits represent the type ID within the extension. + */ + enum ExtensionTypeEncoding : uint8_t { + HIGH_BITS_PREFIX = 16, + LOW_BITS_TYPE = 16, + }; +}; diff --git a/neuralnetworks/1.3/types.t b/neuralnetworks/1.3/types.t new file mode 100644 index 0000000000..d41cfd2c92 --- /dev/null +++ b/neuralnetworks/1.3/types.t @@ -0,0 +1,344 @@ +%% template file for generating types.hal. +%% see frameworks/ml/nn/tools/api/README.md. +/* + * 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.hardware.neuralnetworks@1.3; + +import @1.0::DataLocation; +import @1.0::OperandLifeTime; +import @1.0::PerformanceInfo; +import @1.2::OperandType; +import @1.2::OperationType; +import @1.2::SymmPerChannelQuantParams; + +import android.hidl.safe_union@1.0::Monostate; + +enum OperandType : @1.2::OperandType { +%insert Operand_1.3 +%insert OEMDeprecationAndOperandTypeRangeMaxComment +}; + +/** + * The range of operand values in the OperandType enum. + */ +enum OperandTypeRange : uint32_t { + BASE_MIN = 0, + FUNDAMENTAL_MIN = 0, +%insert Operand_1.3_MAX + OEM_MIN = 10000, + OEM_MAX = 10001, + BASE_MAX = 0xFFFF, +}; + + +/** + * The capabilities of a driver. + * + * Performance of an operation comes from the type of its first operand. + * This represents performance for non extension operand types. + */ +struct Capabilities { + /** + * Driver performance when operating on float32 data but performing + * calculations with range and/or precision as low as that of the IEEE + * 754 16-bit floating-point format. + */ + PerformanceInfo relaxedFloat32toFloat16PerformanceScalar; + PerformanceInfo relaxedFloat32toFloat16PerformanceTensor; + + /** + * Driver performance when operating on a particular data type. + * In the case of float32 data, this is used when the calculations + * are not relaxed. + */ + struct OperandPerformance { + OperandType type; + PerformanceInfo info; + }; + + /** + * Performance by operand type. Must be sorted by OperandType. + * If a particular OperandType is not present in operandPerformance, + * its performance is treated as + * { .execTime = FLT_MAX, .powerUsage = FLT_MAX }. + */ + vec<OperandPerformance> operandPerformance; +}; + +/** + * Describes one operand of the model's graph. + */ +struct Operand { + /** + * The data type. + * + * Besides the values listed in {@link OperandType}, any value above + * {@link OperandTypeRange::BASE_MAX} is possible and should be interpreted + * as an extension type according to {@link Model::extensionNameToPrefix}. + */ + OperandType type; + + /** + * Dimensions of the operand. + * + * For a scalar operand, dimensions.size() must be 0. + * + * A tensor operand with all dimensions specified has "fully + * specified" dimensions. Whenever possible (i.e., whenever the + * dimensions are known at model construction time), a tensor + * operand should have (but is not required to have) fully + * specified dimensions, in order to enable the best possible + * performance. + * + * If a tensor operand's dimensions are not fully specified, the + * dimensions of the operand are deduced from the operand + * dimensions and values of the operation for which that operand + * is an output. + * + * In the following situations, a tensor operand's dimensions must + * be fully specified: + * + * . The operand has lifetime CONSTANT_COPY or + * CONSTANT_REFERENCE. + * + * . The operand has lifetime MODEL_INPUT. Fully + * specified dimensions must either be present in the + * Operand or they must be provided in the corresponding + * RequestArgument. + * EXCEPTION: If the input is optional and omitted + * (by setting the hasNoValue field of the corresponding + * RequestArgument to true) then it need not have fully + * specified dimensions. + * + * A tensor operand with some number of unspecified dimensions is + * represented by setting each unspecified dimension to 0. + * + * A tensor operand with unspecified rank is represented by providing + * an empty dimensions vector. + */ + vec<uint32_t> dimensions; + + /** + * The number of times this operand appears as an operation input. + * + * (For example, if this operand appears once in one operation's + * input list, and three times in another operation's input list, + * then numberOfConsumers = 4.) + */ + uint32_t numberOfConsumers; + + /** + * Quantized scale of the operand. + * + * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM or + * TENSOR_INT32. + */ + float scale; + + /** + * Quantized zero-point offset of the operand. + * + * Only applicable if the operand is of type TENSOR_QUANT8_ASYMM. + */ + int32_t zeroPoint; + + /** + * How the operand is used. + */ + OperandLifeTime lifetime; + + /** + * Where to find the data for this operand. + * If the lifetime is TEMPORARY_VARIABLE, MODEL_INPUT, MODEL_OUTPUT, or + * NO_VALUE: + * - All the fields must be 0. + * If the lifetime is CONSTANT_COPY: + * - location.poolIndex is 0. + * - location.offset is the offset in bytes into Model.operandValues. + * - location.length is set. + * If the lifetime is CONSTANT_REFERENCE: + * - location.poolIndex is set. + * - location.offset is the offset in bytes into the specified pool. + * - location.length is set. + */ + DataLocation location; + + /** + * Additional parameters specific to a particular operand type. + */ + safe_union ExtraParams { + /** + * No additional parameters. + */ + Monostate none; + + /** + * Symmetric per-channel quantization parameters. + * + * Only applicable to operands of type TENSOR_QUANT8_SYMM_PER_CHANNEL. + */ + SymmPerChannelQuantParams channelQuant; + + /** + * Extension operand parameters. + * + * The framework treats this as an opaque data blob. + * The format is up to individual extensions. + */ + vec<uint8_t> extension; + } extraParams; +}; + +/** + * Describes one operation of the model's graph. + */ +struct Operation { + /** + * The operation type. + */ + OperationType type; + + /** + * Describes the table that contains the indexes of the inputs of the + * operation. The offset is the index in the operandIndexes table. + */ + vec<uint32_t> inputs; + + /** + * Describes the table that contains the indexes of the outputs of the + * operation. The offset is the index in the operandIndexes table. + */ + vec<uint32_t> outputs; +}; + +/** + * A Neural Network Model. + * + * This includes not only the execution graph, but also constant data such as + * weights or scalars added at construction time. The only information that + * may not be known is the shape of the input tensors. + */ +struct Model { + /** + * All operands included in the model. + */ + vec<Operand> operands; + + /** + * All operations included in the model. + * + * The operations are sorted into execution order. Every operand + * with lifetime MODEL_OUTPUT or TEMPORARY_VARIABLE must be + * written before it is read. + */ + vec<Operation> operations; + + /** + * Input indexes of the model. There must be at least one. + * + * Each value corresponds to the index of the operand in "operands". + */ + vec<uint32_t> inputIndexes; + + /** + * Output indexes of the model. There must be at least one. + * + * Each value corresponds to the index of the operand in "operands". + */ + vec<uint32_t> outputIndexes; + + /** + * A byte buffer containing operand data that were copied into the model. + * + * An operand's value must be located here if and only if Operand::lifetime + * equals OperandLifeTime::CONSTANT_COPY. + */ + vec<uint8_t> operandValues; + + /** + * A collection of shared memory pools containing operand values. + * + * An operand's value must be located here if and only if Operand::lifetime + * equals OperandLifeTime::CONSTANT_REFERENCE. + */ + vec<memory> pools; + + /** + * 'true' indicates TENSOR_FLOAT32 may be calculated with range and/or + * precision as low as that of the IEEE 754 16-bit floating-point format. + * 'false' indicates TENSOR_FLOAT32 must be calculated using at least the + * range and precision of the IEEE 754 32-bit floating-point format. + */ + bool relaxComputationFloat32toFloat16; + + /** + * The mapping between extension names and prefixes of operand and + * operation type values. + * + * An operand or operation whose numeric type value is above + * {@link OperandTypeRange::BASE_MAX} or + * {@link OperationTypeRange::BASE_MAX} respectively should be interpreted + * as an extension operand. The low + * {@link Model::ExtensionTypeEncoding::LOW_BITS_TYPE} bits of the value + * correspond to the type ID within the extension and the high + * {@link Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX} bits encode + * the "prefix", which maps uniquely to the extension name. + * + * For example, if a model contains an operation whose value is + * 0xAAAABBBB and extensionNameToPrefix contains an entry with + * prefix=0xAAAA and name="vendor.test.test_extension", then + * the operation should be interpreted as the operation 0xBBBB + * of the extension named vendor.test.test_extension. + * + * This is a one-to-one correspondence. That is, there must be at most one + * prefix corresponding to each extension name and at most one extension + * name corresponding to each prefix. + */ + vec<ExtensionNameAndPrefix> extensionNameToPrefix; + + /** + * A correspondence between an extension name and a prefix of operand and + * operation type values. + */ + struct ExtensionNameAndPrefix { + /** + * The extension name. + * + * See {@link Extension::name} for the format specification. + */ + string name; + + /** + * The unique extension identifier within the model. + * + * See {@link Model::extensionNameToPrefix}. + */ + uint16_t prefix; + }; + + /** + * Numeric values of extension operand and operation types have the + * following structure: + * - 16 high bits represent the "prefix", which corresponds uniquely to the + * extension name. + * - 16 low bits represent the type ID within the extension. + */ + enum ExtensionTypeEncoding : uint8_t { + HIGH_BITS_PREFIX = 16, + LOW_BITS_TYPE = 16, + }; +}; diff --git a/neuralnetworks/1.3/vts/OWNERS b/neuralnetworks/1.3/vts/OWNERS new file mode 100644 index 0000000000..b5a8e1f473 --- /dev/null +++ b/neuralnetworks/1.3/vts/OWNERS @@ -0,0 +1,16 @@ +# Neuralnetworks team +butlermichael@google.com +dgross@google.com +jeanluc@google.com +levp@google.com +miaowang@google.com +mikie@google.com +mks@google.com +pszczepaniak@google.com +slavash@google.com +vddang@google.com +xusongw@google.com + +# VTS team +yim@google.com +yuexima@google.com diff --git a/neuralnetworks/1.3/vts/functional/Android.bp b/neuralnetworks/1.3/vts/functional/Android.bp new file mode 100644 index 0000000000..90ce852e3e --- /dev/null +++ b/neuralnetworks/1.3/vts/functional/Android.bp @@ -0,0 +1,58 @@ +// +// 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. +// + +cc_test { + name: "VtsHalNeuralNetworksV1_3TargetTest", + defaults: ["VtsHalTargetTestDefaults"], + srcs: [ + "BasicTests.cpp", + "CompilationCachingTests.cpp", + "GeneratedTestHarness.cpp", + "TestAssertions.cpp", + "ValidateBurst.cpp", + "ValidateModel.cpp", + "ValidateRequest.cpp", + "VtsHalNeuralnetworks.cpp", + ], + shared_libs: [ + "libfmq", + "libnativewindow", + ], + static_libs: [ + "android.hardware.neuralnetworks@1.0", + "android.hardware.neuralnetworks@1.1", + "android.hardware.neuralnetworks@1.2", + "android.hardware.neuralnetworks@1.3", + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libgmock", + "libhidlmemory", + "libneuralnetworks_generated_test_harness", + "libneuralnetworks_utils", + "VtsHalNeuralNetworksV1_0_utils", + "VtsHalNeuralNetworksV1_2Callbacks", + ], + whole_static_libs: [ + "neuralnetworks_generated_V1_0_example", + "neuralnetworks_generated_V1_1_example", + "neuralnetworks_generated_V1_2_example", + "neuralnetworks_generated_V1_3_example", + ], + header_libs: [ + "libneuralnetworks_headers", + ], + test_suites: ["general-tests"], +} diff --git a/neuralnetworks/1.3/vts/functional/BasicTests.cpp b/neuralnetworks/1.3/vts/functional/BasicTests.cpp new file mode 100644 index 0000000000..b64dc2f61b --- /dev/null +++ b/neuralnetworks/1.3/vts/functional/BasicTests.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2018 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. + */ + +#define LOG_TAG "neuralnetworks_hidl_hal_test" + +#include "VtsHalNeuralnetworks.h" + +namespace android::hardware::neuralnetworks::V1_3::vts::functional { + +using V1_0::DeviceStatus; +using V1_0::ErrorStatus; +using V1_0::PerformanceInfo; +using V1_2::Constant; +using V1_2::DeviceType; +using V1_2::Extension; + +// create device test +TEST_P(NeuralnetworksHidlTest, CreateDevice) {} + +// status test +TEST_P(NeuralnetworksHidlTest, StatusTest) { + Return<DeviceStatus> status = kDevice->getStatus(); + ASSERT_TRUE(status.isOk()); + EXPECT_EQ(DeviceStatus::AVAILABLE, static_cast<DeviceStatus>(status)); +} + +// initialization +TEST_P(NeuralnetworksHidlTest, GetCapabilitiesTest) { + using OperandPerformance = Capabilities::OperandPerformance; + Return<void> ret = kDevice->getCapabilities_1_3([](ErrorStatus status, + const Capabilities& capabilities) { + EXPECT_EQ(ErrorStatus::NONE, status); + + auto isPositive = [](const PerformanceInfo& perf) { + return perf.execTime > 0.0f && perf.powerUsage > 0.0f; + }; + + EXPECT_TRUE(isPositive(capabilities.relaxedFloat32toFloat16PerformanceScalar)); + EXPECT_TRUE(isPositive(capabilities.relaxedFloat32toFloat16PerformanceTensor)); + const auto& opPerf = capabilities.operandPerformance; + EXPECT_TRUE(std::all_of( + opPerf.begin(), opPerf.end(), + [isPositive](const OperandPerformance& a) { return isPositive(a.info); })); + EXPECT_TRUE(std::is_sorted(opPerf.begin(), opPerf.end(), + [](const OperandPerformance& a, const OperandPerformance& b) { + return a.type < b.type; + })); + }); + EXPECT_TRUE(ret.isOk()); +} +} // namespace android::hardware::neuralnetworks::V1_3::vts::functional diff --git a/neuralnetworks/1.3/vts/functional/CompilationCachingTests.cpp b/neuralnetworks/1.3/vts/functional/CompilationCachingTests.cpp new file mode 100644 index 0000000000..0ac4738fff --- /dev/null +++ b/neuralnetworks/1.3/vts/functional/CompilationCachingTests.cpp @@ -0,0 +1,1377 @@ +/* + * 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. + */ + +#define LOG_TAG "neuralnetworks_hidl_hal_test" + +#include <android-base/logging.h> +#include <fcntl.h> +#include <ftw.h> +#include <gtest/gtest.h> +#include <hidlmemory/mapping.h> +#include <unistd.h> + +#include <cstdio> +#include <cstdlib> +#include <random> +#include <thread> + +#include "1.2/Callbacks.h" +#include "GeneratedTestHarness.h" +#include "MemoryUtils.h" +#include "TestHarness.h" +#include "Utils.h" +#include "VtsHalNeuralnetworks.h" + +// Forward declaration of the mobilenet generated test models in +// frameworks/ml/nn/runtime/test/generated/. +namespace generated_tests::mobilenet_224_gender_basic_fixed { +const test_helper::TestModel& get_test_model(); +} // namespace generated_tests::mobilenet_224_gender_basic_fixed + +namespace generated_tests::mobilenet_quantized { +const test_helper::TestModel& get_test_model(); +} // namespace generated_tests::mobilenet_quantized + +namespace android::hardware::neuralnetworks::V1_3::vts::functional { + +using namespace test_helper; +using V1_0::ErrorStatus; +using V1_1::ExecutionPreference; +using V1_2::Constant; +using V1_2::IPreparedModel; +using V1_2::OperationType; +using V1_2::implementation::PreparedModelCallback; + +namespace float32_model { + +constexpr auto get_test_model = generated_tests::mobilenet_224_gender_basic_fixed::get_test_model; + +} // namespace float32_model + +namespace quant8_model { + +constexpr auto get_test_model = generated_tests::mobilenet_quantized::get_test_model; + +} // namespace quant8_model + +namespace { + +enum class AccessMode { READ_WRITE, READ_ONLY, WRITE_ONLY }; + +// Creates cache handles based on provided file groups. +// The outer vector corresponds to handles and the inner vector is for fds held by each handle. +void createCacheHandles(const std::vector<std::vector<std::string>>& fileGroups, + const std::vector<AccessMode>& mode, hidl_vec<hidl_handle>* handles) { + handles->resize(fileGroups.size()); + for (uint32_t i = 0; i < fileGroups.size(); i++) { + std::vector<int> fds; + for (const auto& file : fileGroups[i]) { + int fd; + if (mode[i] == AccessMode::READ_ONLY) { + fd = open(file.c_str(), O_RDONLY); + } else if (mode[i] == AccessMode::WRITE_ONLY) { + fd = open(file.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); + } else if (mode[i] == AccessMode::READ_WRITE) { + fd = open(file.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + } else { + FAIL(); + } + ASSERT_GE(fd, 0); + fds.push_back(fd); + } + native_handle_t* cacheNativeHandle = native_handle_create(fds.size(), 0); + ASSERT_NE(cacheNativeHandle, nullptr); + std::copy(fds.begin(), fds.end(), &cacheNativeHandle->data[0]); + (*handles)[i].setTo(cacheNativeHandle, /*shouldOwn=*/true); + } +} + +void createCacheHandles(const std::vector<std::vector<std::string>>& fileGroups, AccessMode mode, + hidl_vec<hidl_handle>* handles) { + createCacheHandles(fileGroups, std::vector<AccessMode>(fileGroups.size(), mode), handles); +} + +// Create a chain of broadcast operations. The second operand is always constant tensor [1]. +// For simplicity, activation scalar is shared. The second operand is not shared +// in the model to let driver maintain a non-trivial size of constant data and the corresponding +// data locations in cache. +// +// --------- activation -------- +// ↓ ↓ ↓ ↓ +// E.g. input -> ADD -> ADD -> ADD -> ... -> ADD -> output +// ↑ ↑ ↑ ↑ +// [1] [1] [1] [1] +// +// This function assumes the operation is either ADD or MUL. +template <typename CppType, TestOperandType operandType> +TestModel createLargeTestModelImpl(TestOperationType op, uint32_t len) { + EXPECT_TRUE(op == TestOperationType::ADD || op == TestOperationType::MUL); + + // Model operations and operands. + std::vector<TestOperation> operations(len); + std::vector<TestOperand> operands(len * 2 + 2); + + // The activation scalar, value = 0. + operands[0] = { + .type = TestOperandType::INT32, + .dimensions = {}, + .numberOfConsumers = len, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = TestOperandLifeTime::CONSTANT_COPY, + .data = TestBuffer::createFromVector<int32_t>({0}), + }; + + // The buffer value of the constant second operand. The logical value is always 1.0f. + CppType bufferValue; + // The scale of the first and second operand. + float scale1, scale2; + if (operandType == TestOperandType::TENSOR_FLOAT32) { + bufferValue = 1.0f; + scale1 = 0.0f; + scale2 = 0.0f; + } else if (op == TestOperationType::ADD) { + bufferValue = 1; + scale1 = 1.0f; + scale2 = 1.0f; + } else { + // To satisfy the constraint on quant8 MUL: input0.scale * input1.scale < output.scale, + // set input1 to have scale = 0.5f and bufferValue = 2, i.e. 1.0f in floating point. + bufferValue = 2; + scale1 = 1.0f; + scale2 = 0.5f; + } + + for (uint32_t i = 0; i < len; i++) { + const uint32_t firstInputIndex = i * 2 + 1; + const uint32_t secondInputIndex = firstInputIndex + 1; + const uint32_t outputIndex = secondInputIndex + 1; + + // The first operation input. + operands[firstInputIndex] = { + .type = operandType, + .dimensions = {1}, + .numberOfConsumers = 1, + .scale = scale1, + .zeroPoint = 0, + .lifetime = (i == 0 ? TestOperandLifeTime::MODEL_INPUT + : TestOperandLifeTime::TEMPORARY_VARIABLE), + .data = (i == 0 ? TestBuffer::createFromVector<CppType>({1}) : TestBuffer()), + }; + + // The second operation input, value = 1. + operands[secondInputIndex] = { + .type = operandType, + .dimensions = {1}, + .numberOfConsumers = 1, + .scale = scale2, + .zeroPoint = 0, + .lifetime = TestOperandLifeTime::CONSTANT_COPY, + .data = TestBuffer::createFromVector<CppType>({bufferValue}), + }; + + // The operation. All operations share the same activation scalar. + // The output operand is created as an input in the next iteration of the loop, in the case + // of all but the last member of the chain; and after the loop as a model output, in the + // case of the last member of the chain. + operations[i] = { + .type = op, + .inputs = {firstInputIndex, secondInputIndex, /*activation scalar*/ 0}, + .outputs = {outputIndex}, + }; + } + + // For TestOperationType::ADD, output = 1 + 1 * len = len + 1 + // For TestOperationType::MUL, output = 1 * 1 ^ len = 1 + CppType outputResult = static_cast<CppType>(op == TestOperationType::ADD ? len + 1u : 1u); + + // The model output. + operands.back() = { + .type = operandType, + .dimensions = {1}, + .numberOfConsumers = 0, + .scale = scale1, + .zeroPoint = 0, + .lifetime = TestOperandLifeTime::MODEL_OUTPUT, + .data = TestBuffer::createFromVector<CppType>({outputResult}), + }; + + return { + .operands = std::move(operands), + .operations = std::move(operations), + .inputIndexes = {1}, + .outputIndexes = {len * 2 + 1}, + .isRelaxed = false, + }; +} + +} // namespace + +// Tag for the compilation caching tests. +class CompilationCachingTestBase : public testing::Test { + protected: + CompilationCachingTestBase(sp<IDevice> device, OperandType type) + : kDevice(std::move(device)), kOperandType(type) {} + + void SetUp() override { + testing::Test::SetUp(); + ASSERT_NE(kDevice.get(), nullptr); + + // Create cache directory. The cache directory and a temporary cache file is always created + // to test the behavior of prepareModelFromCache, even when caching is not supported. + char cacheDirTemp[] = "/data/local/tmp/TestCompilationCachingXXXXXX"; + char* cacheDir = mkdtemp(cacheDirTemp); + ASSERT_NE(cacheDir, nullptr); + mCacheDir = cacheDir; + mCacheDir.push_back('/'); + + Return<void> ret = kDevice->getNumberOfCacheFilesNeeded( + [this](ErrorStatus status, uint32_t numModelCache, uint32_t numDataCache) { + EXPECT_EQ(ErrorStatus::NONE, status); + mNumModelCache = numModelCache; + mNumDataCache = numDataCache; + }); + EXPECT_TRUE(ret.isOk()); + mIsCachingSupported = mNumModelCache > 0 || mNumDataCache > 0; + + // Create empty cache files. + mTmpCache = mCacheDir + "tmp"; + for (uint32_t i = 0; i < mNumModelCache; i++) { + mModelCache.push_back({mCacheDir + "model" + std::to_string(i)}); + } + for (uint32_t i = 0; i < mNumDataCache; i++) { + mDataCache.push_back({mCacheDir + "data" + std::to_string(i)}); + } + // Dummy handles, use AccessMode::WRITE_ONLY for createCacheHandles to create files. + hidl_vec<hidl_handle> modelHandle, dataHandle, tmpHandle; + createCacheHandles(mModelCache, AccessMode::WRITE_ONLY, &modelHandle); + createCacheHandles(mDataCache, AccessMode::WRITE_ONLY, &dataHandle); + createCacheHandles({{mTmpCache}}, AccessMode::WRITE_ONLY, &tmpHandle); + + if (!mIsCachingSupported) { + LOG(INFO) << "NN VTS: Early termination of test because vendor service does not " + "support compilation caching."; + std::cout << "[ ] Early termination of test because vendor service does not " + "support compilation caching." + << std::endl; + } + } + + void TearDown() override { + // If the test passes, remove the tmp directory. Otherwise, keep it for debugging purposes. + if (!testing::Test::HasFailure()) { + // Recursively remove the cache directory specified by mCacheDir. + auto callback = [](const char* entry, const struct stat*, int, struct FTW*) { + return remove(entry); + }; + nftw(mCacheDir.c_str(), callback, 128, FTW_DEPTH | FTW_MOUNT | FTW_PHYS); + } + testing::Test::TearDown(); + } + + // Model and examples creators. According to kOperandType, the following methods will return + // either float32 model/examples or the quant8 variant. + TestModel createTestModel() { + if (kOperandType == OperandType::TENSOR_FLOAT32) { + return float32_model::get_test_model(); + } else { + return quant8_model::get_test_model(); + } + } + + TestModel createLargeTestModel(OperationType op, uint32_t len) { + if (kOperandType == OperandType::TENSOR_FLOAT32) { + return createLargeTestModelImpl<float, TestOperandType::TENSOR_FLOAT32>( + static_cast<TestOperationType>(op), len); + } else { + return createLargeTestModelImpl<uint8_t, TestOperandType::TENSOR_QUANT8_ASYMM>( + static_cast<TestOperationType>(op), len); + } + } + + // See if the service can handle the model. + bool isModelFullySupported(const Model& model) { + bool fullySupportsModel = false; + Return<void> supportedCall = kDevice->getSupportedOperations_1_3( + model, + [&fullySupportsModel, &model](ErrorStatus status, const hidl_vec<bool>& supported) { + ASSERT_EQ(ErrorStatus::NONE, status); + ASSERT_EQ(supported.size(), model.operations.size()); + fullySupportsModel = std::all_of(supported.begin(), supported.end(), + [](bool valid) { return valid; }); + }); + EXPECT_TRUE(supportedCall.isOk()); + return fullySupportsModel; + } + + void saveModelToCache(const Model& model, const hidl_vec<hidl_handle>& modelCache, + const hidl_vec<hidl_handle>& dataCache, + sp<IPreparedModel>* preparedModel = nullptr) { + if (preparedModel != nullptr) *preparedModel = nullptr; + + // Launch prepare model. + sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback(); + hidl_array<uint8_t, sizeof(mToken)> cacheToken(mToken); + Return<ErrorStatus> prepareLaunchStatus = + kDevice->prepareModel_1_3(model, ExecutionPreference::FAST_SINGLE_ANSWER, + modelCache, dataCache, cacheToken, preparedModelCallback); + ASSERT_TRUE(prepareLaunchStatus.isOk()); + ASSERT_EQ(static_cast<ErrorStatus>(prepareLaunchStatus), ErrorStatus::NONE); + + // Retrieve prepared model. + preparedModelCallback->wait(); + ASSERT_EQ(preparedModelCallback->getStatus(), ErrorStatus::NONE); + if (preparedModel != nullptr) { + *preparedModel = IPreparedModel::castFrom(preparedModelCallback->getPreparedModel()) + .withDefault(nullptr); + } + } + + bool checkEarlyTermination(ErrorStatus status) { + if (status == ErrorStatus::GENERAL_FAILURE) { + LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot " + "save the prepared model that it does not support."; + std::cout << "[ ] Early termination of test because vendor service cannot " + "save the prepared model that it does not support." + << std::endl; + return true; + } + return false; + } + + bool checkEarlyTermination(const Model& model) { + if (!isModelFullySupported(model)) { + LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot " + "prepare model that it does not support."; + std::cout << "[ ] Early termination of test because vendor service cannot " + "prepare model that it does not support." + << std::endl; + return true; + } + return false; + } + + void prepareModelFromCache(const hidl_vec<hidl_handle>& modelCache, + const hidl_vec<hidl_handle>& dataCache, + sp<IPreparedModel>* preparedModel, ErrorStatus* status) { + // Launch prepare model from cache. + sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback(); + hidl_array<uint8_t, sizeof(mToken)> cacheToken(mToken); + Return<ErrorStatus> prepareLaunchStatus = kDevice->prepareModelFromCache( + modelCache, dataCache, cacheToken, preparedModelCallback); + ASSERT_TRUE(prepareLaunchStatus.isOk()); + if (static_cast<ErrorStatus>(prepareLaunchStatus) != ErrorStatus::NONE) { + *preparedModel = nullptr; + *status = static_cast<ErrorStatus>(prepareLaunchStatus); + return; + } + + // Retrieve prepared model. + preparedModelCallback->wait(); + *status = preparedModelCallback->getStatus(); + *preparedModel = IPreparedModel::castFrom(preparedModelCallback->getPreparedModel()) + .withDefault(nullptr); + } + + // Absolute path to the temporary cache directory. + std::string mCacheDir; + + // Groups of file paths for model and data cache in the tmp cache directory, initialized with + // outer_size = mNum{Model|Data}Cache, inner_size = 1. The outer vector corresponds to handles + // and the inner vector is for fds held by each handle. + std::vector<std::vector<std::string>> mModelCache; + std::vector<std::vector<std::string>> mDataCache; + + // A separate temporary file path in the tmp cache directory. + std::string mTmpCache; + + uint8_t mToken[static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN)] = {}; + uint32_t mNumModelCache; + uint32_t mNumDataCache; + uint32_t mIsCachingSupported; + + const sp<IDevice> kDevice; + // The primary data type of the testModel. + const OperandType kOperandType; +}; + +using CompilationCachingTestParam = std::tuple<NamedDevice, OperandType>; + +// A parameterized fixture of CompilationCachingTestBase. Every test will run twice, with the first +// pass running with float32 models and the second pass running with quant8 models. +class CompilationCachingTest : public CompilationCachingTestBase, + public testing::WithParamInterface<CompilationCachingTestParam> { + protected: + CompilationCachingTest() + : CompilationCachingTestBase(getData(std::get<NamedDevice>(GetParam())), + std::get<OperandType>(GetParam())) {} +}; + +TEST_P(CompilationCachingTest, CacheSavingAndRetrieval) { + // Create test HIDL model and compile. + const TestModel& testModel = createTestModel(); + const Model model = createModel(testModel); + if (checkEarlyTermination(model)) return; + sp<IPreparedModel> preparedModel = nullptr; + + // Save the compilation to cache. + { + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(model, modelCache, dataCache); + } + + // Retrieve preparedModel from cache. + { + preparedModel = nullptr; + ErrorStatus status; + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (!mIsCachingSupported) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + ASSERT_EQ(preparedModel, nullptr); + return; + } else if (checkEarlyTermination(status)) { + ASSERT_EQ(preparedModel, nullptr); + return; + } else { + ASSERT_EQ(status, ErrorStatus::NONE); + ASSERT_NE(preparedModel, nullptr); + } + } + + // Execute and verify results. + EvaluatePreparedModel(preparedModel, testModel, + /*testDynamicOutputShape=*/false); +} + +TEST_P(CompilationCachingTest, CacheSavingAndRetrievalNonZeroOffset) { + // Create test HIDL model and compile. + const TestModel& testModel = createTestModel(); + const Model model = createModel(testModel); + if (checkEarlyTermination(model)) return; + sp<IPreparedModel> preparedModel = nullptr; + + // Save the compilation to cache. + { + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + uint8_t dummyBytes[] = {0, 0}; + // Write a dummy integer to the cache. + // The driver should be able to handle non-empty cache and non-zero fd offset. + for (uint32_t i = 0; i < modelCache.size(); i++) { + ASSERT_EQ(write(modelCache[i].getNativeHandle()->data[0], &dummyBytes, + sizeof(dummyBytes)), + sizeof(dummyBytes)); + } + for (uint32_t i = 0; i < dataCache.size(); i++) { + ASSERT_EQ( + write(dataCache[i].getNativeHandle()->data[0], &dummyBytes, sizeof(dummyBytes)), + sizeof(dummyBytes)); + } + saveModelToCache(model, modelCache, dataCache); + } + + // Retrieve preparedModel from cache. + { + preparedModel = nullptr; + ErrorStatus status; + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + uint8_t dummyByte = 0; + // Advance the offset of each handle by one byte. + // The driver should be able to handle non-zero fd offset. + for (uint32_t i = 0; i < modelCache.size(); i++) { + ASSERT_GE(read(modelCache[i].getNativeHandle()->data[0], &dummyByte, 1), 0); + } + for (uint32_t i = 0; i < dataCache.size(); i++) { + ASSERT_GE(read(dataCache[i].getNativeHandle()->data[0], &dummyByte, 1), 0); + } + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (!mIsCachingSupported) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + ASSERT_EQ(preparedModel, nullptr); + return; + } else if (checkEarlyTermination(status)) { + ASSERT_EQ(preparedModel, nullptr); + return; + } else { + ASSERT_EQ(status, ErrorStatus::NONE); + ASSERT_NE(preparedModel, nullptr); + } + } + + // Execute and verify results. + EvaluatePreparedModel(preparedModel, testModel, + /*testDynamicOutputShape=*/false); +} + +TEST_P(CompilationCachingTest, SaveToCacheInvalidNumCache) { + // Create test HIDL model and compile. + const TestModel& testModel = createTestModel(); + const Model model = createModel(testModel); + if (checkEarlyTermination(model)) return; + + // Test with number of model cache files greater than mNumModelCache. + { + hidl_vec<hidl_handle> modelCache, dataCache; + // Pass an additional cache file for model cache. + mModelCache.push_back({mTmpCache}); + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + mModelCache.pop_back(); + sp<IPreparedModel> preparedModel = nullptr; + saveModelToCache(model, modelCache, dataCache, &preparedModel); + ASSERT_NE(preparedModel, nullptr); + // Execute and verify results. + EvaluatePreparedModel(preparedModel, testModel, + /*testDynamicOutputShape=*/false); + // Check if prepareModelFromCache fails. + preparedModel = nullptr; + ErrorStatus status; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::INVALID_ARGUMENT) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Test with number of model cache files smaller than mNumModelCache. + if (mModelCache.size() > 0) { + hidl_vec<hidl_handle> modelCache, dataCache; + // Pop out the last cache file. + auto tmp = mModelCache.back(); + mModelCache.pop_back(); + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + mModelCache.push_back(tmp); + sp<IPreparedModel> preparedModel = nullptr; + saveModelToCache(model, modelCache, dataCache, &preparedModel); + ASSERT_NE(preparedModel, nullptr); + // Execute and verify results. + EvaluatePreparedModel(preparedModel, testModel, + /*testDynamicOutputShape=*/false); + // Check if prepareModelFromCache fails. + preparedModel = nullptr; + ErrorStatus status; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::INVALID_ARGUMENT) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Test with number of data cache files greater than mNumDataCache. + { + hidl_vec<hidl_handle> modelCache, dataCache; + // Pass an additional cache file for data cache. + mDataCache.push_back({mTmpCache}); + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + mDataCache.pop_back(); + sp<IPreparedModel> preparedModel = nullptr; + saveModelToCache(model, modelCache, dataCache, &preparedModel); + ASSERT_NE(preparedModel, nullptr); + // Execute and verify results. + EvaluatePreparedModel(preparedModel, testModel, + /*testDynamicOutputShape=*/false); + // Check if prepareModelFromCache fails. + preparedModel = nullptr; + ErrorStatus status; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::INVALID_ARGUMENT) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Test with number of data cache files smaller than mNumDataCache. + if (mDataCache.size() > 0) { + hidl_vec<hidl_handle> modelCache, dataCache; + // Pop out the last cache file. + auto tmp = mDataCache.back(); + mDataCache.pop_back(); + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + mDataCache.push_back(tmp); + sp<IPreparedModel> preparedModel = nullptr; + saveModelToCache(model, modelCache, dataCache, &preparedModel); + ASSERT_NE(preparedModel, nullptr); + // Execute and verify results. + EvaluatePreparedModel(preparedModel, testModel, + /*testDynamicOutputShape=*/false); + // Check if prepareModelFromCache fails. + preparedModel = nullptr; + ErrorStatus status; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::INVALID_ARGUMENT) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + } + ASSERT_EQ(preparedModel, nullptr); + } +} + +TEST_P(CompilationCachingTest, PrepareModelFromCacheInvalidNumCache) { + // Create test HIDL model and compile. + const TestModel& testModel = createTestModel(); + const Model model = createModel(testModel); + if (checkEarlyTermination(model)) return; + + // Save the compilation to cache. + { + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(model, modelCache, dataCache); + } + + // Test with number of model cache files greater than mNumModelCache. + { + sp<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + hidl_vec<hidl_handle> modelCache, dataCache; + mModelCache.push_back({mTmpCache}); + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + mModelCache.pop_back(); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::GENERAL_FAILURE) { + ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Test with number of model cache files smaller than mNumModelCache. + if (mModelCache.size() > 0) { + sp<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + hidl_vec<hidl_handle> modelCache, dataCache; + auto tmp = mModelCache.back(); + mModelCache.pop_back(); + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + mModelCache.push_back(tmp); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::GENERAL_FAILURE) { + ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Test with number of data cache files greater than mNumDataCache. + { + sp<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + hidl_vec<hidl_handle> modelCache, dataCache; + mDataCache.push_back({mTmpCache}); + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + mDataCache.pop_back(); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::GENERAL_FAILURE) { + ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Test with number of data cache files smaller than mNumDataCache. + if (mDataCache.size() > 0) { + sp<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + hidl_vec<hidl_handle> modelCache, dataCache; + auto tmp = mDataCache.back(); + mDataCache.pop_back(); + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + mDataCache.push_back(tmp); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::GENERAL_FAILURE) { + ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT); + } + ASSERT_EQ(preparedModel, nullptr); + } +} + +TEST_P(CompilationCachingTest, SaveToCacheInvalidNumFd) { + // Create test HIDL model and compile. + const TestModel& testModel = createTestModel(); + const Model model = createModel(testModel); + if (checkEarlyTermination(model)) return; + + // Go through each handle in model cache, test with NumFd greater than 1. + for (uint32_t i = 0; i < mNumModelCache; i++) { + hidl_vec<hidl_handle> modelCache, dataCache; + // Pass an invalid number of fds for handle i. + mModelCache[i].push_back(mTmpCache); + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + mModelCache[i].pop_back(); + sp<IPreparedModel> preparedModel = nullptr; + saveModelToCache(model, modelCache, dataCache, &preparedModel); + ASSERT_NE(preparedModel, nullptr); + // Execute and verify results. + EvaluatePreparedModel(preparedModel, testModel, + /*testDynamicOutputShape=*/false); + // Check if prepareModelFromCache fails. + preparedModel = nullptr; + ErrorStatus status; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::INVALID_ARGUMENT) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Go through each handle in model cache, test with NumFd equal to 0. + for (uint32_t i = 0; i < mNumModelCache; i++) { + hidl_vec<hidl_handle> modelCache, dataCache; + // Pass an invalid number of fds for handle i. + auto tmp = mModelCache[i].back(); + mModelCache[i].pop_back(); + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + mModelCache[i].push_back(tmp); + sp<IPreparedModel> preparedModel = nullptr; + saveModelToCache(model, modelCache, dataCache, &preparedModel); + ASSERT_NE(preparedModel, nullptr); + // Execute and verify results. + EvaluatePreparedModel(preparedModel, testModel, + /*testDynamicOutputShape=*/false); + // Check if prepareModelFromCache fails. + preparedModel = nullptr; + ErrorStatus status; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::INVALID_ARGUMENT) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Go through each handle in data cache, test with NumFd greater than 1. + for (uint32_t i = 0; i < mNumDataCache; i++) { + hidl_vec<hidl_handle> modelCache, dataCache; + // Pass an invalid number of fds for handle i. + mDataCache[i].push_back(mTmpCache); + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + mDataCache[i].pop_back(); + sp<IPreparedModel> preparedModel = nullptr; + saveModelToCache(model, modelCache, dataCache, &preparedModel); + ASSERT_NE(preparedModel, nullptr); + // Execute and verify results. + EvaluatePreparedModel(preparedModel, testModel, + /*testDynamicOutputShape=*/false); + // Check if prepareModelFromCache fails. + preparedModel = nullptr; + ErrorStatus status; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::INVALID_ARGUMENT) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Go through each handle in data cache, test with NumFd equal to 0. + for (uint32_t i = 0; i < mNumDataCache; i++) { + hidl_vec<hidl_handle> modelCache, dataCache; + // Pass an invalid number of fds for handle i. + auto tmp = mDataCache[i].back(); + mDataCache[i].pop_back(); + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + mDataCache[i].push_back(tmp); + sp<IPreparedModel> preparedModel = nullptr; + saveModelToCache(model, modelCache, dataCache, &preparedModel); + ASSERT_NE(preparedModel, nullptr); + // Execute and verify results. + EvaluatePreparedModel(preparedModel, testModel, + /*testDynamicOutputShape=*/false); + // Check if prepareModelFromCache fails. + preparedModel = nullptr; + ErrorStatus status; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::INVALID_ARGUMENT) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + } + ASSERT_EQ(preparedModel, nullptr); + } +} + +TEST_P(CompilationCachingTest, PrepareModelFromCacheInvalidNumFd) { + // Create test HIDL model and compile. + const TestModel& testModel = createTestModel(); + const Model model = createModel(testModel); + if (checkEarlyTermination(model)) return; + + // Save the compilation to cache. + { + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(model, modelCache, dataCache); + } + + // Go through each handle in model cache, test with NumFd greater than 1. + for (uint32_t i = 0; i < mNumModelCache; i++) { + sp<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + hidl_vec<hidl_handle> modelCache, dataCache; + mModelCache[i].push_back(mTmpCache); + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + mModelCache[i].pop_back(); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::GENERAL_FAILURE) { + ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Go through each handle in model cache, test with NumFd equal to 0. + for (uint32_t i = 0; i < mNumModelCache; i++) { + sp<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + hidl_vec<hidl_handle> modelCache, dataCache; + auto tmp = mModelCache[i].back(); + mModelCache[i].pop_back(); + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + mModelCache[i].push_back(tmp); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::GENERAL_FAILURE) { + ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Go through each handle in data cache, test with NumFd greater than 1. + for (uint32_t i = 0; i < mNumDataCache; i++) { + sp<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + hidl_vec<hidl_handle> modelCache, dataCache; + mDataCache[i].push_back(mTmpCache); + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + mDataCache[i].pop_back(); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::GENERAL_FAILURE) { + ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Go through each handle in data cache, test with NumFd equal to 0. + for (uint32_t i = 0; i < mNumDataCache; i++) { + sp<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + hidl_vec<hidl_handle> modelCache, dataCache; + auto tmp = mDataCache[i].back(); + mDataCache[i].pop_back(); + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + mDataCache[i].push_back(tmp); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::GENERAL_FAILURE) { + ASSERT_EQ(status, ErrorStatus::INVALID_ARGUMENT); + } + ASSERT_EQ(preparedModel, nullptr); + } +} + +TEST_P(CompilationCachingTest, SaveToCacheInvalidAccessMode) { + // Create test HIDL model and compile. + const TestModel& testModel = createTestModel(); + const Model model = createModel(testModel); + if (checkEarlyTermination(model)) return; + std::vector<AccessMode> modelCacheMode(mNumModelCache, AccessMode::READ_WRITE); + std::vector<AccessMode> dataCacheMode(mNumDataCache, AccessMode::READ_WRITE); + + // Go through each handle in model cache, test with invalid access mode. + for (uint32_t i = 0; i < mNumModelCache; i++) { + hidl_vec<hidl_handle> modelCache, dataCache; + modelCacheMode[i] = AccessMode::READ_ONLY; + createCacheHandles(mModelCache, modelCacheMode, &modelCache); + createCacheHandles(mDataCache, dataCacheMode, &dataCache); + modelCacheMode[i] = AccessMode::READ_WRITE; + sp<IPreparedModel> preparedModel = nullptr; + saveModelToCache(model, modelCache, dataCache, &preparedModel); + ASSERT_NE(preparedModel, nullptr); + // Execute and verify results. + EvaluatePreparedModel(preparedModel, testModel, + /*testDynamicOutputShape=*/false); + // Check if prepareModelFromCache fails. + preparedModel = nullptr; + ErrorStatus status; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::INVALID_ARGUMENT) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + } + ASSERT_EQ(preparedModel, nullptr); + } + + // Go through each handle in data cache, test with invalid access mode. + for (uint32_t i = 0; i < mNumDataCache; i++) { + hidl_vec<hidl_handle> modelCache, dataCache; + dataCacheMode[i] = AccessMode::READ_ONLY; + createCacheHandles(mModelCache, modelCacheMode, &modelCache); + createCacheHandles(mDataCache, dataCacheMode, &dataCache); + dataCacheMode[i] = AccessMode::READ_WRITE; + sp<IPreparedModel> preparedModel = nullptr; + saveModelToCache(model, modelCache, dataCache, &preparedModel); + ASSERT_NE(preparedModel, nullptr); + // Execute and verify results. + EvaluatePreparedModel(preparedModel, testModel, + /*testDynamicOutputShape=*/false); + // Check if prepareModelFromCache fails. + preparedModel = nullptr; + ErrorStatus status; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + if (status != ErrorStatus::INVALID_ARGUMENT) { + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + } + ASSERT_EQ(preparedModel, nullptr); + } +} + +TEST_P(CompilationCachingTest, PrepareModelFromCacheInvalidAccessMode) { + // Create test HIDL model and compile. + const TestModel& testModel = createTestModel(); + const Model model = createModel(testModel); + if (checkEarlyTermination(model)) return; + std::vector<AccessMode> modelCacheMode(mNumModelCache, AccessMode::READ_WRITE); + std::vector<AccessMode> dataCacheMode(mNumDataCache, AccessMode::READ_WRITE); + + // Save the compilation to cache. + { + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(model, modelCache, dataCache); + } + + // Go through each handle in model cache, test with invalid access mode. + for (uint32_t i = 0; i < mNumModelCache; i++) { + sp<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + hidl_vec<hidl_handle> modelCache, dataCache; + modelCacheMode[i] = AccessMode::WRITE_ONLY; + createCacheHandles(mModelCache, modelCacheMode, &modelCache); + createCacheHandles(mDataCache, dataCacheMode, &dataCache); + modelCacheMode[i] = AccessMode::READ_WRITE; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + ASSERT_EQ(preparedModel, nullptr); + } + + // Go through each handle in data cache, test with invalid access mode. + for (uint32_t i = 0; i < mNumDataCache; i++) { + sp<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + hidl_vec<hidl_handle> modelCache, dataCache; + dataCacheMode[i] = AccessMode::WRITE_ONLY; + createCacheHandles(mModelCache, modelCacheMode, &modelCache); + createCacheHandles(mDataCache, dataCacheMode, &dataCache); + dataCacheMode[i] = AccessMode::READ_WRITE; + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + ASSERT_EQ(preparedModel, nullptr); + } +} + +// Copy file contents between file groups. +// The outer vector corresponds to handles and the inner vector is for fds held by each handle. +// The outer vector sizes must match and the inner vectors must have size = 1. +static void copyCacheFiles(const std::vector<std::vector<std::string>>& from, + const std::vector<std::vector<std::string>>& to) { + constexpr size_t kBufferSize = 1000000; + uint8_t buffer[kBufferSize]; + + ASSERT_EQ(from.size(), to.size()); + for (uint32_t i = 0; i < from.size(); i++) { + ASSERT_EQ(from[i].size(), 1u); + ASSERT_EQ(to[i].size(), 1u); + int fromFd = open(from[i][0].c_str(), O_RDONLY); + int toFd = open(to[i][0].c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); + ASSERT_GE(fromFd, 0); + ASSERT_GE(toFd, 0); + + ssize_t readBytes; + while ((readBytes = read(fromFd, &buffer, kBufferSize)) > 0) { + ASSERT_EQ(write(toFd, &buffer, readBytes), readBytes); + } + ASSERT_GE(readBytes, 0); + + close(fromFd); + close(toFd); + } +} + +// Number of operations in the large test model. +constexpr uint32_t kLargeModelSize = 100; +constexpr uint32_t kNumIterationsTOCTOU = 100; + +TEST_P(CompilationCachingTest, SaveToCache_TOCTOU) { + if (!mIsCachingSupported) return; + + // Create test models and check if fully supported by the service. + const TestModel testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize); + const Model modelMul = createModel(testModelMul); + if (checkEarlyTermination(modelMul)) return; + const TestModel testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize); + const Model modelAdd = createModel(testModelAdd); + if (checkEarlyTermination(modelAdd)) return; + + // Save the modelMul compilation to cache. + auto modelCacheMul = mModelCache; + for (auto& cache : modelCacheMul) { + cache[0].append("_mul"); + } + { + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(modelCacheMul, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(modelMul, modelCache, dataCache); + } + + // Use a different token for modelAdd. + mToken[0]++; + + // This test is probabilistic, so we run it multiple times. + for (uint32_t i = 0; i < kNumIterationsTOCTOU; i++) { + // Save the modelAdd compilation to cache. + { + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + + // Spawn a thread to copy the cache content concurrently while saving to cache. + std::thread thread(copyCacheFiles, std::cref(modelCacheMul), std::cref(mModelCache)); + saveModelToCache(modelAdd, modelCache, dataCache); + thread.join(); + } + + // Retrieve preparedModel from cache. + { + sp<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + + // The preparation may fail or succeed, but must not crash. If the preparation succeeds, + // the prepared model must be executed with the correct result and not crash. + if (status != ErrorStatus::NONE) { + ASSERT_EQ(preparedModel, nullptr); + } else { + ASSERT_NE(preparedModel, nullptr); + EvaluatePreparedModel(preparedModel, testModelAdd, + /*testDynamicOutputShape=*/false); + } + } + } +} + +TEST_P(CompilationCachingTest, PrepareFromCache_TOCTOU) { + if (!mIsCachingSupported) return; + + // Create test models and check if fully supported by the service. + const TestModel testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize); + const Model modelMul = createModel(testModelMul); + if (checkEarlyTermination(modelMul)) return; + const TestModel testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize); + const Model modelAdd = createModel(testModelAdd); + if (checkEarlyTermination(modelAdd)) return; + + // Save the modelMul compilation to cache. + auto modelCacheMul = mModelCache; + for (auto& cache : modelCacheMul) { + cache[0].append("_mul"); + } + { + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(modelCacheMul, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(modelMul, modelCache, dataCache); + } + + // Use a different token for modelAdd. + mToken[0]++; + + // This test is probabilistic, so we run it multiple times. + for (uint32_t i = 0; i < kNumIterationsTOCTOU; i++) { + // Save the modelAdd compilation to cache. + { + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(modelAdd, modelCache, dataCache); + } + + // Retrieve preparedModel from cache. + { + sp<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + + // Spawn a thread to copy the cache content concurrently while preparing from cache. + std::thread thread(copyCacheFiles, std::cref(modelCacheMul), std::cref(mModelCache)); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + thread.join(); + + // The preparation may fail or succeed, but must not crash. If the preparation succeeds, + // the prepared model must be executed with the correct result and not crash. + if (status != ErrorStatus::NONE) { + ASSERT_EQ(preparedModel, nullptr); + } else { + ASSERT_NE(preparedModel, nullptr); + EvaluatePreparedModel(preparedModel, testModelAdd, + /*testDynamicOutputShape=*/false); + } + } + } +} + +TEST_P(CompilationCachingTest, ReplaceSecuritySensitiveCache) { + if (!mIsCachingSupported) return; + + // Create test models and check if fully supported by the service. + const TestModel testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize); + const Model modelMul = createModel(testModelMul); + if (checkEarlyTermination(modelMul)) return; + const TestModel testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize); + const Model modelAdd = createModel(testModelAdd); + if (checkEarlyTermination(modelAdd)) return; + + // Save the modelMul compilation to cache. + auto modelCacheMul = mModelCache; + for (auto& cache : modelCacheMul) { + cache[0].append("_mul"); + } + { + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(modelCacheMul, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(modelMul, modelCache, dataCache); + } + + // Use a different token for modelAdd. + mToken[0]++; + + // Save the modelAdd compilation to cache. + { + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(modelAdd, modelCache, dataCache); + } + + // Replace the model cache of modelAdd with modelMul. + copyCacheFiles(modelCacheMul, mModelCache); + + // Retrieve the preparedModel from cache, expect failure. + { + sp<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + ASSERT_EQ(preparedModel, nullptr); + } +} + +static const auto kNamedDeviceChoices = testing::ValuesIn(getNamedDevices()); +static const auto kOperandTypeChoices = + testing::Values(OperandType::TENSOR_FLOAT32, OperandType::TENSOR_QUANT8_ASYMM); + +std::string printCompilationCachingTest( + const testing::TestParamInfo<CompilationCachingTestParam>& info) { + const auto& [namedDevice, operandType] = info.param; + const std::string type = (operandType == OperandType::TENSOR_FLOAT32 ? "float32" : "quant8"); + return gtestCompliantName(getName(namedDevice) + "_" + type); +} + +INSTANTIATE_TEST_CASE_P(TestCompilationCaching, CompilationCachingTest, + testing::Combine(kNamedDeviceChoices, kOperandTypeChoices), + printCompilationCachingTest); + +using CompilationCachingSecurityTestParam = std::tuple<NamedDevice, OperandType, uint32_t>; + +class CompilationCachingSecurityTest + : public CompilationCachingTestBase, + public testing::WithParamInterface<CompilationCachingSecurityTestParam> { + protected: + CompilationCachingSecurityTest() + : CompilationCachingTestBase(getData(std::get<NamedDevice>(GetParam())), + std::get<OperandType>(GetParam())) {} + + void SetUp() { + CompilationCachingTestBase::SetUp(); + generator.seed(kSeed); + } + + // Get a random integer within a closed range [lower, upper]. + template <typename T> + T getRandomInt(T lower, T upper) { + std::uniform_int_distribution<T> dis(lower, upper); + return dis(generator); + } + + // Randomly flip one single bit of the cache entry. + void flipOneBitOfCache(const std::string& filename, bool* skip) { + FILE* pFile = fopen(filename.c_str(), "r+"); + ASSERT_EQ(fseek(pFile, 0, SEEK_END), 0); + long int fileSize = ftell(pFile); + if (fileSize == 0) { + fclose(pFile); + *skip = true; + return; + } + ASSERT_EQ(fseek(pFile, getRandomInt(0l, fileSize - 1), SEEK_SET), 0); + int readByte = fgetc(pFile); + ASSERT_NE(readByte, EOF); + ASSERT_EQ(fseek(pFile, -1, SEEK_CUR), 0); + ASSERT_NE(fputc(static_cast<uint8_t>(readByte) ^ (1U << getRandomInt(0, 7)), pFile), EOF); + fclose(pFile); + *skip = false; + } + + // Randomly append bytes to the cache entry. + void appendBytesToCache(const std::string& filename, bool* skip) { + FILE* pFile = fopen(filename.c_str(), "a"); + uint32_t appendLength = getRandomInt(1, 256); + for (uint32_t i = 0; i < appendLength; i++) { + ASSERT_NE(fputc(getRandomInt<uint8_t>(0, 255), pFile), EOF); + } + fclose(pFile); + *skip = false; + } + + enum class ExpectedResult { GENERAL_FAILURE, NOT_CRASH }; + + // Test if the driver behaves as expected when given corrupted cache or token. + // The modifier will be invoked after save to cache but before prepare from cache. + // The modifier accepts one pointer argument "skip" as the returning value, indicating + // whether the test should be skipped or not. + void testCorruptedCache(ExpectedResult expected, std::function<void(bool*)> modifier) { + const TestModel& testModel = createTestModel(); + const Model model = createModel(testModel); + if (checkEarlyTermination(model)) return; + + // Save the compilation to cache. + { + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + saveModelToCache(model, modelCache, dataCache); + } + + bool skip = false; + modifier(&skip); + if (skip) return; + + // Retrieve preparedModel from cache. + { + sp<IPreparedModel> preparedModel = nullptr; + ErrorStatus status; + hidl_vec<hidl_handle> modelCache, dataCache; + createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache); + createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache); + prepareModelFromCache(modelCache, dataCache, &preparedModel, &status); + + switch (expected) { + case ExpectedResult::GENERAL_FAILURE: + ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE); + ASSERT_EQ(preparedModel, nullptr); + break; + case ExpectedResult::NOT_CRASH: + ASSERT_EQ(preparedModel == nullptr, status != ErrorStatus::NONE); + break; + default: + FAIL(); + } + } + } + + const uint32_t kSeed = std::get<uint32_t>(GetParam()); + std::mt19937 generator; +}; + +TEST_P(CompilationCachingSecurityTest, CorruptedModelCache) { + if (!mIsCachingSupported) return; + for (uint32_t i = 0; i < mNumModelCache; i++) { + testCorruptedCache(ExpectedResult::GENERAL_FAILURE, + [this, i](bool* skip) { flipOneBitOfCache(mModelCache[i][0], skip); }); + } +} + +TEST_P(CompilationCachingSecurityTest, WrongLengthModelCache) { + if (!mIsCachingSupported) return; + for (uint32_t i = 0; i < mNumModelCache; i++) { + testCorruptedCache(ExpectedResult::GENERAL_FAILURE, + [this, i](bool* skip) { appendBytesToCache(mModelCache[i][0], skip); }); + } +} + +TEST_P(CompilationCachingSecurityTest, CorruptedDataCache) { + if (!mIsCachingSupported) return; + for (uint32_t i = 0; i < mNumDataCache; i++) { + testCorruptedCache(ExpectedResult::NOT_CRASH, + [this, i](bool* skip) { flipOneBitOfCache(mDataCache[i][0], skip); }); + } +} + +TEST_P(CompilationCachingSecurityTest, WrongLengthDataCache) { + if (!mIsCachingSupported) return; + for (uint32_t i = 0; i < mNumDataCache; i++) { + testCorruptedCache(ExpectedResult::NOT_CRASH, + [this, i](bool* skip) { appendBytesToCache(mDataCache[i][0], skip); }); + } +} + +TEST_P(CompilationCachingSecurityTest, WrongToken) { + if (!mIsCachingSupported) return; + testCorruptedCache(ExpectedResult::GENERAL_FAILURE, [this](bool* skip) { + // Randomly flip one single bit in mToken. + uint32_t ind = + getRandomInt(0u, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN) - 1); + mToken[ind] ^= (1U << getRandomInt(0, 7)); + *skip = false; + }); +} + +std::string printCompilationCachingSecurityTest( + const testing::TestParamInfo<CompilationCachingSecurityTestParam>& info) { + const auto& [namedDevice, operandType, seed] = info.param; + const std::string type = (operandType == OperandType::TENSOR_FLOAT32 ? "float32" : "quant8"); + return gtestCompliantName(getName(namedDevice) + "_" + type + "_" + std::to_string(seed)); +} + +INSTANTIATE_TEST_CASE_P(TestCompilationCaching, CompilationCachingSecurityTest, + testing::Combine(kNamedDeviceChoices, kOperandTypeChoices, + testing::Range(0U, 10U)), + printCompilationCachingSecurityTest); + +} // namespace android::hardware::neuralnetworks::V1_3::vts::functional diff --git a/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.cpp b/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.cpp new file mode 100644 index 0000000000..8a7ed24c9d --- /dev/null +++ b/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.cpp @@ -0,0 +1,422 @@ +/* + * 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. + */ + +#include "GeneratedTestHarness.h" + +#include <android-base/logging.h> +#include <android/hardware/neuralnetworks/1.0/IDevice.h> +#include <android/hardware/neuralnetworks/1.0/IExecutionCallback.h> +#include <android/hardware/neuralnetworks/1.0/IPreparedModel.h> +#include <android/hardware/neuralnetworks/1.0/IPreparedModelCallback.h> +#include <android/hardware/neuralnetworks/1.0/types.h> +#include <android/hardware/neuralnetworks/1.1/IDevice.h> +#include <android/hardware/neuralnetworks/1.2/IDevice.h> +#include <android/hardware/neuralnetworks/1.2/IExecutionCallback.h> +#include <android/hardware/neuralnetworks/1.2/IPreparedModel.h> +#include <android/hardware/neuralnetworks/1.2/IPreparedModelCallback.h> +#include <android/hardware/neuralnetworks/1.2/types.h> +#include <android/hardware/neuralnetworks/1.3/IDevice.h> +#include <android/hardware/neuralnetworks/1.3/types.h> +#include <android/hidl/allocator/1.0/IAllocator.h> +#include <android/hidl/memory/1.0/IMemory.h> +#include <hidlmemory/mapping.h> + +#include <gtest/gtest.h> +#include <algorithm> +#include <chrono> +#include <iostream> +#include <numeric> + +#include "1.0/Utils.h" +#include "1.2/Callbacks.h" +#include "ExecutionBurstController.h" +#include "MemoryUtils.h" +#include "TestHarness.h" +#include "Utils.h" +#include "VtsHalNeuralnetworks.h" + +namespace android::hardware::neuralnetworks::V1_3::vts::functional { + +using namespace test_helper; +using hidl::memory::V1_0::IMemory; +using V1_0::DataLocation; +using V1_0::ErrorStatus; +using V1_0::OperandLifeTime; +using V1_0::Request; +using V1_1::ExecutionPreference; +using V1_2::Constant; +using V1_2::IPreparedModel; +using V1_2::MeasureTiming; +using V1_2::OperationType; +using V1_2::OutputShape; +using V1_2::SymmPerChannelQuantParams; +using V1_2::Timing; +using V1_2::implementation::ExecutionCallback; +using V1_2::implementation::PreparedModelCallback; +using HidlToken = hidl_array<uint8_t, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN)>; + +enum class OutputType { FULLY_SPECIFIED, UNSPECIFIED, INSUFFICIENT }; + +Model createModel(const TestModel& testModel) { + // Model operands. + hidl_vec<Operand> operands(testModel.operands.size()); + size_t constCopySize = 0, constRefSize = 0; + for (uint32_t i = 0; i < testModel.operands.size(); i++) { + const auto& op = testModel.operands[i]; + + DataLocation loc = {}; + if (op.lifetime == TestOperandLifeTime::CONSTANT_COPY) { + loc = {.poolIndex = 0, + .offset = static_cast<uint32_t>(constCopySize), + .length = static_cast<uint32_t>(op.data.size())}; + constCopySize += op.data.alignedSize(); + } else if (op.lifetime == TestOperandLifeTime::CONSTANT_REFERENCE) { + loc = {.poolIndex = 0, + .offset = static_cast<uint32_t>(constRefSize), + .length = static_cast<uint32_t>(op.data.size())}; + constRefSize += op.data.alignedSize(); + } + + Operand::ExtraParams extraParams; + if (op.type == TestOperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL) { + extraParams.channelQuant(SymmPerChannelQuantParams{ + .scales = op.channelQuant.scales, .channelDim = op.channelQuant.channelDim}); + } + + operands[i] = {.type = static_cast<OperandType>(op.type), + .dimensions = op.dimensions, + .numberOfConsumers = op.numberOfConsumers, + .scale = op.scale, + .zeroPoint = op.zeroPoint, + .lifetime = static_cast<OperandLifeTime>(op.lifetime), + .location = loc, + .extraParams = std::move(extraParams)}; + } + + // Model operations. + hidl_vec<Operation> operations(testModel.operations.size()); + std::transform(testModel.operations.begin(), testModel.operations.end(), operations.begin(), + [](const TestOperation& op) -> Operation { + return {.type = static_cast<OperationType>(op.type), + .inputs = op.inputs, + .outputs = op.outputs}; + }); + + // Constant copies. + hidl_vec<uint8_t> operandValues(constCopySize); + for (uint32_t i = 0; i < testModel.operands.size(); i++) { + const auto& op = testModel.operands[i]; + if (op.lifetime == TestOperandLifeTime::CONSTANT_COPY) { + const uint8_t* begin = op.data.get<uint8_t>(); + const uint8_t* end = begin + op.data.size(); + std::copy(begin, end, operandValues.data() + operands[i].location.offset); + } + } + + // Shared memory. + hidl_vec<hidl_memory> pools = {}; + if (constRefSize > 0) { + hidl_vec_push_back(&pools, nn::allocateSharedMemory(constRefSize)); + CHECK_NE(pools[0].size(), 0u); + + // load data + sp<IMemory> mappedMemory = mapMemory(pools[0]); + CHECK(mappedMemory.get() != nullptr); + uint8_t* mappedPtr = + reinterpret_cast<uint8_t*>(static_cast<void*>(mappedMemory->getPointer())); + CHECK(mappedPtr != nullptr); + + for (uint32_t i = 0; i < testModel.operands.size(); i++) { + const auto& op = testModel.operands[i]; + if (op.lifetime == TestOperandLifeTime::CONSTANT_REFERENCE) { + const uint8_t* begin = op.data.get<uint8_t>(); + const uint8_t* end = begin + op.data.size(); + std::copy(begin, end, mappedPtr + operands[i].location.offset); + } + } + } + + return {.operands = std::move(operands), + .operations = std::move(operations), + .inputIndexes = testModel.inputIndexes, + .outputIndexes = testModel.outputIndexes, + .operandValues = std::move(operandValues), + .pools = std::move(pools), + .relaxComputationFloat32toFloat16 = testModel.isRelaxed}; +} + +static bool isOutputSizeGreaterThanOne(const TestModel& testModel, uint32_t index) { + const auto byteSize = testModel.operands[testModel.outputIndexes[index]].data.size(); + return byteSize > 1u; +} + +static void makeOutputInsufficientSize(uint32_t outputIndex, Request* request) { + auto& length = request->outputs[outputIndex].location.length; + ASSERT_GT(length, 1u); + length -= 1u; +} + +static void makeOutputDimensionsUnspecified(Model* model) { + for (auto i : model->outputIndexes) { + auto& dims = model->operands[i].dimensions; + std::fill(dims.begin(), dims.end(), 0); + } +} + +static Return<ErrorStatus> ExecutePreparedModel(const sp<IPreparedModel>& preparedModel, + const Request& request, MeasureTiming measure, + sp<ExecutionCallback>& callback) { + return preparedModel->execute_1_2(request, measure, callback); +} +static Return<ErrorStatus> ExecutePreparedModel(const sp<IPreparedModel>& preparedModel, + const Request& request, MeasureTiming measure, + hidl_vec<OutputShape>* outputShapes, + Timing* timing) { + ErrorStatus result; + Return<void> ret = preparedModel->executeSynchronously( + request, measure, + [&result, outputShapes, timing](ErrorStatus error, const hidl_vec<OutputShape>& shapes, + const Timing& time) { + result = error; + *outputShapes = shapes; + *timing = time; + }); + if (!ret.isOk()) { + return ErrorStatus::GENERAL_FAILURE; + } + return result; +} +static std::shared_ptr<::android::nn::ExecutionBurstController> CreateBurst( + const sp<IPreparedModel>& preparedModel) { + return android::nn::ExecutionBurstController::create(preparedModel, + std::chrono::microseconds{0}); +} +enum class Executor { ASYNC, SYNC, BURST }; + +void EvaluatePreparedModel(const sp<IPreparedModel>& preparedModel, const TestModel& testModel, + Executor executor, MeasureTiming measure, OutputType outputType) { + // If output0 does not have size larger than one byte, we can not test with insufficient buffer. + if (outputType == OutputType::INSUFFICIENT && !isOutputSizeGreaterThanOne(testModel, 0)) { + return; + } + + Request request = createRequest(testModel); + if (outputType == OutputType::INSUFFICIENT) { + makeOutputInsufficientSize(/*outputIndex=*/0, &request); + } + + ErrorStatus executionStatus; + hidl_vec<OutputShape> outputShapes; + Timing timing; + switch (executor) { + case Executor::ASYNC: { + SCOPED_TRACE("asynchronous"); + + // launch execution + sp<ExecutionCallback> executionCallback = new ExecutionCallback(); + Return<ErrorStatus> executionLaunchStatus = + ExecutePreparedModel(preparedModel, request, measure, executionCallback); + ASSERT_TRUE(executionLaunchStatus.isOk()); + EXPECT_EQ(ErrorStatus::NONE, static_cast<ErrorStatus>(executionLaunchStatus)); + + // retrieve execution status + executionCallback->wait(); + executionStatus = executionCallback->getStatus(); + outputShapes = executionCallback->getOutputShapes(); + timing = executionCallback->getTiming(); + + break; + } + case Executor::SYNC: { + SCOPED_TRACE("synchronous"); + + // execute + Return<ErrorStatus> executionReturnStatus = + ExecutePreparedModel(preparedModel, request, measure, &outputShapes, &timing); + ASSERT_TRUE(executionReturnStatus.isOk()); + executionStatus = static_cast<ErrorStatus>(executionReturnStatus); + + break; + } + case Executor::BURST: { + SCOPED_TRACE("burst"); + + // create burst + const std::shared_ptr<::android::nn::ExecutionBurstController> controller = + CreateBurst(preparedModel); + ASSERT_NE(nullptr, controller.get()); + + // create memory keys + std::vector<intptr_t> keys(request.pools.size()); + for (size_t i = 0; i < keys.size(); ++i) { + keys[i] = reinterpret_cast<intptr_t>(&request.pools[i]); + } + + // execute burst + int n; + std::tie(n, outputShapes, timing, std::ignore) = + controller->compute(request, measure, keys); + executionStatus = nn::convertResultCodeToErrorStatus(n); + + break; + } + } + + if (outputType != OutputType::FULLY_SPECIFIED && + executionStatus == ErrorStatus::GENERAL_FAILURE) { + LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot " + "execute model that it does not support."; + std::cout << "[ ] Early termination of test because vendor service cannot " + "execute model that it does not support." + << std::endl; + GTEST_SKIP(); + } + if (measure == MeasureTiming::NO) { + EXPECT_EQ(UINT64_MAX, timing.timeOnDevice); + EXPECT_EQ(UINT64_MAX, timing.timeInDriver); + } else { + if (timing.timeOnDevice != UINT64_MAX && timing.timeInDriver != UINT64_MAX) { + EXPECT_LE(timing.timeOnDevice, timing.timeInDriver); + } + } + + switch (outputType) { + case OutputType::FULLY_SPECIFIED: + // If the model output operands are fully specified, outputShapes must be either + // either empty, or have the same number of elements as the number of outputs. + ASSERT_EQ(ErrorStatus::NONE, executionStatus); + ASSERT_TRUE(outputShapes.size() == 0 || + outputShapes.size() == testModel.outputIndexes.size()); + break; + case OutputType::UNSPECIFIED: + // If the model output operands are not fully specified, outputShapes must have + // the same number of elements as the number of outputs. + ASSERT_EQ(ErrorStatus::NONE, executionStatus); + ASSERT_EQ(outputShapes.size(), testModel.outputIndexes.size()); + break; + case OutputType::INSUFFICIENT: + ASSERT_EQ(ErrorStatus::OUTPUT_INSUFFICIENT_SIZE, executionStatus); + ASSERT_EQ(outputShapes.size(), testModel.outputIndexes.size()); + ASSERT_FALSE(outputShapes[0].isSufficient); + return; + } + + // Go through all outputs, check returned output shapes. + for (uint32_t i = 0; i < outputShapes.size(); i++) { + EXPECT_TRUE(outputShapes[i].isSufficient); + const auto& expect = testModel.operands[testModel.outputIndexes[i]].dimensions; + const std::vector<uint32_t> actual = outputShapes[i].dimensions; + EXPECT_EQ(expect, actual); + } + + // Retrieve execution results. + const std::vector<TestBuffer> outputs = getOutputBuffers(request); + + // We want "close-enough" results. + checkResults(testModel, outputs); +} + +void EvaluatePreparedModel(const sp<IPreparedModel>& preparedModel, const TestModel& testModel, + bool testDynamicOutputShape) { + if (testDynamicOutputShape) { + EvaluatePreparedModel(preparedModel, testModel, Executor::ASYNC, MeasureTiming::NO, + OutputType::UNSPECIFIED); + EvaluatePreparedModel(preparedModel, testModel, Executor::SYNC, MeasureTiming::NO, + OutputType::UNSPECIFIED); + EvaluatePreparedModel(preparedModel, testModel, Executor::BURST, MeasureTiming::NO, + OutputType::UNSPECIFIED); + EvaluatePreparedModel(preparedModel, testModel, Executor::ASYNC, MeasureTiming::YES, + OutputType::UNSPECIFIED); + EvaluatePreparedModel(preparedModel, testModel, Executor::SYNC, MeasureTiming::YES, + OutputType::UNSPECIFIED); + EvaluatePreparedModel(preparedModel, testModel, Executor::BURST, MeasureTiming::YES, + OutputType::UNSPECIFIED); + EvaluatePreparedModel(preparedModel, testModel, Executor::ASYNC, MeasureTiming::NO, + OutputType::INSUFFICIENT); + EvaluatePreparedModel(preparedModel, testModel, Executor::SYNC, MeasureTiming::NO, + OutputType::INSUFFICIENT); + EvaluatePreparedModel(preparedModel, testModel, Executor::BURST, MeasureTiming::NO, + OutputType::INSUFFICIENT); + EvaluatePreparedModel(preparedModel, testModel, Executor::ASYNC, MeasureTiming::YES, + OutputType::INSUFFICIENT); + EvaluatePreparedModel(preparedModel, testModel, Executor::SYNC, MeasureTiming::YES, + OutputType::INSUFFICIENT); + EvaluatePreparedModel(preparedModel, testModel, Executor::BURST, MeasureTiming::YES, + OutputType::INSUFFICIENT); + } else { + EvaluatePreparedModel(preparedModel, testModel, Executor::ASYNC, MeasureTiming::NO, + OutputType::FULLY_SPECIFIED); + EvaluatePreparedModel(preparedModel, testModel, Executor::SYNC, MeasureTiming::NO, + OutputType::FULLY_SPECIFIED); + EvaluatePreparedModel(preparedModel, testModel, Executor::BURST, MeasureTiming::NO, + OutputType::FULLY_SPECIFIED); + EvaluatePreparedModel(preparedModel, testModel, Executor::ASYNC, MeasureTiming::YES, + OutputType::FULLY_SPECIFIED); + EvaluatePreparedModel(preparedModel, testModel, Executor::SYNC, MeasureTiming::YES, + OutputType::FULLY_SPECIFIED); + EvaluatePreparedModel(preparedModel, testModel, Executor::BURST, MeasureTiming::YES, + OutputType::FULLY_SPECIFIED); + } +} + +void Execute(const sp<IDevice>& device, const TestModel& testModel, bool testDynamicOutputShape) { + Model model = createModel(testModel); + if (testDynamicOutputShape) { + makeOutputDimensionsUnspecified(&model); + } + + sp<IPreparedModel> preparedModel; + createPreparedModel(device, model, &preparedModel); + if (preparedModel == nullptr) return; + + EvaluatePreparedModel(preparedModel, testModel, testDynamicOutputShape); +} + +void GeneratedTestBase::SetUp() { + testing::TestWithParam<GeneratedTestParam>::SetUp(); + ASSERT_NE(kDevice, nullptr); +} + +std::vector<NamedModel> getNamedModels(const FilterFn& filter) { + return TestModelManager::get().getTestModels(filter); +} + +std::string printGeneratedTest(const testing::TestParamInfo<GeneratedTestParam>& info) { + const auto& [namedDevice, namedModel] = info.param; + return gtestCompliantName(getName(namedDevice) + "_" + getName(namedModel)); +} + +// Tag for the generated tests +class GeneratedTest : public GeneratedTestBase {}; + +// Tag for the dynamic output shape tests +class DynamicOutputShapeTest : public GeneratedTest {}; + +TEST_P(GeneratedTest, Test) { + Execute(kDevice, kTestModel, /*testDynamicOutputShape=*/false); +} + +TEST_P(DynamicOutputShapeTest, Test) { + Execute(kDevice, kTestModel, /*testDynamicOutputShape=*/true); +} + +INSTANTIATE_GENERATED_TEST(GeneratedTest, + [](const TestModel& testModel) { return !testModel.expectFailure; }); + +INSTANTIATE_GENERATED_TEST(DynamicOutputShapeTest, + [](const TestModel& testModel) { return !testModel.expectFailure; }); + +} // namespace android::hardware::neuralnetworks::V1_3::vts::functional diff --git a/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.h b/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.h new file mode 100644 index 0000000000..b9277cfd4a --- /dev/null +++ b/neuralnetworks/1.3/vts/functional/GeneratedTestHarness.h @@ -0,0 +1,66 @@ +/* + * 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. + */ + +#ifndef ANDROID_HARDWARE_NEURALNETWORKS_V1_3_GENERATED_TEST_HARNESS_H +#define ANDROID_HARDWARE_NEURALNETWORKS_V1_3_GENERATED_TEST_HARNESS_H + +#include <android/hardware/neuralnetworks/1.2/IPreparedModel.h> +#include <android/hardware/neuralnetworks/1.3/IDevice.h> +#include <android/hardware/neuralnetworks/1.3/types.h> +#include <functional> +#include <vector> +#include "1.0/Utils.h" +#include "TestHarness.h" +#include "VtsHalNeuralnetworks.h" + +namespace android::hardware::neuralnetworks::V1_3::vts::functional { + +using NamedModel = Named<const test_helper::TestModel*>; +using GeneratedTestParam = std::tuple<NamedDevice, NamedModel>; + +class GeneratedTestBase : public testing::TestWithParam<GeneratedTestParam> { + protected: + void SetUp() override; + const sp<IDevice> kDevice = getData(std::get<NamedDevice>(GetParam())); + const test_helper::TestModel& kTestModel = *getData(std::get<NamedModel>(GetParam())); +}; + +using FilterFn = std::function<bool(const test_helper::TestModel&)>; +std::vector<NamedModel> getNamedModels(const FilterFn& filter); + +std::string printGeneratedTest(const testing::TestParamInfo<GeneratedTestParam>& info); + +#define INSTANTIATE_GENERATED_TEST(TestSuite, filter) \ + INSTANTIATE_TEST_SUITE_P(TestGenerated, TestSuite, \ + testing::Combine(testing::ValuesIn(getNamedDevices()), \ + testing::ValuesIn(getNamedModels(filter))), \ + printGeneratedTest) + +// Tag for the validation tests, instantiated in VtsHalNeuralnetworks.cpp. +// TODO: Clean up the hierarchy for ValidationTest. +class ValidationTest : public GeneratedTestBase {}; + +Model createModel(const test_helper::TestModel& testModel); + +void PrepareModel(const sp<IDevice>& device, const Model& model, + sp<V1_2::IPreparedModel>* preparedModel); + +void EvaluatePreparedModel(const sp<V1_2::IPreparedModel>& preparedModel, + const test_helper::TestModel& testModel, bool testDynamicOutputShape); + +} // namespace android::hardware::neuralnetworks::V1_3::vts::functional + +#endif // ANDROID_HARDWARE_NEURALNETWORKS_V1_3_GENERATED_TEST_HARNESS_H diff --git a/neuralnetworks/1.3/vts/functional/TestAssertions.cpp b/neuralnetworks/1.3/vts/functional/TestAssertions.cpp new file mode 100644 index 0000000000..7361078eca --- /dev/null +++ b/neuralnetworks/1.3/vts/functional/TestAssertions.cpp @@ -0,0 +1,144 @@ +/* + * 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. + */ + +#include <android/hardware/neuralnetworks/1.3/types.h> +#include "TestHarness.h" + +namespace android::hardware::neuralnetworks::V1_3 { + +// Make sure that the HIDL enums are compatible with the values defined in +// frameworks/ml/nn/tools/test_generator/test_harness/include/TestHarness.h. +using namespace test_helper; +#define CHECK_TEST_ENUM(EnumType, enumValue) \ + static_assert(static_cast<EnumType>(Test##EnumType::enumValue) == EnumType::enumValue) + +using V1_2::OperationType; + +CHECK_TEST_ENUM(OperandType, FLOAT32); +CHECK_TEST_ENUM(OperandType, INT32); +CHECK_TEST_ENUM(OperandType, UINT32); +CHECK_TEST_ENUM(OperandType, TENSOR_FLOAT32); +CHECK_TEST_ENUM(OperandType, TENSOR_INT32); +CHECK_TEST_ENUM(OperandType, TENSOR_QUANT8_ASYMM); +CHECK_TEST_ENUM(OperandType, BOOL); +CHECK_TEST_ENUM(OperandType, TENSOR_QUANT16_SYMM); +CHECK_TEST_ENUM(OperandType, TENSOR_FLOAT16); +CHECK_TEST_ENUM(OperandType, TENSOR_BOOL8); +CHECK_TEST_ENUM(OperandType, FLOAT16); +CHECK_TEST_ENUM(OperandType, TENSOR_QUANT8_SYMM_PER_CHANNEL); +CHECK_TEST_ENUM(OperandType, TENSOR_QUANT16_ASYMM); +CHECK_TEST_ENUM(OperandType, TENSOR_QUANT8_SYMM); +CHECK_TEST_ENUM(OperandType, TENSOR_QUANT8_ASYMM_SIGNED); + +CHECK_TEST_ENUM(OperationType, ADD); +CHECK_TEST_ENUM(OperationType, AVERAGE_POOL_2D); +CHECK_TEST_ENUM(OperationType, CONCATENATION); +CHECK_TEST_ENUM(OperationType, CONV_2D); +CHECK_TEST_ENUM(OperationType, DEPTHWISE_CONV_2D); +CHECK_TEST_ENUM(OperationType, DEPTH_TO_SPACE); +CHECK_TEST_ENUM(OperationType, DEQUANTIZE); +CHECK_TEST_ENUM(OperationType, EMBEDDING_LOOKUP); +CHECK_TEST_ENUM(OperationType, FLOOR); +CHECK_TEST_ENUM(OperationType, FULLY_CONNECTED); +CHECK_TEST_ENUM(OperationType, HASHTABLE_LOOKUP); +CHECK_TEST_ENUM(OperationType, L2_NORMALIZATION); +CHECK_TEST_ENUM(OperationType, L2_POOL_2D); +CHECK_TEST_ENUM(OperationType, LOCAL_RESPONSE_NORMALIZATION); +CHECK_TEST_ENUM(OperationType, LOGISTIC); +CHECK_TEST_ENUM(OperationType, LSH_PROJECTION); +CHECK_TEST_ENUM(OperationType, LSTM); +CHECK_TEST_ENUM(OperationType, MAX_POOL_2D); +CHECK_TEST_ENUM(OperationType, MUL); +CHECK_TEST_ENUM(OperationType, RELU); +CHECK_TEST_ENUM(OperationType, RELU1); +CHECK_TEST_ENUM(OperationType, RELU6); +CHECK_TEST_ENUM(OperationType, RESHAPE); +CHECK_TEST_ENUM(OperationType, RESIZE_BILINEAR); +CHECK_TEST_ENUM(OperationType, RNN); +CHECK_TEST_ENUM(OperationType, SOFTMAX); +CHECK_TEST_ENUM(OperationType, SPACE_TO_DEPTH); +CHECK_TEST_ENUM(OperationType, SVDF); +CHECK_TEST_ENUM(OperationType, TANH); +CHECK_TEST_ENUM(OperationType, BATCH_TO_SPACE_ND); +CHECK_TEST_ENUM(OperationType, DIV); +CHECK_TEST_ENUM(OperationType, MEAN); +CHECK_TEST_ENUM(OperationType, PAD); +CHECK_TEST_ENUM(OperationType, SPACE_TO_BATCH_ND); +CHECK_TEST_ENUM(OperationType, SQUEEZE); +CHECK_TEST_ENUM(OperationType, STRIDED_SLICE); +CHECK_TEST_ENUM(OperationType, SUB); +CHECK_TEST_ENUM(OperationType, TRANSPOSE); +CHECK_TEST_ENUM(OperationType, ABS); +CHECK_TEST_ENUM(OperationType, ARGMAX); +CHECK_TEST_ENUM(OperationType, ARGMIN); +CHECK_TEST_ENUM(OperationType, AXIS_ALIGNED_BBOX_TRANSFORM); +CHECK_TEST_ENUM(OperationType, BIDIRECTIONAL_SEQUENCE_LSTM); +CHECK_TEST_ENUM(OperationType, BIDIRECTIONAL_SEQUENCE_RNN); +CHECK_TEST_ENUM(OperationType, BOX_WITH_NMS_LIMIT); +CHECK_TEST_ENUM(OperationType, CAST); +CHECK_TEST_ENUM(OperationType, CHANNEL_SHUFFLE); +CHECK_TEST_ENUM(OperationType, DETECTION_POSTPROCESSING); +CHECK_TEST_ENUM(OperationType, EQUAL); +CHECK_TEST_ENUM(OperationType, EXP); +CHECK_TEST_ENUM(OperationType, EXPAND_DIMS); +CHECK_TEST_ENUM(OperationType, GATHER); +CHECK_TEST_ENUM(OperationType, GENERATE_PROPOSALS); +CHECK_TEST_ENUM(OperationType, GREATER); +CHECK_TEST_ENUM(OperationType, GREATER_EQUAL); +CHECK_TEST_ENUM(OperationType, GROUPED_CONV_2D); +CHECK_TEST_ENUM(OperationType, HEATMAP_MAX_KEYPOINT); +CHECK_TEST_ENUM(OperationType, INSTANCE_NORMALIZATION); +CHECK_TEST_ENUM(OperationType, LESS); +CHECK_TEST_ENUM(OperationType, LESS_EQUAL); +CHECK_TEST_ENUM(OperationType, LOG); +CHECK_TEST_ENUM(OperationType, LOGICAL_AND); +CHECK_TEST_ENUM(OperationType, LOGICAL_NOT); +CHECK_TEST_ENUM(OperationType, LOGICAL_OR); +CHECK_TEST_ENUM(OperationType, LOG_SOFTMAX); +CHECK_TEST_ENUM(OperationType, MAXIMUM); +CHECK_TEST_ENUM(OperationType, MINIMUM); +CHECK_TEST_ENUM(OperationType, NEG); +CHECK_TEST_ENUM(OperationType, NOT_EQUAL); +CHECK_TEST_ENUM(OperationType, PAD_V2); +CHECK_TEST_ENUM(OperationType, POW); +CHECK_TEST_ENUM(OperationType, PRELU); +CHECK_TEST_ENUM(OperationType, QUANTIZE); +CHECK_TEST_ENUM(OperationType, QUANTIZED_16BIT_LSTM); +CHECK_TEST_ENUM(OperationType, RANDOM_MULTINOMIAL); +CHECK_TEST_ENUM(OperationType, REDUCE_ALL); +CHECK_TEST_ENUM(OperationType, REDUCE_ANY); +CHECK_TEST_ENUM(OperationType, REDUCE_MAX); +CHECK_TEST_ENUM(OperationType, REDUCE_MIN); +CHECK_TEST_ENUM(OperationType, REDUCE_PROD); +CHECK_TEST_ENUM(OperationType, REDUCE_SUM); +CHECK_TEST_ENUM(OperationType, ROI_ALIGN); +CHECK_TEST_ENUM(OperationType, ROI_POOLING); +CHECK_TEST_ENUM(OperationType, RSQRT); +CHECK_TEST_ENUM(OperationType, SELECT); +CHECK_TEST_ENUM(OperationType, SIN); +CHECK_TEST_ENUM(OperationType, SLICE); +CHECK_TEST_ENUM(OperationType, SPLIT); +CHECK_TEST_ENUM(OperationType, SQRT); +CHECK_TEST_ENUM(OperationType, TILE); +CHECK_TEST_ENUM(OperationType, TOPK_V2); +CHECK_TEST_ENUM(OperationType, TRANSPOSE_CONV_2D); +CHECK_TEST_ENUM(OperationType, UNIDIRECTIONAL_SEQUENCE_LSTM); +CHECK_TEST_ENUM(OperationType, UNIDIRECTIONAL_SEQUENCE_RNN); +CHECK_TEST_ENUM(OperationType, RESIZE_NEAREST_NEIGHBOR); + +#undef CHECK_TEST_ENUM + +} // namespace android::hardware::neuralnetworks::V1_3 diff --git a/neuralnetworks/1.3/vts/functional/ValidateBurst.cpp b/neuralnetworks/1.3/vts/functional/ValidateBurst.cpp new file mode 100644 index 0000000000..2c97294fad --- /dev/null +++ b/neuralnetworks/1.3/vts/functional/ValidateBurst.cpp @@ -0,0 +1,412 @@ +/* + * 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. + */ + +#define LOG_TAG "neuralnetworks_hidl_hal_test" + +#include "VtsHalNeuralnetworks.h" + +#include "1.2/Callbacks.h" +#include "ExecutionBurstController.h" +#include "ExecutionBurstServer.h" +#include "GeneratedTestHarness.h" +#include "TestHarness.h" +#include "Utils.h" + +#include <android-base/logging.h> +#include <chrono> +#include <cstring> + +namespace android::hardware::neuralnetworks::V1_3::vts::functional { + +using nn::ExecutionBurstController; +using nn::RequestChannelSender; +using nn::ResultChannelReceiver; +using V1_0::ErrorStatus; +using V1_0::Request; +using V1_2::FmqRequestDatum; +using V1_2::FmqResultDatum; +using V1_2::IBurstCallback; +using V1_2::IBurstContext; +using V1_2::IPreparedModel; +using V1_2::MeasureTiming; +using V1_2::Timing; +using ExecutionBurstCallback = ExecutionBurstController::ExecutionBurstCallback; + +// This constant value represents the length of an FMQ that is large enough to +// return a result from a burst execution for all of the generated test cases. +constexpr size_t kExecutionBurstChannelLength = 1024; + +// This constant value represents a length of an FMQ that is not large enough +// to return a result from a burst execution for some of the generated test +// cases. +constexpr size_t kExecutionBurstChannelSmallLength = 8; + +///////////////////////// UTILITY FUNCTIONS ///////////////////////// + +static bool badTiming(Timing timing) { + return timing.timeOnDevice == UINT64_MAX && timing.timeInDriver == UINT64_MAX; +} + +static void createBurst(const sp<IPreparedModel>& preparedModel, const sp<IBurstCallback>& callback, + std::unique_ptr<RequestChannelSender>* sender, + std::unique_ptr<ResultChannelReceiver>* receiver, + sp<IBurstContext>* context, + size_t resultChannelLength = kExecutionBurstChannelLength) { + ASSERT_NE(nullptr, preparedModel.get()); + ASSERT_NE(nullptr, sender); + ASSERT_NE(nullptr, receiver); + ASSERT_NE(nullptr, context); + + // create FMQ objects + auto [fmqRequestChannel, fmqRequestDescriptor] = + RequestChannelSender::create(kExecutionBurstChannelLength); + auto [fmqResultChannel, fmqResultDescriptor] = + ResultChannelReceiver::create(resultChannelLength, std::chrono::microseconds{0}); + ASSERT_NE(nullptr, fmqRequestChannel.get()); + ASSERT_NE(nullptr, fmqResultChannel.get()); + ASSERT_NE(nullptr, fmqRequestDescriptor); + ASSERT_NE(nullptr, fmqResultDescriptor); + + // configure burst + ErrorStatus errorStatus; + sp<IBurstContext> burstContext; + const Return<void> ret = preparedModel->configureExecutionBurst( + callback, *fmqRequestDescriptor, *fmqResultDescriptor, + [&errorStatus, &burstContext](ErrorStatus status, const sp<IBurstContext>& context) { + errorStatus = status; + burstContext = context; + }); + ASSERT_TRUE(ret.isOk()); + ASSERT_EQ(ErrorStatus::NONE, errorStatus); + ASSERT_NE(nullptr, burstContext.get()); + + // return values + *sender = std::move(fmqRequestChannel); + *receiver = std::move(fmqResultChannel); + *context = burstContext; +} + +static void createBurstWithResultChannelLength( + const sp<IPreparedModel>& preparedModel, size_t resultChannelLength, + std::shared_ptr<ExecutionBurstController>* controller) { + ASSERT_NE(nullptr, preparedModel.get()); + ASSERT_NE(nullptr, controller); + + // create FMQ objects + std::unique_ptr<RequestChannelSender> sender; + std::unique_ptr<ResultChannelReceiver> receiver; + sp<ExecutionBurstCallback> callback = new ExecutionBurstCallback(); + sp<IBurstContext> context; + ASSERT_NO_FATAL_FAILURE(createBurst(preparedModel, callback, &sender, &receiver, &context, + resultChannelLength)); + ASSERT_NE(nullptr, sender.get()); + ASSERT_NE(nullptr, receiver.get()); + ASSERT_NE(nullptr, context.get()); + + // return values + *controller = std::make_shared<ExecutionBurstController>(std::move(sender), std::move(receiver), + context, callback); +} + +// Primary validation function. This function will take a valid serialized +// request, apply a mutation to it to invalidate the serialized request, then +// pass it to interface calls that use the serialized request. Note that the +// serialized request here is passed by value, and any mutation to the +// serialized request does not leave this function. +static void validate(RequestChannelSender* sender, ResultChannelReceiver* receiver, + const std::string& message, std::vector<FmqRequestDatum> serialized, + const std::function<void(std::vector<FmqRequestDatum>*)>& mutation) { + mutation(&serialized); + + // skip if packet is too large to send + if (serialized.size() > kExecutionBurstChannelLength) { + return; + } + + SCOPED_TRACE(message); + + // send invalid packet + ASSERT_TRUE(sender->sendPacket(serialized)); + + // receive error + auto results = receiver->getBlocking(); + ASSERT_TRUE(results.has_value()); + const auto [status, outputShapes, timing] = std::move(*results); + EXPECT_NE(ErrorStatus::NONE, status); + EXPECT_EQ(0u, outputShapes.size()); + EXPECT_TRUE(badTiming(timing)); +} + +// For validation, valid packet entries are mutated to invalid packet entries, +// or invalid packet entries are inserted into valid packets. This function +// creates pre-set invalid packet entries for convenience. +static std::vector<FmqRequestDatum> createBadRequestPacketEntries() { + const FmqRequestDatum::PacketInformation packetInformation = { + /*.packetSize=*/10, /*.numberOfInputOperands=*/10, /*.numberOfOutputOperands=*/10, + /*.numberOfPools=*/10}; + const FmqRequestDatum::OperandInformation operandInformation = { + /*.hasNoValue=*/false, /*.location=*/{}, /*.numberOfDimensions=*/10}; + const int32_t invalidPoolIdentifier = std::numeric_limits<int32_t>::max(); + std::vector<FmqRequestDatum> bad(7); + bad[0].packetInformation(packetInformation); + bad[1].inputOperandInformation(operandInformation); + bad[2].inputOperandDimensionValue(0); + bad[3].outputOperandInformation(operandInformation); + bad[4].outputOperandDimensionValue(0); + bad[5].poolIdentifier(invalidPoolIdentifier); + bad[6].measureTiming(MeasureTiming::YES); + return bad; +} + +// For validation, valid packet entries are mutated to invalid packet entries, +// or invalid packet entries are inserted into valid packets. This function +// retrieves pre-set invalid packet entries for convenience. This function +// caches these data so they can be reused on subsequent validation checks. +static const std::vector<FmqRequestDatum>& getBadRequestPacketEntries() { + static const std::vector<FmqRequestDatum> bad = createBadRequestPacketEntries(); + return bad; +} + +///////////////////////// REMOVE DATUM //////////////////////////////////// + +static void removeDatumTest(RequestChannelSender* sender, ResultChannelReceiver* receiver, + const std::vector<FmqRequestDatum>& serialized) { + for (size_t index = 0; index < serialized.size(); ++index) { + const std::string message = "removeDatum: removed datum at index " + std::to_string(index); + validate(sender, receiver, message, serialized, + [index](std::vector<FmqRequestDatum>* serialized) { + serialized->erase(serialized->begin() + index); + }); + } +} + +///////////////////////// ADD DATUM //////////////////////////////////// + +static void addDatumTest(RequestChannelSender* sender, ResultChannelReceiver* receiver, + const std::vector<FmqRequestDatum>& serialized) { + const std::vector<FmqRequestDatum>& extra = getBadRequestPacketEntries(); + for (size_t index = 0; index <= serialized.size(); ++index) { + for (size_t type = 0; type < extra.size(); ++type) { + const std::string message = "addDatum: added datum type " + std::to_string(type) + + " at index " + std::to_string(index); + validate(sender, receiver, message, serialized, + [index, type, &extra](std::vector<FmqRequestDatum>* serialized) { + serialized->insert(serialized->begin() + index, extra[type]); + }); + } + } +} + +///////////////////////// MUTATE DATUM //////////////////////////////////// + +static bool interestingCase(const FmqRequestDatum& lhs, const FmqRequestDatum& rhs) { + using Discriminator = FmqRequestDatum::hidl_discriminator; + + const bool differentValues = (lhs != rhs); + const bool sameDiscriminator = (lhs.getDiscriminator() == rhs.getDiscriminator()); + const auto discriminator = rhs.getDiscriminator(); + const bool isDimensionValue = (discriminator == Discriminator::inputOperandDimensionValue || + discriminator == Discriminator::outputOperandDimensionValue); + + return differentValues && !(sameDiscriminator && isDimensionValue); +} + +static void mutateDatumTest(RequestChannelSender* sender, ResultChannelReceiver* receiver, + const std::vector<FmqRequestDatum>& serialized) { + const std::vector<FmqRequestDatum>& change = getBadRequestPacketEntries(); + for (size_t index = 0; index < serialized.size(); ++index) { + for (size_t type = 0; type < change.size(); ++type) { + if (interestingCase(serialized[index], change[type])) { + const std::string message = "mutateDatum: changed datum at index " + + std::to_string(index) + " to datum type " + + std::to_string(type); + validate(sender, receiver, message, serialized, + [index, type, &change](std::vector<FmqRequestDatum>* serialized) { + (*serialized)[index] = change[type]; + }); + } + } + } +} + +///////////////////////// BURST VALIATION TESTS //////////////////////////////////// + +static void validateBurstSerialization(const sp<IPreparedModel>& preparedModel, + const Request& request) { + // create burst + std::unique_ptr<RequestChannelSender> sender; + std::unique_ptr<ResultChannelReceiver> receiver; + sp<ExecutionBurstCallback> callback = new ExecutionBurstCallback(); + sp<IBurstContext> context; + ASSERT_NO_FATAL_FAILURE(createBurst(preparedModel, callback, &sender, &receiver, &context)); + ASSERT_NE(nullptr, sender.get()); + ASSERT_NE(nullptr, receiver.get()); + ASSERT_NE(nullptr, context.get()); + + // load memory into callback slots + std::vector<intptr_t> keys; + keys.reserve(request.pools.size()); + std::transform(request.pools.begin(), request.pools.end(), std::back_inserter(keys), + [](const auto& pool) { return reinterpret_cast<intptr_t>(&pool); }); + const std::vector<int32_t> slots = callback->getSlots(request.pools, keys); + + // ensure slot std::numeric_limits<int32_t>::max() doesn't exist (for + // subsequent slot validation testing) + ASSERT_TRUE(std::all_of(slots.begin(), slots.end(), [](int32_t slot) { + return slot != std::numeric_limits<int32_t>::max(); + })); + + // serialize the request + const auto serialized = android::nn::serialize(request, MeasureTiming::YES, slots); + + // validations + removeDatumTest(sender.get(), receiver.get(), serialized); + addDatumTest(sender.get(), receiver.get(), serialized); + mutateDatumTest(sender.get(), receiver.get(), serialized); +} + +// This test validates that when the Result message size exceeds length of the +// result FMQ, the service instance gracefully fails and returns an error. +static void validateBurstFmqLength(const sp<IPreparedModel>& preparedModel, + const Request& request) { + // create regular burst + std::shared_ptr<ExecutionBurstController> controllerRegular; + ASSERT_NO_FATAL_FAILURE(createBurstWithResultChannelLength( + preparedModel, kExecutionBurstChannelLength, &controllerRegular)); + ASSERT_NE(nullptr, controllerRegular.get()); + + // create burst with small output channel + std::shared_ptr<ExecutionBurstController> controllerSmall; + ASSERT_NO_FATAL_FAILURE(createBurstWithResultChannelLength( + preparedModel, kExecutionBurstChannelSmallLength, &controllerSmall)); + ASSERT_NE(nullptr, controllerSmall.get()); + + // load memory into callback slots + std::vector<intptr_t> keys(request.pools.size()); + for (size_t i = 0; i < keys.size(); ++i) { + keys[i] = reinterpret_cast<intptr_t>(&request.pools[i]); + } + + // collect serialized result by running regular burst + const auto [nRegular, outputShapesRegular, timingRegular, fallbackRegular] = + controllerRegular->compute(request, MeasureTiming::NO, keys); + const ErrorStatus statusRegular = nn::convertResultCodeToErrorStatus(nRegular); + EXPECT_FALSE(fallbackRegular); + + // skip test if regular burst output isn't useful for testing a failure + // caused by having too small of a length for the result FMQ + const std::vector<FmqResultDatum> serialized = + android::nn::serialize(statusRegular, outputShapesRegular, timingRegular); + if (statusRegular != ErrorStatus::NONE || + serialized.size() <= kExecutionBurstChannelSmallLength) { + return; + } + + // by this point, execution should fail because the result channel isn't + // large enough to return the serialized result + const auto [nSmall, outputShapesSmall, timingSmall, fallbackSmall] = + controllerSmall->compute(request, MeasureTiming::NO, keys); + const ErrorStatus statusSmall = nn::convertResultCodeToErrorStatus(nSmall); + EXPECT_NE(ErrorStatus::NONE, statusSmall); + EXPECT_EQ(0u, outputShapesSmall.size()); + EXPECT_TRUE(badTiming(timingSmall)); + EXPECT_FALSE(fallbackSmall); +} + +static bool isSanitized(const FmqResultDatum& datum) { + using Discriminator = FmqResultDatum::hidl_discriminator; + + // check to ensure the padding values in the returned + // FmqResultDatum::OperandInformation are initialized to 0 + if (datum.getDiscriminator() == Discriminator::operandInformation) { + static_assert( + offsetof(FmqResultDatum::OperandInformation, isSufficient) == 0, + "unexpected value for offset of FmqResultDatum::OperandInformation::isSufficient"); + static_assert( + sizeof(FmqResultDatum::OperandInformation::isSufficient) == 1, + "unexpected value for size of FmqResultDatum::OperandInformation::isSufficient"); + static_assert(offsetof(FmqResultDatum::OperandInformation, numberOfDimensions) == 4, + "unexpected value for offset of " + "FmqResultDatum::OperandInformation::numberOfDimensions"); + static_assert(sizeof(FmqResultDatum::OperandInformation::numberOfDimensions) == 4, + "unexpected value for size of " + "FmqResultDatum::OperandInformation::numberOfDimensions"); + static_assert(sizeof(FmqResultDatum::OperandInformation) == 8, + "unexpected value for size of " + "FmqResultDatum::OperandInformation"); + + constexpr size_t paddingOffset = + offsetof(FmqResultDatum::OperandInformation, isSufficient) + + sizeof(FmqResultDatum::OperandInformation::isSufficient); + constexpr size_t paddingSize = + offsetof(FmqResultDatum::OperandInformation, numberOfDimensions) - paddingOffset; + + FmqResultDatum::OperandInformation initialized{}; + std::memset(&initialized, 0, sizeof(initialized)); + + const char* initializedPaddingStart = + reinterpret_cast<const char*>(&initialized) + paddingOffset; + const char* datumPaddingStart = + reinterpret_cast<const char*>(&datum.operandInformation()) + paddingOffset; + + return std::memcmp(datumPaddingStart, initializedPaddingStart, paddingSize) == 0; + } + + // there are no other padding initialization checks required, so return true + // for any sum-type that isn't FmqResultDatum::OperandInformation + return true; +} + +static void validateBurstSanitized(const sp<IPreparedModel>& preparedModel, + const Request& request) { + // create burst + std::unique_ptr<RequestChannelSender> sender; + std::unique_ptr<ResultChannelReceiver> receiver; + sp<ExecutionBurstCallback> callback = new ExecutionBurstCallback(); + sp<IBurstContext> context; + ASSERT_NO_FATAL_FAILURE(createBurst(preparedModel, callback, &sender, &receiver, &context)); + ASSERT_NE(nullptr, sender.get()); + ASSERT_NE(nullptr, receiver.get()); + ASSERT_NE(nullptr, context.get()); + + // load memory into callback slots + std::vector<intptr_t> keys; + keys.reserve(request.pools.size()); + std::transform(request.pools.begin(), request.pools.end(), std::back_inserter(keys), + [](const auto& pool) { return reinterpret_cast<intptr_t>(&pool); }); + const std::vector<int32_t> slots = callback->getSlots(request.pools, keys); + + // send valid request + ASSERT_TRUE(sender->send(request, MeasureTiming::YES, slots)); + + // receive valid result + auto serialized = receiver->getPacketBlocking(); + ASSERT_TRUE(serialized.has_value()); + + // sanitize result + ASSERT_TRUE(std::all_of(serialized->begin(), serialized->end(), isSanitized)) + << "The result serialized data is not properly sanitized"; +} + +///////////////////////////// ENTRY POINT ////////////////////////////////// + +void validateBurst(const sp<IPreparedModel>& preparedModel, const Request& request) { + ASSERT_NO_FATAL_FAILURE(validateBurstSerialization(preparedModel, request)); + ASSERT_NO_FATAL_FAILURE(validateBurstFmqLength(preparedModel, request)); + ASSERT_NO_FATAL_FAILURE(validateBurstSanitized(preparedModel, request)); +} + +} // namespace android::hardware::neuralnetworks::V1_3::vts::functional diff --git a/neuralnetworks/1.3/vts/functional/ValidateModel.cpp b/neuralnetworks/1.3/vts/functional/ValidateModel.cpp new file mode 100644 index 0000000000..44b32a9fec --- /dev/null +++ b/neuralnetworks/1.3/vts/functional/ValidateModel.cpp @@ -0,0 +1,718 @@ +/* + * Copyright (C) 2018 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. + */ + +#define LOG_TAG "neuralnetworks_hidl_hal_test" + +#include "1.0/Utils.h" +#include "1.2/Callbacks.h" +#include "GeneratedTestHarness.h" +#include "VtsHalNeuralnetworks.h" + +namespace android::hardware::neuralnetworks::V1_3::vts::functional { + +using V1_0::ErrorStatus; +using V1_0::OperandLifeTime; +using V1_1::ExecutionPreference; +using V1_2::IPreparedModel; +using V1_2::OperationType; +using V1_2::OperationTypeRange; +using V1_2::SymmPerChannelQuantParams; +using V1_2::implementation::PreparedModelCallback; +using HidlToken = + hidl_array<uint8_t, static_cast<uint32_t>(V1_2::Constant::BYTE_SIZE_OF_CACHE_TOKEN)>; + +///////////////////////// UTILITY FUNCTIONS ///////////////////////// + +static void validateGetSupportedOperations(const sp<IDevice>& device, const std::string& message, + const Model& model) { + SCOPED_TRACE(message + " [getSupportedOperations_1_3]"); + + Return<void> ret = device->getSupportedOperations_1_3( + model, [&](ErrorStatus status, const hidl_vec<bool>&) { + EXPECT_EQ(ErrorStatus::INVALID_ARGUMENT, status); + }); + EXPECT_TRUE(ret.isOk()); +} + +static void validatePrepareModel(const sp<IDevice>& device, const std::string& message, + const Model& model, ExecutionPreference preference) { + SCOPED_TRACE(message + " [prepareModel_1_3]"); + + sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback(); + Return<ErrorStatus> prepareLaunchStatus = + device->prepareModel_1_3(model, preference, hidl_vec<hidl_handle>(), + hidl_vec<hidl_handle>(), HidlToken(), preparedModelCallback); + ASSERT_TRUE(prepareLaunchStatus.isOk()); + ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, static_cast<ErrorStatus>(prepareLaunchStatus)); + + preparedModelCallback->wait(); + ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus(); + ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, prepareReturnStatus); + sp<IPreparedModel> preparedModel = getPreparedModel_1_2(preparedModelCallback); + ASSERT_EQ(nullptr, preparedModel.get()); +} + +static bool validExecutionPreference(ExecutionPreference preference) { + return preference == ExecutionPreference::LOW_POWER || + preference == ExecutionPreference::FAST_SINGLE_ANSWER || + preference == ExecutionPreference::SUSTAINED_SPEED; +} + +// Primary validation function. This function will take a valid model, apply a +// mutation to it to invalidate the model, then pass it to interface calls that +// use the model. Note that the model here is passed by value, and any mutation +// to the model does not leave this function. +static void validate(const sp<IDevice>& device, const std::string& message, Model model, + const std::function<void(Model*)>& mutation, + ExecutionPreference preference = ExecutionPreference::FAST_SINGLE_ANSWER) { + mutation(&model); + if (validExecutionPreference(preference)) { + validateGetSupportedOperations(device, message, model); + } + validatePrepareModel(device, message, model, preference); +} + +static uint32_t addOperand(Model* model) { + return hidl_vec_push_back(&model->operands, + { + .type = OperandType::INT32, + .dimensions = {}, + .numberOfConsumers = 0, + .scale = 0.0f, + .zeroPoint = 0, + .lifetime = OperandLifeTime::MODEL_INPUT, + .location = {.poolIndex = 0, .offset = 0, .length = 0}, + }); +} + +static uint32_t addOperand(Model* model, OperandLifeTime lifetime) { + uint32_t index = addOperand(model); + model->operands[index].numberOfConsumers = 1; + model->operands[index].lifetime = lifetime; + return index; +} + +///////////////////////// VALIDATE MODEL OPERAND TYPE ///////////////////////// + +static const uint32_t invalidOperandTypes[] = { + static_cast<uint32_t>(OperandTypeRange::FUNDAMENTAL_MIN) - 1, + static_cast<uint32_t>(OperandTypeRange::FUNDAMENTAL_MAX) + 1, + static_cast<uint32_t>(OperandTypeRange::OEM_MIN) - 1, + static_cast<uint32_t>(OperandTypeRange::OEM_MAX) + 1, +}; + +static void mutateOperandTypeTest(const sp<IDevice>& device, const Model& model) { + for (size_t operand = 0; operand < model.operands.size(); ++operand) { + for (uint32_t invalidOperandType : invalidOperandTypes) { + const std::string message = "mutateOperandTypeTest: operand " + + std::to_string(operand) + " set to value " + + std::to_string(invalidOperandType); + validate(device, message, model, [operand, invalidOperandType](Model* model) { + model->operands[operand].type = static_cast<OperandType>(invalidOperandType); + }); + } + } +} + +///////////////////////// VALIDATE OPERAND RANK ///////////////////////// + +static uint32_t getInvalidRank(OperandType type) { + switch (type) { + case OperandType::FLOAT16: + case OperandType::FLOAT32: + case OperandType::INT32: + case OperandType::UINT32: + case OperandType::BOOL: + return 1; + case OperandType::TENSOR_BOOL8: + case OperandType::TENSOR_FLOAT16: + case OperandType::TENSOR_FLOAT32: + case OperandType::TENSOR_INT32: + case OperandType::TENSOR_QUANT8_ASYMM: + case OperandType::TENSOR_QUANT8_SYMM: + case OperandType::TENSOR_QUANT16_ASYMM: + case OperandType::TENSOR_QUANT16_SYMM: + case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: + return 0; + default: + return 0; + } +} + +static void mutateOperandRankTest(const sp<IDevice>& device, const Model& model) { + for (size_t operand = 0; operand < model.operands.size(); ++operand) { + const uint32_t invalidRank = getInvalidRank(model.operands[operand].type); + if (invalidRank == 0) { + continue; + } + const std::string message = "mutateOperandRankTest: operand " + std::to_string(operand) + + " has rank of " + std::to_string(invalidRank); + validate(device, message, model, [operand, invalidRank](Model* model) { + model->operands[operand].dimensions = std::vector<uint32_t>(invalidRank, 0); + }); + } +} + +///////////////////////// VALIDATE OPERAND SCALE ///////////////////////// + +static float getInvalidScale(OperandType type) { + switch (type) { + case OperandType::FLOAT16: + case OperandType::FLOAT32: + case OperandType::INT32: + case OperandType::UINT32: + case OperandType::BOOL: + case OperandType::TENSOR_BOOL8: + case OperandType::TENSOR_FLOAT16: + case OperandType::TENSOR_FLOAT32: + case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: + return 1.0f; + case OperandType::TENSOR_INT32: + return -1.0f; + case OperandType::TENSOR_QUANT8_SYMM: + case OperandType::TENSOR_QUANT8_ASYMM: + case OperandType::TENSOR_QUANT16_ASYMM: + case OperandType::TENSOR_QUANT16_SYMM: + return 0.0f; + default: + return 0.0f; + } +} + +static void mutateOperandScaleTest(const sp<IDevice>& device, const Model& model) { + for (size_t operand = 0; operand < model.operands.size(); ++operand) { + const float invalidScale = getInvalidScale(model.operands[operand].type); + const std::string message = "mutateOperandScaleTest: operand " + std::to_string(operand) + + " has scale of " + std::to_string(invalidScale); + validate(device, message, model, [operand, invalidScale](Model* model) { + model->operands[operand].scale = invalidScale; + }); + } +} + +///////////////////////// VALIDATE OPERAND ZERO POINT ///////////////////////// + +static std::vector<int32_t> getInvalidZeroPoints(OperandType type) { + switch (type) { + case OperandType::FLOAT16: + case OperandType::FLOAT32: + case OperandType::INT32: + case OperandType::UINT32: + case OperandType::BOOL: + case OperandType::TENSOR_BOOL8: + case OperandType::TENSOR_FLOAT16: + case OperandType::TENSOR_FLOAT32: + case OperandType::TENSOR_INT32: + case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: + return {1}; + case OperandType::TENSOR_QUANT8_ASYMM: + return {-1, 256}; + case OperandType::TENSOR_QUANT8_SYMM: + return {-129, -1, 1, 128}; + case OperandType::TENSOR_QUANT16_ASYMM: + return {-1, 65536}; + case OperandType::TENSOR_QUANT16_SYMM: + return {-32769, -1, 1, 32768}; + default: + return {}; + } +} + +static void mutateOperandZeroPointTest(const sp<IDevice>& device, const Model& model) { + for (size_t operand = 0; operand < model.operands.size(); ++operand) { + const std::vector<int32_t> invalidZeroPoints = + getInvalidZeroPoints(model.operands[operand].type); + for (int32_t invalidZeroPoint : invalidZeroPoints) { + const std::string message = "mutateOperandZeroPointTest: operand " + + std::to_string(operand) + " has zero point of " + + std::to_string(invalidZeroPoint); + validate(device, message, model, [operand, invalidZeroPoint](Model* model) { + model->operands[operand].zeroPoint = invalidZeroPoint; + }); + } + } +} + +///////////////////////// VALIDATE EXTRA ??? ///////////////////////// + +// TODO: Operand::lifetime +// TODO: Operand::location + +///////////////////////// VALIDATE OPERATION OPERAND TYPE ///////////////////////// + +static void mutateOperand(Operand* operand, OperandType type) { + Operand newOperand = *operand; + newOperand.type = type; + switch (type) { + case OperandType::FLOAT16: + case OperandType::FLOAT32: + case OperandType::INT32: + case OperandType::UINT32: + case OperandType::BOOL: + newOperand.dimensions = hidl_vec<uint32_t>(); + newOperand.scale = 0.0f; + newOperand.zeroPoint = 0; + break; + case OperandType::TENSOR_BOOL8: + case OperandType::TENSOR_FLOAT16: + case OperandType::TENSOR_FLOAT32: + newOperand.dimensions = + operand->dimensions.size() > 0 ? operand->dimensions : hidl_vec<uint32_t>({1}); + newOperand.scale = 0.0f; + newOperand.zeroPoint = 0; + break; + case OperandType::TENSOR_INT32: + newOperand.dimensions = + operand->dimensions.size() > 0 ? operand->dimensions : hidl_vec<uint32_t>({1}); + newOperand.zeroPoint = 0; + break; + case OperandType::TENSOR_QUANT8_ASYMM: + case OperandType::TENSOR_QUANT8_SYMM: + case OperandType::TENSOR_QUANT16_ASYMM: + case OperandType::TENSOR_QUANT16_SYMM: + newOperand.dimensions = + operand->dimensions.size() > 0 ? operand->dimensions : hidl_vec<uint32_t>({1}); + newOperand.scale = operand->scale != 0.0f ? operand->scale : 1.0f; + break; + case OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL: { + newOperand.dimensions = + operand->dimensions.size() > 0 ? operand->dimensions : hidl_vec<uint32_t>({1}); + newOperand.scale = 0.0f; + newOperand.zeroPoint = 0; + + SymmPerChannelQuantParams channelQuant; + channelQuant.channelDim = 0; + channelQuant.scales = hidl_vec<float>( + operand->dimensions.size() > 0 ? static_cast<size_t>(operand->dimensions[0]) + : 0); + for (size_t i = 0; i < channelQuant.scales.size(); ++i) { + channelQuant.scales[i] = 1.0f; + } + newOperand.extraParams.channelQuant(std::move(channelQuant)); + } break; + case OperandType::OEM: + case OperandType::TENSOR_OEM_BYTE: + default: + break; + } + *operand = newOperand; +} + +static bool mutateOperationOperandTypeSkip(size_t operand, OperandType type, const Model& model) { + // Do not test OEM types + if (type == model.operands[operand].type || type == OperandType::OEM || + type == OperandType::TENSOR_OEM_BYTE) { + return true; + } + for (const Operation& operation : model.operations) { + // Skip mutateOperationOperandTypeTest for the following operations. + // - LSH_PROJECTION's second argument is allowed to have any type. + // - ARGMIN and ARGMAX's first argument can be any of + // TENSOR_(FLOAT16|FLOAT32|INT32|QUANT8_ASYMM). + // - CAST's argument can be any of TENSOR_(FLOAT16|FLOAT32|INT32|QUANT8_ASYMM). + // - RANDOM_MULTINOMIAL's argument can be either TENSOR_FLOAT16 or TENSOR_FLOAT32. + // - DEQUANTIZE input can be any of + // TENSOR_(QUANT8_ASYMM|QUANT8_SYMM|QUANT8_SYMM_PER_CHANNEL), output can + // be of either TENSOR_FLOAT16 or TENSOR_FLOAT32. + // - QUANTIZE input can be either TENSOR_FLOAT16 or TENSOR_FLOAT32 + // - CONV_2D filter type (arg 1) can be QUANT8_ASYMM or QUANT8_SYMM_PER_CHANNEL + // - DEPTHWISE_CONV_2D filter type (arg 1) can be QUANT8_ASYMM or QUANT8_SYMM_PER_CHANNEL + // - GROUPED_CONV_2D filter type (arg 1) can be QUANT8_ASYMM or QUANT8_SYMM_PER_CHANNEL + // - TRANSPOSE_CONV_2D filter type (arg 1) can be QUANT8_ASYMM or QUANT8_SYMM_PER_CHANNEL + switch (operation.type) { + case OperationType::LSH_PROJECTION: { + if (operand == operation.inputs[1]) { + return true; + } + } break; + case OperationType::CAST: + case OperationType::ARGMAX: + case OperationType::ARGMIN: { + if (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32 || + type == OperandType::TENSOR_INT32 || type == OperandType::TENSOR_QUANT8_ASYMM) { + return true; + } + } break; + case OperationType::QUANTIZE: + case OperationType::RANDOM_MULTINOMIAL: { + if (operand == operation.inputs[0] && + (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32)) { + return true; + } + } break; + case OperationType::DEQUANTIZE: { + if (operand == operation.inputs[0] && + (type == OperandType::TENSOR_QUANT8_ASYMM || + type == OperandType::TENSOR_QUANT8_SYMM || + type == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL)) { + return true; + } + if (operand == operation.outputs[0] && + (type == OperandType::TENSOR_FLOAT16 || type == OperandType::TENSOR_FLOAT32)) { + return true; + } + } break; + case OperationType::TRANSPOSE_CONV_2D: + case OperationType::GROUPED_CONV_2D: + case OperationType::DEPTHWISE_CONV_2D: + case OperationType::CONV_2D: { + if (operand == operation.inputs[1] && + (type == OperandType::TENSOR_QUANT8_ASYMM || + type == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL)) { + return true; + } + } break; + default: + break; + } + } + return false; +} + +static void mutateOperationOperandTypeTest(const sp<IDevice>& device, const Model& model) { + for (size_t operand = 0; operand < model.operands.size(); ++operand) { + for (OperandType invalidOperandType : hidl_enum_range<OperandType>{}) { + if (mutateOperationOperandTypeSkip(operand, invalidOperandType, model)) { + continue; + } + const std::string message = "mutateOperationOperandTypeTest: operand " + + std::to_string(operand) + " set to type " + + toString(invalidOperandType); + validate(device, message, model, [operand, invalidOperandType](Model* model) { + mutateOperand(&model->operands[operand], invalidOperandType); + }); + } + } +} + +///////////////////////// VALIDATE MODEL OPERATION TYPE ///////////////////////// + +static const uint32_t invalidOperationTypes[] = { + static_cast<uint32_t>(OperationTypeRange::FUNDAMENTAL_MAX) + 1, + static_cast<uint32_t>(OperationTypeRange::OEM_MIN) - 1, + static_cast<uint32_t>(OperationTypeRange::OEM_MAX) + 1, +}; + +static void mutateOperationTypeTest(const sp<IDevice>& device, const Model& model) { + for (size_t operation = 0; operation < model.operations.size(); ++operation) { + for (uint32_t invalidOperationType : invalidOperationTypes) { + const std::string message = "mutateOperationTypeTest: operation " + + std::to_string(operation) + " set to value " + + std::to_string(invalidOperationType); + validate(device, message, model, [operation, invalidOperationType](Model* model) { + model->operations[operation].type = + static_cast<OperationType>(invalidOperationType); + }); + } + } +} + +///////////////////////// VALIDATE MODEL OPERATION INPUT OPERAND INDEX ///////////////////////// + +static void mutateOperationInputOperandIndexTest(const sp<IDevice>& device, const Model& model) { + for (size_t operation = 0; operation < model.operations.size(); ++operation) { + const uint32_t invalidOperand = model.operands.size(); + for (size_t input = 0; input < model.operations[operation].inputs.size(); ++input) { + const std::string message = "mutateOperationInputOperandIndexTest: operation " + + std::to_string(operation) + " input " + + std::to_string(input); + validate(device, message, model, [operation, input, invalidOperand](Model* model) { + model->operations[operation].inputs[input] = invalidOperand; + }); + } + } +} + +///////////////////////// VALIDATE MODEL OPERATION OUTPUT OPERAND INDEX ///////////////////////// + +static void mutateOperationOutputOperandIndexTest(const sp<IDevice>& device, const Model& model) { + for (size_t operation = 0; operation < model.operations.size(); ++operation) { + const uint32_t invalidOperand = model.operands.size(); + for (size_t output = 0; output < model.operations[operation].outputs.size(); ++output) { + const std::string message = "mutateOperationOutputOperandIndexTest: operation " + + std::to_string(operation) + " output " + + std::to_string(output); + validate(device, message, model, [operation, output, invalidOperand](Model* model) { + model->operations[operation].outputs[output] = invalidOperand; + }); + } + } +} + +///////////////////////// REMOVE OPERAND FROM EVERYTHING ///////////////////////// + +static void removeValueAndDecrementGreaterValues(hidl_vec<uint32_t>* vec, uint32_t value) { + if (vec) { + // remove elements matching "value" + auto last = std::remove(vec->begin(), vec->end(), value); + vec->resize(std::distance(vec->begin(), last)); + + // decrement elements exceeding "value" + std::transform(vec->begin(), vec->end(), vec->begin(), + [value](uint32_t v) { return v > value ? v-- : v; }); + } +} + +static void removeOperand(Model* model, uint32_t index) { + hidl_vec_removeAt(&model->operands, index); + for (Operation& operation : model->operations) { + removeValueAndDecrementGreaterValues(&operation.inputs, index); + removeValueAndDecrementGreaterValues(&operation.outputs, index); + } + removeValueAndDecrementGreaterValues(&model->inputIndexes, index); + removeValueAndDecrementGreaterValues(&model->outputIndexes, index); +} + +static bool removeOperandSkip(size_t operand, const Model& model) { + for (const Operation& operation : model.operations) { + // Skip removeOperandTest for the following operations. + // - SPLIT's outputs are not checked during prepareModel. + if (operation.type == OperationType::SPLIT) { + for (const size_t outOprand : operation.outputs) { + if (operand == outOprand) { + return true; + } + } + } + // BIDIRECTIONAL_SEQUENCE_LSTM and BIDIRECTIONAL_SEQUENCE_RNN can have either one or two + // outputs depending on their mergeOutputs parameter. + if (operation.type == OperationType::BIDIRECTIONAL_SEQUENCE_LSTM || + operation.type == OperationType::BIDIRECTIONAL_SEQUENCE_RNN) { + for (const size_t outOprand : operation.outputs) { + if (operand == outOprand) { + return true; + } + } + } + } + return false; +} + +static void removeOperandTest(const sp<IDevice>& device, const Model& model) { + for (size_t operand = 0; operand < model.operands.size(); ++operand) { + if (removeOperandSkip(operand, model)) { + continue; + } + const std::string message = "removeOperandTest: operand " + std::to_string(operand); + validate(device, message, model, + [operand](Model* model) { removeOperand(model, operand); }); + } +} + +///////////////////////// REMOVE OPERATION ///////////////////////// + +static void removeOperation(Model* model, uint32_t index) { + for (uint32_t operand : model->operations[index].inputs) { + model->operands[operand].numberOfConsumers--; + } + hidl_vec_removeAt(&model->operations, index); +} + +static void removeOperationTest(const sp<IDevice>& device, const Model& model) { + for (size_t operation = 0; operation < model.operations.size(); ++operation) { + const std::string message = "removeOperationTest: operation " + std::to_string(operation); + validate(device, message, model, + [operation](Model* model) { removeOperation(model, operation); }); + } +} + +///////////////////////// REMOVE OPERATION INPUT ///////////////////////// + +static bool removeOperationInputSkip(const Operation& op, size_t input) { + // Skip removeOperationInputTest for the following operations. + // - CONCATENATION has at least 2 inputs, with the last element being INT32. + // - CONV_2D, DEPTHWISE_CONV_2D, MAX_POOL_2D, AVERAGE_POOL_2D, L2_POOL_2D, RESIZE_BILINEAR, + // SPACE_TO_DEPTH, SPACE_TO_DEPTH, SPACE_TO_BATCH_ND, BATCH_TO_SPACE_ND can have an optional + // layout parameter. + // - L2_NORMALIZATION, LOCAL_RESPONSE_NORMALIZATION, SOFTMAX can have an optional axis + // parameter. + switch (op.type) { + case OperationType::CONCATENATION: { + if (op.inputs.size() > 2 && input != op.inputs.size() - 1) { + return true; + } + } break; + case OperationType::DEPTHWISE_CONV_2D: { + if ((op.inputs.size() == 12 && input == 11) || (op.inputs.size() == 9 && input == 8)) { + return true; + } + } break; + case OperationType::CONV_2D: + case OperationType::AVERAGE_POOL_2D: + case OperationType::MAX_POOL_2D: + case OperationType::L2_POOL_2D: { + if ((op.inputs.size() == 11 && input == 10) || (op.inputs.size() == 8 && input == 7)) { + return true; + } + } break; + case OperationType::RESIZE_BILINEAR: { + if (op.inputs.size() == 4 && input == 3) { + return true; + } + } break; + case OperationType::SPACE_TO_DEPTH: + case OperationType::DEPTH_TO_SPACE: + case OperationType::BATCH_TO_SPACE_ND: { + if (op.inputs.size() == 3 && input == 2) { + return true; + } + } break; + case OperationType::SPACE_TO_BATCH_ND: { + if (op.inputs.size() == 4 && input == 3) { + return true; + } + } break; + case OperationType::L2_NORMALIZATION: { + if (op.inputs.size() == 2 && input == 1) { + return true; + } + } break; + case OperationType::LOCAL_RESPONSE_NORMALIZATION: { + if (op.inputs.size() == 6 && input == 5) { + return true; + } + } break; + case OperationType::SOFTMAX: { + if (op.inputs.size() == 3 && input == 2) { + return true; + } + } break; + default: + break; + } + return false; +} + +static void removeOperationInputTest(const sp<IDevice>& device, const Model& model) { + for (size_t operation = 0; operation < model.operations.size(); ++operation) { + for (size_t input = 0; input < model.operations[operation].inputs.size(); ++input) { + const Operation& op = model.operations[operation]; + if (removeOperationInputSkip(op, input)) { + continue; + } + const std::string message = "removeOperationInputTest: operation " + + std::to_string(operation) + ", input " + + std::to_string(input); + validate(device, message, model, [operation, input](Model* model) { + uint32_t operand = model->operations[operation].inputs[input]; + model->operands[operand].numberOfConsumers--; + hidl_vec_removeAt(&model->operations[operation].inputs, input); + }); + } + } +} + +///////////////////////// REMOVE OPERATION OUTPUT ///////////////////////// + +static void removeOperationOutputTest(const sp<IDevice>& device, const Model& model) { + for (size_t operation = 0; operation < model.operations.size(); ++operation) { + for (size_t output = 0; output < model.operations[operation].outputs.size(); ++output) { + const std::string message = "removeOperationOutputTest: operation " + + std::to_string(operation) + ", output " + + std::to_string(output); + validate(device, message, model, [operation, output](Model* model) { + hidl_vec_removeAt(&model->operations[operation].outputs, output); + }); + } + } +} + +///////////////////////// MODEL VALIDATION ///////////////////////// + +// TODO: remove model input +// TODO: remove model output +// TODO: add unused operation + +///////////////////////// ADD OPERATION INPUT ///////////////////////// + +static bool addOperationInputSkip(const Operation& op) { + // Skip addOperationInputTest for the following operations. + // - L2_NORMALIZATION, LOCAL_RESPONSE_NORMALIZATION, SOFTMAX can have an optional INT32 axis + // parameter. + if ((op.type == OperationType::L2_NORMALIZATION && op.inputs.size() == 1) || + (op.type == OperationType::LOCAL_RESPONSE_NORMALIZATION && op.inputs.size() == 5) || + (op.type == OperationType::SOFTMAX && op.inputs.size() == 2)) { + return true; + } + return false; +} + +static void addOperationInputTest(const sp<IDevice>& device, const Model& model) { + for (size_t operation = 0; operation < model.operations.size(); ++operation) { + if (addOperationInputSkip(model.operations[operation])) { + continue; + } + const std::string message = "addOperationInputTest: operation " + std::to_string(operation); + validate(device, message, model, [operation](Model* model) { + uint32_t index = addOperand(model, OperandLifeTime::MODEL_INPUT); + hidl_vec_push_back(&model->operations[operation].inputs, index); + hidl_vec_push_back(&model->inputIndexes, index); + }); + } +} + +///////////////////////// ADD OPERATION OUTPUT ///////////////////////// + +static void addOperationOutputTest(const sp<IDevice>& device, const Model& model) { + for (size_t operation = 0; operation < model.operations.size(); ++operation) { + const std::string message = + "addOperationOutputTest: operation " + std::to_string(operation); + validate(device, message, model, [operation](Model* model) { + uint32_t index = addOperand(model, OperandLifeTime::MODEL_OUTPUT); + hidl_vec_push_back(&model->operations[operation].outputs, index); + hidl_vec_push_back(&model->outputIndexes, index); + }); + } +} + +///////////////////////// VALIDATE EXECUTION PREFERENCE ///////////////////////// + +static const int32_t invalidExecutionPreferences[] = { + static_cast<int32_t>(ExecutionPreference::LOW_POWER) - 1, // lower bound + static_cast<int32_t>(ExecutionPreference::SUSTAINED_SPEED) + 1, // upper bound +}; + +static void mutateExecutionPreferenceTest(const sp<IDevice>& device, const Model& model) { + for (int32_t preference : invalidExecutionPreferences) { + const std::string message = + "mutateExecutionPreferenceTest: preference " + std::to_string(preference); + validate( + device, message, model, [](Model*) {}, + static_cast<ExecutionPreference>(preference)); + } +} + +////////////////////////// ENTRY POINT ////////////////////////////// + +void validateModel(const sp<IDevice>& device, const Model& model) { + mutateOperandTypeTest(device, model); + mutateOperandRankTest(device, model); + mutateOperandScaleTest(device, model); + mutateOperandZeroPointTest(device, model); + mutateOperationOperandTypeTest(device, model); + mutateOperationTypeTest(device, model); + mutateOperationInputOperandIndexTest(device, model); + mutateOperationOutputOperandIndexTest(device, model); + removeOperandTest(device, model); + removeOperationTest(device, model); + removeOperationInputTest(device, model); + removeOperationOutputTest(device, model); + addOperationInputTest(device, model); + addOperationOutputTest(device, model); + mutateExecutionPreferenceTest(device, model); +} + +} // namespace android::hardware::neuralnetworks::V1_3::vts::functional diff --git a/neuralnetworks/1.3/vts/functional/ValidateRequest.cpp b/neuralnetworks/1.3/vts/functional/ValidateRequest.cpp new file mode 100644 index 0000000000..c00512c4d3 --- /dev/null +++ b/neuralnetworks/1.3/vts/functional/ValidateRequest.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2018 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. + */ + +#define LOG_TAG "neuralnetworks_hidl_hal_test" + +#include <chrono> +#include "1.0/Utils.h" +#include "1.2/Callbacks.h" +#include "ExecutionBurstController.h" +#include "GeneratedTestHarness.h" +#include "TestHarness.h" +#include "Utils.h" +#include "VtsHalNeuralnetworks.h" + +namespace android::hardware::neuralnetworks::V1_3::vts::functional { + +using V1_0::ErrorStatus; +using V1_0::Request; +using V1_2::IPreparedModel; +using V1_2::MeasureTiming; +using V1_2::OutputShape; +using V1_2::Timing; +using V1_2::implementation::ExecutionCallback; + +///////////////////////// UTILITY FUNCTIONS ///////////////////////// + +static bool badTiming(Timing timing) { + return timing.timeOnDevice == UINT64_MAX && timing.timeInDriver == UINT64_MAX; +} + +// Primary validation function. This function will take a valid request, apply a +// mutation to it to invalidate the request, then pass it to interface calls +// that use the request. Note that the request here is passed by value, and any +// mutation to the request does not leave this function. +static void validate(const sp<IPreparedModel>& preparedModel, const std::string& message, + Request request, const std::function<void(Request*)>& mutation) { + mutation(&request); + + // We'd like to test both with timing requested and without timing + // requested. Rather than running each test both ways, we'll decide whether + // to request timing by hashing the message. We do not use std::hash because + // it is not guaranteed stable across executions. + char hash = 0; + for (auto c : message) { + hash ^= c; + }; + MeasureTiming measure = (hash & 1) ? MeasureTiming::YES : MeasureTiming::NO; + + // asynchronous + { + SCOPED_TRACE(message + " [execute_1_2]"); + + sp<ExecutionCallback> executionCallback = new ExecutionCallback(); + Return<ErrorStatus> executeLaunchStatus = + preparedModel->execute_1_2(request, measure, executionCallback); + ASSERT_TRUE(executeLaunchStatus.isOk()); + ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, static_cast<ErrorStatus>(executeLaunchStatus)); + + executionCallback->wait(); + ErrorStatus executionReturnStatus = executionCallback->getStatus(); + const auto& outputShapes = executionCallback->getOutputShapes(); + Timing timing = executionCallback->getTiming(); + ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, executionReturnStatus); + ASSERT_EQ(outputShapes.size(), 0); + ASSERT_TRUE(badTiming(timing)); + } + + // synchronous + { + SCOPED_TRACE(message + " [executeSynchronously]"); + + Return<void> executeStatus = preparedModel->executeSynchronously( + request, measure, + [](ErrorStatus error, const hidl_vec<OutputShape>& outputShapes, + const Timing& timing) { + ASSERT_EQ(ErrorStatus::INVALID_ARGUMENT, error); + EXPECT_EQ(outputShapes.size(), 0); + EXPECT_TRUE(badTiming(timing)); + }); + ASSERT_TRUE(executeStatus.isOk()); + } + + // burst + { + SCOPED_TRACE(message + " [burst]"); + + // create burst + std::shared_ptr<::android::nn::ExecutionBurstController> burst = + android::nn::ExecutionBurstController::create(preparedModel, + std::chrono::microseconds{0}); + ASSERT_NE(nullptr, burst.get()); + + // create memory keys + std::vector<intptr_t> keys(request.pools.size()); + for (size_t i = 0; i < keys.size(); ++i) { + keys[i] = reinterpret_cast<intptr_t>(&request.pools[i]); + } + + // execute and verify + const auto [n, outputShapes, timing, fallback] = burst->compute(request, measure, keys); + const ErrorStatus status = nn::convertResultCodeToErrorStatus(n); + EXPECT_EQ(ErrorStatus::INVALID_ARGUMENT, status); + EXPECT_EQ(outputShapes.size(), 0); + EXPECT_TRUE(badTiming(timing)); + EXPECT_FALSE(fallback); + + // additional burst testing + if (request.pools.size() > 0) { + // valid free + burst->freeMemory(keys.front()); + + // negative test: invalid free of unknown (blank) memory + burst->freeMemory(intptr_t{}); + + // negative test: double free of memory + burst->freeMemory(keys.front()); + } + } +} + +///////////////////////// REMOVE INPUT //////////////////////////////////// + +static void removeInputTest(const sp<IPreparedModel>& preparedModel, const Request& request) { + for (size_t input = 0; input < request.inputs.size(); ++input) { + const std::string message = "removeInput: removed input " + std::to_string(input); + validate(preparedModel, message, request, + [input](Request* request) { hidl_vec_removeAt(&request->inputs, input); }); + } +} + +///////////////////////// REMOVE OUTPUT //////////////////////////////////// + +static void removeOutputTest(const sp<IPreparedModel>& preparedModel, const Request& request) { + for (size_t output = 0; output < request.outputs.size(); ++output) { + const std::string message = "removeOutput: removed Output " + std::to_string(output); + validate(preparedModel, message, request, + [output](Request* request) { hidl_vec_removeAt(&request->outputs, output); }); + } +} + +///////////////////////////// ENTRY POINT ////////////////////////////////// + +void validateRequest(const sp<IPreparedModel>& preparedModel, const Request& request) { + removeInputTest(preparedModel, request); + removeOutputTest(preparedModel, request); +} + +void validateRequestFailure(const sp<IPreparedModel>& preparedModel, const Request& request) { + SCOPED_TRACE("Expecting request to fail [executeSynchronously]"); + Return<void> executeStatus = preparedModel->executeSynchronously( + request, MeasureTiming::NO, + [](ErrorStatus error, const hidl_vec<OutputShape>& outputShapes, const Timing& timing) { + ASSERT_NE(ErrorStatus::NONE, error); + EXPECT_EQ(outputShapes.size(), 0); + EXPECT_TRUE(badTiming(timing)); + }); + ASSERT_TRUE(executeStatus.isOk()); +} + +} // namespace android::hardware::neuralnetworks::V1_3::vts::functional diff --git a/neuralnetworks/1.3/vts/functional/VtsHalNeuralnetworks.cpp b/neuralnetworks/1.3/vts/functional/VtsHalNeuralnetworks.cpp new file mode 100644 index 0000000000..4f0e150b32 --- /dev/null +++ b/neuralnetworks/1.3/vts/functional/VtsHalNeuralnetworks.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2018 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. + */ + +#define LOG_TAG "neuralnetworks_hidl_hal_test" + +#include "VtsHalNeuralnetworks.h" +#include <android-base/logging.h> +#include <hidl/ServiceManagement.h> +#include <string> +#include <utility> +#include "1.0/Callbacks.h" +#include "1.0/Utils.h" +#include "GeneratedTestHarness.h" +#include "TestHarness.h" + +namespace android::hardware::neuralnetworks::V1_3::vts::functional { + +using HidlToken = + hidl_array<uint8_t, static_cast<uint32_t>(V1_2::Constant::BYTE_SIZE_OF_CACHE_TOKEN)>; +using V1_0::ErrorStatus; +using V1_0::Request; +using V1_1::ExecutionPreference; +using V1_2::IPreparedModel; +using V1_2::implementation::PreparedModelCallback; + +// internal helper function +void createPreparedModel(const sp<IDevice>& device, const Model& model, + sp<IPreparedModel>* preparedModel) { + ASSERT_NE(nullptr, preparedModel); + *preparedModel = nullptr; + + // see if service can handle model + bool fullySupportsModel = false; + const Return<void> supportedCall = device->getSupportedOperations_1_3( + model, [&fullySupportsModel](ErrorStatus status, const hidl_vec<bool>& supported) { + ASSERT_EQ(ErrorStatus::NONE, status); + ASSERT_NE(0ul, supported.size()); + fullySupportsModel = std::all_of(supported.begin(), supported.end(), + [](bool valid) { return valid; }); + }); + ASSERT_TRUE(supportedCall.isOk()); + + // launch prepare model + const sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback(); + const Return<ErrorStatus> prepareLaunchStatus = device->prepareModel_1_3( + model, ExecutionPreference::FAST_SINGLE_ANSWER, hidl_vec<hidl_handle>(), + hidl_vec<hidl_handle>(), HidlToken(), preparedModelCallback); + ASSERT_TRUE(prepareLaunchStatus.isOk()); + ASSERT_EQ(ErrorStatus::NONE, static_cast<ErrorStatus>(prepareLaunchStatus)); + + // retrieve prepared model + preparedModelCallback->wait(); + const ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus(); + *preparedModel = getPreparedModel_1_2(preparedModelCallback); + + // The getSupportedOperations_1_3 call returns a list of operations that are + // guaranteed not to fail if prepareModel_1_3 is called, and + // 'fullySupportsModel' is true i.f.f. the entire model is guaranteed. + // If a driver has any doubt that it can prepare an operation, it must + // return false. So here, if a driver isn't sure if it can support an + // operation, but reports that it successfully prepared the model, the test + // can continue. + if (!fullySupportsModel && prepareReturnStatus != ErrorStatus::NONE) { + ASSERT_EQ(nullptr, preparedModel->get()); + LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot prepare " + "model that it does not support."; + std::cout << "[ ] Early termination of test because vendor service cannot " + "prepare model that it does not support." + << std::endl; + GTEST_SKIP(); + } + ASSERT_EQ(ErrorStatus::NONE, prepareReturnStatus); + ASSERT_NE(nullptr, preparedModel->get()); +} + +void NeuralnetworksHidlTest::SetUp() { + testing::TestWithParam<NeuralnetworksHidlTestParam>::SetUp(); + ASSERT_NE(kDevice, nullptr); +} + +static NamedDevice makeNamedDevice(const std::string& name) { + return {name, IDevice::getService(name)}; +} + +static std::vector<NamedDevice> getNamedDevicesImpl() { + // Retrieves the name of all service instances that implement IDevice, + // including any Lazy HAL instances. + const std::vector<std::string> names = hardware::getAllHalInstanceNames(IDevice::descriptor); + + // Get a handle to each device and pair it with its name. + std::vector<NamedDevice> namedDevices; + namedDevices.reserve(names.size()); + std::transform(names.begin(), names.end(), std::back_inserter(namedDevices), makeNamedDevice); + return namedDevices; +} + +const std::vector<NamedDevice>& getNamedDevices() { + const static std::vector<NamedDevice> devices = getNamedDevicesImpl(); + return devices; +} + +std::string printNeuralnetworksHidlTest( + const testing::TestParamInfo<NeuralnetworksHidlTestParam>& info) { + return gtestCompliantName(getName(info.param)); +} + +INSTANTIATE_DEVICE_TEST(NeuralnetworksHidlTest); + +// Forward declaration from ValidateModel.cpp +void validateModel(const sp<IDevice>& device, const Model& model); +// Forward declaration from ValidateRequest.cpp +void validateRequest(const sp<IPreparedModel>& preparedModel, const V1_0::Request& request); +// Forward declaration from ValidateRequest.cpp +void validateRequestFailure(const sp<IPreparedModel>& preparedModel, const V1_0::Request& request); +// Forward declaration from ValidateBurst.cpp +void validateBurst(const sp<IPreparedModel>& preparedModel, const V1_0::Request& request); + +void validateEverything(const sp<IDevice>& device, const Model& model, const Request& request) { + validateModel(device, model); + + // Create IPreparedModel. + sp<IPreparedModel> preparedModel; + createPreparedModel(device, model, &preparedModel); + if (preparedModel == nullptr) return; + + validateRequest(preparedModel, request); + validateBurst(preparedModel, request); +} + +void validateFailure(const sp<IDevice>& device, const Model& model, const Request& request) { + // TODO: Should this always succeed? + // What if the invalid input is part of the model (i.e., a parameter). + validateModel(device, model); + + // Create IPreparedModel. + sp<IPreparedModel> preparedModel; + createPreparedModel(device, model, &preparedModel); + if (preparedModel == nullptr) return; + + validateRequestFailure(preparedModel, request); +} + +TEST_P(ValidationTest, Test) { + const Model model = createModel(kTestModel); + const Request request = createRequest(kTestModel); + if (kTestModel.expectFailure) { + validateFailure(kDevice, model, request); + } else { + validateEverything(kDevice, model, request); + } +} + +INSTANTIATE_GENERATED_TEST(ValidationTest, [](const test_helper::TestModel&) { return true; }); + +sp<IPreparedModel> getPreparedModel_1_2(const sp<PreparedModelCallback>& callback) { + sp<V1_0::IPreparedModel> preparedModelV1_0 = callback->getPreparedModel(); + return IPreparedModel::castFrom(preparedModelV1_0).withDefault(nullptr); +} + +} // namespace android::hardware::neuralnetworks::V1_3::vts::functional diff --git a/neuralnetworks/1.3/vts/functional/VtsHalNeuralnetworks.h b/neuralnetworks/1.3/vts/functional/VtsHalNeuralnetworks.h new file mode 100644 index 0000000000..fc654ce8f0 --- /dev/null +++ b/neuralnetworks/1.3/vts/functional/VtsHalNeuralnetworks.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifndef ANDROID_HARDWARE_NEURALNETWORKS_V1_3_VTS_HAL_NEURALNETWORKS_H +#define ANDROID_HARDWARE_NEURALNETWORKS_V1_3_VTS_HAL_NEURALNETWORKS_H + +#include <android/hardware/neuralnetworks/1.2/IPreparedModel.h> +#include <android/hardware/neuralnetworks/1.3/IDevice.h> +#include <android/hardware/neuralnetworks/1.3/types.h> +#include <gtest/gtest.h> +#include "1.0/Utils.h" +#include "1.2/Callbacks.h" + +namespace android::hardware::neuralnetworks::V1_3::vts::functional { + +using NamedDevice = Named<sp<IDevice>>; +using NeuralnetworksHidlTestParam = NamedDevice; + +class NeuralnetworksHidlTest : public testing::TestWithParam<NeuralnetworksHidlTestParam> { + protected: + void SetUp() override; + const sp<IDevice> kDevice = getData(GetParam()); +}; + +const std::vector<NamedDevice>& getNamedDevices(); + +std::string printNeuralnetworksHidlTest( + const testing::TestParamInfo<NeuralnetworksHidlTestParam>& info); + +#define INSTANTIATE_DEVICE_TEST(TestSuite) \ + INSTANTIATE_TEST_SUITE_P(PerInstance, TestSuite, testing::ValuesIn(getNamedDevices()), \ + printNeuralnetworksHidlTest) + +// Create an IPreparedModel object. If the model cannot be prepared, +// "preparedModel" will be nullptr instead. +void createPreparedModel(const sp<IDevice>& device, const Model& model, + sp<V1_2::IPreparedModel>* preparedModel); + +// Utility function to get PreparedModel from callback and downcast to V1_2. +sp<V1_2::IPreparedModel> getPreparedModel_1_2( + const sp<V1_2::implementation::PreparedModelCallback>& callback); + +} // namespace android::hardware::neuralnetworks::V1_3::vts::functional + +#endif // ANDROID_HARDWARE_NEURALNETWORKS_V1_3_VTS_HAL_NEURALNETWORKS_H diff --git a/nfc/1.0/vts/functional/Android.bp b/nfc/1.0/vts/functional/Android.bp index c2e365e532..40b82bbf98 100644 --- a/nfc/1.0/vts/functional/Android.bp +++ b/nfc/1.0/vts/functional/Android.bp @@ -21,5 +21,5 @@ cc_test { static_libs: [ "android.hardware.nfc@1.0", ], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/nfc/1.0/vts/functional/AndroidTest.xml b/nfc/1.0/vts/functional/AndroidTest.xml new file mode 100644 index 0000000000..364672b6da --- /dev/null +++ b/nfc/1.0/vts/functional/AndroidTest.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Runs VtsHalNfcV1_0TargetTest."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-native" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" value="VtsHalNfcV1_0TargetTest->/data/local/tmp/VtsHalNfcV1_0TargetTest" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="VtsHalNfcV1_0TargetTest" /> + <option name="native-test-timeout" value="180000"/> + </test> +</configuration> diff --git a/nfc/1.0/vts/functional/VtsHalNfcV1_0TargetTest.cpp b/nfc/1.0/vts/functional/VtsHalNfcV1_0TargetTest.cpp index e17c961d64..1feae9d50f 100644 --- a/nfc/1.0/vts/functional/VtsHalNfcV1_0TargetTest.cpp +++ b/nfc/1.0/vts/functional/VtsHalNfcV1_0TargetTest.cpp @@ -20,11 +20,12 @@ #include <android/hardware/nfc/1.0/INfc.h> #include <android/hardware/nfc/1.0/INfcClientCallback.h> #include <android/hardware/nfc/1.0/types.h> +#include <gtest/gtest.h> #include <hardware/nfc.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> #include <VtsHalHidlTargetCallbackBase.h> -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> using ::android::hardware::nfc::V1_0::INfc; using ::android::hardware::nfc::V1_0::INfcClientCallback; @@ -94,26 +95,11 @@ class NfcClientCallback }; }; -// Test environment for Nfc HIDL HAL. -class NfcHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static NfcHidlEnvironment* Instance() { - static NfcHidlEnvironment* instance = new NfcHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<INfc>(); } - private: - NfcHidlEnvironment() {} -}; - // The main test class for NFC HIDL HAL. -class NfcHidlTest : public ::testing::VtsHalHidlTargetTestBase { +class NfcHidlTest : public ::testing::TestWithParam<std::string> { public: virtual void SetUp() override { - nfc_ = ::testing::VtsHalHidlTargetTestBase::getService<INfc>( - NfcHidlEnvironment::Instance()->getServiceName<INfc>()); + nfc_ = INfc::getService(GetParam()); ASSERT_NE(nfc_, nullptr); nfc_cb_ = new NfcClientCallback(); @@ -186,7 +172,7 @@ class NfcHidlTest : public ::testing::VtsHalHidlTargetTestBase { * Since open and close calls are a part of SetUp() and TearDown(), * the function definition is intentionally kept empty */ -TEST_F(NfcHidlTest, OpenAndClose) {} +TEST_P(NfcHidlTest, OpenAndClose) {} /* * WriteCoreReset: @@ -194,7 +180,7 @@ TEST_F(NfcHidlTest, OpenAndClose) {} * Waits for CORE_RESET_RSP * Checks the status, version number and configuration status */ -TEST_F(NfcHidlTest, WriteCoreReset) { +TEST_P(NfcHidlTest, WriteCoreReset) { std::vector<uint8_t> cmd = CORE_RESET_CMD; NfcData data = cmd; EXPECT_EQ(data.size(), nfc_->write(data)); @@ -229,7 +215,7 @@ TEST_F(NfcHidlTest, WriteCoreReset) { * Waits for CORE_RESET_RSP * Checks the status, version number and configuration status */ -TEST_F(NfcHidlTest, WriteCoreResetConfigReset) { +TEST_P(NfcHidlTest, WriteCoreResetConfigReset) { std::vector<uint8_t> cmd = CORE_RESET_CMD_CONFIG_RESET; NfcData data = cmd; EXPECT_EQ(data.size(), nfc_->write(data)); @@ -264,7 +250,7 @@ TEST_F(NfcHidlTest, WriteCoreResetConfigReset) { * Waits for response * Checks SYNTAX_ERROR status */ -TEST_F(NfcHidlTest, WriteInvalidCommand) { +TEST_P(NfcHidlTest, WriteInvalidCommand) { // Send an Error Command std::vector<uint8_t> cmd = INVALID_COMMAND; NfcData data = cmd; @@ -285,7 +271,7 @@ TEST_F(NfcHidlTest, WriteInvalidCommand) { * Send CORE_CONN_CREATE_CMD for loop-back mode * Check the response */ -TEST_F(NfcHidlTest, WriteInvalidAndThenValidCommand) { +TEST_P(NfcHidlTest, WriteInvalidAndThenValidCommand) { std::vector<uint8_t> cmd = CORE_RESET_CMD; NfcData data = cmd; EXPECT_EQ(data.size(), nfc_->write(data)); @@ -349,7 +335,7 @@ TEST_F(NfcHidlTest, WriteInvalidAndThenValidCommand) { * Checks the data received * Repeat to send total of 1Mb data */ -TEST_F(NfcHidlTest, Bandwidth) { +TEST_P(NfcHidlTest, Bandwidth) { std::vector<uint8_t> cmd = CORE_RESET_CMD; NfcData data = cmd; EXPECT_EQ(data.size(), nfc_->write(data)); @@ -437,7 +423,7 @@ TEST_F(NfcHidlTest, Bandwidth) { * Waits for NfcEvent.OPEN_CPLT * Checks status */ -TEST_F(NfcHidlTest, PowerCycle) { +TEST_P(NfcHidlTest, PowerCycle) { EXPECT_EQ(NfcStatus::OK, nfc_->powerCycle()); // Wait for NfcEvent.OPEN_CPLT auto res = nfc_cb_->WaitForCallback(kCallbackNameSendEvent); @@ -451,7 +437,7 @@ TEST_F(NfcHidlTest, PowerCycle) { * Calls powerCycle() after close() * Checks status */ -TEST_F(NfcHidlTest, PowerCycleAfterClose) { +TEST_P(NfcHidlTest, PowerCycleAfterClose) { EXPECT_EQ(NfcStatus::OK, nfc_->close()); // Wait for CLOSE_CPLT event auto res = nfc_cb_->WaitForCallback(kCallbackNameSendEvent); @@ -474,7 +460,7 @@ TEST_F(NfcHidlTest, PowerCycleAfterClose) { * Calls coreInitialized() with different data * Waits for NfcEvent.POST_INIT_CPLT */ -TEST_F(NfcHidlTest, CoreInitialized) { +TEST_P(NfcHidlTest, CoreInitialized) { NfcData data; data.resize(1); // These parameters might lead to device specific proprietary behavior @@ -501,7 +487,7 @@ TEST_F(NfcHidlTest, CoreInitialized) { * Calls controlGranted() * Checks the return value */ -TEST_F(NfcHidlTest, ControlGranted) { +TEST_P(NfcHidlTest, ControlGranted) { EXPECT_EQ(NfcStatus::OK, nfc_->controlGranted()); } @@ -510,7 +496,7 @@ TEST_F(NfcHidlTest, ControlGranted) { * Call controlGranted() after close * Checks the return value */ -TEST_F(NfcHidlTest, ControlGrantedAfterClose) { +TEST_P(NfcHidlTest, ControlGrantedAfterClose) { EXPECT_EQ(NfcStatus::OK, nfc_->close()); // Wait for CLOSE_CPLT event auto res = nfc_cb_->WaitForCallback(kCallbackNameSendEvent); @@ -532,7 +518,7 @@ TEST_F(NfcHidlTest, ControlGrantedAfterClose) { * Calls prediscover() * Checks the return value */ -TEST_F(NfcHidlTest, PreDiscover) { +TEST_P(NfcHidlTest, PreDiscover) { EXPECT_EQ(NfcStatus::OK, nfc_->prediscover()); } @@ -541,7 +527,7 @@ TEST_F(NfcHidlTest, PreDiscover) { * Call prediscover() after close * Checks the return value */ -TEST_F(NfcHidlTest, PreDiscoverAfterClose) { +TEST_P(NfcHidlTest, PreDiscoverAfterClose) { EXPECT_EQ(NfcStatus::OK, nfc_->close()); // Wait for CLOSE_CPLT event auto res = nfc_cb_->WaitForCallback(kCallbackNameSendEvent); @@ -564,7 +550,7 @@ TEST_F(NfcHidlTest, PreDiscoverAfterClose) { * Calls close() multiple times * Checks status */ -TEST_F(NfcHidlTest, CloseAfterClose) { +TEST_P(NfcHidlTest, CloseAfterClose) { EXPECT_EQ(NfcStatus::OK, nfc_->close()); // Wait for CLOSE_CPLT event auto res = nfc_cb_->WaitForCallback(kCallbackNameSendEvent); @@ -587,15 +573,18 @@ TEST_F(NfcHidlTest, CloseAfterClose) { * Calls open() multiple times * Checks status */ -TEST_F(NfcHidlTest, OpenAfterOpen) { +TEST_P(NfcHidlTest, OpenAfterOpen) { EXPECT_EQ(NfcStatus::OK, nfc_->open(nfc_cb_)); EXPECT_EQ(NfcStatus::OK, nfc_->open(nfc_cb_)); } +INSTANTIATE_TEST_SUITE_P( + PerInstance, NfcHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(INfc::descriptor)), + android::hardware::PrintInstanceNameToString); + int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(NfcHidlEnvironment::Instance()); ::testing::InitGoogleTest(&argc, argv); - NfcHidlEnvironment::Instance()->init(&argc, argv); std::system("svc nfc disable"); /* Turn off NFC */ sleep(5); diff --git a/nfc/1.1/vts/functional/Android.bp b/nfc/1.1/vts/functional/Android.bp index 6698c5a4eb..8da0ce3a78 100644 --- a/nfc/1.1/vts/functional/Android.bp +++ b/nfc/1.1/vts/functional/Android.bp @@ -22,5 +22,5 @@ cc_test { "android.hardware.nfc@1.0", "android.hardware.nfc@1.1", ], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/nfc/1.1/vts/functional/VtsHalNfcV1_1TargetTest.cpp b/nfc/1.1/vts/functional/VtsHalNfcV1_1TargetTest.cpp index 0b7c88bbc2..13537e41f3 100644 --- a/nfc/1.1/vts/functional/VtsHalNfcV1_1TargetTest.cpp +++ b/nfc/1.1/vts/functional/VtsHalNfcV1_1TargetTest.cpp @@ -21,11 +21,12 @@ #include <android/hardware/nfc/1.1/INfc.h> #include <android/hardware/nfc/1.1/INfcClientCallback.h> #include <android/hardware/nfc/1.1/types.h> +#include <gtest/gtest.h> #include <hardware/nfc.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> #include <VtsHalHidlTargetCallbackBase.h> -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> using ::android::hardware::nfc::V1_1::INfc; using ::android::hardware::nfc::V1_1::INfcClientCallback; @@ -83,25 +84,11 @@ class NfcClientCallback : public ::testing::VtsHalHidlTargetCallbackBase<NfcClie }; }; -// Test environment for Nfc HIDL HAL. -class NfcHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static NfcHidlEnvironment* Instance() { - static NfcHidlEnvironment* instance = new NfcHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<INfc>(); } - private: - NfcHidlEnvironment() {} -}; - // The main test class for NFC HIDL HAL. -class NfcHidlTest : public ::testing::VtsHalHidlTargetTestBase { +class NfcHidlTest : public ::testing::TestWithParam<std::string> { public: virtual void SetUp() override { - nfc_ = ::testing::VtsHalHidlTargetTestBase::getService<INfc>(); + nfc_ = INfc::getService(GetParam()); ASSERT_NE(nfc_, nullptr); nfc_cb_ = new NfcClientCallback(); @@ -151,7 +138,7 @@ class NfcHidlTest : public ::testing::VtsHalHidlTargetTestBase { * calls factoryReset() * checks status */ -TEST_F(NfcHidlTest, FactoryReset) { +TEST_P(NfcHidlTest, FactoryReset) { nfc_->factoryReset(); EXPECT_EQ(NfcStatus::OK, nfc_->close()); @@ -174,7 +161,7 @@ TEST_F(NfcHidlTest, FactoryReset) { * Makes an open call, waits for NfcEvent.OPEN_CPLT * Immediately calls closeforPowerOffCase() and waits for NfcEvent.CLOSE_CPLT */ -TEST_F(NfcHidlTest, OpenAndCloseForPowerOff) { +TEST_P(NfcHidlTest, OpenAndCloseForPowerOff) { EXPECT_EQ(NfcStatus::OK, nfc_->closeForPowerOffCase()); // Wait for CLOSE_CPLT event auto res = nfc_cb_->WaitForCallback(kCallbackNameSendEvent); @@ -195,7 +182,7 @@ TEST_F(NfcHidlTest, OpenAndCloseForPowerOff) { * Calls closeForPowerOffCase() * Calls close() - checks failed status */ -TEST_F(NfcHidlTest, CloseForPowerCaseOffAfterClose) { +TEST_P(NfcHidlTest, CloseForPowerCaseOffAfterClose) { EXPECT_EQ(NfcStatus::OK, nfc_->closeForPowerOffCase()); // Wait for CLOSE_CPLT event auto res = nfc_cb_->WaitForCallback(kCallbackNameSendEvent); @@ -218,16 +205,19 @@ TEST_F(NfcHidlTest, CloseForPowerCaseOffAfterClose) { * Calls getConfig() * checks if fields in NfcConfig are populated correctly */ -TEST_F(NfcHidlTest, GetConfig) { +TEST_P(NfcHidlTest, GetConfig) { nfc_->getConfig([](NfcConfig config) { EXPECT_GE(config.maxIsoDepTransceiveLength, MIN_ISO_DEP_TRANSCEIVE_LENGTH); }); } +INSTANTIATE_TEST_SUITE_P( + PerInstance, NfcHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(INfc::descriptor)), + android::hardware::PrintInstanceNameToString); + int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(NfcHidlEnvironment::Instance()); ::testing::InitGoogleTest(&argc, argv); - NfcHidlEnvironment::Instance()->init(&argc, argv); std::system("svc nfc disable"); /* Turn off NFC */ sleep(5); diff --git a/nfc/1.2/vts/functional/Android.bp b/nfc/1.2/vts/functional/Android.bp index 13b254cdc0..7b50a36d74 100644 --- a/nfc/1.2/vts/functional/Android.bp +++ b/nfc/1.2/vts/functional/Android.bp @@ -23,4 +23,5 @@ cc_test { "android.hardware.nfc@1.1", "android.hardware.nfc@1.2", ], + test_suites: ["general-tests", "vts-core"], } diff --git a/nfc/1.2/vts/functional/VtsHalNfcV1_2TargetTest.cpp b/nfc/1.2/vts/functional/VtsHalNfcV1_2TargetTest.cpp index 54d3127a75..3ec088d351 100644 --- a/nfc/1.2/vts/functional/VtsHalNfcV1_2TargetTest.cpp +++ b/nfc/1.2/vts/functional/VtsHalNfcV1_2TargetTest.cpp @@ -20,11 +20,12 @@ #include <android/hardware/nfc/1.1/INfcClientCallback.h> #include <android/hardware/nfc/1.2/INfc.h> #include <android/hardware/nfc/1.2/types.h> +#include <gtest/gtest.h> #include <hardware/nfc.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> #include <VtsHalHidlTargetCallbackBase.h> -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> using ::android::sp; using ::android::hardware::hidl_vec; @@ -83,26 +84,11 @@ class NfcClientCallback : public ::testing::VtsHalHidlTargetCallbackBase<NfcClie }; }; -// Test environment for Nfc HIDL HAL. -class NfcHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static NfcHidlEnvironment* Instance() { - static NfcHidlEnvironment* instance = new NfcHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<INfc>(); } - - private: - NfcHidlEnvironment() {} -}; - // The main test class for NFC HIDL HAL. -class NfcHidlTest : public ::testing::VtsHalHidlTargetTestBase { +class NfcHidlTest : public ::testing::TestWithParam<std::string> { public: virtual void SetUp() override { - nfc_ = ::testing::VtsHalHidlTargetTestBase::getService<INfc>(); + nfc_ = INfc::getService(GetParam()); ASSERT_NE(nfc_, nullptr); nfc_cb_ = new NfcClientCallback(); @@ -152,7 +138,7 @@ class NfcHidlTest : public ::testing::VtsHalHidlTargetTestBase { * Calls getConfig() * checks if fields in NfcConfig are populated correctly */ -TEST_F(NfcHidlTest, GetExtendedConfig) { +TEST_P(NfcHidlTest, GetExtendedConfig) { nfc_->getConfig_1_2([](NfcConfig config) { for (uint8_t uicc : config.offHostRouteUicc) { EXPECT_GE(uicc, MIN_OFFHOST_ROUTE_ID); @@ -169,10 +155,13 @@ TEST_F(NfcHidlTest, GetExtendedConfig) { }); } +INSTANTIATE_TEST_SUITE_P( + PerInstance, NfcHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(INfc::descriptor)), + android::hardware::PrintInstanceNameToString); + int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(NfcHidlEnvironment::Instance()); ::testing::InitGoogleTest(&argc, argv); - NfcHidlEnvironment::Instance()->init(&argc, argv); std::system("svc nfc disable"); /* Turn off NFC */ sleep(5); diff --git a/power/1.0/vts/functional/Android.bp b/power/1.0/vts/functional/Android.bp index a716f027b3..5d5676d36b 100644 --- a/power/1.0/vts/functional/Android.bp +++ b/power/1.0/vts/functional/Android.bp @@ -19,5 +19,5 @@ cc_test { defaults: ["VtsHalTargetTestDefaults"], srcs: ["VtsHalPowerV1_0TargetTest.cpp"], static_libs: ["android.hardware.power@1.0"], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/power/1.0/vts/functional/VtsHalPowerV1_0TargetTest.cpp b/power/1.0/vts/functional/VtsHalPowerV1_0TargetTest.cpp index 999b2b420f..ba08ee733f 100644 --- a/power/1.0/vts/functional/VtsHalPowerV1_0TargetTest.cpp +++ b/power/1.0/vts/functional/VtsHalPowerV1_0TargetTest.cpp @@ -21,9 +21,9 @@ #include <android-base/unique_fd.h> #include <android/hardware/power/1.0/IPower.h> - -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> #include <fcntl.h> #include <algorithm> @@ -45,23 +45,10 @@ using std::vector; #define AVAILABLE_GOVERNORS_PATH \ "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors" -// Test environment for Power HIDL HAL. -class PowerHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static PowerHidlEnvironment* Instance() { - static PowerHidlEnvironment* instance = new PowerHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IPower>(); } -}; - -class PowerHidlTest : public ::testing::VtsHalHidlTargetTestBase { +class PowerHidlTest : public testing::TestWithParam<std::string> { public: virtual void SetUp() override { - power = ::testing::VtsHalHidlTargetTestBase::getService<IPower>( - PowerHidlEnvironment::Instance()->getServiceName<IPower>()); + power = IPower::getService(GetParam()); ASSERT_NE(power, nullptr); } @@ -71,7 +58,7 @@ class PowerHidlTest : public ::testing::VtsHalHidlTargetTestBase { }; // Sanity check Power::setInteractive. -TEST_F(PowerHidlTest, SetInteractive) { +TEST_P(PowerHidlTest, SetInteractive) { Return<void> ret; ret = power->setInteractive(true); @@ -83,7 +70,7 @@ TEST_F(PowerHidlTest, SetInteractive) { // Test Power::setInteractive and Power::powerHint(Launch) // with each available CPU governor, if available -TEST_F(PowerHidlTest, TryDifferentGovernors) { +TEST_P(PowerHidlTest, TryDifferentGovernors) { Return<void> ret; unique_fd fd1(open(CPU_GOVERNOR_PATH, O_RDWR)); @@ -125,7 +112,7 @@ TEST_F(PowerHidlTest, TryDifferentGovernors) { } // Sanity check Power::powerHint on good and bad inputs. -TEST_F(PowerHidlTest, PowerHint) { +TEST_P(PowerHidlTest, PowerHint) { PowerHint badHint = static_cast<PowerHint>(0xA); auto hints = {PowerHint::VSYNC, PowerHint::INTERACTION, PowerHint::VIDEO_ENCODE, PowerHint::VIDEO_DECODE, @@ -163,7 +150,7 @@ TEST_F(PowerHidlTest, PowerHint) { } // Sanity check Power::setFeature() on good and bad inputs. -TEST_F(PowerHidlTest, SetFeature) { +TEST_P(PowerHidlTest, SetFeature) { Return<void> ret; ret = power->setFeature(Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE, true); ASSERT_TRUE(ret.isOk()); @@ -178,7 +165,7 @@ TEST_F(PowerHidlTest, SetFeature) { } // Sanity check Power::getPlatformLowPowerStats(). -TEST_F(PowerHidlTest, GetPlatformLowPowerStats) { +TEST_P(PowerHidlTest, GetPlatformLowPowerStats) { hidl_vec<PowerStatePlatformSleepState> vec; Status s; auto cb = [&vec, &s](hidl_vec<PowerStatePlatformSleepState> states, @@ -191,11 +178,7 @@ TEST_F(PowerHidlTest, GetPlatformLowPowerStats) { ASSERT_TRUE(s == Status::SUCCESS || s == Status::FILESYSTEM_ERROR); } -int main(int argc, char **argv) { - ::testing::AddGlobalTestEnvironment(PowerHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - PowerHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - LOG(INFO) << "Test result = " << status; - return status; -} +INSTANTIATE_TEST_SUITE_P( + PerInstance, PowerHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IPower::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/power/1.1/vts/functional/Android.bp b/power/1.1/vts/functional/Android.bp index de75984532..d9a32df9ab 100644 --- a/power/1.1/vts/functional/Android.bp +++ b/power/1.1/vts/functional/Android.bp @@ -22,5 +22,5 @@ cc_test { "android.hardware.power@1.0", "android.hardware.power@1.1", ], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/power/1.1/vts/functional/VtsHalPowerV1_1TargetTest.cpp b/power/1.1/vts/functional/VtsHalPowerV1_1TargetTest.cpp index 4427b1550e..e9a722c6f8 100644 --- a/power/1.1/vts/functional/VtsHalPowerV1_1TargetTest.cpp +++ b/power/1.1/vts/functional/VtsHalPowerV1_1TargetTest.cpp @@ -17,9 +17,9 @@ #define LOG_TAG "power_hidl_hal_test" #include <android-base/logging.h> #include <android/hardware/power/1.1/IPower.h> - -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> using ::android::hardware::power::V1_1::IPower; using ::android::hardware::power::V1_1::PowerStateSubsystem; @@ -29,23 +29,10 @@ using ::android::hardware::hidl_vec; using ::android::hardware::Return; using ::android::sp; -// Test environment for Power HIDL HAL. -class PowerHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static PowerHidlEnvironment* Instance() { - static PowerHidlEnvironment* instance = new PowerHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IPower>(); } -}; - -class PowerHidlTest : public ::testing::VtsHalHidlTargetTestBase { +class PowerHidlTest : public testing::TestWithParam<std::string> { public: virtual void SetUp() override { - power = ::testing::VtsHalHidlTargetTestBase::getService<IPower>( - PowerHidlEnvironment::Instance()->getServiceName<IPower>()); + power = IPower::getService(GetParam()); ASSERT_NE(power, nullptr); } @@ -55,7 +42,7 @@ class PowerHidlTest : public ::testing::VtsHalHidlTargetTestBase { }; // Sanity check Power::getSubsystemLowPowerStats(). -TEST_F(PowerHidlTest, GetSubsystemLowPowerStats) { +TEST_P(PowerHidlTest, GetSubsystemLowPowerStats) { hidl_vec<PowerStateSubsystem> vec; Status s; auto cb = [&vec, &s](hidl_vec<PowerStateSubsystem> subsystems, @@ -70,7 +57,7 @@ TEST_F(PowerHidlTest, GetSubsystemLowPowerStats) { } // Sanity check Power::powerHintAsync on good and bad inputs. -TEST_F(PowerHidlTest, PowerHintAsync) { +TEST_P(PowerHidlTest, PowerHintAsync) { PowerHint badHint = static_cast<PowerHint>(0xA); auto hints = {PowerHint::VSYNC, PowerHint::INTERACTION, PowerHint::VIDEO_ENCODE, PowerHint::VIDEO_DECODE, PowerHint::LOW_POWER, PowerHint::SUSTAINED_PERFORMANCE, @@ -104,11 +91,7 @@ TEST_F(PowerHidlTest, PowerHintAsync) { } while (std::next_permutation(hints2.begin(), hints2.end(), compareHints)); } -int main(int argc, char **argv) { - ::testing::AddGlobalTestEnvironment(PowerHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - PowerHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - LOG(INFO) << "Test result = " << status; - return status; -} +INSTANTIATE_TEST_SUITE_P( + PerInstance, PowerHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IPower::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/power/1.2/vts/functional/Android.bp b/power/1.2/vts/functional/Android.bp index f424bfa1f0..5385faa8a8 100644 --- a/power/1.2/vts/functional/Android.bp +++ b/power/1.2/vts/functional/Android.bp @@ -23,5 +23,5 @@ cc_test { "android.hardware.power@1.1", "android.hardware.power@1.2", ], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/power/1.2/vts/functional/VtsHalPowerV1_2TargetTest.cpp b/power/1.2/vts/functional/VtsHalPowerV1_2TargetTest.cpp index 5e929971ff..a5ecf5d74b 100644 --- a/power/1.2/vts/functional/VtsHalPowerV1_2TargetTest.cpp +++ b/power/1.2/vts/functional/VtsHalPowerV1_2TargetTest.cpp @@ -17,9 +17,9 @@ #define LOG_TAG "power_hidl_hal_test" #include <android-base/logging.h> #include <android/hardware/power/1.2/IPower.h> - -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> using ::android::sp; using ::android::hardware::hidl_vec; @@ -27,23 +27,10 @@ using ::android::hardware::Return; using ::android::hardware::power::V1_2::IPower; using ::android::hardware::power::V1_2::PowerHint; -// Test environment for Power HIDL HAL. -class PowerHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static PowerHidlEnvironment* Instance() { - static PowerHidlEnvironment* instance = new PowerHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IPower>(); } -}; - -class PowerHidlTest : public ::testing::VtsHalHidlTargetTestBase { +class PowerHidlTest : public testing::TestWithParam<std::string> { public: virtual void SetUp() override { - power = ::testing::VtsHalHidlTargetTestBase::getService<IPower>( - PowerHidlEnvironment::Instance()->getServiceName<IPower>()); + power = IPower::getService(GetParam()); ASSERT_NE(power, nullptr); } @@ -51,7 +38,7 @@ class PowerHidlTest : public ::testing::VtsHalHidlTargetTestBase { }; // Sanity check Power::PowerHintAsync_1_2 on good and bad inputs. -TEST_F(PowerHidlTest, PowerHintAsync_1_2) { +TEST_P(PowerHidlTest, PowerHintAsync_1_2) { std::vector<PowerHint> hints; for (uint32_t i = static_cast<uint32_t>(PowerHint::VSYNC); i <= static_cast<uint32_t>(PowerHint::CAMERA_SHOT); ++i) { @@ -89,11 +76,8 @@ TEST_F(PowerHidlTest, PowerHintAsync_1_2) { } while (std::next_permutation(hints2.begin(), hints2.end(), compareHints)); } -int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(PowerHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - PowerHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - LOG(INFO) << "Test result = " << status; - return status; -} +INSTANTIATE_TEST_SUITE_P( + PerInstance, PowerHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IPower::descriptor)), + android::hardware::PrintInstanceNameToString); + diff --git a/power/1.3/vts/functional/Android.bp b/power/1.3/vts/functional/Android.bp index 06f6e7ae69..77e86197a0 100644 --- a/power/1.3/vts/functional/Android.bp +++ b/power/1.3/vts/functional/Android.bp @@ -24,5 +24,5 @@ cc_test { "android.hardware.power@1.2", "android.hardware.power@1.3", ], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/power/1.3/vts/functional/VtsHalPowerV1_3TargetTest.cpp b/power/1.3/vts/functional/VtsHalPowerV1_3TargetTest.cpp index af1a1d86ab..3cf2adca6f 100644 --- a/power/1.3/vts/functional/VtsHalPowerV1_3TargetTest.cpp +++ b/power/1.3/vts/functional/VtsHalPowerV1_3TargetTest.cpp @@ -17,9 +17,9 @@ #define LOG_TAG "power_hidl_hal_test" #include <android-base/logging.h> #include <android/hardware/power/1.3/IPower.h> - -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> using ::android::sp; using ::android::hardware::hidl_vec; @@ -27,38 +27,21 @@ using ::android::hardware::Return; using ::android::hardware::power::V1_3::IPower; using ::android::hardware::power::V1_3::PowerHint; -// Test environment for Power HIDL HAL. -class PowerHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static PowerHidlEnvironment* Instance() { - static PowerHidlEnvironment* instance = new PowerHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IPower>(); } -}; - -class PowerHidlTest : public ::testing::VtsHalHidlTargetTestBase { +class PowerHidlTest : public testing::TestWithParam<std::string> { public: virtual void SetUp() override { - power = ::testing::VtsHalHidlTargetTestBase::getService<IPower>( - PowerHidlEnvironment::Instance()->getServiceName<IPower>()); + power = IPower::getService(GetParam()); ASSERT_NE(power, nullptr); } sp<IPower> power; }; -TEST_F(PowerHidlTest, PowerHintAsync_1_3) { +TEST_P(PowerHidlTest, PowerHintAsync_1_3) { ASSERT_TRUE(power->powerHintAsync_1_3(PowerHint::EXPENSIVE_RENDERING, 0).isOk()); } -int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(PowerHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - PowerHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - LOG(INFO) << "Test result = " << status; - return status; -} +INSTANTIATE_TEST_SUITE_P( + PerInstance, PowerHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IPower::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/power/stats/1.0/default/PowerStats.cpp b/power/stats/1.0/default/PowerStats.cpp index 78766f2a17..68275cef8a 100644 --- a/power/stats/1.0/default/PowerStats.cpp +++ b/power/stats/1.0/default/PowerStats.cpp @@ -87,7 +87,7 @@ size_t PowerStats::parsePowerRails() { std::string railFileName; std::string spsFileName; uint32_t index = 0; - uint32_t samplingRate; + unsigned long samplingRate; for (const auto& path : mPm.devicePaths) { railFileName = path + "/enabled_rails"; spsFileName = path + "/sampling_rate"; @@ -109,10 +109,11 @@ size_t PowerStats::parsePowerRails() { while (std::getline(railNames, line)) { std::vector<std::string> words = android::base::Split(line, ":"); if (words.size() == 2) { - mPm.railsInfo.emplace(words[0], RailData{.devicePath = path, - .index = index, - .subsysName = words[1], - .samplingRate = samplingRate}); + mPm.railsInfo.emplace( + words[0], RailData{.devicePath = path, + .index = index, + .subsysName = words[1], + .samplingRate = static_cast<uint32_t>(samplingRate)}); index++; } else { ALOGW("Unexpected format in file: %s", railFileName.c_str()); diff --git a/power/stats/1.0/vts/functional/Android.bp b/power/stats/1.0/vts/functional/Android.bp index f564cbe197..ab47061461 100644 --- a/power/stats/1.0/vts/functional/Android.bp +++ b/power/stats/1.0/vts/functional/Android.bp @@ -33,4 +33,5 @@ cc_test { "libfmq", "libutils", ], + test_suites: ["general-tests", "vts-core"], } diff --git a/power/stats/1.0/vts/functional/VtsHalPowerStatsV1_0TargetTest.cpp b/power/stats/1.0/vts/functional/VtsHalPowerStatsV1_0TargetTest.cpp index 835a47b023..3359669810 100644 --- a/power/stats/1.0/vts/functional/VtsHalPowerStatsV1_0TargetTest.cpp +++ b/power/stats/1.0/vts/functional/VtsHalPowerStatsV1_0TargetTest.cpp @@ -16,13 +16,15 @@ #define LOG_TAG "android.power.stats.vts" -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> #include <android-base/logging.h> #include <android/hardware/power/stats/1.0/IPowerStats.h> #include <fmq/MessageQueue.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> #include <hidl/MQDescriptor.h> +#include <hidl/ServiceManagement.h> #include <inttypes.h> + #include <algorithm> #include <random> #include <thread> @@ -49,23 +51,11 @@ using android::hardware::power::stats::V1_0::Status; } // namespace typedef hardware::MessageQueue<EnergyData, kSynchronizedReadWrite> MessageQueueSync; -// Test environment for Power HIDL HAL. -class PowerStatsHidlEnv : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static PowerStatsHidlEnv* Instance() { - static PowerStatsHidlEnv* instance = new PowerStatsHidlEnv; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IPowerStats>(); } -}; -class PowerStatsHidlTest : public ::testing::VtsHalHidlTargetTestBase { +class PowerStatsHidlTest : public ::testing::TestWithParam<std::string> { public: virtual void SetUp() override { - service_ = ::testing::VtsHalHidlTargetTestBase::getService<IPowerStats>( - PowerStatsHidlEnv::Instance()->getServiceName<IPowerStats>()); + service_ = IPowerStats::getService(GetParam()); ASSERT_NE(service_, nullptr); } @@ -157,7 +147,7 @@ void PowerStatsHidlTest::getRandomIds(std::vector<uint32_t>& ids) { } // Each PowerEntity must have a valid name -TEST_F(PowerStatsHidlTest, ValidatePowerEntityNames) { +TEST_P(PowerStatsHidlTest, ValidatePowerEntityNames) { hidl_vec<PowerEntityInfo> infos; getInfos(infos); for (auto info : infos) { @@ -166,18 +156,18 @@ TEST_F(PowerStatsHidlTest, ValidatePowerEntityNames) { } // Each PowerEntity must have a unique ID -TEST_F(PowerStatsHidlTest, ValidatePowerEntityIds) { +TEST_P(PowerStatsHidlTest, ValidatePowerEntityIds) { hidl_vec<PowerEntityInfo> infos; getInfos(infos); - set<uint32_t> ids; + std::set<uint32_t> ids; for (auto info : infos) { ASSERT_TRUE(ids.insert(info.powerEntityId).second); } } // Each PowerEntityStateSpace must have an associated PowerEntityInfo -TEST_F(PowerStatsHidlTest, ValidateStateInfoAssociation) { +TEST_P(PowerStatsHidlTest, ValidateStateInfoAssociation) { hidl_vec<PowerEntityInfo> infos; getInfos(infos); @@ -195,7 +185,7 @@ TEST_F(PowerStatsHidlTest, ValidateStateInfoAssociation) { } // Each state must have a valid name -TEST_F(PowerStatsHidlTest, ValidateStateNames) { +TEST_P(PowerStatsHidlTest, ValidateStateNames) { hidl_vec<PowerEntityStateSpace> stateSpaces; getStateSpaces(stateSpaces); @@ -207,12 +197,12 @@ TEST_F(PowerStatsHidlTest, ValidateStateNames) { } // Each state must have an ID that is unique to the PowerEntityStateSpace -TEST_F(PowerStatsHidlTest, ValidateStateUniqueIds) { +TEST_P(PowerStatsHidlTest, ValidateStateUniqueIds) { hidl_vec<PowerEntityStateSpace> stateSpaces; getStateSpaces(stateSpaces); for (auto stateSpace : stateSpaces) { - set<uint32_t> stateIds; + std::set<uint32_t> stateIds; for (auto state : stateSpace.states) { ASSERT_TRUE(stateIds.insert(state.powerEntityStateId).second); } @@ -221,7 +211,7 @@ TEST_F(PowerStatsHidlTest, ValidateStateUniqueIds) { // getPowerEntityStateInfo must support passing in requested IDs // Results must contain state space information for all requested IDs -TEST_F(PowerStatsHidlTest, ValidateStateInfoAssociationSelect) { +TEST_P(PowerStatsHidlTest, ValidateStateInfoAssociationSelect) { std::vector<uint32_t> randomIds; getRandomIds(randomIds); @@ -244,7 +234,7 @@ TEST_F(PowerStatsHidlTest, ValidateStateInfoAssociationSelect) { } // Requested state space info must match initially obtained stateinfos -TEST_F(PowerStatsHidlTest, ValidateStateInfoSelect) { +TEST_P(PowerStatsHidlTest, ValidateStateInfoSelect) { hidl_vec<PowerEntityStateSpace> stateSpaces; getStateSpaces(stateSpaces); if (stateSpaces.size() == 0) { @@ -279,7 +269,7 @@ TEST_F(PowerStatsHidlTest, ValidateStateInfoSelect) { // stateResidencyResults must contain results for every PowerEntityStateSpace // returned by getPowerEntityStateInfo -TEST_F(PowerStatsHidlTest, ValidateResidencyResultsAssociation) { +TEST_P(PowerStatsHidlTest, ValidateResidencyResultsAssociation) { hidl_vec<PowerEntityStateSpace> stateSpaces; getStateSpaces(stateSpaces); @@ -311,7 +301,7 @@ TEST_F(PowerStatsHidlTest, ValidateResidencyResultsAssociation) { // getPowerEntityStateResidencyData must support passing in requested IDs // stateResidencyResults must contain results for each PowerEntityStateSpace // returned by getPowerEntityStateInfo -TEST_F(PowerStatsHidlTest, ValidateResidencyResultsAssociationSelect) { +TEST_P(PowerStatsHidlTest, ValidateResidencyResultsAssociationSelect) { std::vector<uint32_t> randomIds; getRandomIds(randomIds); if (randomIds.empty()) { @@ -346,7 +336,7 @@ TEST_F(PowerStatsHidlTest, ValidateResidencyResultsAssociationSelect) { } } -TEST_F(PowerStatsHidlTest, ValidateRailInfo) { +TEST_P(PowerStatsHidlTest, ValidateRailInfo) { hidl_vec<RailInfo> rails[2]; Status s; auto cb = [&rails, &s](hidl_vec<RailInfo> rail_subsys, Status status) { @@ -359,7 +349,7 @@ TEST_F(PowerStatsHidlTest, ValidateRailInfo) { /* Rails size should be non-zero on SUCCESS*/ ASSERT_NE(rails[0].size(), 0); /* check if indices returned are unique*/ - set<uint32_t> ids; + std::set<uint32_t> ids; for (auto rail : rails[0]) { ASSERT_TRUE(ids.insert(rail.index).second); } @@ -402,7 +392,7 @@ TEST_F(PowerStatsHidlTest, ValidateRailInfo) { } } -TEST_F(PowerStatsHidlTest, ValidateAllPowerData) { +TEST_P(PowerStatsHidlTest, ValidateAllPowerData) { hidl_vec<EnergyData> measurements[2]; Status s; auto cb = [&measurements, &s](hidl_vec<EnergyData> measure, Status status) { @@ -451,7 +441,7 @@ TEST_F(PowerStatsHidlTest, ValidateAllPowerData) { } } -TEST_F(PowerStatsHidlTest, ValidateFilteredPowerData) { +TEST_P(PowerStatsHidlTest, ValidateFilteredPowerData) { hidl_vec<RailInfo> rails; hidl_vec<EnergyData> measurements; hidl_vec<uint32_t> indices; @@ -559,23 +549,19 @@ void readEnergy(sp<IPowerStats> service_, uint32_t timeMs) { } } -TEST_F(PowerStatsHidlTest, StreamEnergyData) { +TEST_P(PowerStatsHidlTest, StreamEnergyData) { std::time_t seed = std::time(nullptr); std::srand(seed); std::thread thread1 = std::thread(readEnergy, service_, std::rand() % 5000); thread1.join(); } +INSTANTIATE_TEST_SUITE_P( + PerInstance, PowerStatsHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IPowerStats::descriptor)), + android::hardware::PrintInstanceNameToString); + } // namespace vts } // namespace stats } // namespace power } // namespace android - -int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(android::power::stats::vts::PowerStatsHidlEnv::Instance()); - ::testing::InitGoogleTest(&argc, argv); - android::power::stats::vts::PowerStatsHidlEnv::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - LOG(INFO) << "Test result = " << status; - return status; -} diff --git a/radio/1.2/types.hal b/radio/1.2/types.hal index dffebd3e42..f10d753cb9 100644 --- a/radio/1.2/types.hal +++ b/radio/1.2/types.hal @@ -161,7 +161,8 @@ struct NetworkScanRequest { ScanType type; /** - * Time interval in seconds between periodic scans, only valid when type = PERIODIC + * Time interval in seconds between the completion of one scan and the start of a subsequent scan. + * This field is only valid when 'type' is 'PERIODIC'. * Range: ScanIntervalRange:MIN to ScanIntervalRange:MAX */ int32_t interval; diff --git a/radio/1.2/vts/functional/radio_hidl_hal_api.cpp b/radio/1.2/vts/functional/radio_hidl_hal_api.cpp index 5184ef962c..a98f22aacf 100644 --- a/radio/1.2/vts/functional/radio_hidl_hal_api.cpp +++ b/radio/1.2/vts/functional/radio_hidl_hal_api.cpp @@ -46,7 +46,10 @@ TEST_F(RadioHidlTest_v1_2, startNetworkScan) { ::android::hardware::radio::V1_2::NetworkScanRequest request = { .type = ScanType::ONE_SHOT, .interval = 60, - .specifiers = {::GERAN_SPECIFIER_P900, ::GERAN_SPECIFIER_850}}; + .specifiers = {::GERAN_SPECIFIER_P900, ::GERAN_SPECIFIER_850}, + .maxSearchTime = 60, + .incrementalResults = false, + .incrementalResultsPeriodicity = 1}; Return<void> res = radio_v1_2->startNetworkScan_1_2(serial, request); ASSERT_OK(res); diff --git a/radio/1.4/vts/functional/radio_hidl_hal_api.cpp b/radio/1.4/vts/functional/radio_hidl_hal_api.cpp index bf225bcae8..a4953d7a70 100644 --- a/radio/1.4/vts/functional/radio_hidl_hal_api.cpp +++ b/radio/1.4/vts/functional/radio_hidl_hal_api.cpp @@ -183,7 +183,12 @@ TEST_F(RadioHidlTest_v1_4, startNetworkScan) { .channels = {1, 2}}; ::android::hardware::radio::V1_2::NetworkScanRequest request = { - .type = ScanType::ONE_SHOT, .interval = 60, .specifiers = {specifier}}; + .type = ScanType::ONE_SHOT, + .interval = 60, + .specifiers = {specifier}, + .maxSearchTime = 60, + .incrementalResults = false, + .incrementalResultsPeriodicity = 1}; Return<void> res = radio_v1_4->startNetworkScan_1_4(serial, request); ASSERT_OK(res); diff --git a/renderscript/1.0/default/Device.cpp b/renderscript/1.0/default/Device.cpp index d603a12cfb..9a6d7ba9a4 100644 --- a/renderscript/1.0/default/Device.cpp +++ b/renderscript/1.0/default/Device.cpp @@ -86,150 +86,116 @@ dispatchTable loadHAL() { } dispatchTable dispatchHal = { - .SetNativeLibDir = (SetNativeLibDirFnPtr) nullptr, - - .Allocation1DData = - (Allocation1DDataFnPtr)dlsym(handle, "rsAllocation1DData"), - .Allocation1DElementData = (Allocation1DElementDataFnPtr) nullptr, - .Allocation1DRead = - (Allocation1DReadFnPtr)dlsym(handle, "rsAllocation1DRead"), - .Allocation2DData = - (Allocation2DDataFnPtr)dlsym(handle, "rsAllocation2DData"), - .Allocation2DRead = - (Allocation2DReadFnPtr)dlsym(handle, "rsAllocation2DRead"), - .Allocation3DData = - (Allocation3DDataFnPtr)dlsym(handle, "rsAllocation3DData"), - .Allocation3DRead = - (Allocation3DReadFnPtr)dlsym(handle, "rsAllocation3DRead"), - .AllocationAdapterCreate = (AllocationAdapterCreateFnPtr)dlsym( - handle, "rsAllocationAdapterCreate"), - .AllocationAdapterOffset = (AllocationAdapterOffsetFnPtr)dlsym( - handle, "rsAllocationAdapterOffset"), - .AllocationCopy2DRange = (AllocationCopy2DRangeFnPtr)dlsym( - handle, "rsAllocationCopy2DRange"), - .AllocationCopy3DRange = (AllocationCopy3DRangeFnPtr)dlsym( - handle, "rsAllocationCopy3DRange"), - .AllocationCopyToBitmap = (AllocationCopyToBitmapFnPtr)dlsym( - handle, "rsAllocationCopyToBitmap"), - .AllocationCreateFromBitmap = (AllocationCreateFromBitmapFnPtr)dlsym( - handle, "rsAllocationCreateFromBitmap"), - .AllocationCreateStrided = (AllocationCreateStridedFnPtr)dlsym( - handle, "rsAllocationCreateStrided"), - .AllocationCreateTyped = (AllocationCreateTypedFnPtr)dlsym( - handle, "rsAllocationCreateTyped"), - .AllocationCubeCreateFromBitmap = - (AllocationCubeCreateFromBitmapFnPtr)dlsym( - handle, "rsAllocationCubeCreateFromBitmap"), - .AllocationElementData = (AllocationElementDataFnPtr)dlsym( - handle, "rsAllocationElementData"), - .AllocationElementRead = (AllocationElementReadFnPtr)dlsym( - handle, "rsAllocationElementRead"), - .AllocationGenerateMipmaps = (AllocationGenerateMipmapsFnPtr)dlsym( - handle, "rsAllocationGenerateMipmaps"), - .AllocationGetPointer = - (AllocationGetPointerFnPtr)dlsym(handle, "rsAllocationGetPointer"), - .AllocationGetSurface = - (AllocationGetSurfaceFnPtr)dlsym(handle, "rsAllocationGetSurface"), - .AllocationGetType = - (AllocationGetTypeFnPtr)dlsym(handle, "rsaAllocationGetType"), - .AllocationIoReceive = - (AllocationIoReceiveFnPtr)dlsym(handle, "rsAllocationIoReceive"), - .AllocationIoSend = - (AllocationIoSendFnPtr)dlsym(handle, "rsAllocationIoSend"), - .AllocationRead = - (AllocationReadFnPtr)dlsym(handle, "rsAllocationRead"), - .AllocationResize1D = - (AllocationResize1DFnPtr)dlsym(handle, "rsAllocationResize1D"), - .AllocationSetSurface = - (AllocationSetSurfaceFnPtr)dlsym(handle, "rsAllocationSetSurface"), - .AllocationSetupBufferQueue = (AllocationSetupBufferQueueFnPtr)dlsym( - handle, "rsAllocationSetupBufferQueue"), - .AllocationShareBufferQueue = (AllocationShareBufferQueueFnPtr)dlsym( - handle, "rsAllocationShareBufferQueue"), - .AllocationSyncAll = - (AllocationSyncAllFnPtr)dlsym(handle, "rsAllocationSyncAll"), - .AssignName = (AssignNameFnPtr)dlsym(handle, "rsAssignName"), - .ClosureCreate = (ClosureCreateFnPtr)dlsym(handle, "rsClosureCreate"), - .ClosureSetArg = (ClosureSetArgFnPtr)dlsym(handle, "rsClosureSetArg"), - .ClosureSetGlobal = - (ClosureSetGlobalFnPtr)dlsym(handle, "rsClosureSetGlobal"), - .ContextCreateVendor = - (ContextCreateVendorFnPtr)dlsym(handle, "rsContextCreateVendor"), - .ContextDeinitToClient = (ContextDeinitToClientFnPtr)dlsym( - handle, "rsContextDeinitToClient"), - .ContextDestroy = - (ContextDestroyFnPtr)dlsym(handle, "rsContextDestroy"), - .ContextDump = (ContextDumpFnPtr)dlsym(handle, "rsContextDump"), - .ContextFinish = (ContextFinishFnPtr)dlsym(handle, "rsContextFinish"), - .ContextGetMessage = - (ContextGetMessageFnPtr)dlsym(handle, "rsContextGetMessage"), - .ContextInitToClient = - (ContextInitToClientFnPtr)dlsym(handle, "rsContextInitToClient"), - .ContextPeekMessage = - (ContextPeekMessageFnPtr)dlsym(handle, "rsContextPeekMessage"), - .ContextSendMessage = - (ContextSendMessageFnPtr)dlsym(handle, "rsContextSendMessage"), - .ContextSetCacheDir = - (ContextSetCacheDirFnPtr)dlsym(handle, "rsContextSetCacheDir"), - .ContextSetPriority = - (ContextSetPriorityFnPtr)dlsym(handle, "rsContextSetPriority"), - .DeviceCreate = (DeviceCreateFnPtr) nullptr, - .DeviceDestroy = (DeviceDestroyFnPtr) nullptr, - .DeviceSetConfig = (DeviceSetConfigFnPtr) nullptr, - .ElementCreate2 = - (ElementCreate2FnPtr)dlsym(handle, "rsElementCreate2"), - .ElementCreate = (ElementCreateFnPtr)dlsym(handle, "rsElementCreate"), - .ElementGetNativeData = - (ElementGetNativeDataFnPtr)dlsym(handle, "rsaElementGetNativeData"), - .ElementGetSubElements = (ElementGetSubElementsFnPtr)dlsym( - handle, "rsaElementGetSubElements"), - .GetName = (GetNameFnPtr)dlsym(handle, "rsaGetName"), - .InvokeClosureCreate = - (InvokeClosureCreateFnPtr)dlsym(handle, "rsInvokeClosureCreate"), - .ObjDestroy = (ObjDestroyFnPtr)dlsym(handle, "rsObjDestroy"), - .SamplerCreate = (SamplerCreateFnPtr)dlsym(handle, "rsSamplerCreate"), - .ScriptBindAllocation = - (ScriptBindAllocationFnPtr)dlsym(handle, "rsScriptBindAllocation"), - .ScriptCCreate = (ScriptCCreateFnPtr)dlsym(handle, "rsScriptCCreate"), - .ScriptFieldIDCreate = - (ScriptFieldIDCreateFnPtr)dlsym(handle, "rsScriptFieldIDCreate"), - .ScriptForEach = (ScriptForEachFnPtr) nullptr, - .ScriptForEachMulti = - (ScriptForEachMultiFnPtr)dlsym(handle, "rsScriptForEachMulti"), - .ScriptGetVarV = (ScriptGetVarVFnPtr)dlsym(handle, "rsScriptGetVarV"), - .ScriptGroup2Create = - (ScriptGroup2CreateFnPtr)dlsym(handle, "rsScriptGroup2Create"), - .ScriptGroupCreate = - (ScriptGroupCreateFnPtr)dlsym(handle, "rsScriptGroupCreate"), - .ScriptGroupExecute = - (ScriptGroupExecuteFnPtr)dlsym(handle, "rsScriptGroupExecute"), - .ScriptGroupSetInput = - (ScriptGroupSetInputFnPtr)dlsym(handle, "rsScriptGroupSetInput"), - .ScriptGroupSetOutput = - (ScriptGroupSetOutputFnPtr)dlsym(handle, "rsScriptGroupSetOutput"), - .ScriptIntrinsicCreate = (ScriptIntrinsicCreateFnPtr)dlsym( - handle, "rsScriptIntrinsicCreate"), - .ScriptInvoke = (ScriptInvokeFnPtr)dlsym(handle, "rsScriptInvoke"), - .ScriptInvokeIDCreate = - (ScriptInvokeIDCreateFnPtr)dlsym(handle, "rsScriptInvokeIDCreate"), - .ScriptInvokeV = (ScriptInvokeVFnPtr)dlsym(handle, "rsScriptInvokeV"), - .ScriptKernelIDCreate = - (ScriptKernelIDCreateFnPtr)dlsym(handle, "rsScriptKernelIDCreate"), - .ScriptReduce = (ScriptReduceFnPtr)dlsym(handle, "rsScriptReduce"), - .ScriptSetTimeZone = - (ScriptSetTimeZoneFnPtr)dlsym(handle, "rsScriptSetTimeZone"), - .ScriptSetVarD = (ScriptSetVarDFnPtr)dlsym(handle, "rsScriptSetVarD"), - .ScriptSetVarF = (ScriptSetVarFFnPtr)dlsym(handle, "rsScriptSetVarF"), - .ScriptSetVarI = (ScriptSetVarIFnPtr)dlsym(handle, "rsScriptSetVarI"), - .ScriptSetVarJ = (ScriptSetVarJFnPtr)dlsym(handle, "rsScriptSetVarJ"), - .ScriptSetVarObj = - (ScriptSetVarObjFnPtr)dlsym(handle, "rsScriptSetVarObj"), - .ScriptSetVarVE = - (ScriptSetVarVEFnPtr)dlsym(handle, "rsScriptSetVarVE"), - .ScriptSetVarV = (ScriptSetVarVFnPtr)dlsym(handle, "rsScriptSetVarV"), - .TypeCreate = (TypeCreateFnPtr)dlsym(handle, "rsTypeCreate"), - .TypeGetNativeData = - (TypeGetNativeDataFnPtr)dlsym(handle, "rsaTypeGetNativeData"), + .SetNativeLibDir = (SetNativeLibDirFnPtr) nullptr, + + .Allocation1DData = (Allocation1DDataFnPtr)dlsym(handle, "rsAllocation1DData"), + .Allocation1DElementData = (Allocation1DElementDataFnPtr) nullptr, + .Allocation1DRead = (Allocation1DReadFnPtr)dlsym(handle, "rsAllocation1DRead"), + .Allocation2DData = (Allocation2DDataFnPtr)dlsym(handle, "rsAllocation2DData"), + .Allocation2DRead = (Allocation2DReadFnPtr)dlsym(handle, "rsAllocation2DRead"), + .Allocation3DData = (Allocation3DDataFnPtr)dlsym(handle, "rsAllocation3DData"), + .Allocation3DRead = (Allocation3DReadFnPtr)dlsym(handle, "rsAllocation3DRead"), + .AllocationAdapterCreate = + (AllocationAdapterCreateFnPtr)dlsym(handle, "rsAllocationAdapterCreate"), + .AllocationAdapterOffset = + (AllocationAdapterOffsetFnPtr)dlsym(handle, "rsAllocationAdapterOffset"), + .AllocationCopy2DRange = + (AllocationCopy2DRangeFnPtr)dlsym(handle, "rsAllocationCopy2DRange"), + .AllocationCopy3DRange = + (AllocationCopy3DRangeFnPtr)dlsym(handle, "rsAllocationCopy3DRange"), + .AllocationCopyToBitmap = + (AllocationCopyToBitmapFnPtr)dlsym(handle, "rsAllocationCopyToBitmap"), + .AllocationCreateFromBitmap = + (AllocationCreateFromBitmapFnPtr)dlsym(handle, "rsAllocationCreateFromBitmap"), + .AllocationCreateStrided = + (AllocationCreateStridedFnPtr)dlsym(handle, "rsAllocationCreateStrided"), + .AllocationCreateTyped = + (AllocationCreateTypedFnPtr)dlsym(handle, "rsAllocationCreateTyped"), + .AllocationCubeCreateFromBitmap = (AllocationCubeCreateFromBitmapFnPtr)dlsym( + handle, "rsAllocationCubeCreateFromBitmap"), + .AllocationElementData = + (AllocationElementDataFnPtr)dlsym(handle, "rsAllocationElementData"), + .AllocationElementRead = + (AllocationElementReadFnPtr)dlsym(handle, "rsAllocationElementRead"), + .AllocationGenerateMipmaps = + (AllocationGenerateMipmapsFnPtr)dlsym(handle, "rsAllocationGenerateMipmaps"), + .AllocationGetPointer = + (AllocationGetPointerFnPtr)dlsym(handle, "rsAllocationGetPointer"), + .AllocationGetSurface = + (AllocationGetSurfaceFnPtr)dlsym(handle, "rsAllocationGetSurface"), + .AllocationGetType = (AllocationGetTypeFnPtr)dlsym(handle, "rsaAllocationGetType"), + .AllocationIoReceive = (AllocationIoReceiveFnPtr)dlsym(handle, "rsAllocationIoReceive"), + .AllocationIoSend = (AllocationIoSendFnPtr)dlsym(handle, "rsAllocationIoSend"), + .AllocationRead = (AllocationReadFnPtr)dlsym(handle, "rsAllocationRead"), + .AllocationResize1D = (AllocationResize1DFnPtr)dlsym(handle, "rsAllocationResize1D"), + .AllocationSetSurface = + (AllocationSetSurfaceFnPtr)dlsym(handle, "rsAllocationSetSurface"), + .AllocationSyncAll = (AllocationSyncAllFnPtr)dlsym(handle, "rsAllocationSyncAll"), + .AllocationSetupBufferQueue = + (AllocationSetupBufferQueueFnPtr)dlsym(handle, "rsAllocationSetupBufferQueue"), + .AllocationShareBufferQueue = + (AllocationShareBufferQueueFnPtr)dlsym(handle, "rsAllocationShareBufferQueue"), + .AssignName = (AssignNameFnPtr)dlsym(handle, "rsAssignName"), + .ClosureCreate = (ClosureCreateFnPtr)dlsym(handle, "rsClosureCreate"), + .ClosureSetArg = (ClosureSetArgFnPtr)dlsym(handle, "rsClosureSetArg"), + .ClosureSetGlobal = (ClosureSetGlobalFnPtr)dlsym(handle, "rsClosureSetGlobal"), + .ContextCreateVendor = (ContextCreateVendorFnPtr)dlsym(handle, "rsContextCreateVendor"), + .ContextDeinitToClient = + (ContextDeinitToClientFnPtr)dlsym(handle, "rsContextDeinitToClient"), + .ContextDestroy = (ContextDestroyFnPtr)dlsym(handle, "rsContextDestroy"), + .ContextDump = (ContextDumpFnPtr)dlsym(handle, "rsContextDump"), + .ContextFinish = (ContextFinishFnPtr)dlsym(handle, "rsContextFinish"), + .ContextGetMessage = (ContextGetMessageFnPtr)dlsym(handle, "rsContextGetMessage"), + .ContextInitToClient = (ContextInitToClientFnPtr)dlsym(handle, "rsContextInitToClient"), + .ContextPeekMessage = (ContextPeekMessageFnPtr)dlsym(handle, "rsContextPeekMessage"), + .ContextSendMessage = (ContextSendMessageFnPtr)dlsym(handle, "rsContextSendMessage"), + .ContextSetPriority = (ContextSetPriorityFnPtr)dlsym(handle, "rsContextSetPriority"), + .ContextSetCacheDir = (ContextSetCacheDirFnPtr)dlsym(handle, "rsContextSetCacheDir"), + .DeviceCreate = (DeviceCreateFnPtr) nullptr, + .DeviceDestroy = (DeviceDestroyFnPtr) nullptr, + .DeviceSetConfig = (DeviceSetConfigFnPtr) nullptr, + .ElementCreate2 = (ElementCreate2FnPtr)dlsym(handle, "rsElementCreate2"), + .ElementCreate = (ElementCreateFnPtr)dlsym(handle, "rsElementCreate"), + .ElementGetNativeData = + (ElementGetNativeDataFnPtr)dlsym(handle, "rsaElementGetNativeData"), + .ElementGetSubElements = + (ElementGetSubElementsFnPtr)dlsym(handle, "rsaElementGetSubElements"), + .GetName = (GetNameFnPtr)dlsym(handle, "rsaGetName"), + .InvokeClosureCreate = (InvokeClosureCreateFnPtr)dlsym(handle, "rsInvokeClosureCreate"), + .ObjDestroy = (ObjDestroyFnPtr)dlsym(handle, "rsObjDestroy"), + .SamplerCreate = (SamplerCreateFnPtr)dlsym(handle, "rsSamplerCreate"), + .ScriptBindAllocation = + (ScriptBindAllocationFnPtr)dlsym(handle, "rsScriptBindAllocation"), + .ScriptCCreate = (ScriptCCreateFnPtr)dlsym(handle, "rsScriptCCreate"), + .ScriptFieldIDCreate = (ScriptFieldIDCreateFnPtr)dlsym(handle, "rsScriptFieldIDCreate"), + .ScriptForEach = (ScriptForEachFnPtr) nullptr, + .ScriptForEachMulti = (ScriptForEachMultiFnPtr)dlsym(handle, "rsScriptForEachMulti"), + .ScriptGetVarV = (ScriptGetVarVFnPtr)dlsym(handle, "rsScriptGetVarV"), + .ScriptGroup2Create = (ScriptGroup2CreateFnPtr)dlsym(handle, "rsScriptGroup2Create"), + .ScriptGroupCreate = (ScriptGroupCreateFnPtr)dlsym(handle, "rsScriptGroupCreate"), + .ScriptGroupExecute = (ScriptGroupExecuteFnPtr)dlsym(handle, "rsScriptGroupExecute"), + .ScriptGroupSetInput = (ScriptGroupSetInputFnPtr)dlsym(handle, "rsScriptGroupSetInput"), + .ScriptGroupSetOutput = + (ScriptGroupSetOutputFnPtr)dlsym(handle, "rsScriptGroupSetOutput"), + .ScriptIntrinsicCreate = + (ScriptIntrinsicCreateFnPtr)dlsym(handle, "rsScriptIntrinsicCreate"), + .ScriptInvoke = (ScriptInvokeFnPtr)dlsym(handle, "rsScriptInvoke"), + .ScriptInvokeIDCreate = + (ScriptInvokeIDCreateFnPtr)dlsym(handle, "rsScriptInvokeIDCreate"), + .ScriptInvokeV = (ScriptInvokeVFnPtr)dlsym(handle, "rsScriptInvokeV"), + .ScriptKernelIDCreate = + (ScriptKernelIDCreateFnPtr)dlsym(handle, "rsScriptKernelIDCreate"), + .ScriptReduce = (ScriptReduceFnPtr)dlsym(handle, "rsScriptReduce"), + .ScriptSetTimeZone = (ScriptSetTimeZoneFnPtr)dlsym(handle, "rsScriptSetTimeZone"), + .ScriptSetVarD = (ScriptSetVarDFnPtr)dlsym(handle, "rsScriptSetVarD"), + .ScriptSetVarF = (ScriptSetVarFFnPtr)dlsym(handle, "rsScriptSetVarF"), + .ScriptSetVarI = (ScriptSetVarIFnPtr)dlsym(handle, "rsScriptSetVarI"), + .ScriptSetVarJ = (ScriptSetVarJFnPtr)dlsym(handle, "rsScriptSetVarJ"), + .ScriptSetVarObj = (ScriptSetVarObjFnPtr)dlsym(handle, "rsScriptSetVarObj"), + .ScriptSetVarVE = (ScriptSetVarVEFnPtr)dlsym(handle, "rsScriptSetVarVE"), + .ScriptSetVarV = (ScriptSetVarVFnPtr)dlsym(handle, "rsScriptSetVarV"), + .TypeCreate = (TypeCreateFnPtr)dlsym(handle, "rsTypeCreate"), + .TypeGetNativeData = (TypeGetNativeDataFnPtr)dlsym(handle, "rsaTypeGetNativeData"), }; return dispatchHal; diff --git a/sensors/1.0/default/android.hardware.sensors@1.0-service.rc b/sensors/1.0/default/android.hardware.sensors@1.0-service.rc index 1707945a2d..5f978130a0 100644 --- a/sensors/1.0/default/android.hardware.sensors@1.0-service.rc +++ b/sensors/1.0/default/android.hardware.sensors@1.0-service.rc @@ -2,6 +2,6 @@ service vendor.sensors-hal-1-0 /vendor/bin/hw/android.hardware.sensors@1.0-servi interface android.hardware.sensors@1.0::ISensors default class hal user system - group system wakelock input + group system wakelock uhid input capabilities BLOCK_SUSPEND rlimit rtprio 10 10 diff --git a/sensors/1.0/default/convert.cpp b/sensors/1.0/default/convert.cpp index 52f5e4f5a0..53ceb0d92f 100644 --- a/sensors/1.0/default/convert.cpp +++ b/sensors/1.0/default/convert.cpp @@ -68,9 +68,9 @@ void convertFromSensorEvent(const sensors_event_t &src, Event *dst) { typedef ::android::hardware::sensors::V1_0::MetaDataEventType MetaDataEventType; *dst = { - .sensorHandle = src.sensor, - .sensorType = (SensorType)src.type, - .timestamp = src.timestamp + .timestamp = src.timestamp, + .sensorHandle = src.sensor, + .sensorType = (SensorType)src.type, }; switch (dst->sensorType) { diff --git a/sensors/2.0/multihal/Android.bp b/sensors/2.0/multihal/Android.bp index 697bf9e117..c13eaf2e92 100644 --- a/sensors/2.0/multihal/Android.bp +++ b/sensors/2.0/multihal/Android.bp @@ -21,14 +21,15 @@ cc_defaults { shared_libs: [ "android.hardware.sensors@1.0", "android.hardware.sensors@2.0", + "libbase", "libcutils", "libfmq", "libhidlbase", - "libhidltransport", "liblog", "libpower", "libutils", ], + cflags: ["-DLOG_TAG=\"SensorsMultiHal\""], } cc_binary { @@ -42,10 +43,10 @@ cc_binary { srcs: [ "service.cpp", "HalProxy.cpp", + "ScopedWakelock.cpp", ], init_rc: ["android.hardware.sensors@2.0-service-multihal.rc"], vintf_fragments: ["android.hardware.sensors@2.0-multihal.xml"], - cflags: ["-DLOG_TAG=\"SensorsMultiHal\""], } cc_library_headers { @@ -61,8 +62,12 @@ cc_test_library { vendor_available: true, srcs: [ "HalProxy.cpp", + "ScopedWakelock.cpp", ], export_header_lib_headers: [ "android.hardware.sensors@2.0-multihal.header", ], + shared_libs: [ + "libutils", + ], } diff --git a/sensors/2.0/multihal/HalProxy.cpp b/sensors/2.0/multihal/HalProxy.cpp index b4d246690d..b78806ab6b 100644 --- a/sensors/2.0/multihal/HalProxy.cpp +++ b/sensors/2.0/multihal/HalProxy.cpp @@ -16,12 +16,20 @@ #include "HalProxy.h" +#include "SubHal.h" + #include <android/hardware/sensors/2.0/types.h> +#include <android-base/file.h> +#include "hardware_legacy/power.h" + #include <dlfcn.h> +#include <cinttypes> +#include <cmath> #include <fstream> #include <functional> +#include <thread> namespace android { namespace hardware { @@ -29,62 +37,70 @@ namespace sensors { namespace V2_0 { namespace implementation { +using ::android::hardware::sensors::V2_0::EventQueueFlagBits; +using ::android::hardware::sensors::V2_0::WakeLockQueueFlagBits; +using ::android::hardware::sensors::V2_0::implementation::getTimeNow; +using ::android::hardware::sensors::V2_0::implementation::kWakelockTimeoutNs; + typedef ISensorsSubHal*(SensorsHalGetSubHalFunc)(uint32_t*); -// TODO: Use this wake lock name as the prefix to all sensors HAL wake locks acquired. -// constexpr const char* kWakeLockName = "SensorsHAL_WAKEUP"; +static constexpr int32_t kBitsAfterSubHalIndex = 24; -// TODO: Use the following class as a starting point for implementing the full HalProxyCallback -// along with being inspiration for how to implement the ScopedWakelock class. /** - * Callback class used to provide the HalProxy with the index of which subHal is invoking + * Set the subhal index as first byte of sensor handle and return this modified version. + * + * @param sensorHandle The sensor handle to modify. + * @param subHalIndex The index in the hal proxy of the sub hal this sensor belongs to. + * + * @return The modified sensor handle. */ -class SensorsCallbackProxy : public ISensorsCallback { - public: - SensorsCallbackProxy(wp<HalProxy>& halProxy, int32_t subHalIndex) - : mHalProxy(halProxy), mSubHalIndex(subHalIndex) {} - - Return<void> onDynamicSensorsConnected( - const hidl_vec<SensorInfo>& dynamicSensorsAdded) override { - sp<HalProxy> halProxy(mHalProxy.promote()); - if (halProxy != nullptr) { - return halProxy->onDynamicSensorsConnected(dynamicSensorsAdded, mSubHalIndex); - } - return Return<void>(); - } +int32_t setSubHalIndex(int32_t sensorHandle, size_t subHalIndex) { + return sensorHandle | (static_cast<int32_t>(subHalIndex) << kBitsAfterSubHalIndex); +} - Return<void> onDynamicSensorsDisconnected( - const hidl_vec<int32_t>& dynamicSensorHandlesRemoved) override { - sp<HalProxy> halProxy(mHalProxy.promote()); - if (halProxy != nullptr) { - return halProxy->onDynamicSensorsDisconnected(dynamicSensorHandlesRemoved, - mSubHalIndex); - } - return Return<void>(); - } +/** + * Extract the subHalIndex from sensorHandle. + * + * @param sensorHandle The sensorHandle to extract from. + * + * @return The subhal index. + */ +size_t extractSubHalIndex(int32_t sensorHandle) { + return static_cast<size_t>(sensorHandle >> kBitsAfterSubHalIndex); +} - private: - wp<HalProxy>& mHalProxy; - int32_t mSubHalIndex; -}; +/** + * Convert nanoseconds to milliseconds. + * + * @param nanos The nanoseconds input. + * + * @return The milliseconds count. + */ +int64_t msFromNs(int64_t nanos) { + constexpr int64_t nanosecondsInAMillsecond = 1000000; + return nanos / nanosecondsInAMillsecond; +} HalProxy::HalProxy() { const char* kMultiHalConfigFile = "/vendor/etc/sensors/hals.conf"; initializeSubHalListFromConfigFile(kMultiHalConfigFile); - initializeSensorList(); + init(); } HalProxy::HalProxy(std::vector<ISensorsSubHal*>& subHalList) : mSubHalList(subHalList) { - initializeSensorList(); + init(); } HalProxy::~HalProxy() { - // TODO: Join any running threads and clean up FMQs and any other allocated - // state. + stopThreads(); } Return<void> HalProxy::getSensorsList(getSensorsList_cb _hidl_cb) { - _hidl_cb(mSensorList); + std::vector<SensorInfo> sensors; + for (const auto& iter : mSensors) { + sensors.push_back(iter.second); + } + _hidl_cb(sensors); return Void(); } @@ -112,6 +128,9 @@ Return<Result> HalProxy::setOperationMode(OperationMode mode) { } Return<Result> HalProxy::activate(int32_t sensorHandle, bool enabled) { + if (!isSubHalIndexValid(sensorHandle)) { + return Result::BAD_VALUE; + } return getSubHalForSensorHandle(sensorHandle) ->activate(clearSubHalIndex(sensorHandle), enabled); } @@ -122,8 +141,18 @@ Return<Result> HalProxy::initialize( const sp<ISensorsCallback>& sensorsCallback) { Result result = Result::OK; - // TODO: clean up sensor requests, if not already done elsewhere through a death recipient, and - // clean up any other resources that exist (FMQs, flags, threads, etc.) + stopThreads(); + resetSharedWakelock(); + + // So that the pending write events queue can be cleared safely and when we start threads + // again we do not get new events until after initialize resets the subhals. + disableAllSensors(); + + // Clears the queue if any events were pending write before. + mPendingWriteEventsQueue = std::queue<std::pair<std::vector<Event>, size_t>>(); + + // Clears previously connected dynamic sensors + mDynamicSensors.clear(); mDynamicSensorsCallback = sensorsCallback; @@ -131,77 +160,193 @@ Return<Result> HalProxy::initialize( mEventQueue = std::make_unique<EventMessageQueue>(eventQueueDescriptor, true /* resetPointers */); - // Create the EventFlag that is used to signal to the framework that sensor events have been - // written to the Event FMQ - if (EventFlag::createEventFlag(mEventQueue->getEventFlagWord(), &mEventQueueFlag) != OK) { - result = Result::BAD_VALUE; - } - // Create the Wake Lock FMQ that is used by the framework to communicate whenever WAKE_UP // events have been successfully read and handled by the framework. mWakeLockQueue = std::make_unique<WakeLockMessageQueue>(wakeLockDescriptor, true /* resetPointers */); + if (mEventQueueFlag != nullptr) { + EventFlag::deleteEventFlag(&mEventQueueFlag); + } + if (mWakelockQueueFlag != nullptr) { + EventFlag::deleteEventFlag(&mWakelockQueueFlag); + } + if (EventFlag::createEventFlag(mEventQueue->getEventFlagWord(), &mEventQueueFlag) != OK) { + result = Result::BAD_VALUE; + } + if (EventFlag::createEventFlag(mWakeLockQueue->getEventFlagWord(), &mWakelockQueueFlag) != OK) { + result = Result::BAD_VALUE; + } if (!mDynamicSensorsCallback || !mEventQueue || !mWakeLockQueue || mEventQueueFlag == nullptr) { result = Result::BAD_VALUE; } - // TODO: start threads to read wake locks and process events from sub HALs. + mThreadsRun.store(true); + + mPendingWritesThread = std::thread(startPendingWritesThread, this); + mWakelockThread = std::thread(startWakelockThread, this); + + for (size_t i = 0; i < mSubHalList.size(); i++) { + auto subHal = mSubHalList[i]; + const auto& subHalCallback = mSubHalCallbacks[i]; + Result currRes = subHal->initialize(subHalCallback); + if (currRes != Result::OK) { + result = currRes; + ALOGE("Subhal '%s' failed to initialize.", subHal->getName().c_str()); + break; + } + } + + mCurrentOperationMode = OperationMode::NORMAL; return result; } Return<Result> HalProxy::batch(int32_t sensorHandle, int64_t samplingPeriodNs, int64_t maxReportLatencyNs) { + if (!isSubHalIndexValid(sensorHandle)) { + return Result::BAD_VALUE; + } return getSubHalForSensorHandle(sensorHandle) ->batch(clearSubHalIndex(sensorHandle), samplingPeriodNs, maxReportLatencyNs); } Return<Result> HalProxy::flush(int32_t sensorHandle) { + if (!isSubHalIndexValid(sensorHandle)) { + return Result::BAD_VALUE; + } return getSubHalForSensorHandle(sensorHandle)->flush(clearSubHalIndex(sensorHandle)); } -Return<Result> HalProxy::injectSensorData(const Event& /* event */) { - // TODO: Proxy API call to appropriate sub-HAL. - return Result::INVALID_OPERATION; +Return<Result> HalProxy::injectSensorData(const Event& event) { + Result result = Result::OK; + if (mCurrentOperationMode == OperationMode::NORMAL && + event.sensorType != V1_0::SensorType::ADDITIONAL_INFO) { + ALOGE("An event with type != ADDITIONAL_INFO passed to injectSensorData while operation" + " mode was NORMAL."); + result = Result::BAD_VALUE; + } + if (result == Result::OK) { + Event subHalEvent = event; + if (!isSubHalIndexValid(event.sensorHandle)) { + return Result::BAD_VALUE; + } + subHalEvent.sensorHandle = clearSubHalIndex(event.sensorHandle); + result = getSubHalForSensorHandle(event.sensorHandle)->injectSensorData(subHalEvent); + } + return result; } -Return<void> HalProxy::registerDirectChannel(const SharedMemInfo& /* mem */, +Return<void> HalProxy::registerDirectChannel(const SharedMemInfo& mem, registerDirectChannel_cb _hidl_cb) { - // TODO: During init, discover the first sub-HAL in the config that has sensors with direct - // channel support, if any, and proxy the API call there. - _hidl_cb(Result::INVALID_OPERATION, -1 /* channelHandle */); + if (mDirectChannelSubHal == nullptr) { + _hidl_cb(Result::INVALID_OPERATION, -1 /* channelHandle */); + } else { + mDirectChannelSubHal->registerDirectChannel(mem, _hidl_cb); + } return Return<void>(); } -Return<Result> HalProxy::unregisterDirectChannel(int32_t /* channelHandle */) { - // TODO: During init, discover the first sub-HAL in the config that has sensors with direct - // channel support, if any, and proxy the API call there. - return Result::INVALID_OPERATION; +Return<Result> HalProxy::unregisterDirectChannel(int32_t channelHandle) { + Result result; + if (mDirectChannelSubHal == nullptr) { + result = Result::INVALID_OPERATION; + } else { + result = mDirectChannelSubHal->unregisterDirectChannel(channelHandle); + } + return result; } -Return<void> HalProxy::configDirectReport(int32_t /* sensorHandle */, int32_t /* channelHandle */, - RateLevel /* rate */, configDirectReport_cb _hidl_cb) { - // TODO: During init, discover the first sub-HAL in the config that has sensors with direct - // channel support, if any, and proxy the API call there. - _hidl_cb(Result::INVALID_OPERATION, 0 /* reportToken */); +Return<void> HalProxy::configDirectReport(int32_t sensorHandle, int32_t channelHandle, + RateLevel rate, configDirectReport_cb _hidl_cb) { + if (mDirectChannelSubHal == nullptr) { + _hidl_cb(Result::INVALID_OPERATION, -1 /* reportToken */); + } else { + mDirectChannelSubHal->configDirectReport(clearSubHalIndex(sensorHandle), channelHandle, + rate, _hidl_cb); + } return Return<void>(); } -Return<void> HalProxy::debug(const hidl_handle& /* fd */, const hidl_vec<hidl_string>& /* args */) { - // TODO: output debug information +Return<void> HalProxy::debug(const hidl_handle& fd, const hidl_vec<hidl_string>& /*args*/) { + if (fd.getNativeHandle() == nullptr || fd->numFds < 1) { + ALOGE("%s: missing fd for writing", __FUNCTION__); + return Void(); + } + + android::base::borrowed_fd writeFd = dup(fd->data[0]); + + std::ostringstream stream; + stream << "===HalProxy===" << std::endl; + stream << "Internal values:" << std::endl; + stream << " Threads are running: " << (mThreadsRun.load() ? "true" : "false") << std::endl; + int64_t now = getTimeNow(); + stream << " Wakelock timeout start time: " << msFromNs(now - mWakelockTimeoutStartTime) + << " ms ago" << std::endl; + stream << " Wakelock timeout reset time: " << msFromNs(now - mWakelockTimeoutResetTime) + << " ms ago" << std::endl; + // TODO(b/142969448): Add logging for history of wakelock acquisition per subhal. + stream << " Wakelock ref count: " << mWakelockRefCount << std::endl; + stream << " Size of pending write events queue: " << mPendingWriteEventsQueue.size() + << std::endl; + if (!mPendingWriteEventsQueue.empty()) { + stream << " Size of events list on front of pending writes queue: " + << mPendingWriteEventsQueue.front().first.size() << std::endl; + } + stream << " # of non-dynamic sensors across all subhals: " << mSensors.size() << std::endl; + stream << " # of dynamic sensors across all subhals: " << mDynamicSensors.size() << std::endl; + stream << "SubHals (" << mSubHalList.size() << "):" << std::endl; + for (ISensorsSubHal* subHal : mSubHalList) { + stream << " Name: " << subHal->getName() << std::endl; + stream << " Debug dump: " << std::endl; + android::base::WriteStringToFd(stream.str(), writeFd); + subHal->debug(fd, {}); + stream.str(""); + stream << std::endl; + } + android::base::WriteStringToFd(stream.str(), writeFd); return Return<void>(); } -Return<void> HalProxy::onDynamicSensorsConnected( - const hidl_vec<SensorInfo>& /* dynamicSensorsAdded */, int32_t /* subHalIndex */) { - // TODO: Map the SensorInfo to the global list and then invoke the framework's callback. +Return<void> HalProxy::onDynamicSensorsConnected(const hidl_vec<SensorInfo>& dynamicSensorsAdded, + int32_t subHalIndex) { + std::vector<SensorInfo> sensors; + { + std::lock_guard<std::mutex> lock(mDynamicSensorsMutex); + for (SensorInfo sensor : dynamicSensorsAdded) { + if (!subHalIndexIsClear(sensor.sensorHandle)) { + ALOGE("Dynamic sensor added %s had sensorHandle with first byte not 0.", + sensor.name.c_str()); + } else { + sensor.sensorHandle = setSubHalIndex(sensor.sensorHandle, subHalIndex); + mDynamicSensors[sensor.sensorHandle] = sensor; + sensors.push_back(sensor); + } + } + } + mDynamicSensorsCallback->onDynamicSensorsConnected(sensors); return Return<void>(); } Return<void> HalProxy::onDynamicSensorsDisconnected( - const hidl_vec<int32_t>& /* dynamicSensorHandlesRemoved */, int32_t /* subHalIndex */) { - // TODO: Unmap the SensorInfo from the global list and then invoke the framework's callback. + const hidl_vec<int32_t>& dynamicSensorHandlesRemoved, int32_t subHalIndex) { + // TODO: Block this call until all pending events are flushed from queue + std::vector<int32_t> sensorHandles; + { + std::lock_guard<std::mutex> lock(mDynamicSensorsMutex); + for (int32_t sensorHandle : dynamicSensorHandlesRemoved) { + if (!subHalIndexIsClear(sensorHandle)) { + ALOGE("Dynamic sensorHandle removed had first byte not 0."); + } else { + sensorHandle = setSubHalIndex(sensorHandle, subHalIndex); + if (mDynamicSensors.find(sensorHandle) != mDynamicSensors.end()) { + mDynamicSensors.erase(sensorHandle); + sensorHandles.push_back(sensorHandle); + } + } + } + } + mDynamicSensorsCallback->onDynamicSensorsDisconnected(sensorHandles); return Return<void>(); } @@ -239,18 +384,25 @@ void HalProxy::initializeSubHalListFromConfigFile(const char* configFileName) { } } +void HalProxy::initializeSubHalCallbacks() { + for (size_t subHalIndex = 0; subHalIndex < mSubHalList.size(); subHalIndex++) { + sp<IHalProxyCallback> callback = new HalProxyCallback(this, subHalIndex); + mSubHalCallbacks.push_back(callback); + } +} + void HalProxy::initializeSensorList() { for (size_t subHalIndex = 0; subHalIndex < mSubHalList.size(); subHalIndex++) { ISensorsSubHal* subHal = mSubHalList[subHalIndex]; auto result = subHal->getSensorsList([&](const auto& list) { for (SensorInfo sensor : list) { - if ((sensor.sensorHandle & kSensorHandleSubHalIndexMask) != 0) { + if (!subHalIndexIsClear(sensor.sensorHandle)) { ALOGE("SubHal sensorHandle's first byte was not 0"); } else { ALOGV("Loaded sensor: %s", sensor.name.c_str()); - sensor.sensorHandle |= (subHalIndex << 24); + sensor.sensorHandle = setSubHalIndex(sensor.sensorHandle, subHalIndex); setDirectChannelFlags(&sensor, subHal); - mSensorList.push_back(sensor); + mSensors[sensor.sensorHandle] = sensor; } } }); @@ -260,6 +412,193 @@ void HalProxy::initializeSensorList() { } } +void HalProxy::init() { + initializeSubHalCallbacks(); + initializeSensorList(); +} + +void HalProxy::stopThreads() { + mThreadsRun.store(false); + if (mEventQueueFlag != nullptr && mEventQueue != nullptr) { + size_t numToRead = mEventQueue->availableToRead(); + std::vector<Event> events(numToRead); + mEventQueue->read(events.data(), numToRead); + mEventQueueFlag->wake(static_cast<uint32_t>(EventQueueFlagBits::EVENTS_READ)); + } + if (mWakelockQueueFlag != nullptr && mWakeLockQueue != nullptr) { + uint32_t kZero = 0; + mWakeLockQueue->write(&kZero); + mWakelockQueueFlag->wake(static_cast<uint32_t>(WakeLockQueueFlagBits::DATA_WRITTEN)); + } + mWakelockCV.notify_one(); + mEventQueueWriteCV.notify_one(); + if (mPendingWritesThread.joinable()) { + mPendingWritesThread.join(); + } + if (mWakelockThread.joinable()) { + mWakelockThread.join(); + } +} + +void HalProxy::disableAllSensors() { + for (const auto& sensorEntry : mSensors) { + int32_t sensorHandle = sensorEntry.first; + activate(sensorHandle, false /* enabled */); + } + std::lock_guard<std::mutex> dynamicSensorsLock(mDynamicSensorsMutex); + for (const auto& sensorEntry : mDynamicSensors) { + int32_t sensorHandle = sensorEntry.first; + activate(sensorHandle, false /* enabled */); + } +} + +void HalProxy::startPendingWritesThread(HalProxy* halProxy) { + halProxy->handlePendingWrites(); +} + +void HalProxy::handlePendingWrites() { + // TODO: Find a way to optimize locking strategy maybe using two mutexes instead of one. + std::unique_lock<std::mutex> lock(mEventQueueWriteMutex); + while (mThreadsRun.load()) { + mEventQueueWriteCV.wait( + lock, [&] { return !mPendingWriteEventsQueue.empty() || !mThreadsRun.load(); }); + if (mThreadsRun.load()) { + std::vector<Event>& pendingWriteEvents = mPendingWriteEventsQueue.front().first; + size_t numWakeupEvents = mPendingWriteEventsQueue.front().second; + size_t eventQueueSize = mEventQueue->getQuantumCount(); + size_t numToWrite = std::min(pendingWriteEvents.size(), eventQueueSize); + lock.unlock(); + if (!mEventQueue->writeBlocking( + pendingWriteEvents.data(), numToWrite, + static_cast<uint32_t>(EventQueueFlagBits::EVENTS_READ), + static_cast<uint32_t>(EventQueueFlagBits::READ_AND_PROCESS), + kPendingWriteTimeoutNs, mEventQueueFlag)) { + ALOGE("Dropping %zu events after blockingWrite failed.", numToWrite); + if (numWakeupEvents > 0) { + if (pendingWriteEvents.size() > eventQueueSize) { + decrementRefCountAndMaybeReleaseWakelock( + countNumWakeupEvents(pendingWriteEvents, eventQueueSize)); + } else { + decrementRefCountAndMaybeReleaseWakelock(numWakeupEvents); + } + } + } + lock.lock(); + if (pendingWriteEvents.size() > eventQueueSize) { + // TODO: Check if this erase operation is too inefficient. It will copy all the + // events ahead of it down to fill gap off array at front after the erase. + pendingWriteEvents.erase(pendingWriteEvents.begin(), + pendingWriteEvents.begin() + eventQueueSize); + } else { + mPendingWriteEventsQueue.pop(); + } + } + } +} + +void HalProxy::startWakelockThread(HalProxy* halProxy) { + halProxy->handleWakelocks(); +} + +void HalProxy::handleWakelocks() { + std::unique_lock<std::recursive_mutex> lock(mWakelockMutex); + while (mThreadsRun.load()) { + mWakelockCV.wait(lock, [&] { return mWakelockRefCount > 0 || !mThreadsRun.load(); }); + if (mThreadsRun.load()) { + int64_t timeLeft; + if (sharedWakelockDidTimeout(&timeLeft)) { + resetSharedWakelock(); + } else { + uint32_t numWakeLocksProcessed; + lock.unlock(); + bool success = mWakeLockQueue->readBlocking( + &numWakeLocksProcessed, 1, 0, + static_cast<uint32_t>(WakeLockQueueFlagBits::DATA_WRITTEN), timeLeft); + lock.lock(); + if (success) { + decrementRefCountAndMaybeReleaseWakelock( + static_cast<size_t>(numWakeLocksProcessed)); + } + } + } + } + resetSharedWakelock(); +} + +bool HalProxy::sharedWakelockDidTimeout(int64_t* timeLeft) { + bool didTimeout; + int64_t duration = getTimeNow() - mWakelockTimeoutStartTime; + if (duration > kWakelockTimeoutNs) { + didTimeout = true; + } else { + didTimeout = false; + *timeLeft = kWakelockTimeoutNs - duration; + } + return didTimeout; +} + +void HalProxy::resetSharedWakelock() { + std::lock_guard<std::recursive_mutex> lockGuard(mWakelockMutex); + decrementRefCountAndMaybeReleaseWakelock(mWakelockRefCount); + mWakelockTimeoutResetTime = getTimeNow(); +} + +void HalProxy::postEventsToMessageQueue(const std::vector<Event>& events, size_t numWakeupEvents, + ScopedWakelock wakelock) { + size_t numToWrite = 0; + std::lock_guard<std::mutex> lock(mEventQueueWriteMutex); + if (wakelock.isLocked()) { + incrementRefCountAndMaybeAcquireWakelock(numWakeupEvents); + } + if (mPendingWriteEventsQueue.empty()) { + numToWrite = std::min(events.size(), mEventQueue->availableToWrite()); + if (numToWrite > 0) { + if (mEventQueue->write(events.data(), numToWrite)) { + // TODO: While loop if mEventQueue->avaiableToWrite > 0 to possibly fit in more + // writes immediately + mEventQueueFlag->wake(static_cast<uint32_t>(EventQueueFlagBits::READ_AND_PROCESS)); + } else { + numToWrite = 0; + } + } + } + if (numToWrite < events.size()) { + // TODO: Bound the mPendingWriteEventsQueue so that we do not trigger OOMs if framework + // stalls + std::vector<Event> eventsLeft(events.begin() + numToWrite, events.end()); + mPendingWriteEventsQueue.push({eventsLeft, numWakeupEvents}); + mEventQueueWriteCV.notify_one(); + } +} + +bool HalProxy::incrementRefCountAndMaybeAcquireWakelock(size_t delta, + int64_t* timeoutStart /* = nullptr */) { + if (!mThreadsRun.load()) return false; + std::lock_guard<std::recursive_mutex> lockGuard(mWakelockMutex); + if (mWakelockRefCount == 0) { + acquire_wake_lock(PARTIAL_WAKE_LOCK, kWakelockName); + mWakelockCV.notify_one(); + } + mWakelockTimeoutStartTime = getTimeNow(); + mWakelockRefCount += delta; + if (timeoutStart != nullptr) { + *timeoutStart = mWakelockTimeoutStartTime; + } + return true; +} + +void HalProxy::decrementRefCountAndMaybeReleaseWakelock(size_t delta, + int64_t timeoutStart /* = -1 */) { + if (!mThreadsRun.load()) return; + std::lock_guard<std::recursive_mutex> lockGuard(mWakelockMutex); + if (timeoutStart == -1) timeoutStart = mWakelockTimeoutResetTime; + if (mWakelockRefCount == 0 || timeoutStart < mWakelockTimeoutResetTime) return; + mWakelockRefCount -= std::min(mWakelockRefCount, delta); + if (mWakelockRefCount == 0) { + release_wake_lock(kWakelockName); + } +} + void HalProxy::setDirectChannelFlags(SensorInfo* sensorInfo, ISensorsSubHal* subHal) { bool sensorSupportsDirectChannel = (sensorInfo->flags & (V1_0::SensorFlagBits::MASK_DIRECT_REPORT | @@ -274,14 +613,71 @@ void HalProxy::setDirectChannelFlags(SensorInfo* sensorInfo, ISensorsSubHal* sub } } -ISensorsSubHal* HalProxy::getSubHalForSensorHandle(uint32_t sensorHandle) { - return mSubHalList[static_cast<size_t>(sensorHandle >> 24)]; +ISensorsSubHal* HalProxy::getSubHalForSensorHandle(int32_t sensorHandle) { + return mSubHalList[extractSubHalIndex(sensorHandle)]; } -uint32_t HalProxy::clearSubHalIndex(uint32_t sensorHandle) { +bool HalProxy::isSubHalIndexValid(int32_t sensorHandle) { + return extractSubHalIndex(sensorHandle) < mSubHalList.size(); +} + +size_t HalProxy::countNumWakeupEvents(const std::vector<Event>& events, size_t n) { + size_t numWakeupEvents = 0; + for (size_t i = 0; i < n; i++) { + int32_t sensorHandle = events[i].sensorHandle; + if (mSensors[sensorHandle].flags & static_cast<uint32_t>(V1_0::SensorFlagBits::WAKE_UP)) { + numWakeupEvents++; + } + } + return numWakeupEvents; +} + +int32_t HalProxy::clearSubHalIndex(int32_t sensorHandle) { return sensorHandle & (~kSensorHandleSubHalIndexMask); } +bool HalProxy::subHalIndexIsClear(int32_t sensorHandle) { + return (sensorHandle & kSensorHandleSubHalIndexMask) == 0; +} + +void HalProxyCallback::postEvents(const std::vector<Event>& events, ScopedWakelock wakelock) { + if (events.empty() || !mHalProxy->areThreadsRunning()) return; + size_t numWakeupEvents; + std::vector<Event> processedEvents = processEvents(events, &numWakeupEvents); + if (numWakeupEvents > 0) { + ALOG_ASSERT(wakelock.isLocked(), + "Wakeup events posted while wakelock unlocked for subhal" + " w/ index %zu.", + mSubHalIndex); + } else { + ALOG_ASSERT(!wakelock.isLocked(), + "No Wakeup events posted but wakelock locked for subhal" + " w/ index %zu.", + mSubHalIndex); + } + mHalProxy->postEventsToMessageQueue(events, numWakeupEvents, std::move(wakelock)); +} + +ScopedWakelock HalProxyCallback::createScopedWakelock(bool lock) { + ScopedWakelock wakelock(mHalProxy, lock); + return wakelock; +} + +std::vector<Event> HalProxyCallback::processEvents(const std::vector<Event>& events, + size_t* numWakeupEvents) const { + *numWakeupEvents = 0; + std::vector<Event> eventsOut; + for (Event event : events) { + event.sensorHandle = setSubHalIndex(event.sensorHandle, mSubHalIndex); + eventsOut.push_back(event); + const SensorInfo& sensor = mHalProxy->getSensorInfo(event.sensorHandle); + if ((sensor.flags & V1_0::SensorFlagBits::WAKE_UP) != 0) { + (*numWakeupEvents)++; + } + } + return eventsOut; +} + } // namespace implementation } // namespace V2_0 } // namespace sensors diff --git a/sensors/2.0/multihal/ScopedWakelock.cpp b/sensors/2.0/multihal/ScopedWakelock.cpp new file mode 100644 index 0000000000..d85d4a7891 --- /dev/null +++ b/sensors/2.0/multihal/ScopedWakelock.cpp @@ -0,0 +1,48 @@ +/* + * 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. + */ + +#include "ScopedWakelock.h" + +namespace android { +namespace hardware { +namespace sensors { +namespace V2_0 { +namespace implementation { + +int64_t getTimeNow() { + return std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::system_clock::now().time_since_epoch()) + .count(); +} + +ScopedWakelock::ScopedWakelock(IScopedWakelockRefCounter* refCounter, bool locked) + : mRefCounter(refCounter), mLocked(locked) { + if (mLocked) { + mLocked = mRefCounter->incrementRefCountAndMaybeAcquireWakelock(1, &mCreatedAtTimeNs); + } +} + +ScopedWakelock::~ScopedWakelock() { + if (mLocked) { + mRefCounter->decrementRefCountAndMaybeReleaseWakelock(1, mCreatedAtTimeNs); + } +} + +} // namespace implementation +} // namespace V2_0 +} // namespace sensors +} // namespace hardware +} // namespace android
\ No newline at end of file diff --git a/sensors/2.0/multihal/android.hardware.sensors@2.0-service-multihal.rc b/sensors/2.0/multihal/android.hardware.sensors@2.0-service-multihal.rc index 167168919a..a4da3b084b 100644 --- a/sensors/2.0/multihal/android.hardware.sensors@2.0-service-multihal.rc +++ b/sensors/2.0/multihal/android.hardware.sensors@2.0-service-multihal.rc @@ -1,6 +1,7 @@ service vendor.sensors-hal-2-0-multihal /vendor/bin/hw/android.hardware.sensors@2.0-service.multihal class hal user system - group system + group system wakelock + writepid /dev/cpuset/system-background/tasks capabilities BLOCK_SUSPEND rlimit rtprio 10 10 diff --git a/sensors/2.0/multihal/include/HalProxy.h b/sensors/2.0/multihal/include/HalProxy.h index 4ecb58b5a5..b1dd737025 100644 --- a/sensors/2.0/multihal/include/HalProxy.h +++ b/sensors/2.0/multihal/include/HalProxy.h @@ -16,14 +16,24 @@ #pragma once +#include "ScopedWakelock.h" #include "SubHal.h" #include <android/hardware/sensors/2.0/ISensors.h> +#include <android/hardware/sensors/2.0/types.h> #include <fmq/MessageQueue.h> #include <hardware_legacy/power.h> #include <hidl/MQDescriptor.h> #include <hidl/Status.h> +#include <atomic> +#include <condition_variable> +#include <map> +#include <mutex> +#include <queue> +#include <thread> +#include <utility> + namespace android { namespace hardware { namespace sensors { @@ -39,13 +49,15 @@ using ::android::hardware::MQDescriptor; using ::android::hardware::Return; using ::android::hardware::Void; -class HalProxy : public ISensors { +class HalProxy : public ISensors, public IScopedWakelockRefCounter { public: using Event = ::android::hardware::sensors::V1_0::Event; using OperationMode = ::android::hardware::sensors::V1_0::OperationMode; using RateLevel = ::android::hardware::sensors::V1_0::RateLevel; using Result = ::android::hardware::sensors::V1_0::Result; + using SensorInfo = ::android::hardware::sensors::V1_0::SensorInfo; using SharedMemInfo = ::android::hardware::sensors::V1_0::SharedMemInfo; + using ISensorsSubHal = ::android::hardware::sensors::V2_0::implementation::ISensorsSubHal; explicit HalProxy(); // Test only constructor. @@ -91,6 +103,37 @@ class HalProxy : public ISensors { Return<void> onDynamicSensorsDisconnected(const hidl_vec<int32_t>& dynamicSensorHandlesRemoved, int32_t subHalIndex); + // Below methods are for HalProxyCallback + + /** + * Post events to the event message queue if there is room to write them. Otherwise post the + * remaining events to a background thread for a blocking write with a kPendingWriteTimeoutNs + * timeout. + * + * @param events The list of events to post to the message queue. + * @param numWakeupEvents The number of wakeup events in events. + * @param wakelock The wakelock associated with this post of events. + */ + void postEventsToMessageQueue(const std::vector<Event>& events, size_t numWakeupEvents, + ScopedWakelock wakelock); + + /** + * Get the sensor info associated with that sensorHandle. + * + * @param sensorHandle The sensor handle. + * + * @return The sensor info object in the mapping. + */ + const SensorInfo& getSensorInfo(int32_t sensorHandle) { return mSensors[sensorHandle]; } + + bool areThreadsRunning() { return mThreadsRun.load(); } + + // Below methods are from IScopedWakelockRefCounter interface + bool incrementRefCountAndMaybeAcquireWakelock(size_t delta, + int64_t* timeoutStart = nullptr) override; + + void decrementRefCountAndMaybeReleaseWakelock(size_t delta, int64_t timeoutStart = -1) override; + private: using EventMessageQueue = MessageQueue<Event, kSynchronizedReadWrite>; using WakeLockMessageQueue = MessageQueue<uint32_t, kSynchronizedReadWrite>; @@ -106,9 +149,13 @@ class HalProxy : public ISensors { std::unique_ptr<WakeLockMessageQueue> mWakeLockQueue; /** - * Event Flag to signal to the framework when sensor events are available to be read + * Event Flag to signal to the framework when sensor events are available to be read and to + * interrupt event queue blocking write. */ - EventFlag* mEventQueueFlag; + EventFlag* mEventQueueFlag = nullptr; + + //! Event Flag to signal internally that the wakelock queue should stop its blocking read. + EventFlag* mWakelockQueueFlag = nullptr; /** * Callback to the sensors framework to inform it that new sensors have been added or removed. @@ -120,14 +167,20 @@ class HalProxy : public ISensors { */ std::vector<ISensorsSubHal*> mSubHalList; + //! The list of subhal callbacks for each subhal where the indices correlate with mSubHalList + std::vector<const sp<IHalProxyCallback>> mSubHalCallbacks; + /** - * List of SensorInfo objects that contains the sensor info from subhals as + * Map of sensor handles to SensorInfo objects that contains the sensor info from subhals as * well as the modified sensor handle for the framework. * - * The subhal index is encoded in the first byte of the sensor handle and - * the remaining bytes are generated by the subhal to identify the sensor. + * The subhal index is encoded in the first byte of the sensor handle and the remaining + * bytes are generated by the subhal to identify the sensor. */ - std::vector<SensorInfo> mSensorList; + std::map<int32_t, SensorInfo> mSensors; + + //! Map of the dynamic sensors that have been added to halproxy. + std::map<int32_t, SensorInfo> mDynamicSensors; //! The current operation mode for all subhals. OperationMode mCurrentOperationMode = OperationMode::NORMAL; @@ -135,8 +188,52 @@ class HalProxy : public ISensors { //! The single subHal that supports directChannel reporting. ISensorsSubHal* mDirectChannelSubHal = nullptr; + //! The timeout for each pending write on background thread for events. + static const int64_t kPendingWriteTimeoutNs = 5 * INT64_C(1000000000) /* 5 seconds */; + //! The bit mask used to get the subhal index from a sensor handle. - static constexpr uint32_t kSensorHandleSubHalIndexMask = 0xFF000000; + static constexpr int32_t kSensorHandleSubHalIndexMask = 0xFF000000; + + /** + * A FIFO queue of pairs of vector of events and the number of wakeup events in that vector + * which are waiting to be written to the events fmq in the background thread. + */ + std::queue<std::pair<std::vector<Event>, size_t>> mPendingWriteEventsQueue; + + //! The mutex protecting writing to the fmq and the pending events queue + std::mutex mEventQueueWriteMutex; + + //! The condition variable waiting on pending write events to stack up + std::condition_variable mEventQueueWriteCV; + + //! The thread object ptr that handles pending writes + std::thread mPendingWritesThread; + + //! The thread object that handles wakelocks + std::thread mWakelockThread; + + //! The bool indicating whether to end the threads started in initialize + std::atomic_bool mThreadsRun = true; + + //! The mutex protecting access to the dynamic sensors added and removed methods. + std::mutex mDynamicSensorsMutex; + + // WakelockRefCount membar vars below + + //! The mutex protecting the wakelock refcount and subsequent wakelock releases and + //! acquisitions + std::recursive_mutex mWakelockMutex; + + std::condition_variable_any mWakelockCV; + + //! The refcount of how many ScopedWakelocks and pending wakeup events are active + size_t mWakelockRefCount = 0; + + int64_t mWakelockTimeoutStartTime = getTimeNow(); + + int64_t mWakelockTimeoutResetTime = getTimeNow(); + + const char* kWakelockName = "SensorsHAL_WAKEUP"; /** * Initialize the list of SubHal objects in mSubHalList by reading from dynamic libraries @@ -145,12 +242,67 @@ class HalProxy : public ISensors { void initializeSubHalListFromConfigFile(const char* configFileName); /** + * Initialize the HalProxyCallback vector using the list of subhals. + */ + void initializeSubHalCallbacks(); + + /** * Initialize the list of SensorInfo objects in mSensorList by getting sensors from each * subhal. */ void initializeSensorList(); /** + * Calls the helper methods that all ctors use. + */ + void init(); + + /** + * Stops all threads by setting the threads running flag to false and joining to them. + */ + void stopThreads(); + + /** + * Disable all the sensors observed by the HalProxy. + */ + void disableAllSensors(); + + /** + * Starts the thread that handles pending writes to event fmq. + * + * @param halProxy The HalProxy object pointer. + */ + static void startPendingWritesThread(HalProxy* halProxy); + + //! Handles the pending writes on events to eventqueue. + void handlePendingWrites(); + + /** + * Starts the thread that handles decrementing the ref count on wakeup events processed by the + * framework and timing out wakelocks. + * + * @param halProxy The HalProxy object pointer. + */ + static void startWakelockThread(HalProxy* halProxy); + + //! Handles the wakelocks. + void handleWakelocks(); + + /** + * @param timeLeft The variable that should be set to the timeleft before timeout will occur or + * unmodified if timeout occurred. + * + * @return true if the shared wakelock has been held passed the timeout and should be released + */ + bool sharedWakelockDidTimeout(int64_t* timeLeft); + + /** + * Reset all the member variables associated with the wakelock ref count and maybe release + * the shared wakelock. + */ + void resetSharedWakelock(); + + /** * Clear direct channel flags if the HalProxy has already chosen a subhal as its direct channel * subhal. Set the directChannelSubHal pointer to the subHal passed in if this is the first * direct channel enabled sensor seen. @@ -167,7 +319,26 @@ class HalProxy : public ISensors { * * @param sensorHandle The handle used to identify a sensor in one of the subhals. */ - ISensorsSubHal* getSubHalForSensorHandle(uint32_t sensorHandle); + ISensorsSubHal* getSubHalForSensorHandle(int32_t sensorHandle); + + /** + * Checks that sensorHandle's subhal index byte is within bounds of mSubHalList. + * + * @param sensorHandle The sensor handle to check. + * + * @return true if sensorHandles's subhal index byte is valid. + */ + bool isSubHalIndexValid(int32_t sensorHandle); + + /** + * Count the number of wakeup events in the first n events of the vector. + * + * @param events The vector of Event objects. + * @param n The end index not inclusive of events to consider. + * + * @return The number of wakeup events of the considered events. + */ + size_t countNumWakeupEvents(const std::vector<Event>& events, size_t n); /* * Clear out the subhal index bytes from a sensorHandle. @@ -176,7 +347,46 @@ class HalProxy : public ISensors { * * @return The modified version of the sensor handle. */ - static uint32_t clearSubHalIndex(uint32_t sensorHandle); + static int32_t clearSubHalIndex(int32_t sensorHandle); + + /** + * @param sensorHandle The sensor handle to modify. + * + * @return true if subHalIndex byte of sensorHandle is zeroed. + */ + static bool subHalIndexIsClear(int32_t sensorHandle); +}; + +/** + * Callback class used to provide the HalProxy with the index of which subHal is invoking + */ +class HalProxyCallback : public IHalProxyCallback { + using SensorInfo = ::android::hardware::sensors::V1_0::SensorInfo; + + public: + HalProxyCallback(HalProxy* halProxy, int32_t subHalIndex) + : mHalProxy(halProxy), mSubHalIndex(subHalIndex) {} + + Return<void> onDynamicSensorsConnected( + const hidl_vec<SensorInfo>& dynamicSensorsAdded) override { + return mHalProxy->onDynamicSensorsConnected(dynamicSensorsAdded, mSubHalIndex); + } + + Return<void> onDynamicSensorsDisconnected( + const hidl_vec<int32_t>& dynamicSensorHandlesRemoved) override { + return mHalProxy->onDynamicSensorsDisconnected(dynamicSensorHandlesRemoved, mSubHalIndex); + } + + void postEvents(const std::vector<Event>& events, ScopedWakelock wakelock); + + ScopedWakelock createScopedWakelock(bool lock); + + private: + HalProxy* mHalProxy; + int32_t mSubHalIndex; + + std::vector<Event> processEvents(const std::vector<Event>& events, + size_t* numWakeupEvents) const; }; } // namespace implementation diff --git a/sensors/2.0/multihal/include/ScopedWakelock.h b/sensors/2.0/multihal/include/ScopedWakelock.h new file mode 100644 index 0000000000..aa6d9db3d4 --- /dev/null +++ b/sensors/2.0/multihal/include/ScopedWakelock.h @@ -0,0 +1,104 @@ +/* + * 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. + */ + +#pragma once + +#include <android/hardware/sensors/2.0/types.h> + +namespace android { +namespace hardware { +namespace sensors { +namespace V2_0 { +namespace implementation { + +using ::android::hardware::sensors::V2_0::SensorTimeout; + +const int64_t kWakelockTimeoutNs = + static_cast<int64_t>(SensorTimeout::WAKE_LOCK_SECONDS) * INT64_C(1000000000); + +int64_t getTimeNow(); + +class IScopedWakelockRefCounter : public RefBase { + public: + /** + * Increment the wakelock ref count and maybe acquire the shared wakelock if incrementing + * from 0 then return the time of incrementing back to caller. + * + * @param delta The amount to change ref count by. + * @param timeoutStart The ptr to the timestamp in ns that the increment occurred which will be + * set in the function or nullptr if not specified. + * + * @return true if successfully incremented the wakelock ref count. + */ + virtual bool incrementRefCountAndMaybeAcquireWakelock(size_t delta, + int64_t* timeoutStart = nullptr) = 0; + /** + * Decrement the wakelock ref count and maybe release wakelock if ref count ends up 0. + * + * @param delta The amount to change ref count by. + * @param timeoutStart The timestamp in ns that the calling context kept track of when + * incrementing the ref count or -1 by default + */ + virtual void decrementRefCountAndMaybeReleaseWakelock(size_t delta, + int64_t timeoutStart = -1) = 0; + // Virtual dtor needed for compilation success + virtual ~IScopedWakelockRefCounter(){}; +}; + +/** + * Wrapper around wake lock acquisition functions (acquire/release_wake_lock) that provides a + * RAII-style mechanism for keeping a wake lock held for the duration of a scoped block. + * When a ScopedWakelock is created, it increments the reference count stored in the HalProxy + * for the sub-HALs specific wake lock, acquiring the wake lock if necessary. When the object goes + * out of scope, the ref count is decremented, potentially releasing the wake lock if no other + * references to the wake lock exist. + * + * This class is allocated through the createScopedWakelock callback inside the IHalProxyCallback + * provided to sub-HALs during initialization and should be used for all wake lock acquisition + * inside of the sub-HAL to ensure wake locks are not held indefinitely. + * + * The most prevalent use case for this class will be for posting events to the framework through + * the postEvents HalProxy callback. The expectation is that sub-HALs will create this + * ScopedWakelock through the createScopedWakelock upon receiving a sensor events. The lock boolean + * provided to createScopedWakelock will be set the according to whether the sensor events are + * from wakeup sensors. Then, the sub-HAL will perform any processing necessary before invoking the + * postEvents callback passing in the previously created ScopedWakelock. At this point, ownership + * of the object will be passed to the HalProxy that will then be responsible for ensuring any + * wake locks continue to be held, if necessary. + */ +class ScopedWakelock { + public: + ScopedWakelock(ScopedWakelock&&) = default; + ScopedWakelock& operator=(ScopedWakelock&&) = default; + virtual ~ScopedWakelock(); + + bool isLocked() const { return mLocked; } + + private: + friend class HalProxyCallback; + IScopedWakelockRefCounter* mRefCounter; + int64_t mCreatedAtTimeNs; + bool mLocked; + ScopedWakelock(IScopedWakelockRefCounter* refCounter, bool locked); + ScopedWakelock(const ScopedWakelock&) = delete; + ScopedWakelock& operator=(const ScopedWakelock&) = delete; +}; + +} // namespace implementation +} // namespace V2_0 +} // namespace sensors +} // namespace hardware +} // namespace android
\ No newline at end of file diff --git a/sensors/2.0/multihal/include/SubHal.h b/sensors/2.0/multihal/include/SubHal.h index e84cba50f6..92ae3a61df 100644 --- a/sensors/2.0/multihal/include/SubHal.h +++ b/sensors/2.0/multihal/include/SubHal.h @@ -16,15 +16,13 @@ #pragma once +#include "ScopedWakelock.h" + #include <android/hardware/sensors/1.0/types.h> #include <android/hardware/sensors/2.0/ISensors.h> #include <vector> -using ::android::hardware::sensors::V1_0::Event; -using ::android::hardware::sensors::V1_0::Result; -using ::android::hardware::sensors::V1_0::SensorInfo; - // Indicates the current version of the multiHAL interface formatted as (HAL major version) << 24 | // (HAL minor version) << 16 | (multiHAL version) #define SUB_HAL_2_0_VERSION 0x02000000 @@ -35,44 +33,9 @@ namespace sensors { namespace V2_0 { namespace implementation { -/** - * Wrapper around wake lock acquisition functions (acquire/release_wake_lock) that provides a - * RAII-style mechanism for keeping a wake lock held for the duration of a scoped block. - * When a ScopedWakelock is created, it increments the reference count stored in the HalProxy - * for the sub-HALs specific wake lock, acquiring the wake lock if necessary. When the object goes - * out of scope, the ref count is decremented, potentially releasing the wake lock if no other - * references to the wake lock exist. - * - * This class is allocated through the createScopedWakelock callback inside the IHalProxyCallback - * provided to sub-HALs during initialization and should be used for all wake lock acquisition - * inside of the sub-HAL to ensure wake locks are not held indefinitely. - * - * The most prevalent use case for this class will be for posting events to the framework through - * the postEvents HalProxy callback. The expectation is that sub-HALs will create this - * ScopedWakelock through the createScopedWakelock upon receiving a sensor events. The lock boolean - * provided to createScopedWakelock will be set the according to whether the sensor events are - * from wakeup sensors. Then, the sub-HAL will perform any processing necessary before invoking the - * postEvents callback passing in the previously created ScopedWakelock. At this point, ownership - * of the object will be passed to the HalProxy that will then be responsible for ensuring any - * wake locks continue to be held, if necessary. - */ -class ScopedWakelock { - public: - ScopedWakelock(ScopedWakelock&&) = default; - ScopedWakelock& operator=(ScopedWakelock&&) = default; - virtual ~ScopedWakelock() { mLocked = false; }; - - bool isLocked() const { return mLocked; } - - protected: - bool mLocked; - - private: - // TODO: Mark HalProxy's subclass of ScopedWakelock as a friend so that it can be initialized. - ScopedWakelock(); - ScopedWakelock(const ScopedWakelock&) = delete; - ScopedWakelock& operator=(const ScopedWakelock&) = delete; -}; +using ::android::hardware::sensors::V1_0::Event; +using ::android::hardware::sensors::V1_0::Result; +using ::android::hardware::sensors::V1_0::SensorInfo; /** * Interface that contains several callbacks into the HalProxy class to communicate dynamic sensor @@ -167,9 +130,13 @@ class ISensorsSubHal : public ISensors { virtual const std::string getName() = 0; /** - * First method invoked on the sub-HAL after it's allocated through sensorsHalGetSubHal() by the - * HalProxy. Sub-HALs should use this to initialize any state and retain the callback given in - * order to communicate with the HalProxy. + * This is the first method invoked on the sub-HAL after it's allocated through + * sensorsHalGetSubHal() by the HalProxy. Sub-HALs should use this to initialize any state and + * retain the callback given in order to communicate with the HalProxy. Method will be called + * anytime the sensors framework restarts. Therefore, this method will be responsible for + * reseting the state of the subhal and cleaning up and reallocating any previously allocated + * data. Initialize should ensure that the subhal has reset its operation mode to NORMAL state + * as well. * * @param halProxyCallback callback used to inform the HalProxy when a dynamic sensor's state * changes, new sensor events should be sent to the framework, and when a new ScopedWakelock diff --git a/sensors/2.0/multihal/tests/Android.bp b/sensors/2.0/multihal/tests/Android.bp index 21ceb0c2d7..50a55f946e 100644 --- a/sensors/2.0/multihal/tests/Android.bp +++ b/sensors/2.0/multihal/tests/Android.bp @@ -28,11 +28,16 @@ cc_defaults { "libcutils", "libfmq", "libhidlbase", - "libhidltransport", "liblog", "libpower", "libutils", ], + static_libs: [ + "android.hardware.sensors@2.0-HalProxy", + ], + cflags: [ + "-DLOG_TAG=\"FakeSubHal\"" + ], } cc_library { @@ -77,13 +82,16 @@ cc_test { shared_libs: [ "android.hardware.sensors@1.0", "android.hardware.sensors@2.0", + "libbase", "libcutils", "libfmq", "libhidlbase", - "libhidltransport", "liblog", "libpower", "libutils", ], test_suites: ["device-tests"], + cflags: [ + "-DLOG_TAG=\"HalProxyUnitTests\"", + ], } diff --git a/sensors/2.0/multihal/tests/HalProxy_test.cpp b/sensors/2.0/multihal/tests/HalProxy_test.cpp index 1e1f9e9e44..75a4c22d11 100644 --- a/sensors/2.0/multihal/tests/HalProxy_test.cpp +++ b/sensors/2.0/multihal/tests/HalProxy_test.cpp @@ -16,18 +16,33 @@ #include <gtest/gtest.h> #include <android/hardware/sensors/2.0/types.h> +#include <fmq/MessageQueue.h> #include "HalProxy.h" +#include "ScopedWakelock.h" #include "SensorsSubHal.h" +#include <chrono> +#include <set> +#include <thread> #include <vector> namespace { +using ::android::hardware::EventFlag; +using ::android::hardware::hidl_vec; +using ::android::hardware::MessageQueue; +using ::android::hardware::Return; +using ::android::hardware::sensors::V1_0::EventPayload; using ::android::hardware::sensors::V1_0::SensorFlagBits; using ::android::hardware::sensors::V1_0::SensorInfo; using ::android::hardware::sensors::V1_0::SensorType; +using ::android::hardware::sensors::V2_0::EventQueueFlagBits; +using ::android::hardware::sensors::V2_0::ISensorsCallback; +using ::android::hardware::sensors::V2_0::WakeLockQueueFlagBits; using ::android::hardware::sensors::V2_0::implementation::HalProxy; +using ::android::hardware::sensors::V2_0::implementation::HalProxyCallback; +using ::android::hardware::sensors::V2_0::subhal::implementation::AddAndRemoveDynamicSensorsSubHal; using ::android::hardware::sensors::V2_0::subhal::implementation::AllSensorsSubHal; using ::android::hardware::sensors::V2_0::subhal::implementation:: AllSupportDirectChannelSensorsSubHal; @@ -36,10 +51,56 @@ using ::android::hardware::sensors::V2_0::subhal::implementation:: DoesNotSupportDirectChannelSensorsSubHal; using ::android::hardware::sensors::V2_0::subhal::implementation::OnChangeSensorsSubHal; using ::android::hardware::sensors::V2_0::subhal::implementation::SensorsSubHal; - using ::android::hardware::sensors::V2_0::subhal::implementation:: SetOperationModeFailingSensorsSubHal; +using EventMessageQueue = MessageQueue<Event, ::android::hardware::kSynchronizedReadWrite>; +using WakeupMessageQueue = MessageQueue<uint32_t, ::android::hardware::kSynchronizedReadWrite>; + +// The barebones sensors callback class passed into halproxy initialize calls +class SensorsCallback : public ISensorsCallback { + public: + Return<void> onDynamicSensorsConnected( + const hidl_vec<SensorInfo>& /*dynamicSensorsAdded*/) override { + // Nothing yet + return Return<void>(); + } + + Return<void> onDynamicSensorsDisconnected( + const hidl_vec<int32_t>& /*dynamicSensorHandlesRemoved*/) override { + // Nothing yet + return Return<void>(); + } +}; + +// The sensors callback that expects a variable list of sensors to be added +class TestSensorsCallback : public ISensorsCallback { + public: + Return<void> onDynamicSensorsConnected( + const hidl_vec<SensorInfo>& dynamicSensorsAdded) override { + mSensorsConnected.insert(mSensorsConnected.end(), dynamicSensorsAdded.begin(), + dynamicSensorsAdded.end()); + return Return<void>(); + } + + Return<void> onDynamicSensorsDisconnected( + const hidl_vec<int32_t>& dynamicSensorHandlesRemoved) override { + mSensorHandlesDisconnected.insert(mSensorHandlesDisconnected.end(), + dynamicSensorHandlesRemoved.begin(), + dynamicSensorHandlesRemoved.end()); + return Return<void>(); + } + + const std::vector<SensorInfo>& getSensorsConnected() const { return mSensorsConnected; } + const std::vector<int32_t>& getSensorHandlesDisconnected() const { + return mSensorHandlesDisconnected; + } + + private: + std::vector<SensorInfo> mSensorsConnected; + std::vector<int32_t> mSensorHandlesDisconnected; +}; + // Helper declarations follow /** @@ -65,6 +126,66 @@ void testSensorsListFromProxyAndSubHal(const std::vector<SensorInfo>& proxySenso void testSensorsListForOneDirectChannelEnabledSubHal(const std::vector<SensorInfo>& sensorsList, size_t enabledSubHalIndex); +void ackWakeupEventsToHalProxy(size_t numEvents, std::unique_ptr<WakeupMessageQueue>& wakelockQueue, + EventFlag* wakelockQueueFlag); + +bool readEventsOutOfQueue(size_t numEvents, std::unique_ptr<EventMessageQueue>& eventQueue, + EventFlag* eventQueueFlag); + +std::unique_ptr<EventMessageQueue> makeEventFMQ(size_t size); + +std::unique_ptr<WakeupMessageQueue> makeWakelockFMQ(size_t size); + +/** + * Construct and return a HIDL Event type thats sensorHandle refers to a proximity sensor + * which is a wakeup type sensor. + * + * @return A proximity event. + */ +Event makeProximityEvent(); + +/** + * Construct and return a HIDL Event type thats sensorHandle refers to a proximity sensor + * which is a wakeup type sensor. + * + * @return A proximity event. + */ +Event makeAccelerometerEvent(); + +/** + * Make a certain number of proximity type events with the sensorHandle field set to + * the proper number for AllSensorsSubHal subhal type. + * + * @param numEvents The number of events to make. + * + * @return The created list of events. + */ +std::vector<Event> makeMultipleProximityEvents(size_t numEvents); + +/** + * Make a certain number of accelerometer type events with the sensorHandle field set to + * the proper number for AllSensorsSubHal subhal type. + * + * @param numEvents The number of events to make. + * + * @return The created list of events. + */ +std::vector<Event> makeMultipleAccelerometerEvents(size_t numEvents); + +/** + * Given a SensorInfo vector and a sensor handles vector populate 'sensors' with SensorInfo + * objects that have the sensorHandle property set to int32_ts from start to start + size + * (exclusive) and push those sensorHandles also onto 'sensorHandles'. + * + * @param start The starting sensorHandle value. + * @param size The ending (not included) sensorHandle value. + * @param sensors The SensorInfo object vector reference to push_back to. + * @param sensorHandles The sensor handles int32_t vector reference to push_back to. + */ +void makeSensorsAndSensorHandlesStartingAndOfSize(int32_t start, size_t size, + std::vector<SensorInfo>& sensors, + std::vector<int32_t>& sensorHandles); + // Tests follow TEST(HalProxyTest, GetSensorsListOneSubHalTest) { AllSensorsSubHal subHal; @@ -156,6 +277,421 @@ TEST(HalProxyTest, InitDirectChannelThreeSubHalsUnitTest) { }); } +TEST(HalProxyTest, PostSingleNonWakeupEvent) { + constexpr size_t kQueueSize = 5; + AllSensorsSubHal subHal; + std::vector<ISensorsSubHal*> subHals{&subHal}; + HalProxy proxy(subHals); + std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize); + std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize); + ::android::sp<ISensorsCallback> callback = new SensorsCallback(); + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback); + + std::vector<Event> events{makeAccelerometerEvent()}; + subHal.postEvents(events, false /* wakeup */); + + EXPECT_EQ(eventQueue->availableToRead(), 1); +} + +TEST(HalProxyTest, PostMultipleNonWakeupEvent) { + constexpr size_t kQueueSize = 5; + constexpr size_t kNumEvents = 3; + AllSensorsSubHal subHal; + std::vector<ISensorsSubHal*> subHals{&subHal}; + HalProxy proxy(subHals); + std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize); + std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize); + ::android::sp<ISensorsCallback> callback = new SensorsCallback(); + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback); + + std::vector<Event> events = makeMultipleAccelerometerEvents(kNumEvents); + subHal.postEvents(events, false /* wakeup */); + + EXPECT_EQ(eventQueue->availableToRead(), kNumEvents); +} + +TEST(HalProxyTest, PostSingleWakeupEvent) { + constexpr size_t kQueueSize = 5; + AllSensorsSubHal subHal; + std::vector<ISensorsSubHal*> subHals{&subHal}; + HalProxy proxy(subHals); + std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize); + std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize); + ::android::sp<ISensorsCallback> callback = new SensorsCallback(); + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback); + + EventFlag* eventQueueFlag; + EventFlag::createEventFlag(eventQueue->getEventFlagWord(), &eventQueueFlag); + + EventFlag* wakelockQueueFlag; + EventFlag::createEventFlag(wakeLockQueue->getEventFlagWord(), &wakelockQueueFlag); + + std::vector<Event> events{makeProximityEvent()}; + subHal.postEvents(events, true /* wakeup */); + + EXPECT_EQ(eventQueue->availableToRead(), 1); + + readEventsOutOfQueue(1, eventQueue, eventQueueFlag); + ackWakeupEventsToHalProxy(1, wakeLockQueue, wakelockQueueFlag); +} + +TEST(HalProxyTest, PostMultipleWakeupEvents) { + constexpr size_t kQueueSize = 5; + constexpr size_t kNumEvents = 3; + AllSensorsSubHal subHal; + std::vector<ISensorsSubHal*> subHals{&subHal}; + HalProxy proxy(subHals); + std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize); + std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize); + ::android::sp<ISensorsCallback> callback = new SensorsCallback(); + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback); + + EventFlag* eventQueueFlag; + EventFlag::createEventFlag(eventQueue->getEventFlagWord(), &eventQueueFlag); + + EventFlag* wakelockQueueFlag; + EventFlag::createEventFlag(wakeLockQueue->getEventFlagWord(), &wakelockQueueFlag); + + std::vector<Event> events = makeMultipleProximityEvents(kNumEvents); + subHal.postEvents(events, true /* wakeup */); + + EXPECT_EQ(eventQueue->availableToRead(), kNumEvents); + + readEventsOutOfQueue(kNumEvents, eventQueue, eventQueueFlag); + ackWakeupEventsToHalProxy(kNumEvents, wakeLockQueue, wakelockQueueFlag); +} + +TEST(HalProxyTest, PostEventsMultipleSubhals) { + constexpr size_t kQueueSize = 5; + constexpr size_t kNumEvents = 2; + AllSensorsSubHal subHal1, subHal2; + std::vector<ISensorsSubHal*> subHals{&subHal1, &subHal2}; + HalProxy proxy(subHals); + std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize); + std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize); + ::android::sp<ISensorsCallback> callback = new SensorsCallback(); + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback); + + std::vector<Event> events = makeMultipleAccelerometerEvents(kNumEvents); + subHal1.postEvents(events, false /* wakeup */); + + EXPECT_EQ(eventQueue->availableToRead(), kNumEvents); + + subHal2.postEvents(events, false /* wakeup */); + + EXPECT_EQ(eventQueue->availableToRead(), kNumEvents * 2); +} + +TEST(HalProxyTest, PostEventsDelayedWrite) { + constexpr size_t kQueueSize = 5; + constexpr size_t kNumEvents = 6; + AllSensorsSubHal subHal1, subHal2; + std::vector<ISensorsSubHal*> subHals{&subHal1, &subHal2}; + HalProxy proxy(subHals); + std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize); + std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize); + ::android::sp<ISensorsCallback> callback = new SensorsCallback(); + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback); + + EventFlag* eventQueueFlag; + EventFlag::createEventFlag(eventQueue->getEventFlagWord(), &eventQueueFlag); + + std::vector<Event> events = makeMultipleAccelerometerEvents(kNumEvents); + subHal1.postEvents(events, false /* wakeup */); + + EXPECT_EQ(eventQueue->availableToRead(), kQueueSize); + + // readblock a full queue size worth of events out of queue, timeout for half a second + EXPECT_TRUE(readEventsOutOfQueue(kQueueSize, eventQueue, eventQueueFlag)); + + // proxy background thread should have wrote remaining events when it saw space + EXPECT_TRUE(readEventsOutOfQueue(kNumEvents - kQueueSize, eventQueue, eventQueueFlag)); + + EXPECT_EQ(eventQueue->availableToRead(), 0); +} + +TEST(HalProxyTest, PostEventsMultipleSubhalsThreaded) { + constexpr size_t kQueueSize = 5; + constexpr size_t kNumEvents = 2; + AllSensorsSubHal subHal1, subHal2; + std::vector<ISensorsSubHal*> subHals{&subHal1, &subHal2}; + HalProxy proxy(subHals); + std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize); + std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize); + ::android::sp<ISensorsCallback> callback = new SensorsCallback(); + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback); + + std::vector<Event> events = makeMultipleAccelerometerEvents(kNumEvents); + + std::thread t1(&AllSensorsSubHal::postEvents, &subHal1, events, false); + std::thread t2(&AllSensorsSubHal::postEvents, &subHal2, events, false); + + t1.join(); + t2.join(); + + EXPECT_EQ(eventQueue->availableToRead(), kNumEvents * 2); +} + +TEST(HalProxyTest, DestructingWithEventsPendingOnBackgroundThread) { + constexpr size_t kQueueSize = 5; + constexpr size_t kNumEvents = 6; + AllSensorsSubHal subHal; + std::vector<ISensorsSubHal*> subHals{&subHal}; + + std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize); + std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize); + ::android::sp<ISensorsCallback> callback = new SensorsCallback(); + HalProxy proxy(subHals); + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback); + + std::vector<Event> events = makeMultipleAccelerometerEvents(kNumEvents); + subHal.postEvents(events, false /* wakeup */); + + // Destructing HalProxy object with events on the background thread +} + +TEST(HalProxyTest, DestructingWithUnackedWakeupEventsPosted) { + constexpr size_t kQueueSize = 5; + AllSensorsSubHal subHal; + std::vector<ISensorsSubHal*> subHals{&subHal}; + + std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize); + std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize); + ::android::sp<ISensorsCallback> callback = new SensorsCallback(); + HalProxy proxy(subHals); + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback); + + std::vector<Event> events{makeProximityEvent()}; + subHal.postEvents(events, true /* wakeup */); + + // Not sending any acks back through wakeLockQueue + + // Destructing HalProxy object with unacked wakeup events posted +} + +TEST(HalProxyTest, ReinitializeWithEventsPendingOnBackgroundThread) { + constexpr size_t kQueueSize = 5; + constexpr size_t kNumEvents = 10; + AllSensorsSubHal subHal; + std::vector<ISensorsSubHal*> subHals{&subHal}; + + std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize); + std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize); + ::android::sp<ISensorsCallback> callback = new SensorsCallback(); + HalProxy proxy(subHals); + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback); + + std::vector<Event> events = makeMultipleAccelerometerEvents(kNumEvents); + subHal.postEvents(events, false /* wakeup */); + + eventQueue = makeEventFMQ(kQueueSize); + wakeLockQueue = makeWakelockFMQ(kQueueSize); + + Result secondInitResult = + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback); + EXPECT_EQ(secondInitResult, Result::OK); + // Small sleep so that pending writes thread has a change to hit writeBlocking call. + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + Event eventOut; + EXPECT_FALSE(eventQueue->read(&eventOut)); +} + +TEST(HalProxyTest, ReinitializingWithUnackedWakeupEventsPosted) { + constexpr size_t kQueueSize = 5; + AllSensorsSubHal subHal; + std::vector<ISensorsSubHal*> subHals{&subHal}; + + std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize); + std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize); + ::android::sp<ISensorsCallback> callback = new SensorsCallback(); + HalProxy proxy(subHals); + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback); + + std::vector<Event> events{makeProximityEvent()}; + subHal.postEvents(events, true /* wakeup */); + + // Not sending any acks back through wakeLockQueue + + eventQueue = makeEventFMQ(kQueueSize); + wakeLockQueue = makeWakelockFMQ(kQueueSize); + + Result secondInitResult = + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback); + EXPECT_EQ(secondInitResult, Result::OK); +} + +TEST(HalProxyTest, InitializeManyTimesInARow) { + constexpr size_t kQueueSize = 5; + constexpr size_t kNumTimesToInit = 100; + AllSensorsSubHal subHal; + std::vector<ISensorsSubHal*> subHals{&subHal}; + + std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize); + std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize); + ::android::sp<ISensorsCallback> callback = new SensorsCallback(); + HalProxy proxy(subHals); + + for (size_t i = 0; i < kNumTimesToInit; i++) { + Result secondInitResult = + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback); + EXPECT_EQ(secondInitResult, Result::OK); + } +} + +TEST(HalProxyTest, OperationModeResetOnInitialize) { + constexpr size_t kQueueSize = 5; + AllSensorsSubHal subHal; + std::vector<ISensorsSubHal*> subHals{&subHal}; + std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize); + std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize); + ::android::sp<ISensorsCallback> callback = new SensorsCallback(); + HalProxy proxy(subHals); + proxy.setOperationMode(OperationMode::DATA_INJECTION); + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback); + Event event = makeAccelerometerEvent(); + // Should not be able to inject a non AdditionInfo type event because operation mode should + // have been reset to NORMAL + EXPECT_EQ(proxy.injectSensorData(event), Result::BAD_VALUE); +} + +TEST(HalProxyTest, DynamicSensorsDiscardedOnInitialize) { + constexpr size_t kQueueSize = 5; + constexpr size_t kNumSensors = 5; + AddAndRemoveDynamicSensorsSubHal subHal; + std::vector<ISensorsSubHal*> subHals{&subHal}; + std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize); + std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize); + HalProxy proxy(subHals); + + std::vector<SensorInfo> sensorsToConnect; + std::vector<int32_t> sensorHandlesToAttemptToRemove; + makeSensorsAndSensorHandlesStartingAndOfSize(1, kNumSensors, sensorsToConnect, + sensorHandlesToAttemptToRemove); + + std::vector<int32_t> nonDynamicSensorHandles; + for (int32_t sensorHandle = 1; sensorHandle < 10; sensorHandle++) { + nonDynamicSensorHandles.push_back(sensorHandle); + } + + TestSensorsCallback* callback = new TestSensorsCallback(); + ::android::sp<ISensorsCallback> callbackPtr = callback; + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callbackPtr); + subHal.addDynamicSensors(sensorsToConnect); + + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callbackPtr); + subHal.removeDynamicSensors(sensorHandlesToAttemptToRemove); + + std::vector<int32_t> sensorHandlesActuallyRemoved = callback->getSensorHandlesDisconnected(); + + // Should not have received the sensorHandles for any dynamic sensors that were removed since + // all of them should have been removed in the second initialize call. + EXPECT_TRUE(sensorHandlesActuallyRemoved.empty()); +} + +TEST(HalProxyTest, DynamicSensorsConnectedTest) { + constexpr size_t kNumSensors = 3; + AddAndRemoveDynamicSensorsSubHal subHal; + std::vector<ISensorsSubHal*> subHals{&subHal}; + HalProxy proxy(subHals); + std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(0); + std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(0); + + std::vector<SensorInfo> sensorsToConnect; + std::vector<int32_t> sensorHandlesToExpect; + makeSensorsAndSensorHandlesStartingAndOfSize(1, kNumSensors, sensorsToConnect, + sensorHandlesToExpect); + + TestSensorsCallback* callback = new TestSensorsCallback(); + ::android::sp<ISensorsCallback> callbackPtr = callback; + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callbackPtr); + subHal.addDynamicSensors(sensorsToConnect); + + std::vector<SensorInfo> sensorsSeen = callback->getSensorsConnected(); + EXPECT_EQ(kNumSensors, sensorsSeen.size()); + for (size_t i = 0; i < kNumSensors; i++) { + auto sensorHandleSeen = sensorsSeen[i].sensorHandle; + // Note since only one subhal we do not need to change first byte for expected + auto sensorHandleExpected = sensorHandlesToExpect[i]; + EXPECT_EQ(sensorHandleSeen, sensorHandleExpected); + } +} + +TEST(HalProxyTest, DynamicSensorsDisconnectedTest) { + constexpr size_t kNumSensors = 3; + AddAndRemoveDynamicSensorsSubHal subHal; + std::vector<ISensorsSubHal*> subHals{&subHal}; + HalProxy proxy(subHals); + std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(0); + std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(0); + + std::vector<SensorInfo> sensorsToConnect; + std::vector<int32_t> sensorHandlesToExpect; + makeSensorsAndSensorHandlesStartingAndOfSize(20, kNumSensors, sensorsToConnect, + sensorHandlesToExpect); + + std::vector<int32_t> nonDynamicSensorHandles; + for (int32_t sensorHandle = 1; sensorHandle < 10; sensorHandle++) { + nonDynamicSensorHandles.push_back(sensorHandle); + } + + std::set<int32_t> nonDynamicSensorHandlesSet(nonDynamicSensorHandles.begin(), + nonDynamicSensorHandles.end()); + + std::vector<int32_t> sensorHandlesToAttemptToRemove; + sensorHandlesToAttemptToRemove.insert(sensorHandlesToAttemptToRemove.end(), + sensorHandlesToExpect.begin(), + sensorHandlesToExpect.end()); + sensorHandlesToAttemptToRemove.insert(sensorHandlesToAttemptToRemove.end(), + nonDynamicSensorHandles.begin(), + nonDynamicSensorHandles.end()); + + TestSensorsCallback* callback = new TestSensorsCallback(); + ::android::sp<ISensorsCallback> callbackPtr = callback; + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callbackPtr); + subHal.addDynamicSensors(sensorsToConnect); + subHal.removeDynamicSensors(sensorHandlesToAttemptToRemove); + + std::vector<int32_t> sensorHandlesSeen = callback->getSensorHandlesDisconnected(); + EXPECT_EQ(kNumSensors, sensorHandlesSeen.size()); + for (size_t i = 0; i < kNumSensors; i++) { + auto sensorHandleSeen = sensorHandlesSeen[i]; + // Note since only one subhal we do not need to change first byte for expected + auto sensorHandleExpected = sensorHandlesToExpect[i]; + EXPECT_EQ(sensorHandleSeen, sensorHandleExpected); + EXPECT_TRUE(nonDynamicSensorHandlesSet.find(sensorHandleSeen) == + nonDynamicSensorHandlesSet.end()); + } +} + +TEST(HalProxyTest, InvalidSensorHandleSubHalIndexProxyCalls) { + constexpr size_t kNumSubHals = 3; + constexpr size_t kQueueSize = 5; + int32_t kNumSubHalsInt32 = static_cast<int32_t>(kNumSubHals); + std::vector<AllSensorsSubHal> subHalObjs(kNumSubHals); + std::vector<ISensorsSubHal*> subHals; + for (const auto& subHal : subHalObjs) { + subHals.push_back((ISensorsSubHal*)(&subHal)); + } + + std::unique_ptr<EventMessageQueue> eventQueue = makeEventFMQ(kQueueSize); + std::unique_ptr<WakeupMessageQueue> wakeLockQueue = makeWakelockFMQ(kQueueSize); + ::android::sp<ISensorsCallback> callback = new SensorsCallback(); + HalProxy proxy(subHals); + // Initialize for the injectSensorData call so callback postEvents is valid + proxy.initialize(*eventQueue->getDesc(), *wakeLockQueue->getDesc(), callback); + + // For testing proxy.injectSensorData properly + proxy.setOperationMode(OperationMode::DATA_INJECTION); + + // kNumSubHalsInt32 index is one off the end of mSubHalList in proxy object + EXPECT_EQ(proxy.activate(0x00000001 | (kNumSubHalsInt32 << 24), true), Result::BAD_VALUE); + EXPECT_EQ(proxy.batch(0x00000001 | (kNumSubHalsInt32 << 24), 0, 0), Result::BAD_VALUE); + EXPECT_EQ(proxy.flush(0x00000001 | (kNumSubHalsInt32 << 24)), Result::BAD_VALUE); + Event event; + event.sensorHandle = 0x00000001 | (kNumSubHalsInt32 << 24); + EXPECT_EQ(proxy.injectSensorData(event), Result::BAD_VALUE); +} + // Helper implementations follow void testSensorsListFromProxyAndSubHal(const std::vector<SensorInfo>& proxySensorsList, const std::vector<SensorInfo>& subHalSensorsList) { @@ -187,4 +723,79 @@ void testSensorsListForOneDirectChannelEnabledSubHal(const std::vector<SensorInf } } +void ackWakeupEventsToHalProxy(size_t numEvents, std::unique_ptr<WakeupMessageQueue>& wakelockQueue, + EventFlag* wakelockQueueFlag) { + uint32_t numEventsUInt32 = static_cast<uint32_t>(numEvents); + wakelockQueue->write(&numEventsUInt32); + wakelockQueueFlag->wake(static_cast<uint32_t>(WakeLockQueueFlagBits::DATA_WRITTEN)); +} + +bool readEventsOutOfQueue(size_t numEvents, std::unique_ptr<EventMessageQueue>& eventQueue, + EventFlag* eventQueueFlag) { + constexpr int64_t kReadBlockingTimeout = INT64_C(500000000); + std::vector<Event> events(numEvents); + return eventQueue->readBlocking(events.data(), numEvents, + static_cast<uint32_t>(EventQueueFlagBits::EVENTS_READ), + static_cast<uint32_t>(EventQueueFlagBits::READ_AND_PROCESS), + kReadBlockingTimeout, eventQueueFlag); +} + +std::unique_ptr<EventMessageQueue> makeEventFMQ(size_t size) { + return std::make_unique<EventMessageQueue>(size, true); +} + +std::unique_ptr<WakeupMessageQueue> makeWakelockFMQ(size_t size) { + return std::make_unique<WakeupMessageQueue>(size, true); +} + +Event makeProximityEvent() { + Event event; + event.timestamp = 0xFF00FF00; + // This is the sensorhandle of proximity, which is wakeup type + event.sensorHandle = 0x00000008; + event.sensorType = SensorType::PROXIMITY; + event.u = EventPayload(); + return event; +} + +Event makeAccelerometerEvent() { + Event event; + event.timestamp = 0xFF00FF00; + // This is the sensorhandle of proximity, which is wakeup type + event.sensorHandle = 0x00000001; + event.sensorType = SensorType::ACCELEROMETER; + event.u = EventPayload(); + return event; +} + +std::vector<Event> makeMultipleProximityEvents(size_t numEvents) { + std::vector<Event> events; + for (size_t i = 0; i < numEvents; i++) { + events.push_back(makeProximityEvent()); + } + return events; +} + +std::vector<Event> makeMultipleAccelerometerEvents(size_t numEvents) { + std::vector<Event> events; + for (size_t i = 0; i < numEvents; i++) { + events.push_back(makeAccelerometerEvent()); + } + return events; +} + +void makeSensorsAndSensorHandlesStartingAndOfSize(int32_t start, size_t size, + std::vector<SensorInfo>& sensors, + std::vector<int32_t>& sensorHandles) { + for (int32_t sensorHandle = start; sensorHandle < start + static_cast<int32_t>(size); + sensorHandle++) { + SensorInfo sensor; + // Just set the sensorHandle field to the correct value so as to not have + // to compare every field + sensor.sensorHandle = sensorHandle; + sensors.push_back(sensor); + sensorHandles.push_back(sensorHandle); + } +} + } // namespace diff --git a/sensors/2.0/multihal/tests/fake_subhal/SensorsSubHal.cpp b/sensors/2.0/multihal/tests/fake_subhal/SensorsSubHal.cpp index d581c49a32..0da424622f 100644 --- a/sensors/2.0/multihal/tests/fake_subhal/SensorsSubHal.cpp +++ b/sensors/2.0/multihal/tests/fake_subhal/SensorsSubHal.cpp @@ -158,6 +158,7 @@ Return<void> SensorsSubHal::debug(const hidl_handle& fd, const hidl_vec<hidl_str Return<Result> SensorsSubHal::initialize(const sp<IHalProxyCallback>& halProxyCallback) { mCallback = halProxyCallback; + setOperationMode(OperationMode::NORMAL); return Result::OK; } @@ -221,6 +222,16 @@ Return<void> DoesNotSupportDirectChannelSensorsSubHal::getSensorsList(getSensors return Void(); } +void AddAndRemoveDynamicSensorsSubHal::addDynamicSensors( + const std::vector<SensorInfo>& sensorsAdded) { + mCallback->onDynamicSensorsConnected(sensorsAdded); +} + +void AddAndRemoveDynamicSensorsSubHal::removeDynamicSensors( + const std::vector<int32_t>& sensorHandlesRemoved) { + mCallback->onDynamicSensorsDisconnected(sensorHandlesRemoved); +} + } // namespace implementation } // namespace subhal } // namespace V2_0 diff --git a/sensors/2.0/multihal/tests/fake_subhal/SensorsSubHal.h b/sensors/2.0/multihal/tests/fake_subhal/SensorsSubHal.h index 61caa2c39e..c1e36472cf 100644 --- a/sensors/2.0/multihal/tests/fake_subhal/SensorsSubHal.h +++ b/sensors/2.0/multihal/tests/fake_subhal/SensorsSubHal.h @@ -98,13 +98,6 @@ class SensorsSubHal : public ISensorsSubHal, public ISensorsEventCallback { */ std::map<int32_t, std::shared_ptr<Sensor>> mSensors; - private: - /** - * The current operation mode of the multihal framework. Ensures that all subhals are set to - * the same operation mode. - */ - OperationMode mCurrentOperationMode = OperationMode::NORMAL; - /** * Callback used to communicate to the HalProxy when dynamic sensors are connected / * disconnected, sensor events need to be sent to the framework, and when a wakelock should be @@ -112,6 +105,13 @@ class SensorsSubHal : public ISensorsSubHal, public ISensorsEventCallback { */ sp<IHalProxyCallback> mCallback; + private: + /** + * The current operation mode of the multihal framework. Ensures that all subhals are set to + * the same operation mode. + */ + OperationMode mCurrentOperationMode = OperationMode::NORMAL; + /** * The next available sensor handle */ @@ -151,6 +151,12 @@ class DoesNotSupportDirectChannelSensorsSubHal : public AllSensorsSubHal { Return<void> getSensorsList(getSensorsList_cb _hidl_cb) override; }; +class AddAndRemoveDynamicSensorsSubHal : public AllSensorsSubHal { + public: + void addDynamicSensors(const std::vector<SensorInfo>& sensorsAdded); + void removeDynamicSensors(const std::vector<int32_t>& sensorHandlesAdded); +}; + } // namespace implementation } // namespace subhal } // namespace V2_0 diff --git a/sensors/common/vts/utils/GrallocWrapper.cpp b/sensors/common/vts/utils/GrallocWrapper.cpp index 1cad9135b7..e63faa2e1c 100644 --- a/sensors/common/vts/utils/GrallocWrapper.cpp +++ b/sensors/common/vts/utils/GrallocWrapper.cpp @@ -147,8 +147,8 @@ BufferDescriptor GrallocHalWrapper<AllocatorT, MapperT>::getDescriptor(uint32_t .width = size, .height = 1, .layerCount = 1, - .usage = kBufferUsage, .format = static_cast<decltype(descriptorInfo.format)>(PixelFormat::BLOB), + .usage = kBufferUsage, }; BufferDescriptor descriptor; diff --git a/thermal/1.0/vts/functional/Android.bp b/thermal/1.0/vts/functional/Android.bp index 6bda558337..d183bd8a0e 100644 --- a/thermal/1.0/vts/functional/Android.bp +++ b/thermal/1.0/vts/functional/Android.bp @@ -19,6 +19,6 @@ cc_test { defaults: ["VtsHalTargetTestDefaults"], srcs: ["VtsHalThermalV1_0TargetTest.cpp"], static_libs: ["android.hardware.thermal@1.0"], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/thermal/1.0/vts/functional/VtsHalThermalV1_0TargetTest.cpp b/thermal/1.0/vts/functional/VtsHalThermalV1_0TargetTest.cpp index 6f059ef5bb..aa1c0ce425 100644 --- a/thermal/1.0/vts/functional/VtsHalThermalV1_0TargetTest.cpp +++ b/thermal/1.0/vts/functional/VtsHalThermalV1_0TargetTest.cpp @@ -21,11 +21,12 @@ #define LOG_TAG "thermal_hidl_hal_test" -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> #include <android-base/logging.h> #include <android/hardware/thermal/1.0/IThermal.h> #include <android/hardware/thermal/1.0/types.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> #include <unistd.h> using ::android::hardware::hidl_string; @@ -46,26 +47,11 @@ using ::android::sp; #define MAX_DEVICE_TEMPERATURE 200 #define MAX_FAN_SPEED 20000 -// Test environment for Thermal HIDL HAL. -class ThermalHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static ThermalHidlEnvironment* Instance() { - static ThermalHidlEnvironment* instance = new ThermalHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IThermal>(); } - private: - ThermalHidlEnvironment() {} -}; - // The main test class for THERMAL HIDL HAL. -class ThermalHidlTest : public ::testing::VtsHalHidlTargetTestBase { +class ThermalHidlTest : public testing::TestWithParam<std::string> { public: virtual void SetUp() override { - thermal_ = ::testing::VtsHalHidlTargetTestBase::getService<IThermal>( - ThermalHidlEnvironment::Instance()->getServiceName<IThermal>()); + thermal_ = IThermal::getService(GetParam()); ASSERT_NE(thermal_, nullptr); baseSize_ = 0; names_.clear(); @@ -178,7 +164,7 @@ class ThermalHidlTest : public ::testing::VtsHalHidlTargetTestBase { }; // Sanity test for Thermal::getTemperatures(). -TEST_F(ThermalHidlTest, TemperatureTest) { +TEST_P(ThermalHidlTest, TemperatureTest) { hidl_vec<Temperature> passed; for (size_t i = 0; i < MONITORING_OPERATION_NUMBER; ++i) { thermal_->getTemperatures( @@ -193,7 +179,7 @@ TEST_F(ThermalHidlTest, TemperatureTest) { } // Sanity test for Thermal::getCpuUsages(). -TEST_F(ThermalHidlTest, CpuUsageTest) { +TEST_P(ThermalHidlTest, CpuUsageTest) { hidl_vec<CpuUsage> passed; for (size_t i = 0; i < MONITORING_OPERATION_NUMBER; ++i) { thermal_->getCpuUsages( @@ -208,7 +194,7 @@ TEST_F(ThermalHidlTest, CpuUsageTest) { } // Sanity test for Thermal::getCoolingDevices(). -TEST_F(ThermalHidlTest, CoolingDeviceTest) { +TEST_P(ThermalHidlTest, CoolingDeviceTest) { hidl_vec<CoolingDevice> passed; for (size_t i = 0; i < MONITORING_OPERATION_NUMBER; ++i) { thermal_->getCoolingDevices([&passed]( @@ -222,11 +208,7 @@ TEST_F(ThermalHidlTest, CoolingDeviceTest) { } } -int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(ThermalHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - ThermalHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - LOG(INFO) << "Test result = " << status; - return status; -} +INSTANTIATE_TEST_SUITE_P( + PerInstance, ThermalHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IThermal::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/thermal/1.1/vts/functional/Android.bp b/thermal/1.1/vts/functional/Android.bp index 9a16c30fba..2c43d79766 100644 --- a/thermal/1.1/vts/functional/Android.bp +++ b/thermal/1.1/vts/functional/Android.bp @@ -22,6 +22,5 @@ cc_test { "android.hardware.thermal@1.0", "android.hardware.thermal@1.1", ], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } - diff --git a/thermal/1.1/vts/functional/VtsHalThermalV1_1TargetTest.cpp b/thermal/1.1/vts/functional/VtsHalThermalV1_1TargetTest.cpp index 91c8b6ec76..bc7b2ee3ce 100644 --- a/thermal/1.1/vts/functional/VtsHalThermalV1_1TargetTest.cpp +++ b/thermal/1.1/vts/functional/VtsHalThermalV1_1TargetTest.cpp @@ -17,10 +17,11 @@ #include <android/hardware/thermal/1.1/IThermal.h> #include <android/hardware/thermal/1.1/IThermalCallback.h> #include <android/hardware/thermal/1.0/types.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> #include <VtsHalHidlTargetCallbackBase.h> -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> using ::android::hardware::thermal::V1_0::Temperature; using ::android::hardware::thermal::V1_0::TemperatureType; @@ -63,26 +64,11 @@ class ThermalCallback } }; -// Test environment for Thermal HIDL HAL. -class ThermalHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static ThermalHidlEnvironment* Instance() { - static ThermalHidlEnvironment* instance = new ThermalHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IThermal>(); } - private: - ThermalHidlEnvironment() {} -}; - // The main test class for THERMAL HIDL HAL 1.1. -class ThermalHidlTest : public ::testing::VtsHalHidlTargetTestBase { +class ThermalHidlTest : public testing::TestWithParam<std::string> { public: virtual void SetUp() override { - mThermal = ::testing::VtsHalHidlTargetTestBase::getService<IThermal>( - ThermalHidlEnvironment::Instance()->getServiceName<IThermal>()); + mThermal = IThermal::getService(GetParam()); ASSERT_NE(mThermal, nullptr); mThermalCallback = new(std::nothrow) ThermalCallback(); ASSERT_NE(mThermalCallback, nullptr); @@ -104,7 +90,7 @@ class ThermalHidlTest : public ::testing::VtsHalHidlTargetTestBase { // This just calls into and back from our local ThermalCallback impl. // Note: a real thermal throttling event from the Thermal HAL could be // inadvertently received here. -TEST_F(ThermalHidlTest, NotifyThrottlingTest) { +TEST_P(ThermalHidlTest, NotifyThrottlingTest) { auto ret = mThermalCallback->notifyThrottling(true, kThrottleTemp); ASSERT_TRUE(ret.isOk()); auto res = mThermalCallback->WaitForCallback(kCallbackNameNotifyThrottling); @@ -114,11 +100,7 @@ TEST_F(ThermalHidlTest, NotifyThrottlingTest) { EXPECT_EQ(kThrottleTemp, res.args->temperature); } -int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(ThermalHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - ThermalHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - cout << "Test result = " << status << std::endl; - return status; -} +INSTANTIATE_TEST_SUITE_P( + PerInstance, ThermalHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IThermal::descriptor)), + android::hardware::PrintInstanceNameToString);
\ No newline at end of file diff --git a/thermal/2.0/vts/functional/Android.bp b/thermal/2.0/vts/functional/Android.bp index f4e95f8ae5..09405763a2 100644 --- a/thermal/2.0/vts/functional/Android.bp +++ b/thermal/2.0/vts/functional/Android.bp @@ -22,5 +22,6 @@ cc_test { "android.hardware.thermal@1.0", "android.hardware.thermal@2.0", ], + test_suites: ["general-tests", "vts-core"], } diff --git a/thermal/2.0/vts/functional/VtsHalThermalV2_0TargetTest.cpp b/thermal/2.0/vts/functional/VtsHalThermalV2_0TargetTest.cpp index d0f2e84afd..75536a6057 100644 --- a/thermal/2.0/vts/functional/VtsHalThermalV2_0TargetTest.cpp +++ b/thermal/2.0/vts/functional/VtsHalThermalV2_0TargetTest.cpp @@ -18,9 +18,11 @@ #include <android/hardware/thermal/2.0/IThermalChangedCallback.h> #include <android/hardware/thermal/2.0/types.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> + #include <VtsHalHidlTargetCallbackBase.h> -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> using ::android::sp; using ::android::hardware::hidl_enum_range; @@ -63,27 +65,11 @@ class ThermalCallback : public ::testing::VtsHalHidlTargetCallbackBase<ThermalCa } }; -// Test environment for Thermal HIDL HAL. -class ThermalHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static ThermalHidlEnvironment* Instance() { - static ThermalHidlEnvironment* instance = new ThermalHidlEnvironment; - return instance; - } - - void registerTestServices() override { registerTestService<IThermal>(); } - - private: - ThermalHidlEnvironment() {} -}; - // The main test class for THERMAL HIDL HAL 2.0. -class ThermalHidlTest : public ::testing::VtsHalHidlTargetTestBase { +class ThermalHidlTest : public testing::TestWithParam<std::string> { public: virtual void SetUp() override { - mThermal = ::testing::VtsHalHidlTargetTestBase::getService<IThermal>( - ThermalHidlEnvironment::Instance()->getServiceName<IThermal>()); + mThermal = IThermal::getService(GetParam()); ASSERT_NE(mThermal, nullptr); mThermalCallback = new (std::nothrow) ThermalCallback(); ASSERT_NE(mThermalCallback, nullptr); @@ -119,7 +105,7 @@ class ThermalHidlTest : public ::testing::VtsHalHidlTargetTestBase { // This just calls into and back from our local ThermalChangedCallback impl. // Note: a real thermal throttling event from the Thermal HAL could be // inadvertently received here. -TEST_F(ThermalHidlTest, NotifyThrottlingTest) { +TEST_P(ThermalHidlTest, NotifyThrottlingTest) { auto ret = mThermalCallback->notifyThrottling(kThrottleTemp); ASSERT_TRUE(ret.isOk()); auto res = mThermalCallback->WaitForCallback(kCallbackNameNotifyThrottling); @@ -129,7 +115,7 @@ TEST_F(ThermalHidlTest, NotifyThrottlingTest) { } // Test Thermal->registerThermalChangedCallback. -TEST_F(ThermalHidlTest, RegisterThermalChangedCallbackTest) { +TEST_P(ThermalHidlTest, RegisterThermalChangedCallbackTest) { // Expect to fail with same callback auto ret = mThermal->registerThermalChangedCallback( mThermalCallback, false, TemperatureType::SKIN, @@ -159,7 +145,7 @@ TEST_F(ThermalHidlTest, RegisterThermalChangedCallbackTest) { } // Test Thermal->unregisterThermalChangedCallback. -TEST_F(ThermalHidlTest, UnregisterThermalChangedCallbackTest) { +TEST_P(ThermalHidlTest, UnregisterThermalChangedCallbackTest) { sp<ThermalCallback> localThermalCallback = new (std::nothrow) ThermalCallback(); // Expect to fail as the callback was not registered before auto ret = mThermal->unregisterThermalChangedCallback( @@ -184,7 +170,7 @@ TEST_F(ThermalHidlTest, UnregisterThermalChangedCallbackTest) { } // Sanity test for Thermal::getCurrentTemperatures(). -TEST_F(ThermalHidlTest, TemperatureTest) { +TEST_P(ThermalHidlTest, TemperatureTest) { mThermal->getCurrentTemperatures(false, TemperatureType::SKIN, [](ThermalStatus status, hidl_vec<Temperature> temperatures) { if (temperatures.size()) { @@ -214,7 +200,7 @@ TEST_F(ThermalHidlTest, TemperatureTest) { } // Sanity test for Thermal::getTemperatureThresholds(). -TEST_F(ThermalHidlTest, TemperatureThresholdTest) { +TEST_P(ThermalHidlTest, TemperatureThresholdTest) { mThermal->getTemperatureThresholds( false, TemperatureType::SKIN, [](ThermalStatus status, hidl_vec<TemperatureThreshold> temperatures) { @@ -242,7 +228,7 @@ TEST_F(ThermalHidlTest, TemperatureThresholdTest) { } // Sanity test for Thermal::getCurrentCoolingDevices(). -TEST_F(ThermalHidlTest, CoolingDeviceTest) { +TEST_P(ThermalHidlTest, CoolingDeviceTest) { mThermal->getCurrentCoolingDevices( false, CoolingType::CPU, [](ThermalStatus status, hidl_vec<CoolingDevice> cooling_devices) { if (cooling_devices.size()) { @@ -271,11 +257,7 @@ TEST_F(ThermalHidlTest, CoolingDeviceTest) { } } -int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(ThermalHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - ThermalHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - cout << "Test result = " << status << std::endl; - return status; -} +INSTANTIATE_TEST_SUITE_P( + PerInstance, ThermalHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IThermal::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/tv/tuner/1.0/Android.bp b/tv/tuner/1.0/Android.bp index 986518b327..09265f7fce 100644 --- a/tv/tuner/1.0/Android.bp +++ b/tv/tuner/1.0/Android.bp @@ -13,6 +13,7 @@ hidl_interface { "IDescrambler.hal", "IFrontend.hal", "IFrontendCallback.hal", + "ILnb.hal", "ITuner.hal", ], interfaces: [ diff --git a/tv/tuner/1.0/IDemux.hal b/tv/tuner/1.0/IDemux.hal index e03095b2e3..7fd7e265bc 100644 --- a/tv/tuner/1.0/IDemux.hal +++ b/tv/tuner/1.0/IDemux.hal @@ -1,3 +1,19 @@ +/* + * 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.hardware.tv.tuner@1.0; import IDemuxCallback; @@ -46,9 +62,9 @@ interface IDemux { * * It is used by the client to get the descriptor of the filter's Fast * Message Queue. The data in FMQ is filtered out from MPEG transport - * stream. The data is origanized to data blocks which may have + * stream. The data is organized to data blocks which may have * different length. The length's information of one or multiple data blocks - * is sent to client throught DemuxFilterEvent. + * is sent to client through DemuxFilterEvent. * * @param filterId the ID of the filter. * @return result Result status of the operation. @@ -81,7 +97,7 @@ interface IDemux { /** * Start the filter. * - * It is used by the client to ask the filter to start filterring data. + * It is used by the client to ask the filter to start filtering data. * * @param filterId the ID of the filter. * @return result Result status of the operation. @@ -202,7 +218,7 @@ interface IDemux { * * It is used by the client to get the descriptor of the output's Fast * Message Queue. The data in FMQ is muxed packets output from selected - * filters. The packet's format is specifed by DemuxDataFormat in + * filters. The packet's format is specified by DemuxDataFormat in * DemuxOutputSettings. * * @return result Result status of the operation. @@ -236,7 +252,7 @@ interface IDemux { * INVALID_STATE if failed for wrong state. * UNKNOWN_ERROR if failed for other reasons. */ - attachOutputTsFilter(DemuxFilterId filterId) generates (Result result); + attachOutputFilter(DemuxFilterId filterId) generates (Result result); /** * Detach one filter from the demux's output. @@ -250,7 +266,7 @@ interface IDemux { * INVALID_STATE if failed for wrong state. * UNKNOWN_ERROR if failed for other reasons. */ - detachOutputTsFilter(DemuxFilterId filterId) generates (Result result); + detachOutputFilter(DemuxFilterId filterId) generates (Result result); /** * Start to take data to the demux's output. diff --git a/tv/tuner/1.0/IDemuxCallback.hal b/tv/tuner/1.0/IDemuxCallback.hal index 55e84200ab..7bce9efeb1 100644 --- a/tv/tuner/1.0/IDemuxCallback.hal +++ b/tv/tuner/1.0/IDemuxCallback.hal @@ -1,3 +1,19 @@ +/* + * 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.hardware.tv.tuner@1.0; interface IDemuxCallback { diff --git a/tv/tuner/1.0/IDescrambler.hal b/tv/tuner/1.0/IDescrambler.hal index d078657d65..61ff1df4bd 100644 --- a/tv/tuner/1.0/IDescrambler.hal +++ b/tv/tuner/1.0/IDescrambler.hal @@ -1,3 +1,19 @@ +/* + * 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.hardware.tv.tuner@1.0; /** * Descrambler is used to descramble input data. diff --git a/tv/tuner/1.0/IFrontend.hal b/tv/tuner/1.0/IFrontend.hal index f7237bae73..83e390ddcf 100644 --- a/tv/tuner/1.0/IFrontend.hal +++ b/tv/tuner/1.0/IFrontend.hal @@ -16,6 +16,7 @@ package android.hardware.tv.tuner@1.0; import IFrontendCallback; +import ILnb; /** * A Tuner Frontend is used to tune to a frequency and lock signal. @@ -81,4 +82,80 @@ interface IFrontend { * UNKNOWN_ERROR if failed for other reasons. */ close() generates (Result result); + + /** + * Scan the frontend to use the settings given. + * + * This uses the frontend to start a scan from signal delivery information. + * If previous scan isn't completed, this call MUST stop previous scan, + * and start a new scan. + * Scan is an async call, with FrontendScanMessage sent via callback. + * + * @param settings Signal delivery information which the frontend uses to + * scan the signal. + * @param type the type which the frontend uses to scan the signal. + * + * @return result Result status of the operation. + * SUCCESS if successful, + * INVALID_STATE if tuning can't be applied at current stage, + * UNKNOWN_ERROR if tuning failed for other reasons. + */ + scan(FrontendSettings settings, FrontendScanType type) generates (Result result); + + /** + * Stops a previous scanning. + * + * If the method completes successfully, the frontend stop previous + * scanning. + * + * @return result Result status of the operation. + * SUCCESS if successfully stop tuning. + * UNKNOWN_ERROR if failed for other reasons. + */ + stopScan() generates (Result result); + + /** + * Gets the statuses of the frontend. + * + * This retrieve the statuses of the frontend for given status types. + * + * @param statusTypes an array of status type which the caller request. + * + * @return result Result status of the operation. + * SUCCESS if successful, + * INVALID_STATE if tuning can't be applied at current stage, + * UNKNOWN_ERROR if tuning failed for other reasons. + * @return statuses an array of statuses which response the caller's + * request. + */ + getStatus(vec<FrontendStatusType> statusTypes) generates (Result result, vec<FrontendStatus> statuses); + + /** + * Sets Low-Noise Block downconverter (LNB) for satellite frontend. + * + * This assigns a hardware LNB resource to the satellite frontend. It can be + * called multiple times to update LNB assignment. The LNB resource must be + * released when the frontend is closed. + * + * @param lnbId the Id of assigned LNB resource. + * + * @return result Result status of the operation. + * SUCCESS if successful, + * INVALID_STATE if the frontend can't be set with a LNB, such as + * cable frontend. + * UNKNOWN_ERROR if failed for other reasons. + */ + setLnb(LnbId lnbId) generates (Result result); + + /** + * Enable or Disable Low Noise Amplifier (LNA). + * + * @param bEnable true if activate LNA module; false if deactivate LNA + * + * @return result Result status of the operation. + * SUCCESS if successful, + * INVALID_STATE if the frontend doesn't support LNA. + * UNKNOWN_ERROR if failed for other reasons. + */ + setLna(bool bEnable) generates (Result result); }; diff --git a/tv/tuner/1.0/IFrontendCallback.hal b/tv/tuner/1.0/IFrontendCallback.hal index e9070495c3..8896a09b62 100644 --- a/tv/tuner/1.0/IFrontendCallback.hal +++ b/tv/tuner/1.0/IFrontendCallback.hal @@ -33,5 +33,13 @@ interface IFrontendCallback { * Specification Version 4.2. */ oneway onDiseqcMessage(vec<uint8_t> diseqcMessage); -}; + /** + * The callback function that must be called by HAL implementation to notify + * the client of scan messages. + * + * @param type the type of scan message. + * @param message the scan message sent by HAL to the client. + */ + oneway onScanMessage(FrontendScanMessageType type, FrontendScanMessage message); +}; diff --git a/tv/tuner/1.0/ILnb.hal b/tv/tuner/1.0/ILnb.hal new file mode 100644 index 0000000000..6b7119e046 --- /dev/null +++ b/tv/tuner/1.0/ILnb.hal @@ -0,0 +1,86 @@ +/* + * 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.hardware.tv.tuner@1.0; + +/** + * A Tuner LNB (low-noise block downconverter) is used by satellite frontend + * to receive the microwave signal from the satellite, amplify it, and + * downconvert the frequency to a lower frequency. + */ +interface ILnb { + /** + * Set the lnb's power voltage. + * + * @param voltage the power's voltage the Lnb to use. + * @return result Result status of the operation. + * SUCCESS if successful, + * INVALID_ARGUMENT if the selected voltage isn't allowed, + * UNKNOWN_ERROR if failed for other reasons. + */ + setVoltage(FrontendLnbVoltage voltage) generates (Result result); + + /** + * Set the lnb's tone mode. + * + * @param tone the tone mode the Lnb to use. + * @return result Result status of the operation. + * SUCCESS if successful, + * INVALID_ARGUMENT if the selected tone mode isn't allowed, + * UNKNOWN_ERROR if failed for other reasons. + */ + setTone(FrontendLnbTone tone) generates (Result result); + + /** + * Select the lnb's position. + * + * @param position the position the Lnb to use. + * @return result Result status of the operation. + * SUCCESS if successful, + * INVALID_ARGUMENT if the selected position isn't allowed, + * UNKNOWN_ERROR if failed for other reasons. + */ + setSatellitePosition(FrontendLnbPosition position) generates (Result result); + + /** + * Sends DiSEqC (Digital Satellite Equipment Control) message. + * + * Client sends DiSeqc message to DiSEqc to LNB. The response message from + * the device comes back to the client through frontend's callback + * onDiseqcMessage. + * + * @param diseqcMessage a byte array of data for DiSEqC message which is + * specified by EUTELSAT Bus Functional Specification Version 4.2. + * + * @return result Result status of the operation. + * SUCCESS if successful, + * INVALID_STATE if the frontend can't send DiSEqc Message, such as + * cable frontend. + * UNKNOWN_ERROR if failed for other reasons. + */ + sendDiseqcMessage(vec<uint8_t> diseqcMessage) generates (Result result); + + /** + * Releases the LNB instance + * + * Associated resources are released. close may be called more than once. + * Calls to any other method after this will return an error + * + * @return result Result status of the operation. + * SUCCESS if successful, + * UNKNOWN_ERROR if failed for other reasons. + */ + close() generates (Result result); +}; diff --git a/tv/tuner/1.0/ITuner.hal b/tv/tuner/1.0/ITuner.hal index a0f3e8e7d1..1cf0e387b6 100644 --- a/tv/tuner/1.0/ITuner.hal +++ b/tv/tuner/1.0/ITuner.hal @@ -19,10 +19,11 @@ package android.hardware.tv.tuner@1.0; import IDemux; import IDescrambler; import IFrontend; +import ILnb; /** * Top level interface to manage Frontend, Demux and Decrambler hardware - * resouces which are needed for Android TV. + * resources which are needed for Android TV. */ interface ITuner { /** @@ -45,6 +46,7 @@ interface ITuner { * @param frontendId the id of the frontend to be opened. * @return result Result status of the operation. * SUCCESS if successful, + * UNAVAILABLE if no resource. * UNKNOWN_ERROR if creation failed for other reasons. * @return frontend the newly created frontend interface. */ @@ -62,10 +64,20 @@ interface ITuner { * @return demuxId newly created demux id. * @return demux the newly created demux interface. */ - openDemux() + openDemux() generates (Result result, DemuxId demuxId, IDemux demux); /** + * Retrieve the Demux's Capabilities. + * + * @return result Result status of the operation. + * SUCCESS if successful, + * UNKNOWN_ERROR if the inquiry failed for other reasons. + * @return caps the Demux's Capabilities. + */ + getDemuxCaps() generates (Result result, DemuxCapabilities caps); + + /** * Create a new instance of Descrambler. * * It is used by the client to create a Descrambler instance. @@ -75,6 +87,46 @@ interface ITuner { * UNKNOWN_ERROR if creation failed for other reasons. * @return descrambler the newly created descrambler interface. */ - openDescrambler() + openDescrambler() generates (Result result, IDescrambler descrambler); + + /** + * Retrieve the frontend's information. + * + * @param frontendId the id of the frontend to be inquiried. + * @return result Result status of the operation. + * SUCCESS if successful, + * UNKNOWN_ERROR if the inquiry failed for other reasons. + * @return info the frontend's information. + */ + getFrontendInfo(FrontendId frontendId) + generates (Result result, FrontendInfo info); + + /** + * Get low-noise block downconverter (LNB) IDs. + * + * It is used by the client to get all available LNBs' IDs. + * + * @return result Result status of the operation. + * SUCCESS if successful, + * UNKNOWN_ERROR if tuning failed for other reasons. + * @return frontendIds an array of LnbId for the available LNBs. + */ + getLnbIds() generates (Result result, vec<LnbId> lnbIds); + + /** + * Create a new instance of Lnb given a lnbId. + * + * It is used by the client to create a Lnb instance for satellite Frontend. + * + * @param lnbId the id of the LNB to be opened. + * @return result Result status of the operation. + * SUCCESS if successful, + * UNAVAILABLE if no resource. + * UNKNOWN_ERROR if creation failed for other reasons. + * @return lnb the newly created Lnb interface. + */ + openLnbById(LnbId lnbId) + generates (Result result, ILnb lnb); }; + diff --git a/tv/tuner/1.0/default/Android.bp b/tv/tuner/1.0/default/Android.bp index b211dd249f..0ae8bcdac8 100644 --- a/tv/tuner/1.0/default/Android.bp +++ b/tv/tuner/1.0/default/Android.bp @@ -8,6 +8,7 @@ cc_defaults { "Descrambler.cpp", "Demux.cpp", "Tuner.cpp", + "Lnb.cpp", "service.cpp", ], diff --git a/tv/tuner/1.0/default/Demux.cpp b/tv/tuner/1.0/default/Demux.cpp index 889e42ed06..8bb79f9702 100644 --- a/tv/tuner/1.0/default/Demux.cpp +++ b/tv/tuner/1.0/default/Demux.cpp @@ -67,8 +67,9 @@ const std::vector<uint8_t> fakeDataInputBuffer{ 0x73, 0x63, 0x65, 0x6e, 0x65, }; -Demux::Demux(uint32_t demuxId) { +Demux::Demux(uint32_t demuxId, sp<Tuner> tuner) { mDemuxId = demuxId; + mTunerService = tuner; } Demux::~Demux() {} @@ -76,9 +77,20 @@ Demux::~Demux() {} Return<Result> Demux::setFrontendDataSource(uint32_t frontendId) { ALOGV("%s", __FUNCTION__); - mSourceFrontendId = frontendId; + if (mTunerService == nullptr) { + return Result::NOT_INITIALIZED; + } - return Result::SUCCESS; + mFrontend = mTunerService->getFrontendById(frontendId); + + if (mFrontend == nullptr) { + return Result::INVALID_STATE; + } + + mFrontendSourceFile = mFrontend->getSourceFile(); + + mTunerService->setFrontendAsDemuxSource(frontendId, mDemuxId); + return startBroadcastInputLoop(); } Return<void> Demux::addFilter(DemuxFilterType type, uint32_t bufferSize, @@ -94,12 +106,15 @@ Return<void> Demux::addFilter(DemuxFilterType type, uint32_t bufferSize, } else { filterId = ++mLastUsedFilterId; - mDemuxCallbacks.resize(filterId + 1); + mFilterCallbacks.resize(filterId + 1); mFilterMQs.resize(filterId + 1); mFilterEvents.resize(filterId + 1); mFilterEventFlags.resize(filterId + 1); mFilterThreadRunning.resize(filterId + 1); mFilterThreads.resize(filterId + 1); + mFilterPids.resize(filterId + 1); + mFilterOutputs.resize(filterId + 1); + mFilterStatus.resize(filterId + 1); } mUsedFilterIds.insert(filterId); @@ -111,7 +126,7 @@ Return<void> Demux::addFilter(DemuxFilterType type, uint32_t bufferSize, } // Add callback - mDemuxCallbacks[filterId] = cb; + mFilterCallbacks[filterId] = cb; // Mapping from the filter ID to the filter event DemuxFilterEvent event{ @@ -142,58 +157,71 @@ Return<void> Demux::getFilterQueueDesc(uint32_t filterId, getFilterQueueDesc_cb return Void(); } -Return<Result> Demux::configureFilter(uint32_t /* filterId */, - const DemuxFilterSettings& /* settings */) { +Return<Result> Demux::configureFilter(uint32_t filterId, const DemuxFilterSettings& settings) { ALOGV("%s", __FUNCTION__); - return Result::SUCCESS; -} - -Return<Result> Demux::startFilter(uint32_t filterId) { - ALOGV("%s", __FUNCTION__); - Result result; - - if (mUsedFilterIds.find(filterId) == mUsedFilterIds.end()) { - ALOGW("No filter with id: %d exists to start filter", filterId); - return Result::INVALID_ARGUMENT; - } - switch (mFilterEvents[filterId].filterType) { case DemuxFilterType::SECTION: - result = startFilterLoop(filterId); + mFilterPids[filterId] = settings.section().tpid; break; case DemuxFilterType::PES: - result = startPesFilterHandler(filterId); + mFilterPids[filterId] = settings.pesData().tpid; break; case DemuxFilterType::TS: - result = startTsFilterHandler(); - return Result::SUCCESS; + mFilterPids[filterId] = settings.ts().tpid; + break; case DemuxFilterType::AUDIO: + mFilterPids[filterId] = settings.audio().tpid; + break; case DemuxFilterType::VIDEO: - result = startMediaFilterHandler(filterId); + mFilterPids[filterId] = settings.video().tpid; break; case DemuxFilterType::RECORD: - result = startRecordFilterHandler(filterId); + mFilterPids[filterId] = settings.record().tpid; break; case DemuxFilterType::PCR: - result = startPcrFilterHandler(); - return Result::SUCCESS; + mFilterPids[filterId] = settings.pcr().tpid; + break; default: return Result::UNKNOWN_ERROR; } + return Result::SUCCESS; +} + +Return<Result> Demux::startFilter(uint32_t filterId) { + ALOGV("%s", __FUNCTION__); + Result result; + + if (mUsedFilterIds.find(filterId) == mUsedFilterIds.end()) { + ALOGW("No filter with id: %d exists to start filter", filterId); + return Result::INVALID_ARGUMENT; + } + + result = startFilterLoop(filterId); return result; } -Return<Result> Demux::stopFilter(uint32_t /* filterId */) { +Return<Result> Demux::stopFilter(uint32_t filterId) { ALOGV("%s", __FUNCTION__); + mFilterThreadRunning[filterId] = false; + + std::lock_guard<std::mutex> lock(mFilterThreadLock); + return Result::SUCCESS; } -Return<Result> Demux::flushFilter(uint32_t /* filterId */) { +Return<Result> Demux::flushFilter(uint32_t filterId) { ALOGV("%s", __FUNCTION__); + // temp implementation to flush the FMQ + int size = mFilterMQs[filterId]->availableToRead(); + char* buffer = new char[size]; + mOutputMQ->read((unsigned char*)&buffer[0], size); + delete[] buffer; + mFilterStatus[filterId] = DemuxFilterStatus::DATA_READY; + return Result::SUCCESS; } @@ -234,10 +262,12 @@ Return<Result> Demux::close() { mFilterThreads.clear(); mUnusedFilterIds.clear(); mUsedFilterIds.clear(); - mDemuxCallbacks.clear(); + mFilterCallbacks.clear(); mFilterMQs.clear(); mFilterEvents.clear(); mFilterEventFlags.clear(); + mFilterOutputs.clear(); + mFilterPids.clear(); mLastUsedFilterId = -1; return Result::SUCCESS; @@ -277,19 +307,21 @@ Return<void> Demux::getOutputQueueDesc(getOutputQueueDesc_cb _hidl_cb) { return Void(); } -Return<Result> Demux::configureOutput(const DemuxOutputSettings& /* settings */) { +Return<Result> Demux::configureOutput(const DemuxOutputSettings& settings) { ALOGV("%s", __FUNCTION__); + mOutputConfigured = true; + mOutputSettings = settings; return Result::SUCCESS; } -Return<Result> Demux::attachOutputTsFilter(uint32_t /*filterId*/) { +Return<Result> Demux::attachOutputFilter(uint32_t /*filterId*/) { ALOGV("%s", __FUNCTION__); return Result::SUCCESS; } -Return<Result> Demux::detachOutputTsFilter(uint32_t /* filterId */) { +Return<Result> Demux::detachOutputFilter(uint32_t /* filterId */) { ALOGV("%s", __FUNCTION__); return Result::SUCCESS; @@ -353,15 +385,26 @@ Return<void> Demux::getInputQueueDesc(getInputQueueDesc_cb _hidl_cb) { return Void(); } -Return<Result> Demux::configureInput(const DemuxInputSettings& /* settings */) { +Return<Result> Demux::configureInput(const DemuxInputSettings& settings) { ALOGV("%s", __FUNCTION__); + mInputConfigured = true; + mInputSettings = settings; + return Result::SUCCESS; } Return<Result> Demux::startInput() { ALOGV("%s", __FUNCTION__); + if (!mInputCallback) { + return Result::NOT_INITIALIZED; + } + + if (!mInputConfigured) { + return Result::INVALID_STATE; + } + pthread_create(&mInputThread, NULL, __threadLoopInput, this); pthread_setname_np(mInputThread, "demux_input_waiting_loop"); @@ -373,6 +416,10 @@ Return<Result> Demux::startInput() { Return<Result> Demux::stopInput() { ALOGV("%s", __FUNCTION__); + mInputThreadRunning = false; + + std::lock_guard<std::mutex> lock(mInputThreadLock); + return Result::SUCCESS; } @@ -403,36 +450,75 @@ Result Demux::startFilterLoop(uint32_t filterId) { return Result::SUCCESS; } -Result Demux::startSectionFilterHandler(uint32_t filterId, vector<uint8_t> data) { - if (!writeSectionsAndCreateEvent(filterId, data)) { +Result Demux::startSectionFilterHandler(uint32_t filterId) { + if (mFilterOutputs[filterId].empty()) { + return Result::SUCCESS; + } + if (!writeSectionsAndCreateEvent(filterId, mFilterOutputs[filterId])) { ALOGD("[Demux] filter %d fails to write into FMQ. Ending thread", filterId); return Result::UNKNOWN_ERROR; } + mFilterOutputs[filterId].clear(); + return Result::SUCCESS; } Result Demux::startPesFilterHandler(uint32_t filterId) { - // TODO generate multiple events in one event callback - DemuxFilterPesEvent pesEvent; - pesEvent = { - // temp dump meta data - .streamId = 0, - .dataLength = 530, - }; - mFilterEvents[filterId].events.resize(1); - mFilterEvents[filterId].events[0].pes(pesEvent); - /*pthread_create(&mThreadId, NULL, __threadLoop, this); - pthread_setname_np(mThreadId, "demux_section_filter_waiting_loop");*/ - if (!writeDataToFilterMQ(fakeDataInputBuffer, filterId)) { - return Result::INVALID_STATE; + std::lock_guard<std::mutex> lock(mFilterEventLock); + if (mFilterOutputs[filterId].empty()) { + return Result::SUCCESS; } - if (mDemuxCallbacks[filterId] == nullptr) { - return Result::NOT_INITIALIZED; + for (int i = 0; i < mFilterOutputs[filterId].size(); i += 188) { + if (mPesSizeLeft == 0) { + uint32_t prefix = (mFilterOutputs[filterId][i + 4] << 16) | + (mFilterOutputs[filterId][i + 5] << 8) | + mFilterOutputs[filterId][i + 6]; + ALOGD("[Demux] prefix %d", prefix); + if (prefix == 0x000001) { + // TODO handle mulptiple Pes filters + mPesSizeLeft = + (mFilterOutputs[filterId][i + 8] << 8) | mFilterOutputs[filterId][i + 9]; + mPesSizeLeft += 6; + ALOGD("[Demux] pes data length %d", mPesSizeLeft); + } else { + continue; + } + } + + int endPoint = min(184, mPesSizeLeft); + // append data and check size + vector<uint8_t>::const_iterator first = mFilterOutputs[filterId].begin() + i + 4; + vector<uint8_t>::const_iterator last = mFilterOutputs[filterId].begin() + i + 4 + endPoint; + mPesOutput.insert(mPesOutput.end(), first, last); + // size does not match then continue + mPesSizeLeft -= endPoint; + if (mPesSizeLeft > 0) { + continue; + } + // size match then create event + if (!writeDataToFilterMQ(mPesOutput, filterId)) { + mFilterOutputs[filterId].clear(); + return Result::INVALID_STATE; + } + maySendFilterStatusCallback(filterId); + DemuxFilterPesEvent pesEvent; + pesEvent = { + // temp dump meta data + .streamId = mPesOutput[3], + .dataLength = static_cast<uint16_t>(mPesOutput.size()), + }; + ALOGD("[Demux] assembled pes data length %d", pesEvent.dataLength); + + int size = mFilterEvents[filterId].events.size(); + mFilterEvents[filterId].events.resize(size + 1); + mFilterEvents[filterId].events[size].pes(pesEvent); + mPesOutput.clear(); } - mDemuxCallbacks[filterId]->onFilterEvent(mFilterEvents[filterId]); + mFilterOutputs[filterId].clear(); + return Result::SUCCESS; } @@ -451,6 +537,8 @@ Result Demux::startMediaFilterHandler(uint32_t filterId) { }; mFilterEvents[filterId].events.resize(1); mFilterEvents[filterId].events[0].media() = mediaEvent; + + mFilterOutputs[filterId].clear(); // TODO handle write FQM for media stream return Result::SUCCESS; } @@ -465,6 +553,8 @@ Result Demux::startRecordFilterHandler(uint32_t filterId) { recordEvent.indexMask.tsIndexMask() = 0x01; mFilterEvents[filterId].events.resize(1); mFilterEvents[filterId].events[0].ts() = recordEvent; + + mFilterOutputs[filterId].clear(); return Result::SUCCESS; } @@ -499,18 +589,18 @@ bool Demux::createFilterMQ(uint32_t bufferSize, uint32_t filterId) { bool Demux::writeSectionsAndCreateEvent(uint32_t filterId, vector<uint8_t> data) { // TODO check how many sections has been read std::lock_guard<std::mutex> lock(mFilterEventLock); - int size = mFilterEvents[filterId].events.size(); - mFilterEvents[filterId].events.resize(size + 1); if (!writeDataToFilterMQ(data, filterId)) { return false; } + int size = mFilterEvents[filterId].events.size(); + mFilterEvents[filterId].events.resize(size + 1); DemuxFilterSectionEvent secEvent; secEvent = { // temp dump meta data .tableId = 0, .version = 1, .sectionNum = 1, - .dataLength = 530, + .dataLength = static_cast<uint16_t>(data.size()), }; mFilterEvents[filterId].events[size].section(secEvent); return true; @@ -524,21 +614,44 @@ bool Demux::writeDataToFilterMQ(const std::vector<uint8_t>& data, uint32_t filte return false; } -bool Demux::filterAndOutputData() { - ALOGD("[Demux] start to dispatch data to filters"); +bool Demux::readInputFMQ() { // Read input data from the input FMQ int size = mInputMQ->availableToRead(); + int inputPacketSize = mInputSettings.packetSize; vector<uint8_t> dataOutputBuffer; - dataOutputBuffer.resize(size); - mInputMQ->read(dataOutputBuffer.data(), size); + dataOutputBuffer.resize(inputPacketSize); + + // Dispatch the packet to the PID matching filter output buffer + for (int i = 0; i < size / inputPacketSize; i++) { + if (!mInputMQ->read(dataOutputBuffer.data(), inputPacketSize)) { + return false; + } + startTsFilter(dataOutputBuffer); + } + return true; +} + +void Demux::startTsFilter(vector<uint8_t> data) { + set<uint32_t>::iterator it; + for (it = mUsedFilterIds.begin(); it != mUsedFilterIds.end(); it++) { + uint16_t pid = ((data[1] & 0x1f) << 8) | ((data[2] & 0xff)); + ALOGW("start ts filter pid: %d", pid); + if (pid == mFilterPids[*it]) { + mFilterOutputs[*it].insert(mFilterOutputs[*it].end(), data.begin(), data.end()); + } + } +} + +bool Demux::startFilterDispatcher() { Result result; - // Filter the data and feed the output to each filter set<uint32_t>::iterator it; + + // Handle the output data per filter type for (it = mUsedFilterIds.begin(); it != mUsedFilterIds.end(); it++) { switch (mFilterEvents[*it].filterType) { case DemuxFilterType::SECTION: - result = startSectionFilterHandler(*it, dataOutputBuffer); + result = startSectionFilterHandler(*it); break; case DemuxFilterType::PES: result = startPesFilterHandler(*it); @@ -578,6 +691,7 @@ void* Demux::__threadLoopInput(void* user) { void Demux::filterThreadLoop(uint32_t filterId) { ALOGD("[Demux] filter %d threadLoop start.", filterId); + std::lock_guard<std::mutex> lock(mFilterThreadLock); mFilterThreadRunning[filterId] = true; // For the first time of filter output, implementation needs to send the filter @@ -589,8 +703,10 @@ void Demux::filterThreadLoop(uint32_t filterId) { continue; } // After successfully write, send a callback and wait for the read to be done - mDemuxCallbacks[filterId]->onFilterEvent(mFilterEvents[filterId]); + mFilterCallbacks[filterId]->onFilterEvent(mFilterEvents[filterId]); mFilterEvents[filterId].events.resize(0); + mFilterStatus[filterId] = DemuxFilterStatus::DATA_READY; + mFilterCallbacks[filterId]->onFilterStatus(filterId, mFilterStatus[filterId]); break; } @@ -610,18 +726,20 @@ void Demux::filterThreadLoop(uint32_t filterId) { break; } - if (mDemuxCallbacks[filterId] == nullptr) { + if (mFilterCallbacks[filterId] == nullptr) { ALOGD("[Demux] filter %d does not hava callback. Ending thread", filterId); break; } + maySendFilterStatusCallback(filterId); + while (mFilterThreadRunning[filterId]) { std::lock_guard<std::mutex> lock(mFilterEventLock); if (mFilterEvents[filterId].events.size() == 0) { continue; } // After successfully write, send a callback and wait for the read to be done - mDemuxCallbacks[filterId]->onFilterEvent(mFilterEvents[filterId]); + mFilterCallbacks[filterId]->onFilterEvent(mFilterEvents[filterId]); mFilterEvents[filterId].events.resize(0); break; } @@ -640,6 +758,7 @@ void Demux::filterThreadLoop(uint32_t filterId) { void Demux::inputThreadLoop() { ALOGD("[Demux] input threadLoop start."); + std::lock_guard<std::mutex> lock(mInputThreadLock); mInputThreadRunning = true; while (mInputThreadRunning) { @@ -651,18 +770,139 @@ void Demux::inputThreadLoop() { ALOGD("[Demux] wait for data ready on the input FMQ"); continue; } - // Our current implementation filter the data and write it into the filter FMQ immedaitely + // Our current implementation filter the data and write it into the filter FMQ immediately // after the DATA_READY from the VTS/framework - if (!filterAndOutputData()) { + if (!readInputFMQ() || !startFilterDispatcher()) { ALOGD("[Demux] input data failed to be filtered. Ending thread"); break; } + + maySendInputStatusCallback(); } mInputThreadRunning = false; ALOGD("[Demux] input thread ended."); } +void Demux::maySendInputStatusCallback() { + std::lock_guard<std::mutex> lock(mInputStatusLock); + int availableToRead = mInputMQ->availableToRead(); + int availableToWrite = mInputMQ->availableToWrite(); + + DemuxInputStatus newStatus = + checkInputStatusChange(availableToWrite, availableToRead, mInputSettings.highThreshold, + mInputSettings.lowThreshold); + if (mIntputStatus != newStatus) { + mInputCallback->onInputStatus(newStatus); + mIntputStatus = newStatus; + } +} + +void Demux::maySendFilterStatusCallback(uint32_t filterId) { + std::lock_guard<std::mutex> lock(mFilterStatusLock); + int availableToRead = mFilterMQs[filterId]->availableToRead(); + int availableToWrite = mFilterMQs[filterId]->availableToWrite(); + int fmqSize = mFilterMQs[filterId]->getQuantumCount(); + + DemuxFilterStatus newStatus = + checkFilterStatusChange(filterId, availableToWrite, availableToRead, + ceil(fmqSize * 0.75), ceil(fmqSize * 0.25)); + if (mFilterStatus[filterId] != newStatus) { + mFilterCallbacks[filterId]->onFilterStatus(filterId, newStatus); + mFilterStatus[filterId] = newStatus; + } +} + +DemuxInputStatus Demux::checkInputStatusChange(uint32_t availableToWrite, uint32_t availableToRead, + uint32_t highThreshold, uint32_t lowThreshold) { + if (availableToWrite == 0) { + return DemuxInputStatus::SPACE_FULL; + } else if (availableToRead > highThreshold) { + return DemuxInputStatus::SPACE_ALMOST_FULL; + } else if (availableToRead < lowThreshold) { + return DemuxInputStatus::SPACE_ALMOST_EMPTY; + } else if (availableToRead == 0) { + return DemuxInputStatus::SPACE_EMPTY; + } + return mIntputStatus; +} + +DemuxFilterStatus Demux::checkFilterStatusChange(uint32_t filterId, uint32_t availableToWrite, + uint32_t availableToRead, uint32_t highThreshold, + uint32_t lowThreshold) { + if (availableToWrite == 0) { + return DemuxFilterStatus::OVERFLOW; + } else if (availableToRead > highThreshold) { + return DemuxFilterStatus::HIGH_WATER; + } else if (availableToRead < lowThreshold) { + return DemuxFilterStatus::LOW_WATER; + } + return mFilterStatus[filterId]; +} + +Result Demux::startBroadcastInputLoop() { + pthread_create(&mBroadcastInputThread, NULL, __threadLoopBroadcast, this); + pthread_setname_np(mBroadcastInputThread, "broadcast_input_thread"); + + return Result::SUCCESS; +} + +void* Demux::__threadLoopBroadcast(void* user) { + Demux* const self = static_cast<Demux*>(user); + self->broadcastInputThreadLoop(); + return 0; +} + +void Demux::broadcastInputThreadLoop() { + std::lock_guard<std::mutex> lock(mBroadcastInputThreadLock); + mBroadcastInputThreadRunning = true; + mKeepFetchingDataFromFrontend = true; + + // open the stream and get its length + std::ifstream inputData(mFrontendSourceFile, std::ifstream::binary); + // TODO take the packet size from the frontend setting + int packetSize = 188; + int writePacketAmount = 6; + char* buffer = new char[packetSize]; + ALOGW("[Demux] broadcast input thread loop start %s", mFrontendSourceFile.c_str()); + if (!inputData.is_open()) { + mBroadcastInputThreadRunning = false; + ALOGW("[Demux] Error %s", strerror(errno)); + } + + while (mBroadcastInputThreadRunning) { + // move the stream pointer for packet size * 6 every read until the end + while (mKeepFetchingDataFromFrontend) { + for (int i = 0; i < writePacketAmount; i++) { + inputData.read(buffer, packetSize); + if (!inputData) { + mBroadcastInputThreadRunning = false; + break; + } + // filter and dispatch filter output + vector<uint8_t> byteBuffer; + byteBuffer.resize(packetSize); + for (int index = 0; index < byteBuffer.size(); index++) { + byteBuffer[index] = static_cast<uint8_t>(buffer[index]); + } + startTsFilter(byteBuffer); + } + startFilterDispatcher(); + sleep(1); + } + } + + ALOGW("[Demux] Broadcast Input thread end."); + delete[] buffer; + inputData.close(); +} + +void Demux::stopBroadcastInput() { + mKeepFetchingDataFromFrontend = false; + mBroadcastInputThreadRunning = false; + std::lock_guard<std::mutex> lock(mBroadcastInputThreadLock); +} + } // namespace implementation } // namespace V1_0 } // namespace tuner diff --git a/tv/tuner/1.0/default/Demux.h b/tv/tuner/1.0/default/Demux.h index 2fdde8dcf8..ba0b9b099a 100644 --- a/tv/tuner/1.0/default/Demux.h +++ b/tv/tuner/1.0/default/Demux.h @@ -19,7 +19,10 @@ #include <android/hardware/tv/tuner/1.0/IDemux.h> #include <fmq/MessageQueue.h> +#include <math.h> #include <set> +#include "Frontend.h" +#include "Tuner.h" using namespace std; @@ -40,9 +43,12 @@ using ::android::hardware::tv::tuner::V1_0::Result; using FilterMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>; +class Tuner; +class Frontend; + class Demux : public IDemux { public: - Demux(uint32_t demuxId); + Demux(uint32_t demuxId, sp<Tuner> tuner); ~Demux(); @@ -91,9 +97,9 @@ class Demux : public IDemux { virtual Return<Result> configureOutput(const DemuxOutputSettings& settings) override; - virtual Return<Result> attachOutputTsFilter(uint32_t filterId) override; + virtual Return<Result> attachOutputFilter(uint32_t filterId) override; - virtual Return<Result> detachOutputTsFilter(uint32_t filterId) override; + virtual Return<Result> detachOutputFilter(uint32_t filterId) override; virtual Return<Result> startOutput() override; @@ -103,7 +109,17 @@ class Demux : public IDemux { virtual Return<Result> removeOutput() override; + // Functions interacts with Tuner Service + void stopBroadcastInput(); + private: + // Tuner service + sp<Tuner> mTunerService; + + // Frontend source + sp<Frontend> mFrontend; + string mFrontendSourceFile; + // A struct that passes the arguments to a newly created filter thread struct ThreadArgs { Demux* user; @@ -115,13 +131,14 @@ class Demux : public IDemux { * They are also responsible to write the filtered output into the filter FMQ * and update the filterEvent bound with the same filterId. */ - Result startSectionFilterHandler(uint32_t filterId, vector<uint8_t> data); + Result startSectionFilterHandler(uint32_t filterId); Result startPesFilterHandler(uint32_t filterId); Result startTsFilterHandler(); Result startMediaFilterHandler(uint32_t filterId); Result startRecordFilterHandler(uint32_t filterId); Result startPcrFilterHandler(); Result startFilterLoop(uint32_t filterId); + Result startBroadcastInputLoop(); /** * To create a FilterMQ with the the next available Filter ID. @@ -136,18 +153,28 @@ class Demux : public IDemux { bool writeDataToFilterMQ(const std::vector<uint8_t>& data, uint32_t filterId); bool readDataFromMQ(); bool writeSectionsAndCreateEvent(uint32_t filterId, vector<uint8_t> data); + void maySendInputStatusCallback(); + void maySendFilterStatusCallback(uint32_t filterId); + DemuxInputStatus checkInputStatusChange(uint32_t availableToWrite, uint32_t availableToRead, + uint32_t highThreshold, uint32_t lowThreshold); + DemuxFilterStatus checkFilterStatusChange(uint32_t filterId, uint32_t availableToWrite, + uint32_t availableToRead, uint32_t highThreshold, + uint32_t lowThreshold); /** * A dispatcher to read and dispatch input data to all the started filters. * Each filter handler handles the data filtering/output writing/filterEvent updating. */ - bool filterAndOutputData(); + bool readInputFMQ(); + void startTsFilter(vector<uint8_t> data); + bool startFilterDispatcher(); static void* __threadLoopFilter(void* data); static void* __threadLoopInput(void* user); + static void* __threadLoopBroadcast(void* user); void filterThreadLoop(uint32_t filterId); void inputThreadLoop(); + void broadcastInputThreadLoop(); uint32_t mDemuxId; - uint32_t mSourceFrontendId; /** * Record the last used filter id. Initial value is -1. * Filter Id starts with 0. @@ -169,6 +196,8 @@ class Demux : public IDemux { * A list of created FilterMQ ptrs. * The array number is the filter ID. */ + vector<uint16_t> mFilterPids; + vector<vector<uint8_t>> mFilterOutputs; vector<unique_ptr<FilterMQ>> mFilterMQs; vector<EventFlag*> mFilterEventFlags; vector<DemuxFilterEvent> mFilterEvents; @@ -179,18 +208,30 @@ class Demux : public IDemux { /** * Demux callbacks used on filter events or IO buffer status */ - vector<sp<IDemuxCallback>> mDemuxCallbacks; + vector<sp<IDemuxCallback>> mFilterCallbacks; sp<IDemuxCallback> mInputCallback; sp<IDemuxCallback> mOutputCallback; + bool mInputConfigured = false; + bool mOutputConfigured = false; + DemuxInputSettings mInputSettings; + DemuxOutputSettings mOutputSettings; + // Thread handlers pthread_t mInputThread; pthread_t mOutputThread; + pthread_t mBroadcastInputThread; vector<pthread_t> mFilterThreads; + + // FMQ status local records + DemuxInputStatus mIntputStatus; + vector<DemuxFilterStatus> mFilterStatus; /** * If a specific filter's writing loop is still running */ vector<bool> mFilterThreadRunning; bool mInputThreadRunning; + bool mBroadcastInputThreadRunning; + bool mKeepFetchingDataFromFrontend; /** * Lock to protect writes to the FMQs */ @@ -198,12 +239,26 @@ class Demux : public IDemux { /** * Lock to protect writes to the filter event */ + // TODO make each filter separate event lock std::mutex mFilterEventLock; /** + * Lock to protect writes to the input status + */ + std::mutex mInputStatusLock; + std::mutex mFilterStatusLock; + std::mutex mBroadcastInputThreadLock; + std::mutex mFilterThreadLock; + std::mutex mInputThreadLock; + /** * How many times a filter should write * TODO make this dynamic/random/can take as a parameter */ const uint16_t SECTION_WRITE_COUNT = 10; + + // temp handle single PES filter + // TODO handle mulptiple Pes filters + int mPesSizeLeft = 0; + vector<uint8_t> mPesOutput; }; } // namespace implementation diff --git a/tv/tuner/1.0/default/Frontend.cpp b/tv/tuner/1.0/default/Frontend.cpp index 3dcc2b1ebd..1e07eddbfe 100644 --- a/tv/tuner/1.0/default/Frontend.cpp +++ b/tv/tuner/1.0/default/Frontend.cpp @@ -27,14 +27,10 @@ namespace tuner { namespace V1_0 { namespace implementation { -Frontend::Frontend() { - // Init callback to nullptr - mCallback = nullptr; -} - -Frontend::Frontend(FrontendType type, FrontendId id) { +Frontend::Frontend(FrontendType type, FrontendId id, sp<Tuner> tuner) { mType = type; mId = id; + mTunerService = tuner; // Init callback to nullptr mCallback = nullptr; } @@ -67,13 +63,52 @@ Return<Result> Frontend::tune(const FrontendSettings& /* settings */) { return Result::INVALID_STATE; } - mCallback->onEvent(FrontendEventType::NO_SIGNAL); + // TODO dynamically allocate file to the source file + mSourceStreamFile = FRONTEND_STREAM_FILE; + + mCallback->onEvent(FrontendEventType::LOCKED); return Result::SUCCESS; } Return<Result> Frontend::stopTune() { ALOGV("%s", __FUNCTION__); + mTunerService->frontendStopTune(mId); + + return Result::SUCCESS; +} + +Return<Result> Frontend::scan(const FrontendSettings& /* settings */, FrontendScanType /* type */) { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +Return<Result> Frontend::stopScan() { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +Return<void> Frontend::getStatus(const hidl_vec<FrontendStatusType>& /* statusTypes */, + getStatus_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + vector<FrontendStatus> statuses; + _hidl_cb(Result::SUCCESS, statuses); + + return Void(); +} + +Return<Result> Frontend::setLna(bool /* bEnable */) { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +Return<Result> Frontend::setLnb(uint32_t /* lnb */) { + ALOGV("%s", __FUNCTION__); + return Result::SUCCESS; } @@ -85,6 +120,10 @@ FrontendId Frontend::getFrontendId() { return mId; } +string Frontend::getSourceFile() { + return mSourceStreamFile; +} + } // namespace implementation } // namespace V1_0 } // namespace tuner diff --git a/tv/tuner/1.0/default/Frontend.h b/tv/tuner/1.0/default/Frontend.h index f77a0d8894..07fa7b93ab 100644 --- a/tv/tuner/1.0/default/Frontend.h +++ b/tv/tuner/1.0/default/Frontend.h @@ -18,7 +18,9 @@ #define ANDROID_HARDWARE_TV_TUNER_V1_0_FRONTEND_H_ #include <android/hardware/tv/tuner/1.0/IFrontend.h> -#include <android/hardware/tv/tuner/1.0/ITuner.h> +#include <fstream> +#include <iostream> +#include "Tuner.h" using namespace std; @@ -35,10 +37,11 @@ using ::android::hardware::tv::tuner::V1_0::IFrontend; using ::android::hardware::tv::tuner::V1_0::IFrontendCallback; using ::android::hardware::tv::tuner::V1_0::Result; +class Tuner; + class Frontend : public IFrontend { public: - Frontend(); - Frontend(FrontendType type, FrontendId id); + Frontend(FrontendType type, FrontendId id, sp<Tuner> tuner); virtual Return<Result> close() override; @@ -48,15 +51,33 @@ class Frontend : public IFrontend { virtual Return<Result> stopTune() override; + virtual Return<Result> scan(const FrontendSettings& settings, FrontendScanType type) override; + + virtual Return<Result> stopScan() override; + + virtual Return<void> getStatus(const hidl_vec<FrontendStatusType>& statusTypes, + getStatus_cb _hidl_cb) override; + + virtual Return<Result> setLna(bool bEnable) override; + + virtual Return<Result> setLnb(uint32_t lnb) override; + FrontendType getFrontendType(); FrontendId getFrontendId(); + string getSourceFile(); + private: virtual ~Frontend(); sp<IFrontendCallback> mCallback; + sp<Tuner> mTunerService; FrontendType mType = FrontendType::UNDEFINED; FrontendId mId = 0; + + const string FRONTEND_STREAM_FILE = "/vendor/etc/test1.ts"; + string mSourceStreamFile; + std::ifstream mFrontendData; }; } // namespace implementation diff --git a/tv/tuner/1.0/default/Lnb.cpp b/tv/tuner/1.0/default/Lnb.cpp new file mode 100644 index 0000000000..1446f7f344 --- /dev/null +++ b/tv/tuner/1.0/default/Lnb.cpp @@ -0,0 +1,68 @@ +/* + * 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. + */ + +#define LOG_TAG "android.hardware.tv.tuner@1.0-Lnb" + +#include "Lnb.h" +#include <utils/Log.h> + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +Lnb::Lnb() {} + +Lnb::~Lnb() {} + +Return<Result> Lnb::setVoltage(FrontendLnbVoltage /* voltage */) { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +Return<Result> Lnb::setTone(FrontendLnbTone /* tone */) { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +Return<Result> Lnb::setSatellitePosition(FrontendLnbPosition /* position */) { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +Return<Result> Lnb::sendDiseqcMessage(const hidl_vec<uint8_t>& /* diseqcMessage */) { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +Return<Result> Lnb::close() { + ALOGV("%s", __FUNCTION__); + + return Result::SUCCESS; +} + +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android
\ No newline at end of file diff --git a/tv/tuner/1.0/default/Lnb.h b/tv/tuner/1.0/default/Lnb.h new file mode 100644 index 0000000000..4c251f7591 --- /dev/null +++ b/tv/tuner/1.0/default/Lnb.h @@ -0,0 +1,62 @@ +/* + * 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. + */ + +#ifndef ANDROID_HARDWARE_TV_TUNER_V1_0_LNB_H_ +#define ANDROID_HARDWARE_TV_TUNER_V1_0_LNB_H_ + +#include <android/hardware/tv/tuner/1.0/ILnb.h> +#include <android/hardware/tv/tuner/1.0/ITuner.h> + +using namespace std; + +namespace android { +namespace hardware { +namespace tv { +namespace tuner { +namespace V1_0 { +namespace implementation { + +using ::android::hardware::tv::tuner::V1_0::FrontendLnbPosition; +using ::android::hardware::tv::tuner::V1_0::FrontendLnbTone; +using ::android::hardware::tv::tuner::V1_0::FrontendLnbVoltage; +using ::android::hardware::tv::tuner::V1_0::Result; + +class Lnb : public ILnb { + public: + Lnb(); + + virtual Return<Result> setVoltage(FrontendLnbVoltage voltage) override; + + virtual Return<Result> setTone(FrontendLnbTone tone) override; + + virtual Return<Result> setSatellitePosition(FrontendLnbPosition position) override; + + virtual Return<Result> sendDiseqcMessage(const hidl_vec<uint8_t>& diseqcMessage) override; + + virtual Return<Result> close() override; + + private: + virtual ~Lnb(); +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace tuner +} // namespace tv +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_TV_TUNER_V1_0_LNB_H_
\ No newline at end of file diff --git a/tv/tuner/1.0/default/Tuner.cpp b/tv/tuner/1.0/default/Tuner.cpp index 68b34368ee..f86b28d3c6 100644 --- a/tv/tuner/1.0/default/Tuner.cpp +++ b/tv/tuner/1.0/default/Tuner.cpp @@ -22,6 +22,7 @@ #include "Demux.h" #include "Descrambler.h" #include "Frontend.h" +#include "Lnb.h" namespace android { namespace hardware { @@ -37,14 +38,14 @@ Tuner::Tuner() { // Array index matches their FrontendId in the default impl mFrontendSize = 8; mFrontends.resize(mFrontendSize); - mFrontends[0] = new Frontend(); - mFrontends[1] = new Frontend(FrontendType::ATSC, 1); - mFrontends[2] = new Frontend(FrontendType::DVBC, 2); - mFrontends[3] = new Frontend(FrontendType::DVBS, 3); - mFrontends[4] = new Frontend(FrontendType::DVBT, 4); - mFrontends[5] = new Frontend(FrontendType::ISDBT, 5); - mFrontends[6] = new Frontend(FrontendType::ANALOG, 6); - mFrontends[7] = new Frontend(FrontendType::ATSC, 7); + mFrontends[0] = new Frontend(FrontendType::DVBT, 0, this); + mFrontends[1] = new Frontend(FrontendType::ATSC, 1, this); + mFrontends[2] = new Frontend(FrontendType::DVBC, 2, this); + mFrontends[3] = new Frontend(FrontendType::DVBS, 3, this); + mFrontends[4] = new Frontend(FrontendType::DVBT, 4, this); + mFrontends[5] = new Frontend(FrontendType::ISDBT, 5, this); + mFrontends[6] = new Frontend(FrontendType::ANALOG, 6, this); + mFrontends[7] = new Frontend(FrontendType::ATSC, 7, this); } Tuner::~Tuner() {} @@ -80,12 +81,22 @@ Return<void> Tuner::openDemux(openDemux_cb _hidl_cb) { DemuxId demuxId = mLastUsedId + 1; mLastUsedId += 1; - sp<IDemux> demux = new Demux(demuxId); + sp<Demux> demux = new Demux(demuxId, this); + mDemuxes[demuxId] = demux; _hidl_cb(Result::SUCCESS, demuxId, demux); return Void(); } +Return<void> Tuner::getDemuxCaps(getDemuxCaps_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + DemuxCapabilities caps; + + _hidl_cb(Result::SUCCESS, caps); + return Void(); +} + Return<void> Tuner::openDescrambler(openDescrambler_cb _hidl_cb) { ALOGV("%s", __FUNCTION__); @@ -95,6 +106,52 @@ Return<void> Tuner::openDescrambler(openDescrambler_cb _hidl_cb) { return Void(); } +Return<void> Tuner::getFrontendInfo(FrontendId /* frontendId */, getFrontendInfo_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + FrontendInfo info; + + _hidl_cb(Result::SUCCESS, info); + return Void(); +} + +Return<void> Tuner::getLnbIds(getLnbIds_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + vector<LnbId> lnbIds; + + _hidl_cb(Result::SUCCESS, lnbIds); + return Void(); +} + +Return<void> Tuner::openLnbById(LnbId /* lnbId */, openLnbById_cb _hidl_cb) { + ALOGV("%s", __FUNCTION__); + + sp<ILnb> lnb = new Lnb(); + + _hidl_cb(Result::SUCCESS, lnb); + return Void(); +} + +sp<Frontend> Tuner::getFrontendById(uint32_t frontendId) { + ALOGV("%s", __FUNCTION__); + + return mFrontends[frontendId]; +} + +void Tuner::setFrontendAsDemuxSource(uint32_t frontendId, uint32_t demuxId) { + mFrontendToDemux[frontendId] = demuxId; +} + +void Tuner::frontendStopTune(uint32_t frontendId) { + map<uint32_t, uint32_t>::iterator it = mFrontendToDemux.find(frontendId); + uint32_t demuxId; + if (it != mFrontendToDemux.end()) { + demuxId = it->second; + mDemuxes[demuxId]->stopBroadcastInput(); + } +} + } // namespace implementation } // namespace V1_0 } // namespace tuner diff --git a/tv/tuner/1.0/default/Tuner.h b/tv/tuner/1.0/default/Tuner.h index 12e959406a..96da257b0a 100644 --- a/tv/tuner/1.0/default/Tuner.h +++ b/tv/tuner/1.0/default/Tuner.h @@ -18,6 +18,8 @@ #define ANDROID_HARDWARE_TV_TUNER_V1_0_TUNER_H_ #include <android/hardware/tv/tuner/1.0/ITuner.h> +#include <map> +#include "Demux.h" #include "Frontend.h" using namespace std; @@ -29,6 +31,9 @@ namespace tuner { namespace V1_0 { namespace implementation { +class Frontend; +class Demux; + class Tuner : public ITuner { public: Tuner(); @@ -39,12 +44,29 @@ class Tuner : public ITuner { virtual Return<void> openDemux(openDemux_cb _hidl_cb) override; + virtual Return<void> getDemuxCaps(getDemuxCaps_cb _hidl_cb) override; + virtual Return<void> openDescrambler(openDescrambler_cb _hidl_cb) override; + virtual Return<void> getFrontendInfo(FrontendId frontendId, + getFrontendInfo_cb _hidl_cb) override; + + virtual Return<void> getLnbIds(getLnbIds_cb _hidl_cb) override; + + virtual Return<void> openLnbById(LnbId lnbId, openLnbById_cb _hidl_cb) override; + + sp<Frontend> getFrontendById(uint32_t frontendId); + + void setFrontendAsDemuxSource(uint32_t frontendId, uint32_t demuxId); + + void frontendStopTune(uint32_t frontendId); + private: virtual ~Tuner(); // Static mFrontends array to maintain local frontends information vector<sp<Frontend>> mFrontends; + std::map<uint32_t, uint32_t> mFrontendToDemux; + std::map<uint32_t, sp<Demux>> mDemuxes; // To maintain how many Frontends we have int mFrontendSize; // The last used demux id. Initial value is -1. diff --git a/tv/tuner/1.0/default/service.cpp b/tv/tuner/1.0/default/service.cpp index c360fcf05c..7bbc09e45c 100644 --- a/tv/tuner/1.0/default/service.cpp +++ b/tv/tuner/1.0/default/service.cpp @@ -21,7 +21,6 @@ #define LOG_TAG "android.hardware.tv.tuner@1.0-service" #endif -#include <binder/ProcessState.h> #include <hidl/HidlTransportSupport.h> #include <hidl/LegacySupport.h> diff --git a/tv/tuner/1.0/types.hal b/tv/tuner/1.0/types.hal index 77f7eadb79..890c1edd01 100644 --- a/tv/tuner/1.0/types.hal +++ b/tv/tuner/1.0/types.hal @@ -41,42 +41,121 @@ typedef uint32_t FrontendId; enum FrontendType : uint32_t { UNDEFINED = 0, ANALOG, + /* Advanced Television Systems Committee (ATSC) Standard A/72. */ ATSC, + /* Advanced Television Systems Committee (ATSC 3.0) Standard A/300. */ + ATSC3, + /** + * Digital Video Broadcasting - Cable + * DVB Cable Frontend Standard ETSI EN 300 468 V1.15.1. + */ DVBC, + /** + * Digital Video Broadcasting - Satellite + * DVB Satellite Frontend Standard ETSI EN 300 468 V1.15.1 and + * ETSI EN 302 307-2 V1.1.1. + */ DVBS, + /** + * Digital Video Broadcasting - Terrestrial + * DVB Terrestrial Frontend Standard ETSI EN 300 468 V1.15.1 and + * ETSI EN 302 755 V1.4.1. + */ DVBT, + /* Integrated Services Digital Broadcasting-Satellite (ISDB-S) + * ARIB STD-B20 is technical document of ISDB-S. + */ + ISDBS, + /* Integrated Services Digital Broadcasting-Satellite (ISDB-S) + * ARIB STD-B44 is technical document of ISDB-S3. + */ + ISDBS3, + /* Integrated Services Digital Broadcasting-Terrestrial (ISDB-T or SBTVD) + * ABNT NBR 15603 is technical document of ISDB-T. + */ ISDBT, }; /** * Inner Forward Error Correction type as specified in ETSI EN 300 468 V1.15.1 - * It's a 4-bit field specifying the inner FEC scheme used according to the - * table 35 in the spec. + * and ETSI EN 302 307-2 V1.1.1. */ @export -enum FrontendInnerFec : uint32_t { +enum FrontendInnerFec : uint64_t { /* Not defined */ FEC_UNDEFINED = 0, + /* hardware is able to detect and set FEC automatically */ + AUTO = 1 << 0, /* 1/2 conv. code rate */ - FEC_1_2 = 1 << 0, + FEC_1_2 = 1 << 1, + /* 1/3 conv. code rate */ + FEC_1_3 = 1 << 2, + /* 1/4 conv. code rate */ + FEC_1_4 = 1 << 3, + /* 1/5 conv. code rate */ + FEC_1_5 = 1 << 4, /* 2/3 conv. code rate */ - FEC_2_3 = 1 << 1, + FEC_2_3 = 1 << 5, + /* 2/5 conv. code rate */ + FEC_2_5 = 1 << 6, + /* 2/9 conv. code rate */ + FEC_2_9 = 1 << 7, /* 3/4 conv. code rate */ - FEC_3_4 = 1 << 2, + FEC_3_4 = 1 << 8, + /* 3/5 conv. code rate */ + FEC_3_5 = 1 << 9, + /* 4/5 conv. code rate */ + FEC_4_5 = 1 << 10, + /* 4/15 conv. code rate */ + FEC_4_15 = 1 << 11, /* 5/6 conv. code rate */ - FEC_5_6 = 1 << 3, + FEC_5_6 = 1 << 12, + /* 5/9 conv. code rate */ + FEC_5_9 = 1 << 13, + /* 6/7 conv. code rate */ + FEC_6_7 = 1 << 14, /* 7/8 conv. code rate */ - FEC_7_8 = 1 << 4, + FEC_7_8 = 1 << 15, + /* 7/9 conv. code rate */ + FEC_7_9 = 1 << 16, + /* 7/15 conv. code rate */ + FEC_7_15 = 1 << 17, /* 8/9 conv. code rate */ - FEC_8_9 = 1 << 5, - /* 3/5 conv. code rate */ - FEC_3_5 = 1 << 6, - /* 4/5 conv. code rate */ - FEC_4_5 = 1 << 7, + FEC_8_9 = 1 << 18, + /* 8/15 conv. code rate */ + FEC_8_15 = 1 << 19, /* 9/10 conv. code rate */ - FEC_9_10 = 1 << 8, - /* hardware is able to detect and set FEC automatically */ - FEC_AUTO = 1 << 9, + FEC_9_10 = 1 << 20, + /* 9/20 conv. code rate */ + FEC_9_20 = 1 << 21, + /* 11/15 conv. code rate */ + FEC_11_15 = 1 << 22, + /* 11/20 conv. code rate */ + FEC_11_20 = 1 << 23, + /* 11/45 conv. code rate */ + FEC_11_45 = 1 << 24, + /* 13/18 conv. code rate */ + FEC_13_18 = 1 << 25, + /* 13/45 conv. code rate */ + FEC_13_45 = 1 << 26, + /* 14/45 conv. code rate */ + FEC_14_45 = 1 << 27, + /* 23/36 conv. code rate */ + FEC_23_36 = 1 << 28, + /* 25/36 conv. code rate */ + FEC_25_36 = 1 << 29, + /* 26/45 conv. code rate */ + FEC_26_45 = 1 << 30, + /* 28/45 conv. code rate */ + FEC_28_45 = 1 << 31, + /* 29/45 conv. code rate */ + FEC_29_45 = 1 << 32, + /* 31/45 conv. code rate */ + FEC_31_45 = 1 << 33, + /* 32/45 conv. code rate */ + FEC_32_45 = 1 << 34, + /* 77/90 conv. code rate */ + FEC_77_90 = 1 << 35, }; /** @@ -85,8 +164,10 @@ enum FrontendInnerFec : uint32_t { @export enum FrontendAtscModulation : uint32_t { UNDEFINED = 0, - MOD_8VSB = 1 << 0, - MOD_16VSB = 1 << 1, + /** hardware is able to detect and set modulation automatically */ + AUTO = 1 << 0, + MOD_8VSB = 1 << 2, + MOD_16VSB = 1 << 3, }; /** @@ -99,21 +180,812 @@ struct FrontendAtscSettings { }; /** + * Capabilities for ATSC Frontend. + */ +struct FrontendAtscCapabilities { + /** Modulation capability */ + bitfield<FrontendAtscModulation> modulationCap; +}; + +/** + * Modulation Type for ATSC3. + */ +@export +enum FrontendAtsc3Modulation : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set modulation automatically */ + AUTO = 1 << 0, + MOD_QPSK = 1 << 1, + MOD_16QAM = 1 << 2, + MOD_64QAM = 1 << 3, + MOD_256QAM = 1 << 4, + MOD_1024QAM = 1 << 5, + MOD_4096QAM = 1 << 6, +}; + +/** + * Bandwidth for ATSC3. + */ +@export +enum FrontendAtsc3Bandwidth : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set bandwidth automatically */ + AUTO = 1 << 0, + BANDWIDTH_6MHZ = 1 << 1, + BANDWIDTH_7MHZ = 1 << 2, + BANDWIDTH_8MHZ = 1 << 3, +}; + +/** + * Time Interleave Mode for ATSC3. + */ +@export +enum FrontendAtsc3TimeInterleaveMode : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set TimeInterleaveMode automatically */ + AUTO = 1 << 0, + CTI = 1 << 1, + HTI = 1 << 2, +}; + +/** + * Code Rate for ATSC3. + */ +@export +enum FrontendAtsc3CodeRate : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set Coderate automatically */ + AUTO = 1 << 0, + CODERATE_2_15 = 1 << 1, + CODERATE_3_15 = 1 << 2, + CODERATE_4_15 = 1 << 3, + CODERATE_5_15 = 1 << 4, + CODERATE_6_15 = 1 << 5, + CODERATE_7_15 = 1 << 6, + CODERATE_8_15 = 1 << 7, + CODERATE_9_15 = 1 << 8, + CODERATE_10_15 = 1 << 9, + CODERATE_11_15 = 1 << 10, + CODERATE_12_15 = 1 << 11, + CODERATE_13_15 = 1 << 12, +}; + +/** + * Forward Error Correction (FEC) for ATSC3. + */ +@export +enum FrontendAtsc3Fec : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set FEC automatically */ + AUTO = 1 << 0, + BCH_LDPC_16K = 1 << 1, + BCH_LDPC_64K = 1 << 2, + CRC_LDPC_16K = 1 << 3, + CRC_LDPC_64K = 1 << 4, + LDPC_16K = 1 << 5, + LDPC_64K = 1 << 6, +}; + +/** + * Demodulator Output Format for an ATSC3 Frontend. + */ +@export +enum FrontendAtsc3DemodOutputFormat : uint8_t { + /** Dummy. Scan uses this. */ + UNDEFINED = 0, + /** ALP format. Typically used in US region. */ + ATSC3_LINKLAYER_PACKET = 1 << 0, + /** BaseBand packet format. Typically used in Korea region. */ + BASEBAND_PACKET = 1 << 1, +}; + +/** + * PLP basis Signal Settings for an ATSC3 Frontend. + */ +struct FrontendAtsc3PlpSettings { + uint8_t plpId; + FrontendAtsc3Modulation modulation; + FrontendAtsc3TimeInterleaveMode interleaveMode; + FrontendAtsc3CodeRate codeRate; + FrontendAtsc3Fec fec; +}; + +/** + * Signal Settings for an ATSC3 Frontend. + */ +struct FrontendAtsc3Settings { + /** Signal frequency in Hertz */ + uint32_t frequency; + /** Bandwidth of tuning band. */ + FrontendAtsc3Bandwidth bandwidth; + FrontendAtsc3DemodOutputFormat demodOutputFormat; + vec<FrontendAtsc3PlpSettings> plpSettings; +}; + +/** + * Capabilities for ATSC3 Frontend. + */ +struct FrontendAtsc3Capabilities { + /** Bandwidth capability */ + bitfield<FrontendAtsc3Bandwidth> bandwidthCap; + /** Modulation capability */ + bitfield<FrontendAtsc3Modulation> modulationCap; + /** TimeInterleaveMode capability */ + bitfield<FrontendAtsc3TimeInterleaveMode> timeInterleaveModeCap; + /** CodeRate capability */ + bitfield<FrontendAtsc3CodeRate> codeRateCap; + /** FEC capability */ + bitfield<FrontendAtsc3Fec> fecCap; + /** Demodulator Output Format capability */ + bitfield<FrontendAtsc3DemodOutputFormat> demodOutputFormatCap; +}; + +/** + * Modulation Type for DVBS. + */ +@export +enum FrontendDvbsModulation : int32_t { + UNDEFINED = 0, + /** hardware is able to detect and set Modulation automatically */ + AUTO = 1 << 0, + MOD_QPSK = 1 << 1, + MOD_8PSK = 1 << 2, + MOD_16QAM = 1 << 3, + MOD_16PSK = 1 << 4, + MOD_32PSK = 1 << 5, + MOD_ACM = 1 << 6, + MOD_8APSK = 1 << 7, + MOD_16APSK = 1 << 8, + MOD_32APSK = 1 << 9, + MOD_64APSK = 1 << 10, + MOD_128APSK = 1 << 11, + MOD_256APSK = 1 << 12, + /** Reserved for Proprietary modulation */ + MOD_RESERVED = 1 << 13, +}; + +/** + * Roll Off value for DVBS. + */ +@export +enum FrontendDvbsRolloff : uint32_t { + UNDEFINED, + ROLLOFF_0_35, + ROLLOFF_0_25, + ROLLOFF_0_20, + ROLLOFF_0_15, + ROLLOFF_0_10, + ROLLOFF_0_5, +}; + +/** + * Pilot mode for DVBS. + */ +@export +enum FrontendDvbsPilot : uint32_t { + UNDEFINED, + ON, + OFF, + AUTO, +}; + +/** + * Code Rate for DVBS. + */ +struct FrontendDvbsCodeRate { + FrontendInnerFec fec; + bool isLinear; + /* true if enable short frame */ + bool isShortFrames; + /* bits number in 1000 symbol. 0 if use the default. */ + uint32_t bitsPer1000Symbol; +}; + +/** + * Sub standards in DVBS. + */ +@export +enum FrontendDvbsStandard : uint8_t { + AUTO = 1 << 0, + S = 1 << 1, + S2 = 1 << 2, + S2X = 1 << 3, +}; + +/** + * Signal Settings for an DVBS Frontend. + */ +struct FrontendDvbsSettings { + /** Signal frequency in Hertz */ + uint32_t frequency; + FrontendDvbsModulation modulation; + FrontendDvbsCodeRate coderate; + /** Symbols per second */ + uint32_t symbolRate; + FrontendDvbsRolloff rolloff; + FrontendDvbsPilot pilot; + uint32_t inputStreamId; + FrontendDvbsStandard standard; +}; + +/** + * Capabilities for DVBS Frontend. + */ +struct FrontendDvbsCapabilities { + bitfield<FrontendDvbsModulation> modulationCap; + bitfield<FrontendInnerFec> innerfecCap; + bitfield<FrontendDvbsStandard> standard; +}; + +/** + * Modulation Type for DVBC. + */ +@export +enum FrontendDvbcModulation : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set Modulation automatically */ + AUTO = 1 << 0, + MOD_16QAM = 1 << 1, + MOD_32QAM = 1 << 2, + MOD_64QAM = 1 << 3, + MOD_128QAM = 1 << 4, + MOD_256QAM = 1 << 5, +}; + +/** + * Outer Forward Error Correction (FEC) Type for DVBC. + */ +@export +enum FrontendDvbcOuterFec : uint32_t { + UNDEFINED = 0, + OUTER_FEC_NONE, + OUTER_FEC_RS, +}; + +/** + * Annex Type for DVBC. + */ +@export +enum FrontendDvbcAnnex : uint8_t { + UNDEFINED = 0, + A = 1 << 0, + B = 1 << 1, + C = 1 << 2, +}; + +/** + * Spectral Inversion Type for DVBC. + */ +@export +enum FrontendDvbcSpectralInversion : uint32_t { + UNDEFINED, + NORMAL, + INVERTED, +}; + +/** + * Signal Settings for an DVBC Frontend. + */ +struct FrontendDvbcSettings { + /** Signal frequency in Hertz */ + uint32_t frequency; + FrontendDvbcModulation modulation; + FrontendInnerFec fec; + /** Symbols per second */ + uint32_t symbolRate; + FrontendDvbcOuterFec outerFec; + FrontendDvbcAnnex annex; + FrontendDvbcSpectralInversion spectralInversion; +}; + +/** + * Capabilities for DVBC Frontend. + */ +struct FrontendDvbcCapabilities { + bitfield<FrontendDvbcModulation> modulationCap; + bitfield<FrontendInnerFec> fecCap; + bitfield<FrontendDvbcAnnex> annexCap; +}; + +/** + * Bandwidth Type for DVBT. + */ +@export +enum FrontendDvbtBandwidth : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set Bandwidth automatically */ + AUTO = 1 << 0, + BANDWIDTH_8MHZ = 1 << 1, + BANDWIDTH_7MHZ = 1 << 2, + BANDWIDTH_6MHZ = 1 << 3, + BANDWIDTH_5MHZ = 1 << 4, + BANDWIDTH_1_7MHZ = 1 << 5, + BANDWIDTH_10MHZ = 1 << 6, +}; + +/** + * Constellation Type for DVBT. + */ +@export +enum FrontendDvbtConstellation : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set Constellation automatically */ + AUTO = 1 << 0, + CONSTELLATION_QPSK = 1 << 1, + CONSTELLATION_16QAM = 1 << 2, + CONSTELLATION_64QAM = 1 << 3, + CONSTELLATION_256QAM = 1 << 4, +}; + +/** + * Hierarchy Type for DVBT. + */ +@export +enum FrontendDvbtHierarchy : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set Hierarchy automatically */ + AUTO = 1 << 0, + HIERARCHY_NON_NATIVE = 1 << 1, + HIERARCHY_1_NATIVE = 1 << 2, + HIERARCHY_2_NATIVE = 1 << 3, + HIERARCHY_4_NATIVE = 1 << 4, + HIERARCHY_NON_INDEPTH = 1 << 5, + HIERARCHY_1_INDEPTH = 1 << 6, + HIERARCHY_2_INDEPTH = 1 << 7, + HIERARCHY_4_INDEPTH = 1 << 8, +}; + +/** + * Hierarchy Type for DVBT. + */ +@export +enum FrontendDvbtCoderate : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set Hierarchy automatically */ + AUTO = 1 << 0, + CODERATE_1_2 = 1 << 1, + CODERATE_2_3 = 1 << 2, + CODERATE_3_4 = 1 << 3, + CODERATE_5_6 = 1 << 4, + CODERATE_7_8 = 1 << 5, + CODERATE_3_5 = 1 << 6, + CODERATE_4_5 = 1 << 7, + CODERATE_6_7 = 1 << 8, + CODERATE_8_9 = 1 << 9, +}; + +/** + * Guard Interval Type for DVBT. + */ +@export +enum FrontendDvbtGuardInterval : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set Guard Interval automatically */ + AUTO = 1 << 0, + INTERVAL_1_32 = 1 << 1, + INTERVAL_1_16 = 1 << 2, + INTERVAL_1_8 = 1 << 3, + INTERVAL_1_4 = 1 << 4, + INTERVAL_1_128 = 1 << 5, + INTERVAL_19_128 = 1 << 6, + INTERVAL_19_256 = 1 << 7, +}; + +/** + * Transmission Mode for DVBT. + */ +@export +enum FrontendDvbtTransmissionMode : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set Transmission Mode automatically */ + AUTO = 1 << 0, + MODE_2K = 1 << 1, + MODE_8K = 1 << 2, + MODE_4K = 1 << 3, + MODE_1K = 1 << 4, + MODE_16K = 1 << 5, + MODE_32K = 1 << 6, +}; + +/** + * Physical Layer Pipe (PLP) Mode for DVBT. + */ +enum FrontendDvbtPlpMode : uint32_t { + UNDEFINED, + AUTO, + MANUAL, +}; + +/** + * Sub standards in DVBT. + */ +@export +enum FrontendDvbtStandard : uint8_t { + AUTO = 1 << 0, + T = 1 << 1, + T2 = 1 << 2, +}; + +/** * Signal Setting for DVBT Frontend. */ struct FrontendDvbtSettings { /** Signal frequency in Hertz */ uint32_t frequency; - FrontendAtscModulation modulation; - FrontendInnerFec fec; + FrontendDvbtTransmissionMode transmissionMode; + FrontendDvbtBandwidth bandwidth; + FrontendDvbtConstellation constellation; + FrontendDvbtHierarchy hierarchy; + /** Code Rate for High Priority level */ + FrontendDvbtCoderate hpCoderate; + /** Code Rate for Low Priority level */ + FrontendDvbtCoderate lpCoderate; + FrontendDvbtGuardInterval guardInterval; + bool isHighPriority; + FrontendDvbtStandard standard; + bool isMiso; + FrontendDvbtPlpMode plpMode; + /** Physical Layer Pipe (PLP) Id */ + uint8_t plpId; + /** Group Id for Physical Layer Pipe (PLP) */ + uint8_t plpGroupId; }; /** - * Modulation Type for ATSC. + * Capabilities for DVBT Frontend. + */ +struct FrontendDvbtCapabilities { + bitfield<FrontendDvbtTransmissionMode> transmissionModeCap; + bitfield<FrontendDvbtBandwidth> bandwidthCap; + bitfield<FrontendDvbtConstellation> constellationCap; + bitfield<FrontendDvbtCoderate> coderateCap; + bitfield<FrontendDvbtHierarchy> hierarchyCap; + bitfield<FrontendDvbtGuardInterval> guardIntervalCap; + bool isT2Supported; + bool isMisoSupported; +}; + +/** + * Roll Off Type for ISDBS. + */ +@export +enum FrontendIsdbsRolloff : uint32_t { + UNDEFINED, + ROLLOFF_0_35, +}; + +/** + * Modulation Type for ISDBS. + */ +@export +enum FrontendIsdbsModulation : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set Modulation automatically */ + AUTO = 1 << 0, + MOD_BPSK = 1 << 1, + MOD_QPSK = 1 << 2, + MOD_TC8PSK = 1 << 3, +}; + +/** + * Code Rate Type for ISDBS. + */ +@export +enum FrontendIsdbsCoderate : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set Code Rate automatically */ + AUTO = 1 << 0, + CODERATE_1_2 = 1 << 1, + CODERATE_2_3 = 1 << 2, + CODERATE_3_4 = 1 << 3, + CODERATE_5_6 = 1 << 4, + CODERATE_7_8 = 1 << 5, +}; + +/** + * Stream Id Type for ISDBS. + */ +@export +enum FrontendIsdbsStreamIdType : uint32_t { + STREAM_ID, + RELATIVE_STREAM_NUMBER, +}; + +/** + * Signal Setting for ISDBS Frontend. + */ +struct FrontendIsdbsSettings { + /** Signal frequency in Hertz */ + uint32_t frequency; + uint16_t streamId; + FrontendIsdbsStreamIdType streamIdType; + FrontendIsdbsModulation modulation; + FrontendIsdbsCoderate coderate; + /** Symbols per second */ + uint32_t symbolRate; + FrontendIsdbsRolloff rolloff; +}; + +/** + * Capabilities for ISDBS Frontend. + */ +struct FrontendIsdbsCapabilities { + bitfield<FrontendIsdbsModulation> modulationCap; + bitfield<FrontendIsdbsCoderate> coderateCap; +}; + +/** + * Roll of Type for ISDBS3. + */ +@export +enum FrontendIsdbs3Rolloff : uint32_t { + UNDEFINED, + ROLLOFF_0_03, +}; + +/** + * Modulaltion Type for ISDBS3. + */ +@export +enum FrontendIsdbs3Modulation : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set Modulation automatically */ + AUTO = 1 << 5, + MOD_BPSK = 1 << 1, + MOD_QPSK = 1 << 2, + MOD_8PSK = 1 << 3, + MOD_16APSK = 1 << 4, + MOD_32APSK = 1 << 5, +}; + +/** + * Code Rate Type for ISDBS3. + */ +@export +enum FrontendIsdbs3Coderate : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set Code Rate automatically */ + AUTO = 1 << 0, + CODERATE_1_3 = 1 << 1, + CODERATE_2_5 = 1 << 2, + CODERATE_1_2 = 1 << 3, + CODERATE_3_5 = 1 << 4, + CODERATE_2_3 = 1 << 5, + CODERATE_3_4 = 1 << 6, + CODERATE_7_9 = 1 << 7, + CODERATE_4_5 = 1 << 8, + CODERATE_5_6 = 1 << 9, + CODERATE_7_8 = 1 << 10, + CODERATE_9_10 = 1 << 11, +}; + +/** + * Signal Setting for ISDBS3 Frontend. + */ +struct FrontendIsdbs3Settings { + /** Signal frequency in Hertz */ + uint32_t frequency; + uint16_t streamId; + FrontendIsdbsStreamIdType streamIdType; + FrontendIsdbs3Modulation modulation; + FrontendIsdbs3Coderate coderate; + /** Symbols per second */ + uint32_t symbolRate; + FrontendIsdbs3Rolloff rolloff; +}; + +/** + * Capabilities for ISDBS3 Frontend. + */ +struct FrontendIsdbs3Capabilities { + bitfield<FrontendIsdbs3Modulation> modulationCap; + bitfield<FrontendIsdbs3Coderate> coderateCap; +}; + +/** + * Mode for ISDBT. + */ +@export +enum FrontendIsdbtMode : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set Mode automatically */ + AUTO = 1 << 0, + MODE_1 = 1 << 1, + MODE_2 = 1 << 2, + MODE_3 = 1 << 3, +}; + +/** + * Bandwidth for ISDBT. + */ +@export +enum FrontendIsdbtBandwidth : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set Bandwidth automatically */ + AUTO = 1 << 0, + BANDWIDTH_8MHZ = 1 << 1, + BANDWIDTH_7MHZ = 1 << 2, + BANDWIDTH_6MHZ = 1 << 3, +}; + +/** + * Modulation for ISDBT. + */ +@export +enum FrontendIsdbtModulation : uint32_t { + UNDEFINED = 0, + /** hardware is able to detect and set Modulation automatically */ + AUTO = 1 << 0, + MOD_DQPSK = 1 << 1, + MOD_QPSK = 1 << 2, + MOD_16QAM = 1 << 3, + MOD_64QAM = 1 << 4, +}; + +/** Code Rate for ISDBT. */ +typedef FrontendDvbtCoderate FrontendIsdbtCoderate; + +/** Guard Interval for ISDBT. */ +typedef FrontendDvbtGuardInterval FrontendIsdbtGuardInterval; + +/** + * Signal Setting for ISDBT Frontend. + */ +struct FrontendIsdbtSettings { + /** Signal frequency in Hertz */ + uint32_t frequency; + FrontendIsdbtModulation modulation; + FrontendIsdbtBandwidth bandwidth; + FrontendIsdbtMode mode; + FrontendIsdbtCoderate coderate; + FrontendIsdbtGuardInterval guardInterval; + uint32_t serviceAreaId; +}; + +/** + * Capabilities for ISDBT Frontend. + */ +struct FrontendIsdbtCapabilities { + bitfield<FrontendIsdbtMode> modeCap; + bitfield<FrontendIsdbtBandwidth> bandwidthCap; + bitfield<FrontendIsdbtModulation> constellationCap; + bitfield<FrontendIsdbtCoderate> coderateCap; + bitfield<FrontendIsdbtGuardInterval> guardIntervalCap; +}; + +/** + * Signal Type for Analog Frontend. + */ +@export +enum FrontendAnalogType : uint32_t { + UNDEFINED = 0, + PAL = 1 << 0, + SECAM = 1 << 1, + NTSC = 1 << 2, +}; + +/** + * Standard Interchange Format (SIF) for Analog Frontend. + */ +@export +enum FrontendAnalogSifStandard : uint32_t { + UNDEFINED = 0, + BG = 1 << 0, + BG_A2 = 1 << 1, + BG_NICAM = 1 << 2, + I = 1 << 3, + DK = 1 << 4, + DK1 = 1 << 5, + DK2 = 1 << 6, + DK3 = 1 << 7, + DK_NICAM = 1 << 8, + L = 1 << 9, + M = 1 << 10, + M_BTSC = 1 << 11, + M_A2 = 1 << 12, + M_EIA_J = 1 << 13, + I_NICAM = 1 << 14, + L_NICAM = 1 << 15, + L_PRIME = 1 << 16, +}; + +/** + * Signal Setting for Analog Frontend. + */ +struct FrontendAnalogSettings { + /** Signal frequency in Hertz */ + uint32_t frequency; + FrontendAnalogType type; + FrontendAnalogSifStandard sifStandard; +}; + +/** + * Capabilities for Analog Frontend. + */ +struct FrontendAnalogCapabilities { + bitfield<FrontendAnalogType> typeCap; + bitfield<FrontendAnalogSifStandard> sifStandardCap; +}; + +/** + * Signal Setting for Frontend. */ safe_union FrontendSettings { + FrontendAnalogSettings analog; FrontendAtscSettings atsc; + FrontendAtsc3Settings atsc3; + FrontendDvbsSettings dvbs; + FrontendDvbcSettings dvbc; FrontendDvbtSettings dvbt; + FrontendIsdbsSettings isdbs; + FrontendIsdbs3Settings isdbs3; + FrontendIsdbtSettings isdbt; +}; + +/** + * Scan type for Frontend. + */ +enum FrontendScanType : uint32_t { + SCAN_UNDEFINED = 0, + SCAN_AUTO = 1 << 0, + SCAN_BLIND = 1 << 1, +}; + +/** + * Scan Message Type for Frontend. + */ +enum FrontendScanMessageType : uint32_t { + /** Scan locked the signal. */ + LOCKED, + /** Scan stopped. */ + END, + /** Scan progress report. */ + PROGRESS_PERCENT, + /** Locked frequency report. */ + FREQUENCY, + /** Locked symbol rate. */ + SYMBOL_RATE, + /** Locked Plp Ids for DVBT2 frontend. */ + PLP_IDS, + /** Locked group Ids for DVBT2 frontend. */ + GROUP_IDS, + /** Stream Ids. */ + INPUT_STREAM_IDS, + /** Locked signal standard. */ + STANDARD, + /** PLP status in a tuned frequency band for ATSC3 frontend. */ + ATSC3_PLP_INFO, +}; + +/** + * ATSC3.0 PLP information for scan + */ +struct FrontendScanAtsc3PlpInfo { + uint8_t plpId; + bool bLlsFlag; +}; + +/** + * Scan Message for Frontend. + */ +safe_union FrontendScanMessage { + bool isLocked; + bool isEnd; + /** scan progress percent (0..100) */ + uint8_t progressPercent; + /** Signal frequency in Hertz */ + uint32_t frequency; + /** Symbols per second */ + uint32_t symbolRate; + vec<uint8_t> plpIds; + vec<uint8_t> groupIds; + vec<uint16_t> inputStreamIds; + safe_union standard { + FrontendDvbsStandard sStd; + FrontendDvbtStandard tStd; + } std; + /** A list of PLP status in a tuned frequency band for ATSC3 frontend. */ + vec<FrontendScanAtsc3PlpInfo> atsc3PlpInfos; }; /** @@ -122,20 +994,245 @@ safe_union FrontendSettings { @export enum FrontendEventType : uint32_t { /** - * If frontend locked the signal which is specified by tune method, HAL sent + * If frontend locked the signal which is specified by tune method, HAL sends * Locked event. */ LOCKED, /** * If frontend can't locked the signal which is specified by tune method, - * HAL sent NO_SIGNAL event. + * HAL sends NO_SIGNAL event. */ NO_SIGNAL, /** - * If frontend detect that the locked signal get lost, HAL sent LOST_LOCK + * If frontend detect that the locked signal get lost, HAL sends LOST_LOCK * event. */ LOST_LOCK, + /** + * If frontend detect that incoming Diseqc message is overflow. + */ + DISEQC_RX_OVERFLOW, + /** + * If frontend detect that outgoing Diseqc message isn't delivered on time. + */ + DISEQC_RX_TIMEOUT, + /** + * If frontend detect that the incoming Diseqc message has parity error. + */ + DISEQC_RX_PARITY_ERROR, + /** + * If frontend detect that the LNB is overload. + */ + LNB_OVERLOAD, +}; + +/** + * Frontend Status Type. + */ +@export +enum FrontendStatusType : uint32_t { + /** Lock status for Demod. */ + DEMOD_LOCK, + /** Signal to Noise Ratio. */ + SNR, + /** Bit Error Ratio. */ + BER, + /** Packages Error Ratio. */ + PER, + /** Bit Error Ratio before FEC. */ + PRE_BER, + /* + * Signal Quality (0..100). Good data over total data in percent can be + * used as a way to present Signal Quality. + */ + SIGNAL_QUALITY, + /** Signal Strength. */ + SIGNAL_STRENGTH, + /** Symbol Rate. */ + SYMBOL_RATE, + /** Forward Error Correction Type. */ + FEC, + /** Modulation Type. */ + MODULATION, + /** Spectral Inversion Type. */ + SPECTRAL, + /** LNB Voltage. */ + LNB_VOLTAGE, + /** Physical Layer Pipe ID. */ + PLP_ID, + /** Status for Emergency Warning Broadcasting System. */ + EWBS, + /** Automatic Gain Control. */ + AGC, + /** Low Noise Amplifier. */ + LNA, + /** Lock status for stream. */ + STREAM_LOCK, + /** Error status by layer. */ + LAYER_ERROR, + /** CN value by VBER. */ + VBER_CN, + /** CN value by LBER. */ + LBER_CN, + /** CN value by XER. */ + XER_CN, + /** Moduration Error Ratio. */ + MER, + /** Difference between tuning frequency and actual locked frequency. */ + FREQ_OFFSET, + /* Hierarchy for DVBT. */ + HIERARCHY, + /** Lock status for RF. */ + RF_LOCK, + /** PLP information in a frequency band for ATSC3.0 frontend. */ + ATSC3_PLP_INFO, +}; + +/** + * Status for each tuning PLPs + */ +struct FrontendStatusAtsc3PlpInfo { + /** PLP Id value. */ + uint8_t plpId; + /** Demod Lock/Unlock status of this particular PLP. */ + bool isLocked; + /** Uncorrectable Error Counts (UEC) of this particular PLP since last tune operation. */ + uint32_t uec; +}; + + +/** + * Modulation Type for Frontend's status. + */ +safe_union FrontendModulationStatus { + FrontendDvbcModulation dvbc; + FrontendDvbsModulation dvbs; + FrontendIsdbsModulation isdbs; + FrontendIsdbs3Modulation isdbs3; + FrontendIsdbtModulation isdbt; +}; + +/** + * The status for Frontend. + */ +safe_union FrontendStatus { + /** Lock status for Demod in True/False. */ + bool isDemodLocked; + /** SNR value measured by 0.001 dB. */ + int32_t snr; + /** The number of error bit per 1 billion bits. */ + uint32_t ber; + /** The number of error package per 1 billion packages. */ + uint32_t per; + /** The number of error bit per 1 billion bits before FEC. */ + uint32_t preBer; + /** Signal Quality in percent. */ + uint32_t signalQuality; + /** Signal Strength measured by 0.001 dBm. */ + int32_t signalStrength; + /** Symbols per second */ + uint32_t symbolRate; + FrontendInnerFec innerFec; + FrontendModulationStatus modulation; + FrontendDvbcSpectralInversion inversion; + FrontendLnbVoltage lnbVoltage; + uint8_t plpId; + bool isEWBS; + /** AGC value is normalized from 0 to 255. */ + uint8_t agc; + bool isLnaOn; + bool isStreamLock; + vec<bool> isLayerError; + /** CN value by VBER measured by 0.001 dB */ + int32_t vberCn; + /** CN value by LBER measured by 0.001 dB */ + int32_t lberCn; + /** CN value by XER measured by 0.001 dB */ + int32_t xerCn; + /** MER value measured by 0.001 dB */ + int32_t mer; + /** Frequency difference in Hertz. */ + int32_t freqOffset; + FrontendDvbtHierarchy hierarchy; + bool isRfLocked; + /** A list of PLP status for tuned PLPs for ATSC3 frontend. */ + vec<FrontendStatusAtsc3PlpInfo> plpInfo; +}; + +/** + * Information for the Frontend. + */ +struct FrontendInfo { + FrontendType type; + /** Frequency in Hertz */ + uint32_t minFrequency; + /** Frequency in Hertz */ + uint32_t maxFrequency; + /** Minimum symbols per second */ + uint32_t minSymbolRate; + /** Maximum symbols per second */ + uint32_t maxSymbolRate; + /** Range in Hertz */ + uint32_t acquireRange; + /* + * Frontends are assigned with the same exclusiveGroupId if they can't + * function at same time. For instance, they share same hardware module. + */ + uint32_t exclusiveGroupId; + /** A list of supported status types which client can inquiry */ + vec<FrontendStatusType> statusCaps; + safe_union FrontendCapabilities { + FrontendAnalogCapabilities analogCaps; + FrontendAtscCapabilities atscCaps; + FrontendAtsc3Capabilities atsc3Caps; + FrontendDvbsCapabilities dvbsCaps; + FrontendDvbcCapabilities dvbcCaps; + FrontendDvbtCapabilities dvbtCaps; + FrontendIsdbsCapabilities isdbsCaps; + FrontendIsdbs3Capabilities isdbs3Caps; + FrontendIsdbtCapabilities isdbtCaps; + } frontendCaps; +}; + +/* + * Low-Noise Block downconverter (LNB) ID is used to associate with a hardware + * LNB module. + */ +typedef uint32_t LnbId; + +/** + * Power Voltage Type for LNB. + */ +@export +enum FrontendLnbVoltage : uint32_t { + NONE, + VOLTAGE_5V, + VOLTAGE_11V, + VOLTAGE_12V, + VOLTAGE_13V, + VOLTAGE_14V, + VOLTAGE_15V, + VOLTAGE_18V, + VOLTAGE_19V, +}; + +/** + * Tone Type for LNB. + */ +@export +enum FrontendLnbTone : int32_t { + NONE, + CONTINUOUS, +}; + +/** + * The Position of LNB. + */ +@export +enum FrontendLnbPosition : int32_t { + UNDEFINED, + POSITION_A, + POSITION_B, }; /* Demux ID is used to associate with a hardware demux resource. */ @@ -166,7 +1263,7 @@ enum DemuxFilterType : uint32_t { */ AUDIO, /** - * A filter to filter Vidoe Metadata out from input stream. + * A filter to filter Video Metadata out from input stream. */ VIDEO, /** @@ -248,7 +1345,7 @@ struct DemuxFilterSectionSettings { /* Version number for Section Filter */ uint16_t version; /* true if the filter checks CRC and discards data with wrong CRC */ - bool checkCrc; + bool isCheckCrc; /* true if the filter repeats the data with the same version */ bool isRepeat; /* true if the filter output raw data */ @@ -265,7 +1362,7 @@ struct DemuxFilterPesDataSettings { DemuxTpid tpid; DemuxStreamId streamId; /* true if the filter output raw data */ - bool bIsRaw; + bool isRaw; }; /** @@ -283,7 +1380,7 @@ struct DemuxFilterAudioSettings { /** * true if the filter output goes to decoder directly in pass through mode. */ - bool bPassthrough; + bool isPassthrough; }; /** @@ -294,7 +1391,7 @@ struct DemuxFilterVideoSettings { /** * true if the filter output goes to decoder directly in pass through mode. */ - bool bPassthrough; + bool isPassthrough; }; /** @@ -491,6 +1588,8 @@ enum DemuxDataFormat : uint32_t { PES, /* Data is Elementary Stream. */ ES, + /* Data is TLV (type-length-value) Stream for JP SHV */ + SHV_TLV, }; /** @@ -550,6 +1649,10 @@ enum DemuxInputStatus : uint32_t { SPACE_FULL = 1 << 3, }; +/** + * The Settings for the demux's input. + */ +@export struct DemuxInputSettings { /** * Register for interested status events so that the HAL can send these @@ -575,3 +1678,30 @@ struct DemuxInputSettings { */ uint8_t packetSize; }; + +/** + * Capabilities for Demux. + */ +@export +struct DemuxCapabilities { + /* The number of Demux to be supported. */ + uint32_t numDemux; + /* The number of Input to be supported. */ + uint32_t numInput; + /* The number of Output to be supported. */ + uint32_t numOutput; + /* The number of TS Filter to be supported. */ + uint32_t numTsFilter; + /* The number of Section Filter to be supported. */ + uint32_t numSectionFilter; + /* The number of Audio Filter to be supported. */ + uint32_t numAudioFilter; + /* The number of Video Filter to be supported. */ + uint32_t numVideoFilter; + /* The number of PES Filter to be supported. */ + uint32_t numPesFilter; + /* The number of PCR Filter to be supported. */ + uint32_t numPcrFilter; + /* The maximum number of bytes is supported in the mask of Section Filter. */ + uint32_t numBytesInSectionFilter; +}; diff --git a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp index d272d710f3..7936185af5 100644 --- a/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp +++ b/tv/tuner/1.0/vts/functional/VtsHalTvTunerV1_0TargetTest.cpp @@ -36,8 +36,10 @@ #include <utils/Mutex.h> #include <fstream> #include <iostream> +#include <map> #define WAIT_TIMEOUT 3000000000 +#define WAIT_TIMEOUT_data_ready 3000000000 * 4 using android::Condition; using android::IMemory; @@ -57,8 +59,10 @@ using android::hardware::Return; using android::hardware::Void; using android::hardware::tv::tuner::V1_0::DemuxDataFormat; using android::hardware::tv::tuner::V1_0::DemuxFilterEvent; +using android::hardware::tv::tuner::V1_0::DemuxFilterPesDataSettings; using android::hardware::tv::tuner::V1_0::DemuxFilterPesEvent; using android::hardware::tv::tuner::V1_0::DemuxFilterSectionEvent; +using android::hardware::tv::tuner::V1_0::DemuxFilterSectionSettings; using android::hardware::tv::tuner::V1_0::DemuxFilterSettings; using android::hardware::tv::tuner::V1_0::DemuxFilterStatus; using android::hardware::tv::tuner::V1_0::DemuxFilterType; @@ -72,6 +76,8 @@ using android::hardware::tv::tuner::V1_0::FrontendDvbtSettings; using android::hardware::tv::tuner::V1_0::FrontendEventType; using android::hardware::tv::tuner::V1_0::FrontendId; using android::hardware::tv::tuner::V1_0::FrontendInnerFec; +using android::hardware::tv::tuner::V1_0::FrontendScanMessage; +using android::hardware::tv::tuner::V1_0::FrontendScanMessageType; using android::hardware::tv::tuner::V1_0::FrontendSettings; using android::hardware::tv::tuner::V1_0::IDemux; using android::hardware::tv::tuner::V1_0::IDemuxCallback; @@ -127,9 +133,6 @@ const std::vector<uint8_t> goldenDataOutputBuffer{ const uint16_t FMQ_SIZE_4K = 0x1000; const uint32_t FMQ_SIZE_1M = 0x100000; -// Equal to SECTION_WRITE_COUNT on the HAL impl side -// The HAL impl will repeatedly write to the FMQ the count times -const uint16_t SECTION_READ_COUNT = 10; struct FilterConf { DemuxFilterType type; @@ -159,12 +162,21 @@ class FrontendCallback : public IFrontendCallback { return Void(); } + virtual Return<void> onScanMessage(FrontendScanMessageType /* type */, + const FrontendScanMessage& /* message */) override { + android::Mutex::Autolock autoLock(mMsgLock); + mScanMessageReceived = true; + mMsgCondition.signal(); + return Void(); + }; + void testOnEvent(sp<IFrontend>& frontend, FrontendSettings settings); void testOnDiseqcMessage(sp<IFrontend>& frontend, FrontendSettings settings); private: bool mEventReceived = false; bool mDiseqcMessageReceived = false; + bool mScanMessageReceived = false; FrontendEventType mEventType; hidl_vec<uint8_t> mEventMessage; android::Mutex mMsgLock; @@ -202,11 +214,15 @@ void FrontendCallback::testOnDiseqcMessage(sp<IFrontend>& frontend, FrontendSett class DemuxCallback : public IDemuxCallback { public: virtual Return<void> onFilterEvent(const DemuxFilterEvent& filterEvent) override { - ALOGW("[VTS] FILTER EVENT %d", filterEvent.filterId); android::Mutex::Autolock autoLock(mMsgLock); - mFilterEventReceived = true; - // maybe assemble here?? - mFilterEvent = filterEvent; + // Temprarily we treat the first coming back filter data on the matching pid a success + // once all of the MQ are cleared, means we got all the expected output + mFilterIdToEvent[filterEvent.filterId] = filterEvent; + readFilterEventData(filterEvent.filterId); + mPidFilterOutputCount++; + // mFilterIdToMQ.erase(filterEvent.filterId); + + // startFilterEventThread(filterEvent); mMsgCondition.signal(); return Void(); } @@ -220,13 +236,16 @@ class DemuxCallback : public IDemuxCallback { virtual Return<void> onInputStatus(DemuxInputStatus status) override { // android::Mutex::Autolock autoLock(mMsgLock); + ALOGW("[vts] input status %d", status); switch (status) { case DemuxInputStatus::SPACE_EMPTY: case DemuxInputStatus::SPACE_ALMOST_EMPTY: + ALOGW("[vts] keep inputing %d", status); mKeepWritingInputFMQ = true; break; case DemuxInputStatus::SPACE_ALMOST_FULL: case DemuxInputStatus::SPACE_FULL: + ALOGW("[vts] stop inputing %d", status); mKeepWritingInputFMQ = false; break; } @@ -234,185 +253,216 @@ class DemuxCallback : public IDemuxCallback { } void testOnFilterEvent(uint32_t filterId); - void testOnSectionFilterEvent(sp<IDemux>& demux, uint32_t filterId, MQDesc& filterMQDescriptor, - MQDesc& inputMQDescriptor); - void startPlaybackInputThread(InputConf inputConf, MQDesc& inputMQDescriptor); - bool readAndCompareSectionEventData(); + void testFilterDataOutput(); + void stopInputThread(); + void startPlaybackInputThread(InputConf inputConf, MQDesc& inputMQDescriptor); + void startFilterEventThread(DemuxFilterEvent event); static void* __threadLoopInput(void* threadArgs); - void inputThreadLoop(InputConf inputConf, bool* keepWritingInputFMQ, MQDesc& inputMQDescriptor); + static void* __threadLoopFilter(void* threadArgs); + void inputThreadLoop(InputConf* inputConf, bool* keepWritingInputFMQ); + void filterThreadLoop(DemuxFilterEvent& event); + + void updateFilterMQ(uint32_t filterId, MQDesc& filterMQDescriptor); + void updateGoldenOutputMap(uint32_t filterId, string goldenOutputFile); + bool readFilterEventData(uint32_t filterId); private: struct InputThreadArgs { DemuxCallback* user; - InputConf inputConf; + InputConf* inputConf; bool* keepWritingInputFMQ; - MQDesc& inputMQDesc; }; - bool mFilterEventReceived = false; + struct FilterThreadArgs { + DemuxCallback* user; + DemuxFilterEvent event; + }; + uint16_t mDataLength = 0; std::vector<uint8_t> mDataOutputBuffer; - std::unique_ptr<FilterMQ> mFilterMQ; + + bool mFilterEventReceived; + std::map<uint32_t, string> mFilterIdToGoldenOutput; + + std::map<uint32_t, std::unique_ptr<FilterMQ>> mFilterIdToMQ; std::unique_ptr<FilterMQ> mInputMQ; - uint16_t mDataLength = 0; - DemuxFilterEvent mFilterEvent; + std::map<uint32_t, EventFlag*> mFilterIdToMQEventFlag; + std::map<uint32_t, DemuxFilterEvent> mFilterIdToEvent; + EventFlag* mInputMQEventFlag; + android::Mutex mMsgLock; - android::Mutex mReadLock; + android::Mutex mFilterOutputLock; + android::Mutex mInputThreadLock; android::Condition mMsgCondition; - EventFlag* mFilterMQEventFlag; - EventFlag* mInputMQEventFlag; - bool mKeepWritingInputFMQ; + android::Condition mFilterOutputCondition; + + bool mKeepWritingInputFMQ = true; bool mInputThreadRunning; pthread_t mInputThread; -}; + pthread_t mFilterThread; -void DemuxCallback::testOnFilterEvent(uint32_t filterId) { - android::Mutex::Autolock autoLock(mMsgLock); - while (!mFilterEventReceived) { - if (-ETIMEDOUT == mMsgCondition.waitRelative(mMsgLock, WAIT_TIMEOUT)) { - EXPECT_TRUE(false) << "filter event not received within timeout"; - return; - } - } - // Reset the filter event recieved flag - mFilterEventReceived = false; - // Check if filter id match - EXPECT_TRUE(filterId == mFilterEvent.filterId) << "filter id match"; -} + int mPidFilterOutputCount = 0; +}; void DemuxCallback::startPlaybackInputThread(InputConf inputConf, MQDesc& inputMQDescriptor) { + mInputMQ = std::make_unique<FilterMQ>(inputMQDescriptor, true /* resetPointers */); + EXPECT_TRUE(mInputMQ); struct InputThreadArgs* threadArgs = (struct InputThreadArgs*)malloc(sizeof(struct InputThreadArgs)); threadArgs->user = this; - threadArgs->inputConf = inputConf; + threadArgs->inputConf = &inputConf; threadArgs->keepWritingInputFMQ = &mKeepWritingInputFMQ; - threadArgs->inputMQDesc = inputMQDescriptor; pthread_create(&mInputThread, NULL, __threadLoopInput, (void*)threadArgs); pthread_setname_np(mInputThread, "test_playback_input_loop"); } -/*void DemuxCallback::testPlaybackDataFlow(bool* keepWritingInputFMQ) { - // timeout logic here - - // assemble logic here - +void DemuxCallback::startFilterEventThread(DemuxFilterEvent event) { + struct FilterThreadArgs* threadArgs = + (struct FilterThreadArgs*)malloc(sizeof(struct FilterThreadArgs)); + threadArgs->user = this; + threadArgs->event = event; -}*/ + pthread_create(&mFilterThread, NULL, __threadLoopFilter, (void*)threadArgs); + pthread_setname_np(mFilterThread, "test_playback_input_loop"); +} -void DemuxCallback::testOnSectionFilterEvent(sp<IDemux>& demux, uint32_t filterId, - MQDesc& filterMQDescriptor, - MQDesc& inputMQDescriptor) { - Result status; - // Create MQ to read the output into the local buffer - mFilterMQ = std::make_unique<FilterMQ>(filterMQDescriptor, true /* resetPointers */); - EXPECT_TRUE(mFilterMQ); - // Get the MQ to write the input to the HAL - mInputMQ = std::make_unique<FilterMQ>(inputMQDescriptor, true /* resetPointers */); - EXPECT_TRUE(mInputMQ); - // Create the EventFlag that is used to signal the HAL impl that data have been - // read the Filter FMQ - EXPECT_TRUE(EventFlag::createEventFlag(mFilterMQ->getEventFlagWord(), &mFilterMQEventFlag) == - android::OK); - // Create the EventFlag that is used to signal the HAL impl that data have been - // written into the Input FMQ - EXPECT_TRUE(EventFlag::createEventFlag(mInputMQ->getEventFlagWord(), &mInputMQEventFlag) == - android::OK); - // Start filter - status = demux->startFilter(filterId); - status = demux->startInput(); - - EXPECT_EQ(status, Result::SUCCESS); - // Test start filter and receive callback event - for (int i = 0; i < SECTION_READ_COUNT; i++) { - // Write input FMQ and notify the Tuner Implementation - EXPECT_TRUE(mInputMQ->write(goldenDataOutputBuffer.data(), goldenDataOutputBuffer.size())); - mInputMQEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY)); - testOnFilterEvent(filterId); - // checksum of mDataOutputBuffer and Input golden input - if (readAndCompareSectionEventData() && i < SECTION_READ_COUNT - 1) { - mFilterMQEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_CONSUMED)); +void DemuxCallback::testFilterDataOutput() { + android::Mutex::Autolock autoLock(mMsgLock); + while (mPidFilterOutputCount < 1) { + if (-ETIMEDOUT == mMsgCondition.waitRelative(mMsgLock, WAIT_TIMEOUT)) { + EXPECT_TRUE(false) << "filter output matching pid does not output within timeout"; + return; } } + mPidFilterOutputCount = 0; + ALOGW("[vts] pass and stop"); } -bool DemuxCallback::readAndCompareSectionEventData() { - bool result = false; - for (int i = 0; i < mFilterEvent.events.size(); i++) { - DemuxFilterSectionEvent event = mFilterEvent.events[i].section(); - mDataLength = event.dataLength; - EXPECT_TRUE(mDataLength == goldenDataOutputBuffer.size()) << "buffer size does not match"; +void DemuxCallback::stopInputThread() { + mInputThreadRunning = false; + mKeepWritingInputFMQ = false; - mDataOutputBuffer.resize(mDataLength); - result = mFilterMQ->read(mDataOutputBuffer.data(), mDataLength); - EXPECT_TRUE(result) << "can't read from Filter MQ"; + android::Mutex::Autolock autoLock(mInputThreadLock); +} - for (int i = 0; i < mDataLength; i++) { - EXPECT_TRUE(goldenDataOutputBuffer[i] == mDataOutputBuffer[i]) << "data does not match"; - } - } - return result; +void DemuxCallback::updateFilterMQ(uint32_t filterId, MQDesc& filterMQDescriptor) { + mFilterIdToMQ[filterId] = + std::make_unique<FilterMQ>(filterMQDescriptor, true /* resetPointers */); + EXPECT_TRUE(mFilterIdToMQ[filterId]); + EXPECT_TRUE(EventFlag::createEventFlag(mFilterIdToMQ[filterId]->getEventFlagWord(), + &mFilterIdToMQEventFlag[filterId]) == android::OK); +} + +void DemuxCallback::updateGoldenOutputMap(uint32_t filterId, string goldenOutputFile) { + mFilterIdToGoldenOutput[filterId] = goldenOutputFile; } void* DemuxCallback::__threadLoopInput(void* threadArgs) { DemuxCallback* const self = static_cast<DemuxCallback*>(((struct InputThreadArgs*)threadArgs)->user); self->inputThreadLoop(((struct InputThreadArgs*)threadArgs)->inputConf, - ((struct InputThreadArgs*)threadArgs)->keepWritingInputFMQ, - ((struct InputThreadArgs*)threadArgs)->inputMQDesc); + ((struct InputThreadArgs*)threadArgs)->keepWritingInputFMQ); return 0; } -void DemuxCallback::inputThreadLoop(InputConf inputConf, bool* keepWritingInputFMQ, - MQDesc& inputMQDescriptor) { +void DemuxCallback::inputThreadLoop(InputConf* inputConf, bool* keepWritingInputFMQ) { + android::Mutex::Autolock autoLock(mInputThreadLock); mInputThreadRunning = true; - std::unique_ptr inputMQ = - std::make_unique<FilterMQ>(inputMQDescriptor, true /* resetPointers */); - EXPECT_TRUE(inputMQ); - // Create the EventFlag that is used to signal the HAL impl that data have been // written into the Input FMQ EventFlag* inputMQEventFlag; - EXPECT_TRUE(EventFlag::createEventFlag(inputMQ->getEventFlagWord(), &inputMQEventFlag) == + EXPECT_TRUE(EventFlag::createEventFlag(mInputMQ->getEventFlagWord(), &inputMQEventFlag) == android::OK); // open the stream and get its length - std::ifstream inputData(inputConf.inputDataFile /*"ts/test1.ts"*/, std::ifstream::binary); - int writeSize = inputConf.setting.packetSize * 6; + std::ifstream inputData(inputConf->inputDataFile, std::ifstream::binary); + int writeSize = inputConf->setting.packetSize * 6; char* buffer = new char[writeSize]; - if (!inputData) { - // log + ALOGW("[vts] input thread loop start %s", inputConf->inputDataFile.c_str()); + if (!inputData.is_open()) { mInputThreadRunning = false; + ALOGW("[vts] Error %s", strerror(errno)); } while (mInputThreadRunning) { - // move the stream pointer for packet size * 2k? every read until end + // move the stream pointer for packet size * 6 every read until the end while (*keepWritingInputFMQ) { inputData.read(buffer, writeSize); if (!inputData) { int leftSize = inputData.gcount(); + if (leftSize == 0) { + mInputThreadRunning = false; + break; + } inputData.clear(); inputData.read(buffer, leftSize); // Write the left over of the input data and quit the thread if (leftSize > 0) { - EXPECT_TRUE(inputMQ->write((unsigned char*)&buffer[0], - leftSize / inputConf.setting.packetSize)); + EXPECT_TRUE(mInputMQ->write((unsigned char*)&buffer[0], leftSize)); inputMQEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY)); } mInputThreadRunning = false; break; } // Write input FMQ and notify the Tuner Implementation - EXPECT_TRUE(inputMQ->write((unsigned char*)&buffer[0], 6)); + EXPECT_TRUE(mInputMQ->write((unsigned char*)&buffer[0], writeSize)); inputMQEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY)); inputData.seekg(writeSize, inputData.cur); + sleep(1); } } + ALOGW("[vts] Input thread end."); + delete[] buffer; inputData.close(); } +void* DemuxCallback::__threadLoopFilter(void* threadArgs) { + DemuxCallback* const self = + static_cast<DemuxCallback*>(((struct FilterThreadArgs*)threadArgs)->user); + self->filterThreadLoop(((struct FilterThreadArgs*)threadArgs)->event); + return 0; +} + +void DemuxCallback::filterThreadLoop(DemuxFilterEvent& /* event */) { + android::Mutex::Autolock autoLock(mFilterOutputLock); + // Read from mFilterIdToMQ[event.filterId] per event and filter type + + // Assemble to filterOutput[filterId] + + // check if filterOutput[filterId] matches goldenOutput[filterId] + + // If match, remove filterId entry from MQ map + + // end thread +} + +bool DemuxCallback::readFilterEventData(uint32_t filterId) { + bool result = false; + DemuxFilterEvent filterEvent = mFilterIdToEvent[filterId]; + ALOGW("[vts] reading from filter FMQ %d", filterId); + // todo separate filter handlers + for (int i = 0; i < filterEvent.events.size(); i++) { + DemuxFilterPesEvent event = filterEvent.events[i].pes(); + mDataLength = event.dataLength; + // EXPECT_TRUE(mDataLength == goldenDataOutputBuffer.size()) << "buffer size does not + // match"; + + mDataOutputBuffer.resize(mDataLength); + result = mFilterIdToMQ[filterId]->read(mDataOutputBuffer.data(), mDataLength); + EXPECT_TRUE(result) << "can't read from Filter MQ"; + + /*for (int i = 0; i < mDataLength; i++) { + EXPECT_TRUE(goldenDataOutputBuffer[i] == mDataOutputBuffer[i]) << "data does not match"; + }*/ + } + mFilterIdToMQEventFlag[filterId]->wake( + static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_CONSUMED)); + return result; +} + // Test environment for Tuner HIDL HAL. class TunerHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { public: @@ -447,6 +497,7 @@ class TunerHidlTest : public ::testing::VtsHalHidlTargetTestBase { sp<DemuxCallback> mDemuxCallback; MQDesc mFilterMQDescriptor; MQDesc mInputMQDescriptor; + vector<uint32_t> mUsedFilterIds; uint32_t mDemuxId; uint32_t mFilterId; @@ -459,19 +510,21 @@ class TunerHidlTest : public ::testing::VtsHalHidlTargetTestBase { ::testing::AssertionResult stopTuneFrontend(int32_t frontendId); ::testing::AssertionResult closeFrontend(int32_t frontendId); ::testing::AssertionResult createDemux(); - ::testing::AssertionResult createDemuxWithFrontend(int32_t frontendId); + ::testing::AssertionResult createDemuxWithFrontend(int32_t frontendId, + FrontendSettings settings); ::testing::AssertionResult getInputMQDescriptor(); ::testing::AssertionResult addInputToDemux(DemuxInputSettings setting); - ::testing::AssertionResult addSectionFilterToDemux(); ::testing::AssertionResult addFilterToDemux(DemuxFilterType type, DemuxFilterSettings setting); ::testing::AssertionResult getFilterMQDescriptor(const uint32_t filterId); ::testing::AssertionResult closeDemux(); ::testing::AssertionResult createDescrambler(); ::testing::AssertionResult closeDescrambler(); - ::testing::AssertionResult readSectionFilterDataOutput(); ::testing::AssertionResult playbackDataFlowTest(vector<FilterConf> filterConf, - InputConf inputConf, string goldenOutput); + InputConf inputConf, + vector<string> goldenOutputFiles); + ::testing::AssertionResult broadcastDataFlowTest(vector<FilterConf> filterConf, + vector<string> goldenOutputFiles); }; ::testing::AssertionResult TunerHidlTest::createFrontend(int32_t frontendId) { @@ -502,13 +555,11 @@ class TunerHidlTest : public ::testing::VtsHalHidlTargetTestBase { .frequency = 0, .modulation = FrontendAtscModulation::UNDEFINED, }; - frontendSettings.atsc() = frontendAtscSettings; + frontendSettings.atsc(frontendAtscSettings); mFrontendCallback->testOnEvent(mFrontend, frontendSettings); FrontendDvbtSettings frontendDvbtSettings{ .frequency = 0, - .modulation = FrontendAtscModulation::UNDEFINED, - .fec = FrontendInnerFec::FEC_UNDEFINED, }; frontendSettings.dvbt(frontendDvbtSettings); mFrontendCallback->testOnEvent(mFrontend, frontendSettings); @@ -548,7 +599,8 @@ class TunerHidlTest : public ::testing::VtsHalHidlTargetTestBase { return ::testing::AssertionResult(status == Result::SUCCESS); } -::testing::AssertionResult TunerHidlTest::createDemuxWithFrontend(int32_t frontendId) { +::testing::AssertionResult TunerHidlTest::createDemuxWithFrontend(int32_t frontendId, + FrontendSettings settings) { Result status; if (!mDemux && createDemux() == ::testing::AssertionFailure()) { @@ -559,6 +611,8 @@ class TunerHidlTest : public ::testing::VtsHalHidlTargetTestBase { return ::testing::AssertionFailure(); } + mFrontendCallback->testOnEvent(mFrontend, settings); + status = mDemux->setFrontendDataSource(frontendId); return ::testing::AssertionResult(status == Result::SUCCESS); @@ -623,7 +677,7 @@ class TunerHidlTest : public ::testing::VtsHalHidlTargetTestBase { mDemuxCallback = new DemuxCallback(); } - // Add section filter to the local demux + // Add playback input to the local demux status = mDemux->addInput(FMQ_SIZE_1M, mDemuxCallback); if (status != Result::SUCCESS) { @@ -650,28 +704,6 @@ class TunerHidlTest : public ::testing::VtsHalHidlTargetTestBase { return ::testing::AssertionResult(status == Result::SUCCESS); } -::testing::AssertionResult TunerHidlTest::addSectionFilterToDemux() { - Result status; - - if (!mDemux && createDemux() == ::testing::AssertionFailure()) { - return ::testing::AssertionFailure(); - } - - // Create demux callback - if (!mDemuxCallback) { - mDemuxCallback = new DemuxCallback(); - } - - // Add section filter to the local demux - mDemux->addFilter(DemuxFilterType::SECTION, FMQ_SIZE_4K, mDemuxCallback, - [&](Result result, uint32_t filterId) { - mFilterId = filterId; - status = result; - }); - - return ::testing::AssertionResult(status == Result::SUCCESS); -} - ::testing::AssertionResult TunerHidlTest::addFilterToDemux(DemuxFilterType type, DemuxFilterSettings setting) { Result status; @@ -717,35 +749,10 @@ class TunerHidlTest : public ::testing::VtsHalHidlTargetTestBase { return ::testing::AssertionResult(status == Result::SUCCESS); } -::testing::AssertionResult TunerHidlTest::readSectionFilterDataOutput() { - // Filter Configuration Module - DemuxInputSettings setting{ - .statusMask = 0xf, - .lowThreshold = 0x1000, - .highThreshold = 0x100000, - .dataFormat = DemuxDataFormat::TS, - .packetSize = 188, - }; - if (addSectionFilterToDemux() == ::testing::AssertionFailure() || - getFilterMQDescriptor(mFilterId) == ::testing::AssertionFailure() || - addInputToDemux(setting) == ::testing::AssertionFailure() || - getInputMQDescriptor() == ::testing::AssertionFailure()) { - return ::testing::AssertionFailure(); - } - - // Data Verify Module - // Test start filter and read the output data - mDemuxCallback->testOnSectionFilterEvent(mDemux, mFilterId, mFilterMQDescriptor, - mInputMQDescriptor); - - // Clean Up Module - return closeDemux(); //::testing::AssertionSuccess(); -} - -::testing::AssertionResult TunerHidlTest::playbackDataFlowTest(vector<FilterConf> filterConf, - InputConf inputConf, - string /*goldenOutput*/) { +::testing::AssertionResult TunerHidlTest::playbackDataFlowTest( + vector<FilterConf> filterConf, InputConf inputConf, vector<string> /*goldenOutputFiles*/) { Result status; + int filterIdsSize; // Filter Configuration Module for (int i = 0; i < filterConf.size(); i++) { if (addFilterToDemux(filterConf[i].type, filterConf[i].setting) == @@ -754,6 +761,15 @@ class TunerHidlTest : public ::testing::VtsHalHidlTargetTestBase { getFilterMQDescriptor(mFilterId) == ::testing::AssertionFailure()) { return ::testing::AssertionFailure(); } + filterIdsSize = mUsedFilterIds.size(); + mUsedFilterIds.resize(filterIdsSize + 1); + mUsedFilterIds[filterIdsSize] = mFilterId; + mDemuxCallback->updateFilterMQ(mFilterId, mFilterMQDescriptor); + // mDemuxCallback->updateGoldenOutputMap(mFilterId, goldenOutputFiles[i]); + status = mDemux->startFilter(mFilterId); + if (status != Result::SUCCESS) { + return ::testing::AssertionFailure(); + } } // Playback Input Module @@ -769,12 +785,77 @@ class TunerHidlTest : public ::testing::VtsHalHidlTargetTestBase { } // Data Verify Module - // golden output, created FMQ to read and EventFlags to DATA_CONSUMED - // Maintain each filter's real output (and how to assemble?????) - // mDemuxCallback->testPlaybackDataFlow(); + mDemuxCallback->testFilterDataOutput(); + mDemuxCallback->stopInputThread(); + + // Clean Up Module + for (int i = 0; i <= filterIdsSize; i++) { + if (mDemux->stopFilter(mUsedFilterIds[i]) != Result::SUCCESS) { + return ::testing::AssertionFailure(); + } + } + if (mDemux->stopInput() != Result::SUCCESS) { + return ::testing::AssertionFailure(); + } + return closeDemux(); +} + +::testing::AssertionResult TunerHidlTest::broadcastDataFlowTest( + vector<FilterConf> filterConf, vector<string> /*goldenOutputFiles*/) { + Result status; + hidl_vec<FrontendId> feIds; + + mService->getFrontendIds([&](Result result, const hidl_vec<FrontendId>& frontendIds) { + status = result; + feIds = frontendIds; + }); + + if (feIds.size() == 0) { + ALOGW("[ WARN ] Frontend isn't available"); + return ::testing::AssertionFailure(); + } + + FrontendDvbtSettings dvbt{ + .frequency = 1000, + }; + FrontendSettings settings; + settings.dvbt(dvbt); + + if (createDemuxWithFrontend(feIds[0], settings) != ::testing::AssertionSuccess()) { + return ::testing::AssertionFailure(); + } + + int filterIdsSize; + // Filter Configuration Module + for (int i = 0; i < filterConf.size(); i++) { + if (addFilterToDemux(filterConf[i].type, filterConf[i].setting) == + ::testing::AssertionFailure() || + // TODO use a map to save the FMQs/EvenFlags and pass to callback + getFilterMQDescriptor(mFilterId) == ::testing::AssertionFailure()) { + return ::testing::AssertionFailure(); + } + filterIdsSize = mUsedFilterIds.size(); + mUsedFilterIds.resize(filterIdsSize + 1); + mUsedFilterIds[filterIdsSize] = mFilterId; + mDemuxCallback->updateFilterMQ(mFilterId, mFilterMQDescriptor); + status = mDemux->startFilter(mFilterId); + if (status != Result::SUCCESS) { + return ::testing::AssertionFailure(); + } + } + + // Data Verify Module + mDemuxCallback->testFilterDataOutput(); // Clean Up Module - // TODO what about remove input, remove filters + for (int i = 0; i <= filterIdsSize; i++) { + if (mDemux->stopFilter(mUsedFilterIds[i]) != Result::SUCCESS) { + return ::testing::AssertionFailure(); + } + } + if (mFrontend->stopTune() != Result::SUCCESS) { + return ::testing::AssertionFailure(); + } return closeDemux(); } @@ -861,7 +942,7 @@ TEST_F(TunerHidlTest, CloseFrontend) { } } -TEST_F(TunerHidlTest, CreateDemuxWithFrontend) { +/*TEST_F(TunerHidlTest, CreateDemuxWithFrontend) { Result status; hidl_vec<FrontendId> feIds; @@ -876,10 +957,17 @@ TEST_F(TunerHidlTest, CreateDemuxWithFrontend) { return; } + FrontendDvbtSettings dvbt{ + .frequency = 1000, + }; + FrontendSettings settings; + settings.dvbt(dvbt); + for (size_t i = 0; i < feIds.size(); i++) { - ASSERT_TRUE(createDemuxWithFrontend(feIds[i])); + ASSERT_TRUE(createDemuxWithFrontend(feIds[i], settings)); + mFrontend->stopTune(); } -} +}*/ TEST_F(TunerHidlTest, CreateDemux) { description("Create Demux"); @@ -904,9 +992,63 @@ TEST_F(TunerHidlTest, CloseDescrambler) { /* * DATA FLOW TESTS */ -TEST_F(TunerHidlTest, ReadSectionFilterOutput) { - description("Read data output from FMQ of a Section Filter"); - ASSERT_TRUE(readSectionFilterDataOutput()); +TEST_F(TunerHidlTest, PlaybackDataFlowWithPesFilterTest) { + description("Feed ts data from playback and configure pes filter to get output"); + + // todo modulize the filter conf parser + vector<FilterConf> filterConf; + filterConf.resize(1); + + DemuxFilterSettings filterSetting; + DemuxFilterPesDataSettings pesFilterSetting{ + .tpid = 18, + }; + filterSetting.pesData(pesFilterSetting); + FilterConf pesFilterConf{ + .type = DemuxFilterType::PES, + .setting = filterSetting, + }; + filterConf[0] = pesFilterConf; + + DemuxInputSettings inputSetting{ + .statusMask = 0xf, + .lowThreshold = 0x1000, + .highThreshold = 0x07fff, + .dataFormat = DemuxDataFormat::TS, + .packetSize = 188, + }; + + InputConf inputConf{ + .inputDataFile = "/vendor/etc/test1.ts", + .setting = inputSetting, + }; + + vector<string> goldenOutputFiles; + + ASSERT_TRUE(playbackDataFlowTest(filterConf, inputConf, goldenOutputFiles)); +} + +TEST_F(TunerHidlTest, BroadcastDataFlowWithPesFilterTest) { + description("Feed ts data from frontend and test with PES filter"); + + // todo modulize the filter conf parser + vector<FilterConf> filterConf; + filterConf.resize(1); + + DemuxFilterSettings filterSetting; + DemuxFilterPesDataSettings pesFilterSetting{ + .tpid = 18, + }; + filterSetting.pesData(pesFilterSetting); + FilterConf pesFilterConf{ + .type = DemuxFilterType::PES, + .setting = filterSetting, + }; + filterConf[0] = pesFilterConf; + + vector<string> goldenOutputFiles; + + ASSERT_TRUE(broadcastDataFlowTest(filterConf, goldenOutputFiles)); } } // namespace diff --git a/tv/tuner/README.md b/tv/tuner/README.md new file mode 100644 index 0000000000..aa1f62d422 --- /dev/null +++ b/tv/tuner/README.md @@ -0,0 +1,12 @@ +# Tuner HALs + +## Overview + +TV specific tuners. + +See 1.0/ITuner.hal for an overview. + +*** note +**Warning:** The HALs are not (yet) frozen, as the HAL definition is +expected to evolve between Android releases. +*** diff --git a/vibrator/1.0/vts/functional/Android.bp b/vibrator/1.0/vts/functional/Android.bp index 391d3d49f8..10ec2cbab6 100644 --- a/vibrator/1.0/vts/functional/Android.bp +++ b/vibrator/1.0/vts/functional/Android.bp @@ -19,6 +19,6 @@ cc_test { defaults: ["VtsHalTargetTestDefaults"], srcs: ["VtsHalVibratorV1_0TargetTest.cpp"], static_libs: ["android.hardware.vibrator@1.0"], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/vibrator/1.0/vts/functional/VtsHalVibratorV1_0TargetTest.cpp b/vibrator/1.0/vts/functional/VtsHalVibratorV1_0TargetTest.cpp index 6f8aa02892..2aee3385d7 100644 --- a/vibrator/1.0/vts/functional/VtsHalVibratorV1_0TargetTest.cpp +++ b/vibrator/1.0/vts/functional/VtsHalVibratorV1_0TargetTest.cpp @@ -21,8 +21,9 @@ #include <android/hardware/vibrator/1.0/types.h> #include <unistd.h> -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> using ::android::sp; using ::android::hardware::hidl_enum_range; @@ -35,27 +36,11 @@ using ::android::hardware::vibrator::V1_0::Status; #define EXPECT_OK(ret) EXPECT_TRUE((ret).isOk()) -// Test environment for Vibrator HIDL HAL. -class VibratorHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static VibratorHidlEnvironment* Instance() { - static VibratorHidlEnvironment* instance = new VibratorHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IVibrator>(); } - - private: - VibratorHidlEnvironment() {} -}; - // The main test class for VIBRATOR HIDL HAL. -class VibratorHidlTest : public ::testing::VtsHalHidlTargetTestBase { +class VibratorHidlTest : public ::testing::TestWithParam<std::string> { public: virtual void SetUp() override { - vibrator = ::testing::VtsHalHidlTargetTestBase::getService<IVibrator>( - VibratorHidlEnvironment::Instance()->getServiceName<IVibrator>()); + vibrator = IVibrator::getService(GetParam()); ASSERT_NE(vibrator, nullptr); } @@ -79,13 +64,13 @@ static void validatePerformEffectBadInput(Status status, uint32_t lengthMs) { << "Effects that return UNSUPPORTED_OPERATION must have a duration of zero"; } -TEST_F(VibratorHidlTest, OnThenOffBeforeTimeout) { +TEST_P(VibratorHidlTest, OnThenOffBeforeTimeout) { EXPECT_EQ(Status::OK, vibrator->on(2000)); sleep(1); EXPECT_EQ(Status::OK, vibrator->off()); } -TEST_F(VibratorHidlTest, PerformEffect) { +TEST_P(VibratorHidlTest, PerformEffect) { vibrator->perform(Effect::CLICK, EffectStrength::MEDIUM, validatePerformEffect); vibrator->perform(Effect::DOUBLE_CLICK, EffectStrength::LIGHT, validatePerformEffect); } @@ -93,7 +78,7 @@ TEST_F(VibratorHidlTest, PerformEffect) { /* * Test to make sure effect values above the valid range are rejected. */ -TEST_F(VibratorHidlTest, PerformEffect_BadEffects_AboveValidRange) { +TEST_P(VibratorHidlTest, PerformEffect_BadEffects_AboveValidRange) { Effect effect = *std::prev(hidl_enum_range<Effect>().end()); Effect badEffect = static_cast<Effect>(static_cast<int32_t>(effect) + 1); EXPECT_OK(vibrator->perform(badEffect, EffectStrength::LIGHT, validatePerformEffectBadInput)); @@ -102,7 +87,7 @@ TEST_F(VibratorHidlTest, PerformEffect_BadEffects_AboveValidRange) { /* * Test to make sure effect values below the valid range are rejected. */ -TEST_F(VibratorHidlTest, PerformEffect_BadEffects_BelowValidRange) { +TEST_P(VibratorHidlTest, PerformEffect_BadEffects_BelowValidRange) { Effect effect = *hidl_enum_range<Effect>().begin(); Effect badEffect = static_cast<Effect>(static_cast<int32_t>(effect) - 1); EXPECT_OK(vibrator->perform(badEffect, EffectStrength::LIGHT, validatePerformEffectBadInput)); @@ -111,7 +96,7 @@ TEST_F(VibratorHidlTest, PerformEffect_BadEffects_BelowValidRange) { /* * Test to make sure strength values above the valid range are rejected. */ -TEST_F(VibratorHidlTest, PerformEffect_BadStrength_AboveValidRange) { +TEST_P(VibratorHidlTest, PerformEffect_BadStrength_AboveValidRange) { EffectStrength strength = *std::prev(hidl_enum_range<EffectStrength>().end()); EffectStrength badStrength = static_cast<EffectStrength>(static_cast<int32_t>(strength) + 1); EXPECT_OK(vibrator->perform(Effect::CLICK, badStrength, validatePerformEffectBadInput)); @@ -120,13 +105,13 @@ TEST_F(VibratorHidlTest, PerformEffect_BadStrength_AboveValidRange) { /* * Test to make sure strength values below the valid range are rejected. */ -TEST_F(VibratorHidlTest, PerformEffect_BadStrength_BelowValidRange) { +TEST_P(VibratorHidlTest, PerformEffect_BadStrength_BelowValidRange) { EffectStrength strength = *hidl_enum_range<EffectStrength>().begin(); EffectStrength badStrength = static_cast<EffectStrength>(static_cast<int32_t>(strength) - 1); EXPECT_OK(vibrator->perform(Effect::CLICK, badStrength, validatePerformEffectBadInput)); } -TEST_F(VibratorHidlTest, ChangeVibrationalAmplitude) { +TEST_P(VibratorHidlTest, ChangeVibrationalAmplitude) { if (vibrator->supportsAmplitudeControl()) { EXPECT_EQ(Status::OK, vibrator->setAmplitude(1)); EXPECT_EQ(Status::OK, vibrator->on(2000)); @@ -137,23 +122,19 @@ TEST_F(VibratorHidlTest, ChangeVibrationalAmplitude) { } } -TEST_F(VibratorHidlTest, AmplitudeOutsideRangeFails) { +TEST_P(VibratorHidlTest, AmplitudeOutsideRangeFails) { if (vibrator->supportsAmplitudeControl()) { EXPECT_EQ(Status::BAD_VALUE, vibrator->setAmplitude(0)); } } -TEST_F(VibratorHidlTest, SetAmplitudeReturnUnsupportedOperationIfNotSupported) { +TEST_P(VibratorHidlTest, SetAmplitudeReturnUnsupportedOperationIfNotSupported) { if (!vibrator->supportsAmplitudeControl()) { EXPECT_EQ(Status::UNSUPPORTED_OPERATION, vibrator->setAmplitude(1)); } } -int main(int argc, char **argv) { - ::testing::AddGlobalTestEnvironment(VibratorHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - VibratorHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - LOG(INFO) << "Test result = " << status; - return status; -} +INSTANTIATE_TEST_SUITE_P( + PerInstance, VibratorHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IVibrator::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/vibrator/1.1/vts/functional/Android.bp b/vibrator/1.1/vts/functional/Android.bp index c65ff4157b..4cde350e79 100644 --- a/vibrator/1.1/vts/functional/Android.bp +++ b/vibrator/1.1/vts/functional/Android.bp @@ -22,6 +22,6 @@ cc_test { "android.hardware.vibrator@1.0", "android.hardware.vibrator@1.1", ], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/vibrator/1.1/vts/functional/VtsHalVibratorV1_1TargetTest.cpp b/vibrator/1.1/vts/functional/VtsHalVibratorV1_1TargetTest.cpp index 3c3ebf2329..da94308f0c 100644 --- a/vibrator/1.1/vts/functional/VtsHalVibratorV1_1TargetTest.cpp +++ b/vibrator/1.1/vts/functional/VtsHalVibratorV1_1TargetTest.cpp @@ -16,11 +16,12 @@ #define LOG_TAG "vibrator_hidl_hal_test" -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> #include <android-base/logging.h> #include <android/hardware/vibrator/1.1/IVibrator.h> #include <android/hardware/vibrator/1.1/types.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> #include <unistd.h> using ::android::sp; @@ -34,27 +35,11 @@ using ::android::hardware::vibrator::V1_1::IVibrator; #define EXPECT_OK(ret) EXPECT_TRUE((ret).isOk()) -// Test environment for Vibrator HIDL HAL. -class VibratorHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static VibratorHidlEnvironment* Instance() { - static VibratorHidlEnvironment* instance = new VibratorHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IVibrator>(); } - - private: - VibratorHidlEnvironment() {} -}; - // The main test class for VIBRATOR HIDL HAL 1.1. -class VibratorHidlTest_1_1 : public ::testing::VtsHalHidlTargetTestBase { +class VibratorHidlTest_1_1 : public ::testing::TestWithParam<std::string> { public: virtual void SetUp() override { - vibrator = ::testing::VtsHalHidlTargetTestBase::getService<IVibrator>( - VibratorHidlEnvironment::Instance()->getServiceName<IVibrator>()); + vibrator = IVibrator::getService(GetParam()); ASSERT_NE(vibrator, nullptr); } @@ -80,7 +65,7 @@ static void validatePerformEffectBadInput(Status status, uint32_t lengthMs) { << "Effects that return UNSUPPORTED_OPERATION must have a duration of zero"; } -TEST_F(VibratorHidlTest_1_1, PerformEffect_1_1) { +TEST_P(VibratorHidlTest_1_1, PerformEffect_1_1) { vibrator->perform_1_1(Effect_1_1::CLICK, EffectStrength::MEDIUM, validatePerformEffect); vibrator->perform_1_1(Effect_1_1::TICK, EffectStrength::STRONG, validatePerformEffect); } @@ -88,7 +73,7 @@ TEST_F(VibratorHidlTest_1_1, PerformEffect_1_1) { /* * Test to make sure effect values above the valid range are rejected. */ -TEST_F(VibratorHidlTest_1_1, PerformEffect_1_1_BadEffects_AboveValidRange) { +TEST_P(VibratorHidlTest_1_1, PerformEffect_1_1_BadEffects_AboveValidRange) { Effect_1_1 effect = *std::prev(hidl_enum_range<Effect_1_1>().end()); Effect_1_1 badEffect = static_cast<Effect_1_1>(static_cast<int32_t>(effect) + 1); EXPECT_OK( @@ -98,7 +83,7 @@ TEST_F(VibratorHidlTest_1_1, PerformEffect_1_1_BadEffects_AboveValidRange) { /* * Test to make sure effect values below the valid range are rejected. */ -TEST_F(VibratorHidlTest_1_1, PerformEffect_1_1_BadEffects_BelowValidRange) { +TEST_P(VibratorHidlTest_1_1, PerformEffect_1_1_BadEffects_BelowValidRange) { Effect_1_1 effect = *hidl_enum_range<Effect_1_1>().begin(); Effect_1_1 badEffect = static_cast<Effect_1_1>(static_cast<int32_t>(effect) - 1); EXPECT_OK( @@ -108,7 +93,7 @@ TEST_F(VibratorHidlTest_1_1, PerformEffect_1_1_BadEffects_BelowValidRange) { /* * Test to make sure strength values above the valid range are rejected. */ -TEST_F(VibratorHidlTest_1_1, PerformEffect_1_1_BadStrength_AboveValidRange) { +TEST_P(VibratorHidlTest_1_1, PerformEffect_1_1_BadStrength_AboveValidRange) { EffectStrength strength = *std::prev(hidl_enum_range<EffectStrength>().end()); EffectStrength badStrength = static_cast<EffectStrength>(static_cast<int32_t>(strength) + 1); EXPECT_OK(vibrator->perform_1_1(Effect_1_1::CLICK, badStrength, validatePerformEffectBadInput)); @@ -117,17 +102,13 @@ TEST_F(VibratorHidlTest_1_1, PerformEffect_1_1_BadStrength_AboveValidRange) { /* * Test to make sure strength values below the valid range are rejected. */ -TEST_F(VibratorHidlTest_1_1, PerformEffect_1_1_BadStrength_BelowValidRange) { +TEST_P(VibratorHidlTest_1_1, PerformEffect_1_1_BadStrength_BelowValidRange) { EffectStrength strength = *hidl_enum_range<EffectStrength>().begin(); EffectStrength badStrength = static_cast<EffectStrength>(static_cast<int32_t>(strength) - 1); EXPECT_OK(vibrator->perform_1_1(Effect_1_1::CLICK, badStrength, validatePerformEffectBadInput)); } -int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(VibratorHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - VibratorHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - LOG(INFO) << "Test result = " << status; - return status; -} +INSTANTIATE_TEST_SUITE_P( + PerInstance, VibratorHidlTest_1_1, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IVibrator::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/vibrator/1.2/vts/functional/Android.bp b/vibrator/1.2/vts/functional/Android.bp index 1e3ec97e51..e7052f2b6d 100644 --- a/vibrator/1.2/vts/functional/Android.bp +++ b/vibrator/1.2/vts/functional/Android.bp @@ -23,6 +23,6 @@ cc_test { "android.hardware.vibrator@1.1", "android.hardware.vibrator@1.2", ], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/vibrator/1.2/vts/functional/VtsHalVibratorV1_2TargetTest.cpp b/vibrator/1.2/vts/functional/VtsHalVibratorV1_2TargetTest.cpp index d69695a51e..2058e85100 100644 --- a/vibrator/1.2/vts/functional/VtsHalVibratorV1_2TargetTest.cpp +++ b/vibrator/1.2/vts/functional/VtsHalVibratorV1_2TargetTest.cpp @@ -16,12 +16,13 @@ #define LOG_TAG "vibrator_hidl_hal_test" -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> #include <android-base/logging.h> #include <android/hardware/vibrator/1.0/types.h> #include <android/hardware/vibrator/1.2/IVibrator.h> #include <android/hardware/vibrator/1.2/types.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> #include <unistd.h> using ::android::hardware::vibrator::V1_0::Status; @@ -35,27 +36,11 @@ using ::android::sp; #define EXPECT_OK(ret) ASSERT_TRUE((ret).isOk()) -// Test environment for Vibrator HIDL HAL. -class VibratorHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static VibratorHidlEnvironment* Instance() { - static VibratorHidlEnvironment* instance = new VibratorHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IVibrator>(); } - - private: - VibratorHidlEnvironment() {} -}; - // The main test class for VIBRATOR HIDL HAL 1.2. -class VibratorHidlTest_1_2 : public ::testing::VtsHalHidlTargetTestBase { +class VibratorHidlTest_1_2 : public ::testing::TestWithParam<std::string> { public: virtual void SetUp() override { - vibrator = ::testing::VtsHalHidlTargetTestBase::getService<IVibrator>( - VibratorHidlEnvironment::Instance()->getServiceName<IVibrator>()); + vibrator = IVibrator::getService(GetParam()); ASSERT_NE(vibrator, nullptr); } @@ -85,7 +70,7 @@ static void validatePerformEffectBadInput(Status status, uint32_t lengthMs) { * Test to make sure effects within the valid range return are either supported and return OK with * a valid duration, or are unsupported and return UNSUPPORTED_OPERATION with a duration of 0. */ -TEST_F(VibratorHidlTest_1_2, PerformEffect_1_2) { +TEST_P(VibratorHidlTest_1_2, PerformEffect_1_2) { for (const auto& effect : hidl_enum_range<Effect>()) { for (const auto& strength : hidl_enum_range<EffectStrength>()) { EXPECT_OK(vibrator->perform_1_2(effect, strength, validatePerformEffect)); @@ -96,7 +81,7 @@ TEST_F(VibratorHidlTest_1_2, PerformEffect_1_2) { /* * Test to make sure effect values above the valid range are rejected. */ -TEST_F(VibratorHidlTest_1_2, PerformEffect_1_2_BadEffects_AboveValidRange) { +TEST_P(VibratorHidlTest_1_2, PerformEffect_1_2_BadEffects_AboveValidRange) { Effect effect = *std::prev(hidl_enum_range<Effect>().end()); Effect badEffect = static_cast<Effect>(static_cast<int32_t>(effect) + 1); EXPECT_OK( @@ -106,7 +91,7 @@ TEST_F(VibratorHidlTest_1_2, PerformEffect_1_2_BadEffects_AboveValidRange) { /* * Test to make sure effect values below the valid range are rejected. */ -TEST_F(VibratorHidlTest_1_2, PerformEffect_1_2_BadEffects_BelowValidRange) { +TEST_P(VibratorHidlTest_1_2, PerformEffect_1_2_BadEffects_BelowValidRange) { Effect effect = *hidl_enum_range<Effect>().begin(); Effect badEffect = static_cast<Effect>(static_cast<int32_t>(effect) - 1); EXPECT_OK( @@ -116,7 +101,7 @@ TEST_F(VibratorHidlTest_1_2, PerformEffect_1_2_BadEffects_BelowValidRange) { /* * Test to make sure strength values above the valid range are rejected. */ -TEST_F(VibratorHidlTest_1_2, PerformEffect_1_2_BadStrength_AboveValidRange) { +TEST_P(VibratorHidlTest_1_2, PerformEffect_1_2_BadStrength_AboveValidRange) { EffectStrength strength = *std::prev(hidl_enum_range<EffectStrength>().end()); EffectStrength badStrength = static_cast<EffectStrength>(static_cast<int32_t>(strength) + 1); EXPECT_OK(vibrator->perform_1_2(Effect::THUD, badStrength, validatePerformEffectBadInput)); @@ -125,17 +110,13 @@ TEST_F(VibratorHidlTest_1_2, PerformEffect_1_2_BadStrength_AboveValidRange) { /* * Test to make sure strength values below the valid range are rejected. */ -TEST_F(VibratorHidlTest_1_2, PerformEffect_1_2_BadStrength_BelowValidRange) { +TEST_P(VibratorHidlTest_1_2, PerformEffect_1_2_BadStrength_BelowValidRange) { EffectStrength strength = *hidl_enum_range<EffectStrength>().begin(); EffectStrength badStrength = static_cast<EffectStrength>(static_cast<int32_t>(strength) - 1); EXPECT_OK(vibrator->perform_1_2(Effect::THUD, badStrength, validatePerformEffectBadInput)); } -int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(VibratorHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - VibratorHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - LOG(INFO) << "Test result = " << status; - return status; -} +INSTANTIATE_TEST_SUITE_P( + PerInstance, VibratorHidlTest_1_2, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IVibrator::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/vibrator/1.3/example/android.hardware.vibrator@1.3-service.example.rc b/vibrator/1.3/example/android.hardware.vibrator@1.3-service.example.rc deleted file mode 100644 index ed7a562cfc..0000000000 --- a/vibrator/1.3/example/android.hardware.vibrator@1.3-service.example.rc +++ /dev/null @@ -1,4 +0,0 @@ -service vendor.vibrator-1-3 /vendor/bin/hw/android.hardware.vibrator@1.3-service.example - class hal - user system - group system diff --git a/vibrator/1.3/vts/functional/Android.bp b/vibrator/1.3/vts/functional/Android.bp index 5b4c8933d2..038dc5ca59 100644 --- a/vibrator/1.3/vts/functional/Android.bp +++ b/vibrator/1.3/vts/functional/Android.bp @@ -24,6 +24,6 @@ cc_test { "android.hardware.vibrator@1.2", "android.hardware.vibrator@1.3", ], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/vibrator/1.3/vts/functional/VtsHalVibratorV1_3TargetTest.cpp b/vibrator/1.3/vts/functional/VtsHalVibratorV1_3TargetTest.cpp index 818f9c7ab4..3cd34300b9 100644 --- a/vibrator/1.3/vts/functional/VtsHalVibratorV1_3TargetTest.cpp +++ b/vibrator/1.3/vts/functional/VtsHalVibratorV1_3TargetTest.cpp @@ -16,11 +16,12 @@ #define LOG_TAG "vibrator_hidl_hal_test" -#include <VtsHalHidlTargetTestBase.h> -#include <VtsHalHidlTargetTestEnvBase.h> #include <android-base/logging.h> #include <android/hardware/vibrator/1.0/types.h> #include <android/hardware/vibrator/1.3/IVibrator.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> #include <unistd.h> using ::android::sp; @@ -34,27 +35,11 @@ using ::android::hardware::vibrator::V1_3::IVibrator; #define EXPECT_OK(ret) ASSERT_TRUE((ret).isOk()) -// Test environment for Vibrator HIDL HAL. -class VibratorHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static VibratorHidlEnvironment* Instance() { - static VibratorHidlEnvironment* instance = new VibratorHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService<IVibrator>(); } - - private: - VibratorHidlEnvironment() {} -}; - // The main test class for VIBRATOR HIDL HAL 1.3. -class VibratorHidlTest_1_3 : public ::testing::VtsHalHidlTargetTestBase { +class VibratorHidlTest_1_3 : public ::testing::TestWithParam<std::string> { public: virtual void SetUp() override { - vibrator = ::testing::VtsHalHidlTargetTestBase::getService<IVibrator>( - VibratorHidlEnvironment::Instance()->getServiceName<IVibrator>()); + vibrator = IVibrator::getService(GetParam()); ASSERT_NE(vibrator, nullptr); } @@ -63,7 +48,7 @@ class VibratorHidlTest_1_3 : public ::testing::VtsHalHidlTargetTestBase { sp<IVibrator> vibrator; }; -TEST_F(VibratorHidlTest_1_3, ChangeVibrationalExternalControl) { +TEST_P(VibratorHidlTest_1_3, ChangeVibrationalExternalControl) { if (vibrator->supportsExternalControl()) { EXPECT_EQ(Status::OK, vibrator->setExternalControl(true)); sleep(1); @@ -72,7 +57,7 @@ TEST_F(VibratorHidlTest_1_3, ChangeVibrationalExternalControl) { } } -TEST_F(VibratorHidlTest_1_3, SetExternalControlReturnUnsupportedOperationIfNotSupported) { +TEST_P(VibratorHidlTest_1_3, SetExternalControlReturnUnsupportedOperationIfNotSupported) { if (!vibrator->supportsExternalControl()) { EXPECT_EQ(Status::UNSUPPORTED_OPERATION, vibrator->setExternalControl(true)); } @@ -98,7 +83,7 @@ static void validatePerformEffect(Status status, uint32_t lengthMs) { * Test to make sure effects within the valid range return are either supported and return OK with * a valid duration, or are unsupported and return UNSUPPORTED_OPERATION with a duration of 0. */ -TEST_F(VibratorHidlTest_1_3, PerformEffect_1_3) { +TEST_P(VibratorHidlTest_1_3, PerformEffect_1_3) { for (const auto& effect : hidl_enum_range<Effect>()) { for (const auto& strength : hidl_enum_range<EffectStrength>()) { EXPECT_OK(vibrator->perform_1_3(effect, strength, validatePerformEffect)); @@ -109,7 +94,7 @@ TEST_F(VibratorHidlTest_1_3, PerformEffect_1_3) { /* * Test to make sure effect values above the valid range are rejected. */ -TEST_F(VibratorHidlTest_1_3, PerformEffect_1_3_BadEffects_AboveValidRange) { +TEST_P(VibratorHidlTest_1_3, PerformEffect_1_3_BadEffects_AboveValidRange) { Effect effect = *std::prev(hidl_enum_range<Effect>().end()); Effect badEffect = static_cast<Effect>(static_cast<int32_t>(effect) + 1); EXPECT_OK(vibrator->perform_1_3(badEffect, EffectStrength::LIGHT, @@ -119,7 +104,7 @@ TEST_F(VibratorHidlTest_1_3, PerformEffect_1_3_BadEffects_AboveValidRange) { /* * Test to make sure effect values below the valid range are rejected. */ -TEST_F(VibratorHidlTest_1_3, PerformEffect_1_3_BadEffects_BelowValidRange) { +TEST_P(VibratorHidlTest_1_3, PerformEffect_1_3_BadEffects_BelowValidRange) { Effect effect = *hidl_enum_range<Effect>().begin(); Effect badEffect = static_cast<Effect>(static_cast<int32_t>(effect) - 1); EXPECT_OK(vibrator->perform_1_3(badEffect, EffectStrength::LIGHT, @@ -129,7 +114,7 @@ TEST_F(VibratorHidlTest_1_3, PerformEffect_1_3_BadEffects_BelowValidRange) { /* * Test to make sure strength values above the valid range are rejected. */ -TEST_F(VibratorHidlTest_1_3, PerformEffect_1_3_BadStrength_AboveValidRange) { +TEST_P(VibratorHidlTest_1_3, PerformEffect_1_3_BadStrength_AboveValidRange) { EffectStrength strength = *std::prev(hidl_enum_range<EffectStrength>().end()); EffectStrength badStrength = static_cast<EffectStrength>(static_cast<int32_t>(strength) + 1); EXPECT_OK(vibrator->perform_1_3(Effect::THUD, badStrength, @@ -139,18 +124,14 @@ TEST_F(VibratorHidlTest_1_3, PerformEffect_1_3_BadStrength_AboveValidRange) { /* * Test to make sure strength values below the valid range are rejected. */ -TEST_F(VibratorHidlTest_1_3, PerformEffect_1_3_BadStrength_BelowValidRange) { +TEST_P(VibratorHidlTest_1_3, PerformEffect_1_3_BadStrength_BelowValidRange) { EffectStrength strength = *hidl_enum_range<EffectStrength>().begin(); EffectStrength badStrength = static_cast<EffectStrength>(static_cast<int32_t>(strength) - 1); EXPECT_OK(vibrator->perform_1_3(Effect::THUD, badStrength, validatePerformEffectUnsupportedOperation)); } -int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(VibratorHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - VibratorHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - LOG(INFO) << "Test result = " << status; - return status; -} +INSTANTIATE_TEST_SUITE_P( + PerInstance, VibratorHidlTest_1_3, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IVibrator::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/vibrator/1.4/Android.bp b/vibrator/1.4/Android.bp new file mode 100644 index 0000000000..cf31fcdab5 --- /dev/null +++ b/vibrator/1.4/Android.bp @@ -0,0 +1,22 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.vibrator@1.4", + root: "android.hardware", + vndk: { + enabled: true, + }, + srcs: [ + "types.hal", + "IVibrator.hal", + "IVibratorCallback.hal", + ], + interfaces: [ + "android.hardware.vibrator@1.0", + "android.hardware.vibrator@1.1", + "android.hardware.vibrator@1.2", + "android.hardware.vibrator@1.3", + "android.hidl.base@1.0", + ], + gen_java: true, +} diff --git a/vibrator/1.4/IVibrator.hal b/vibrator/1.4/IVibrator.hal new file mode 100644 index 0000000000..913abe3ed1 --- /dev/null +++ b/vibrator/1.4/IVibrator.hal @@ -0,0 +1,57 @@ +/* + * 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.hardware.vibrator@1.4; + +import @1.0::EffectStrength; +import @1.3::Effect; +import @1.0::Status; +import @1.3::IVibrator; +import IVibratorCallback; + +interface IVibrator extends @1.3::IVibrator { + /** + * Determine capabilities of the vibrator HAL. + */ + getCapabilities() generates (bitfield<Capabilities> capabilities); + + /** + * Turn on vibrator + * + * This function must only be called after the previous timeout has expired or + * was canceled (through off()). + * @param timeoutMs number of milliseconds to vibrate. + * @param callback A callback used to inform Frameworks of state change, if supported. + * @return vibratorOnRet whether vibrator command was successful or not. + */ + on_1_4(uint32_t timeoutMs, IVibratorCallback callback) generates (Status vibratorOnRet); + + /** + * Fire off a predefined haptic event. + * + * @param effect The type of haptic event to trigger. + * @param strength The intensity of haptic event to trigger. + * @param callback A callback used to inform Frameworks of state change, if supported. + * @return status Whether the effect was successfully performed or not. Must + * return Status::UNSUPPORTED_OPERATION if the effect is not supported. + * @return lengthMs The length of time the event is expected to take in + * milliseconds. This doesn't need to be perfectly accurate, but should be a reasonable + * approximation. Should be a positive, non-zero value if the returned status is Status::OK, + * and set to 0 otherwise. + */ + perform_1_4(Effect effect, EffectStrength strength, IVibratorCallback callback) + generates (Status status, uint32_t lengthMs); +}; diff --git a/vibrator/1.4/IVibratorCallback.hal b/vibrator/1.4/IVibratorCallback.hal new file mode 100644 index 0000000000..76281bc589 --- /dev/null +++ b/vibrator/1.4/IVibratorCallback.hal @@ -0,0 +1,21 @@ +/* + * 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.hardware.vibrator@1.4; + +interface IVibratorCallback { + oneway onComplete(); +}; diff --git a/vibrator/1.4/types.hal b/vibrator/1.4/types.hal new file mode 100644 index 0000000000..acc49b15c0 --- /dev/null +++ b/vibrator/1.4/types.hal @@ -0,0 +1,22 @@ +/* + * 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.hardware.vibrator@1.4; + +enum Capabilities : uint32_t { + ON_COMPLETION_CALLBACK = 1 << 0, + PERFORM_COMPLETION_CALLBACK = 1 << 1, +}; diff --git a/vibrator/1.4/vts/functional/Android.bp b/vibrator/1.4/vts/functional/Android.bp new file mode 100644 index 0000000000..202a824077 --- /dev/null +++ b/vibrator/1.4/vts/functional/Android.bp @@ -0,0 +1,33 @@ +// +// 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. +// + +cc_test { + name: "VtsHalVibratorV1_4TargetTest", + defaults: ["VtsHalTargetTestDefaults"], + srcs: ["VtsHalVibratorV1_4TargetTest.cpp"], + static_libs: [ + "android.hardware.vibrator@1.0", + "android.hardware.vibrator@1.1", + "android.hardware.vibrator@1.2", + "android.hardware.vibrator@1.3", + "android.hardware.vibrator@1.4", + ], + test_suites: [ + "general-tests", + "vts-core", + ], +} + diff --git a/vibrator/1.4/vts/functional/VtsHalVibratorV1_4TargetTest.cpp b/vibrator/1.4/vts/functional/VtsHalVibratorV1_4TargetTest.cpp new file mode 100644 index 0000000000..1b6abe9e52 --- /dev/null +++ b/vibrator/1.4/vts/functional/VtsHalVibratorV1_4TargetTest.cpp @@ -0,0 +1,203 @@ +/* + * 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. + */ + +#define LOG_TAG "vibrator_hidl_hal_test" + +#include <android-base/logging.h> +#include <android/hardware/vibrator/1.0/types.h> +#include <android/hardware/vibrator/1.4/IVibrator.h> +#include <gtest/gtest.h> +#include <hidl/GtestPrinter.h> +#include <hidl/ServiceManagement.h> + +#include <getopt.h> +#include <unistd.h> + +#include <future> + +using ::android::sp; +using ::android::hardware::hidl_bitfield; +using ::android::hardware::hidl_enum_range; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::vibrator::V1_0::EffectStrength; +using ::android::hardware::vibrator::V1_0::Status; +using ::android::hardware::vibrator::V1_3::Effect; +using ::android::hardware::vibrator::V1_4::Capabilities; +using ::android::hardware::vibrator::V1_4::IVibrator; +using ::android::hardware::vibrator::V1_4::IVibratorCallback; + +static uint32_t sCompletionLimitMs = UINT32_MAX; + +#define EXPECT_OK(ret) ASSERT_TRUE((ret).isOk()) + +class CompletionCallback : public IVibratorCallback { + public: + CompletionCallback(std::function<void()> callback) : mCallback(callback) {} + Return<void> onComplete() override { + mCallback(); + return Void(); + } + + private: + std::function<void()> mCallback; +}; + +class VibratorHidlTest_1_4 : public testing::TestWithParam<std::string> { + public: + virtual void SetUp() override { + vibrator = IVibrator::getService(GetParam()); + ASSERT_NE(vibrator, nullptr); + capabilities = vibrator->getCapabilities(); + } + + virtual void TearDown() override {} + + sp<IVibrator> vibrator; + hidl_bitfield<Capabilities> capabilities; +}; + +TEST_P(VibratorHidlTest_1_4, OnWithCallback) { + if (capabilities & Capabilities::ON_COMPLETION_CALLBACK) { + std::promise<void> completionPromise; + std::future<void> completionFuture{completionPromise.get_future()}; + sp<CompletionCallback> callback = + new CompletionCallback([&completionPromise] { completionPromise.set_value(); }); + uint32_t duration = 250; + std::chrono::milliseconds timeout{duration * 2}; + EXPECT_EQ(Status::OK, vibrator->on_1_4(duration, callback)); + EXPECT_EQ(completionFuture.wait_for(timeout), std::future_status::ready); + vibrator->off(); + } +} + +static void validatePerformEffectUnsupportedOperation(Status status, uint32_t lengthMs) { + ASSERT_EQ(Status::UNSUPPORTED_OPERATION, status); + ASSERT_EQ(static_cast<uint32_t>(0), lengthMs) + << "Effects that return UNSUPPORTED_OPERATION must have a duration of zero"; +} + +static void validatePerformEffect(Status status, uint32_t lengthMs) { + ASSERT_TRUE(status == Status::OK || status == Status::UNSUPPORTED_OPERATION); + if (status == Status::OK) { + ASSERT_LT(static_cast<uint32_t>(0), lengthMs) + << "Effects that return OK must return a positive duration"; + } else { + validatePerformEffectUnsupportedOperation(status, lengthMs); + } +} + +/* + * Test to make sure effects within the valid range return are either supported and return OK with + * a valid duration, or are unsupported and return UNSUPPORTED_OPERATION with a duration of 0. + */ +TEST_P(VibratorHidlTest_1_4, PerformEffect_1_4) { + Status performStatus; + uint32_t performLength; + auto validateWrapper = [&](Status status, uint32_t lengthMs) { + performStatus = status; + performLength = lengthMs; + validatePerformEffect(status, lengthMs); + }; + for (const auto& effect : hidl_enum_range<Effect>()) { + for (const auto& strength : hidl_enum_range<EffectStrength>()) { + std::promise<void> completionPromise; + std::future<void> completionFuture{completionPromise.get_future()}; + sp<CompletionCallback> callback = + new CompletionCallback([&completionPromise] { completionPromise.set_value(); }); + EXPECT_OK(vibrator->perform_1_4(effect, strength, callback, validateWrapper)); + if (performStatus == Status::OK && performLength < sCompletionLimitMs && + (capabilities & Capabilities::PERFORM_COMPLETION_CALLBACK)) { + std::chrono::milliseconds timeout{performLength * 2}; + EXPECT_EQ(completionFuture.wait_for(timeout), std::future_status::ready); + } + } + } +} + +/* + * Test to make sure effect values above the valid range are rejected. + */ +TEST_P(VibratorHidlTest_1_4, PerformEffect_1_4_BadEffects_AboveValidRange) { + Effect effect = *std::prev(hidl_enum_range<Effect>().end()); + Effect badEffect = static_cast<Effect>(static_cast<int32_t>(effect) + 1); + EXPECT_OK(vibrator->perform_1_4(badEffect, EffectStrength::LIGHT, nullptr, + validatePerformEffectUnsupportedOperation)); +} + +/* + * Test to make sure effect values below the valid range are rejected. + */ +TEST_P(VibratorHidlTest_1_4, PerformEffect_1_4_BadEffects_BelowValidRange) { + Effect effect = *hidl_enum_range<Effect>().begin(); + Effect badEffect = static_cast<Effect>(static_cast<int32_t>(effect) - 1); + EXPECT_OK(vibrator->perform_1_4(badEffect, EffectStrength::LIGHT, nullptr, + validatePerformEffectUnsupportedOperation)); +} + +/* + * Test to make sure strength values above the valid range are rejected. + */ +TEST_P(VibratorHidlTest_1_4, PerformEffect_1_4_BadStrength_AboveValidRange) { + EffectStrength strength = *std::prev(hidl_enum_range<EffectStrength>().end()); + EffectStrength badStrength = static_cast<EffectStrength>(static_cast<int32_t>(strength) + 1); + EXPECT_OK(vibrator->perform_1_4(Effect::THUD, badStrength, nullptr, + validatePerformEffectUnsupportedOperation)); +} + +/* + * Test to make sure strength values below the valid range are rejected. + */ +TEST_P(VibratorHidlTest_1_4, PerformEffect_1_4_BadStrength_BelowValidRange) { + EffectStrength strength = *hidl_enum_range<EffectStrength>().begin(); + EffectStrength badStrength = static_cast<EffectStrength>(static_cast<int32_t>(strength) - 1); + EXPECT_OK(vibrator->perform_1_4(Effect::THUD, badStrength, nullptr, + validatePerformEffectUnsupportedOperation)); +} + +INSTANTIATE_TEST_SUITE_P( + PerInstance, VibratorHidlTest_1_4, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IVibrator::descriptor)), + android::hardware::PrintInstanceNameToString); + +enum { + OPTION_COMPLETION_LIMIT_MS, +}; + +int main(int argc, char** argv) { + struct option options[] = { + {"completion-limit-ms", required_argument, 0, OPTION_COMPLETION_LIMIT_MS}, {}}; + + printf("Running main() from %s\n", __FILE__); + testing::InitGoogleTest(&argc, argv); + + while (true) { + int opt = getopt_long(argc, argv, "", options, nullptr); + if (opt == -1) { + break; + } + switch (opt) { + case OPTION_COMPLETION_LIMIT_MS: + std::istringstream(optarg) >> sCompletionLimitMs; + break; + default: + printf("Unrecognized option\n"); + return -EINVAL; + } + } + + return RUN_ALL_TESTS(); +} diff --git a/vibrator/1.3/example/Android.bp b/vibrator/1.x/example/Android.bp index 07f1c26db3..afbbb759ac 100644 --- a/vibrator/1.3/example/Android.bp +++ b/vibrator/1.x/example/Android.bp @@ -14,11 +14,11 @@ // limitations under the License. cc_binary { - name: "android.hardware.vibrator@1.3-service.example", + name: "android.hardware.vibrator@1.x-service.example", vendor: true, relative_install_path: "hw", - init_rc: ["android.hardware.vibrator@1.3-service.example.rc"], - vintf_fragments: ["android.hardware.vibrator@1.3-service.example.xml"], + init_rc: ["android.hardware.vibrator@1.x-service.example.rc"], + vintf_fragments: ["android.hardware.vibrator@1.x-service.example.xml"], srcs: ["service.cpp", "Vibrator.cpp"], cflags: ["-Wall", "-Werror"], shared_libs: [ @@ -29,5 +29,6 @@ cc_binary { "android.hardware.vibrator@1.1", "android.hardware.vibrator@1.2", "android.hardware.vibrator@1.3", + "android.hardware.vibrator@1.4", ], } diff --git a/vibrator/1.3/example/OWNERS b/vibrator/1.x/example/OWNERS index 4b34968b9f..4b34968b9f 100644 --- a/vibrator/1.3/example/OWNERS +++ b/vibrator/1.x/example/OWNERS diff --git a/vibrator/1.3/example/Vibrator.cpp b/vibrator/1.x/example/Vibrator.cpp index b529437108..4dd1cb90b6 100644 --- a/vibrator/1.3/example/Vibrator.cpp +++ b/vibrator/1.x/example/Vibrator.cpp @@ -23,7 +23,7 @@ namespace android { namespace hardware { namespace vibrator { -namespace V1_3 { +namespace V1_4 { namespace implementation { static constexpr uint32_t MS_PER_S = 1000; @@ -100,7 +100,25 @@ Return<Status> Vibrator::setExternalControl(bool enabled) { } } -Return<void> Vibrator::perform_1_3(Effect effect, EffectStrength strength, perform_cb _hidl_cb) { +Return<void> Vibrator::perform_1_3(V1_3::Effect effect, EffectStrength strength, + perform_cb _hidl_cb) { + return perform<decltype(effect)>(effect, strength, _hidl_cb); +} + +// Methods from ::android::hardware::vibrator::V1_4::IVibrator follow. + +Return<hidl_bitfield<Capabilities>> Vibrator::getCapabilities() { + return Capabilities::ON_COMPLETION_CALLBACK | Capabilities::PERFORM_COMPLETION_CALLBACK; +} + +Return<Status> Vibrator::on_1_4(uint32_t timeoutMs, const sp<IVibratorCallback>& callback) { + mCallback = callback; + return on(timeoutMs); +} + +Return<void> Vibrator::perform_1_4(V1_3::Effect effect, EffectStrength strength, + const sp<IVibratorCallback>& callback, perform_cb _hidl_cb) { + mCallback = callback; return perform<decltype(effect)>(effect, strength, _hidl_cb); } @@ -148,6 +166,14 @@ Status Vibrator::enable(bool enabled) { return Status::UNSUPPORTED_OPERATION; } else { ALOGI("Enabled: %s -> %s\n", mEnabled ? "true" : "false", enabled ? "true" : "false"); + if (mEnabled && !enabled) { + if (auto callback = mCallback) { + mCallback = nullptr; + if (auto ret = callback->onComplete(); !ret.isOk()) { + ALOGE("Failed completion callback: %s", ret.description().c_str()); + } + } + } mEnabled = enabled; return Status::OK; } @@ -271,7 +297,7 @@ uint8_t Vibrator::strengthToAmplitude(EffectStrength strength, Status* status) { } } // namespace implementation -} // namespace V1_3 +} // namespace V1_4 } // namespace vibrator } // namespace hardware } // namespace android diff --git a/vibrator/1.3/example/Vibrator.h b/vibrator/1.x/example/Vibrator.h index 5180774552..ff634315ec 100644 --- a/vibrator/1.3/example/Vibrator.h +++ b/vibrator/1.x/example/Vibrator.h @@ -13,20 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef ANDROID_HARDWARE_VIBRATOR_V1_3_VIBRATOR_H -#define ANDROID_HARDWARE_VIBRATOR_V1_3_VIBRATOR_H +#ifndef ANDROID_HARDWARE_VIBRATOR_V1_x_VIBRATOR_H +#define ANDROID_HARDWARE_VIBRATOR_V1_x_VIBRATOR_H -#include <android/hardware/vibrator/1.3/IVibrator.h> +#include <android/hardware/vibrator/1.4/IVibrator.h> #include <hidl/Status.h> namespace android { namespace hardware { namespace vibrator { -namespace V1_3 { +namespace V1_4 { namespace implementation { using android::hardware::vibrator::V1_0::EffectStrength; using android::hardware::vibrator::V1_0::Status; +using android::hardware::vibrator::V1_3::Effect; class Vibrator : public IVibrator { public: @@ -51,7 +52,14 @@ class Vibrator : public IVibrator { // Methods from ::android::hardware::vibrator::V1_3::IVibrator follow. Return<bool> supportsExternalControl() override; Return<Status> setExternalControl(bool enabled) override; - Return<void> perform_1_3(Effect effect, EffectStrength strength, perform_cb _hidl_cb) override; + Return<void> perform_1_3(V1_3::Effect effect, EffectStrength strength, + perform_cb _hidl_cb) override; + + // Methods from ::android::hardware::vibrator::V1_4::IVibrator follow. + Return<hidl_bitfield<Capabilities>> getCapabilities() override; + Return<Status> on_1_4(uint32_t timeoutMs, const sp<IVibratorCallback>& callback) override; + Return<void> perform_1_4(V1_3::Effect effect, EffectStrength strength, + const sp<IVibratorCallback>& callback, perform_cb _hidl_cb) override; private: Return<void> perform(Effect effect, EffectStrength strength, perform_cb _hidl_cb); @@ -72,11 +80,12 @@ class Vibrator : public IVibrator { bool mExternalControl{false}; std::mutex mMutex; timer_t mTimer{nullptr}; + sp<IVibratorCallback> mCallback{nullptr}; }; } // namespace implementation -} // namespace V1_3 +} // namespace V1_4 } // namespace vibrator } // namespace hardware } // namespace android -#endif // ANDROID_HARDWARE_VIBRATOR_V1_3_VIBRATOR_H +#endif // ANDROID_HARDWARE_VIBRATOR_V1_x_VIBRATOR_H diff --git a/vibrator/1.x/example/android.hardware.vibrator@1.x-service.example.rc b/vibrator/1.x/example/android.hardware.vibrator@1.x-service.example.rc new file mode 100644 index 0000000000..4893db6b90 --- /dev/null +++ b/vibrator/1.x/example/android.hardware.vibrator@1.x-service.example.rc @@ -0,0 +1,4 @@ +service vendor.vibrator-1-x /vendor/bin/hw/android.hardware.vibrator@1.x-service.example + class hal + user system + group system diff --git a/vibrator/1.3/example/android.hardware.vibrator@1.3-service.example.xml b/vibrator/1.x/example/android.hardware.vibrator@1.x-service.example.xml index 172aa2178c..ebc8c4bcb8 100644 --- a/vibrator/1.3/example/android.hardware.vibrator@1.3-service.example.xml +++ b/vibrator/1.x/example/android.hardware.vibrator@1.x-service.example.xml @@ -2,7 +2,7 @@ <hal format="hidl"> <name>android.hardware.vibrator</name> <transport>hwbinder</transport> - <version>1.3</version> + <version>1.4</version> <interface> <name>IVibrator</name> <instance>default</instance> diff --git a/vibrator/1.3/example/service.cpp b/vibrator/1.x/example/service.cpp index 449996e280..13c66912e8 100644 --- a/vibrator/1.3/example/service.cpp +++ b/vibrator/1.x/example/service.cpp @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#define LOG_TAG "android.hardware.vibrator@1.3-service.example" +#define LOG_TAG "android.hardware.vibrator@1.x-service.example" -#include <android/hardware/vibrator/1.3/IVibrator.h> +#include <android/hardware/vibrator/1.4/IVibrator.h> #include <hidl/HidlTransportSupport.h> #include "Vibrator.h" using android::hardware::configureRpcThreadpool; using android::hardware::joinRpcThreadpool; -using android::hardware::vibrator::V1_3::IVibrator; -using android::hardware::vibrator::V1_3::implementation::Vibrator; +using android::hardware::vibrator::V1_4::IVibrator; +using android::hardware::vibrator::V1_4::implementation::Vibrator; using namespace android; status_t registerVibratorService() { diff --git a/wifi/1.0/vts/functional/Android.bp b/wifi/1.0/vts/functional/Android.bp index 397ad179af..6fa6e7ed6d 100644 --- a/wifi/1.0/vts/functional/Android.bp +++ b/wifi/1.0/vts/functional/Android.bp @@ -28,7 +28,9 @@ cc_library_static { shared_libs: [ "libnativehelper", ], - static_libs: ["android.hardware.wifi@1.0"], + static_libs: [ + "android.hardware.wifi@1.0", + ], } cc_test { @@ -36,7 +38,6 @@ cc_test { defaults: ["VtsHalTargetTestDefaults"], srcs: [ "VtsHalWifiV1_0TargetTest.cpp", - "wifi_ap_iface_hidl_test.cpp", "wifi_chip_hidl_test.cpp", "wifi_p2p_iface_hidl_test.cpp", "wifi_rtt_controller_hidl_test.cpp", @@ -52,11 +53,14 @@ cc_test { test_suites: ["general-tests"], } +// These tests are split out so that they can be conditioned on presence of the +// "android.hardware.wifi.aware" feature. cc_test { name: "VtsHalWifiNanV1_0TargetTest", defaults: ["VtsHalTargetTestDefaults"], srcs: [ "VtsHalWifiV1_0TargetTest.cpp", + "wifi_chip_hidl_nan_test.cpp", "wifi_nan_iface_hidl_test.cpp", ], static_libs: [ @@ -65,3 +69,20 @@ cc_test { ], test_suites: ["general-tests"], } + +// These tests are split out so that they can be conditioned on presence of +// the hostapd HAL, which indicates SoftAP support. +cc_test { + name: "VtsHalWifiApV1_0TargetTest", + defaults: ["VtsHalTargetTestDefaults"], + srcs: [ + "VtsHalWifiV1_0TargetTest.cpp", + "wifi_ap_iface_hidl_test.cpp", + "wifi_chip_hidl_ap_test.cpp", + ], + static_libs: [ + "VtsHalWifiV1_0TargetTestUtil", + "android.hardware.wifi@1.0", + ], + test_suites: ["general-tests"], +} diff --git a/wifi/1.0/vts/functional/VtsHalWifiV1_0TargetTest.cpp b/wifi/1.0/vts/functional/VtsHalWifiV1_0TargetTest.cpp index e7b85938cf..9d25014229 100644 --- a/wifi/1.0/vts/functional/VtsHalWifiV1_0TargetTest.cpp +++ b/wifi/1.0/vts/functional/VtsHalWifiV1_0TargetTest.cpp @@ -41,10 +41,7 @@ int main(int argc, char** argv) { ::testing::AddGlobalTestEnvironment(gEnv); ::testing::InitGoogleTest(&argc, argv); gEnv->init(&argc, argv); - int status = gEnv->initFromOptions(argc, argv); - if (status == 0) { - status = RUN_ALL_TESTS(); - LOG(INFO) << "Test result = " << status; - } + int status = RUN_ALL_TESTS(); + LOG(INFO) << "Test result = " << status; return status; } diff --git a/wifi/1.0/vts/functional/wifi_ap_iface_hidl_test.cpp b/wifi/1.0/vts/functional/wifi_ap_iface_hidl_test.cpp index e5762f28f0..c55221d105 100644 --- a/wifi/1.0/vts/functional/wifi_ap_iface_hidl_test.cpp +++ b/wifi/1.0/vts/functional/wifi_ap_iface_hidl_test.cpp @@ -29,21 +29,17 @@ using ::android::hardware::wifi::V1_0::WifiBand; using ::android::hardware::wifi::V1_0::WifiStatusCode; using ::android::sp; -extern WifiHidlEnvironment* gEnv; - /** * Fixture to use for all AP Iface HIDL interface tests. */ class WifiApIfaceHidlTest : public ::testing::VtsHalHidlTargetTestBase { public: virtual void SetUp() override { - if (!gEnv->isSoftApOn) return; wifi_ap_iface_ = getWifiApIface(); ASSERT_NE(nullptr, wifi_ap_iface_.get()); } virtual void TearDown() override { - if (!gEnv->isSoftApOn) return; stopWifi(); } @@ -57,7 +53,6 @@ class WifiApIfaceHidlTest : public ::testing::VtsHalHidlTargetTestBase { * successfully created. */ TEST(WifiApIfaceHidlTestNoFixture, Create) { - if (!gEnv->isSoftApOn) return; EXPECT_NE(nullptr, getWifiApIface().get()); stopWifi(); } @@ -67,7 +62,6 @@ TEST(WifiApIfaceHidlTestNoFixture, Create) { * Ensures that the correct interface type is returned for AP interface. */ TEST_F(WifiApIfaceHidlTest, GetType) { - if (!gEnv->isSoftApOn) return; const auto& status_and_type = HIDL_INVOKE(wifi_ap_iface_, getType); EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_type.first.code); EXPECT_EQ(IfaceType::AP, status_and_type.second); @@ -79,7 +73,6 @@ TEST_F(WifiApIfaceHidlTest, GetType) { * status code. */ TEST_F(WifiApIfaceHidlTest, SetCountryCode) { - if (!gEnv->isSoftApOn) return; const android::hardware::hidl_array<int8_t, 2> kCountryCode{ std::array<int8_t, 2>{{0x55, 0x53}}}; EXPECT_EQ(WifiStatusCode::SUCCESS, @@ -91,7 +84,6 @@ TEST_F(WifiApIfaceHidlTest, SetCountryCode) { * Ensures that we can retrieve valid frequencies for 2.4 GHz band. */ TEST_F(WifiApIfaceHidlTest, GetValidFrequenciesForBand) { - if (!gEnv->isSoftApOn) return; const auto& status_and_freqs = HIDL_INVOKE( wifi_ap_iface_, getValidFrequenciesForBand, WifiBand::BAND_24GHZ); EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_freqs.first.code); diff --git a/wifi/1.0/vts/functional/wifi_chip_hidl_ap_test.cpp b/wifi/1.0/vts/functional/wifi_chip_hidl_ap_test.cpp new file mode 100644 index 0000000000..232ffdd58b --- /dev/null +++ b/wifi/1.0/vts/functional/wifi_chip_hidl_ap_test.cpp @@ -0,0 +1,168 @@ +/* + * 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. + */ + +#include <android-base/logging.h> + +#include <android/hardware/wifi/1.0/IWifiChip.h> + +#include <VtsHalHidlTargetTestBase.h> + +#include "wifi_hidl_call_util.h" +#include "wifi_hidl_test_utils.h" + +using ::android::sp; +using ::android::hardware::wifi::V1_0::ChipModeId; +using ::android::hardware::wifi::V1_0::IfaceType; +using ::android::hardware::wifi::V1_0::IWifiApIface; +using ::android::hardware::wifi::V1_0::IWifiChip; +using ::android::hardware::wifi::V1_0::IWifiIface; +using ::android::hardware::wifi::V1_0::WifiStatus; +using ::android::hardware::wifi::V1_0::WifiStatusCode; + +/** + * Fixture for IWifiChip tests that are conditioned on SoftAP support. + */ +class WifiChipHidlApTest : public ::testing::VtsHalHidlTargetTestBase { + public: + virtual void SetUp() override { + wifi_chip_ = getWifiChip(); + ASSERT_NE(nullptr, wifi_chip_.get()); + } + + virtual void TearDown() override { stopWifi(); } + + protected: + // Helper function to configure the Chip in one of the supported modes. + // Most of the non-mode-configuration-related methods require chip + // to be first configured. + ChipModeId configureChipForIfaceType(IfaceType type, bool expectSuccess) { + ChipModeId mode_id; + EXPECT_EQ(expectSuccess, + configureChipToSupportIfaceType(wifi_chip_, type, &mode_id)); + return mode_id; + } + + std::string getIfaceName(const sp<IWifiIface>& iface) { + const auto& status_and_name = HIDL_INVOKE(iface, getName); + EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_name.first.code); + return status_and_name.second; + } + + WifiStatusCode createApIface(sp<IWifiApIface>* ap_iface) { + const auto& status_and_iface = HIDL_INVOKE(wifi_chip_, createApIface); + *ap_iface = status_and_iface.second; + return status_and_iface.first.code; + } + + WifiStatusCode removeApIface(const std::string& name) { + return HIDL_INVOKE(wifi_chip_, removeApIface, name).code; + } + + sp<IWifiChip> wifi_chip_; +}; + +/* + * CreateApIface + * Configures the chip in AP mode and ensures that at least 1 iface creation + * succeeds. + */ +TEST_F(WifiChipHidlApTest, CreateApIface) { + configureChipForIfaceType(IfaceType::AP, true); + + sp<IWifiApIface> iface; + EXPECT_EQ(WifiStatusCode::SUCCESS, createApIface(&iface)); + EXPECT_NE(nullptr, iface.get()); +} + +/* + * GetApIfaceNames + * Configures the chip in AP mode and ensures that the iface list is empty + * before creating the iface. Then, create the iface and ensure that + * iface name is returned via the list. + */ +TEST_F(WifiChipHidlApTest, GetApIfaceNames) { + configureChipForIfaceType(IfaceType::AP, true); + + const auto& status_and_iface_names1 = + HIDL_INVOKE(wifi_chip_, getApIfaceNames); + EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names1.first.code); + EXPECT_EQ(0u, status_and_iface_names1.second.size()); + + sp<IWifiApIface> iface; + EXPECT_EQ(WifiStatusCode::SUCCESS, createApIface(&iface)); + EXPECT_NE(nullptr, iface.get()); + + std::string iface_name = getIfaceName(iface); + const auto& status_and_iface_names2 = + HIDL_INVOKE(wifi_chip_, getApIfaceNames); + EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names2.first.code); + EXPECT_EQ(1u, status_and_iface_names2.second.size()); + EXPECT_EQ(iface_name, status_and_iface_names2.second[0]); + + EXPECT_EQ(WifiStatusCode::SUCCESS, removeApIface(iface_name)); + const auto& status_and_iface_names3 = + HIDL_INVOKE(wifi_chip_, getApIfaceNames); + EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names3.first.code); + EXPECT_EQ(0u, status_and_iface_names3.second.size()); +} + +/* + * GetApIface + * Configures the chip in AP mode and create an iface. Then, retrieve + * the iface object using the correct name and ensure any other name + * doesn't retrieve an iface object. + */ +TEST_F(WifiChipHidlApTest, GetApIface) { + configureChipForIfaceType(IfaceType::AP, true); + + sp<IWifiApIface> ap_iface; + EXPECT_EQ(WifiStatusCode::SUCCESS, createApIface(&ap_iface)); + EXPECT_NE(nullptr, ap_iface.get()); + + std::string iface_name = getIfaceName(ap_iface); + const auto& status_and_iface1 = + HIDL_INVOKE(wifi_chip_, getApIface, iface_name); + EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface1.first.code); + EXPECT_NE(nullptr, status_and_iface1.second.get()); + + std::string invalid_name = iface_name + "0"; + const auto& status_and_iface2 = + HIDL_INVOKE(wifi_chip_, getApIface, invalid_name); + EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, status_and_iface2.first.code); + EXPECT_EQ(nullptr, status_and_iface2.second.get()); +} + +/* + * RemoveApIface + * Configures the chip in AP mode and create an iface. Then, remove + * the iface object using the correct name and ensure any other name + * doesn't remove the iface. + */ +TEST_F(WifiChipHidlApTest, RemoveApIface) { + configureChipForIfaceType(IfaceType::AP, true); + + sp<IWifiApIface> ap_iface; + EXPECT_EQ(WifiStatusCode::SUCCESS, createApIface(&ap_iface)); + EXPECT_NE(nullptr, ap_iface.get()); + + std::string iface_name = getIfaceName(ap_iface); + std::string invalid_name = iface_name + "0"; + EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, removeApIface(invalid_name)); + EXPECT_EQ(WifiStatusCode::SUCCESS, removeApIface(iface_name)); + + // No such iface exists now. So, this should return failure. + EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, removeApIface(iface_name)); +} diff --git a/wifi/1.0/vts/functional/wifi_chip_hidl_nan_test.cpp b/wifi/1.0/vts/functional/wifi_chip_hidl_nan_test.cpp new file mode 100644 index 0000000000..595f23a109 --- /dev/null +++ b/wifi/1.0/vts/functional/wifi_chip_hidl_nan_test.cpp @@ -0,0 +1,169 @@ +/* + * 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. + */ + +#include <android-base/logging.h> + +#include <android/hardware/wifi/1.0/IWifiChip.h> + +#include <VtsHalHidlTargetTestBase.h> + +#include "wifi_hidl_call_util.h" +#include "wifi_hidl_test_utils.h" + +using ::android::sp; +using ::android::hardware::wifi::V1_0::ChipModeId; +using ::android::hardware::wifi::V1_0::IfaceType; +using ::android::hardware::wifi::V1_0::IWifiChip; +using ::android::hardware::wifi::V1_0::IWifiIface; +using ::android::hardware::wifi::V1_0::IWifiNanIface; +using ::android::hardware::wifi::V1_0::WifiStatus; +using ::android::hardware::wifi::V1_0::WifiStatusCode; + +/** + * Fixture for IWifiChip tests that are conditioned on NAN support. + */ +class WifiChipHidlNanTest : public ::testing::VtsHalHidlTargetTestBase { + public: + virtual void SetUp() override { + wifi_chip_ = getWifiChip(); + ASSERT_NE(nullptr, wifi_chip_.get()); + } + + virtual void TearDown() override { stopWifi(); } + + protected: + // Helper function to configure the Chip in one of the supported modes. + // Most of the non-mode-configuration-related methods require chip + // to be first configured. + ChipModeId configureChipForIfaceType(IfaceType type, bool expectSuccess) { + ChipModeId mode_id; + EXPECT_EQ(expectSuccess, + configureChipToSupportIfaceType(wifi_chip_, type, &mode_id)); + return mode_id; + } + + std::string getIfaceName(const sp<IWifiIface>& iface) { + const auto& status_and_name = HIDL_INVOKE(iface, getName); + EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_name.first.code); + return status_and_name.second; + } + + WifiStatusCode createNanIface(sp<IWifiNanIface>* nan_iface) { + const auto& status_and_iface = HIDL_INVOKE(wifi_chip_, createNanIface); + *nan_iface = status_and_iface.second; + return status_and_iface.first.code; + } + + WifiStatusCode removeNanIface(const std::string& name) { + return HIDL_INVOKE(wifi_chip_, removeNanIface, name).code; + } + + sp<IWifiChip> wifi_chip_; +}; + +/* + * CreateNanIface + * Configures the chip in NAN mode and ensures that at least 1 iface creation + * succeeds. + */ +TEST_F(WifiChipHidlNanTest, CreateNanIface) { + configureChipForIfaceType(IfaceType::NAN, true); + + sp<IWifiNanIface> iface; + ASSERT_EQ(WifiStatusCode::SUCCESS, createNanIface(&iface)); + EXPECT_NE(nullptr, iface.get()); +} + +/* + * GetNanIfaceNames + * Configures the chip in NAN mode and ensures that the iface list is empty + * before creating the iface. Then, create the iface and ensure that + * iface name is returned via the list. + */ +TEST_F(WifiChipHidlNanTest, GetNanIfaceNames) { + configureChipForIfaceType(IfaceType::NAN, true); + + const auto& status_and_iface_names1 = + HIDL_INVOKE(wifi_chip_, getNanIfaceNames); + ASSERT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names1.first.code); + EXPECT_EQ(0u, status_and_iface_names1.second.size()); + + sp<IWifiNanIface> iface; + EXPECT_EQ(WifiStatusCode::SUCCESS, createNanIface(&iface)); + EXPECT_NE(nullptr, iface.get()); + + std::string iface_name = getIfaceName(iface); + const auto& status_and_iface_names2 = + HIDL_INVOKE(wifi_chip_, getNanIfaceNames); + EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names2.first.code); + EXPECT_EQ(1u, status_and_iface_names2.second.size()); + EXPECT_EQ(iface_name, status_and_iface_names2.second[0]); + + EXPECT_EQ(WifiStatusCode::SUCCESS, removeNanIface(iface_name)); + const auto& status_and_iface_names3 = + HIDL_INVOKE(wifi_chip_, getNanIfaceNames); + EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names3.first.code); + EXPECT_EQ(0u, status_and_iface_names3.second.size()); +} + +/* + * GetNanIface + * Configures the chip in NAN mode and create an iface. Then, retrieve + * the iface object using the correct name and ensure any other name + * doesn't retrieve an iface object. + */ +TEST_F(WifiChipHidlNanTest, GetNanIface) { + configureChipForIfaceType(IfaceType::NAN, true); + + sp<IWifiNanIface> nan_iface; + EXPECT_EQ(WifiStatusCode::SUCCESS, createNanIface(&nan_iface)); + EXPECT_NE(nullptr, nan_iface.get()); + + std::string iface_name = getIfaceName(nan_iface); + const auto& status_and_iface1 = + HIDL_INVOKE(wifi_chip_, getNanIface, iface_name); + EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface1.first.code); + EXPECT_NE(nullptr, status_and_iface1.second.get()); + + std::string invalid_name = iface_name + "0"; + const auto& status_and_iface2 = + HIDL_INVOKE(wifi_chip_, getNanIface, invalid_name); + EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, status_and_iface2.first.code); + EXPECT_EQ(nullptr, status_and_iface2.second.get()); +} + +/* + * RemoveNanIface + * Configures the chip in NAN mode and create an iface. Then, remove + * the iface object using the correct name and ensure any other name + * doesn't remove the iface. + */ +TEST_F(WifiChipHidlNanTest, RemoveNanIface) { + configureChipForIfaceType(IfaceType::NAN, true); + + sp<IWifiNanIface> nan_iface; + EXPECT_EQ(WifiStatusCode::SUCCESS, createNanIface(&nan_iface)); + EXPECT_NE(nullptr, nan_iface.get()); + + std::string iface_name = getIfaceName(nan_iface); + std::string invalid_name = iface_name + "0"; + EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, removeNanIface(invalid_name)); + + EXPECT_EQ(WifiStatusCode::SUCCESS, removeNanIface(iface_name)); + + // No such iface exists now. So, this should return failure. + EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, removeNanIface(iface_name)); +} diff --git a/wifi/1.0/vts/functional/wifi_chip_hidl_test.cpp b/wifi/1.0/vts/functional/wifi_chip_hidl_test.cpp index 1b7e821906..2601b78743 100644 --- a/wifi/1.0/vts/functional/wifi_chip_hidl_test.cpp +++ b/wifi/1.0/vts/functional/wifi_chip_hidl_test.cpp @@ -36,9 +36,7 @@ using ::android::hardware::wifi::V1_0::WifiDebugHostWakeReasonStats; using ::android::hardware::wifi::V1_0::WifiStatus; using ::android::hardware::wifi::V1_0::WifiStatusCode; using ::android::hardware::wifi::V1_0::IWifiChip; -using ::android::hardware::wifi::V1_0::IWifiApIface; using ::android::hardware::wifi::V1_0::IWifiIface; -using ::android::hardware::wifi::V1_0::IWifiNanIface; using ::android::hardware::wifi::V1_0::IWifiP2pIface; using ::android::hardware::wifi::V1_0::IWifiRttController; using ::android::hardware::wifi::V1_0::IWifiStaIface; @@ -64,7 +62,10 @@ bool hasAnyRingBufferCapabilities(uint32_t caps) { } // namespace /** - * Fixture to use for all Wifi chip HIDL interface tests. + * Fixture for IWifiChip tests. + * + * Tests that require SoftAP or NAN support should go into WifiChipHidlApTest or + * WifiChipHidlNanTest respectively. */ class WifiChipHidlTest : public ::testing::VtsHalHidlTargetTestBase { public: @@ -114,26 +115,6 @@ class WifiChipHidlTest : public ::testing::VtsHalHidlTargetTestBase { return status_and_name.second; } - WifiStatusCode createApIface(sp<IWifiApIface>* ap_iface) { - const auto& status_and_iface = HIDL_INVOKE(wifi_chip_, createApIface); - *ap_iface = status_and_iface.second; - return status_and_iface.first.code; - } - - WifiStatusCode removeApIface(const std::string& name) { - return HIDL_INVOKE(wifi_chip_, removeApIface, name).code; - } - - WifiStatusCode createNanIface(sp<IWifiNanIface>* nan_iface) { - const auto& status_and_iface = HIDL_INVOKE(wifi_chip_, createNanIface); - *nan_iface = status_and_iface.second; - return status_and_iface.first.code; - } - - WifiStatusCode removeNanIface(const std::string& name) { - return HIDL_INVOKE(wifi_chip_, removeNanIface, name).code; - } - WifiStatusCode createP2pIface(sp<IWifiP2pIface>* p2p_iface) { const auto& status_and_iface = HIDL_INVOKE(wifi_chip_, createP2pIface); *p2p_iface = status_and_iface.second; @@ -360,201 +341,6 @@ TEST_F(WifiChipHidlTest, GetDebugHostWakeReasonStats) { } /* - * CreateApIface - * Configures the chip in AP mode and ensures that at least 1 iface creation - * succeeds. - */ -TEST_F(WifiChipHidlTest, CreateApIface) { - if (!gEnv->isSoftApOn) return; - configureChipForIfaceType(IfaceType::AP, true); - - sp<IWifiApIface> iface; - EXPECT_EQ(WifiStatusCode::SUCCESS, createApIface(&iface)); - EXPECT_NE(nullptr, iface.get()); -} - -/* - * GetApIfaceNames - * Configures the chip in AP mode and ensures that the iface list is empty - * before creating the iface. Then, create the iface and ensure that - * iface name is returned via the list. - */ -TEST_F(WifiChipHidlTest, GetApIfaceNames) { - if (!gEnv->isSoftApOn) return; - configureChipForIfaceType(IfaceType::AP, true); - - const auto& status_and_iface_names1 = - HIDL_INVOKE(wifi_chip_, getApIfaceNames); - EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names1.first.code); - EXPECT_EQ(0u, status_and_iface_names1.second.size()); - - sp<IWifiApIface> iface; - EXPECT_EQ(WifiStatusCode::SUCCESS, createApIface(&iface)); - EXPECT_NE(nullptr, iface.get()); - - std::string iface_name = getIfaceName(iface); - const auto& status_and_iface_names2 = - HIDL_INVOKE(wifi_chip_, getApIfaceNames); - EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names2.first.code); - EXPECT_EQ(1u, status_and_iface_names2.second.size()); - EXPECT_EQ(iface_name, status_and_iface_names2.second[0]); - - EXPECT_EQ(WifiStatusCode::SUCCESS, removeApIface(iface_name)); - const auto& status_and_iface_names3 = - HIDL_INVOKE(wifi_chip_, getApIfaceNames); - EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names3.first.code); - EXPECT_EQ(0u, status_and_iface_names3.second.size()); -} - -/* - * GetApIface - * Configures the chip in AP mode and create an iface. Then, retrieve - * the iface object using the correct name and ensure any other name - * doesn't retrieve an iface object. - */ -TEST_F(WifiChipHidlTest, GetApIface) { - if (!gEnv->isSoftApOn) return; - configureChipForIfaceType(IfaceType::AP, true); - - sp<IWifiApIface> ap_iface; - EXPECT_EQ(WifiStatusCode::SUCCESS, createApIface(&ap_iface)); - EXPECT_NE(nullptr, ap_iface.get()); - - std::string iface_name = getIfaceName(ap_iface); - const auto& status_and_iface1 = - HIDL_INVOKE(wifi_chip_, getApIface, iface_name); - EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface1.first.code); - EXPECT_NE(nullptr, status_and_iface1.second.get()); - - std::string invalid_name = iface_name + "0"; - const auto& status_and_iface2 = - HIDL_INVOKE(wifi_chip_, getApIface, invalid_name); - EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, status_and_iface2.first.code); - EXPECT_EQ(nullptr, status_and_iface2.second.get()); -} - -/* - * RemoveApIface - * Configures the chip in AP mode and create an iface. Then, remove - * the iface object using the correct name and ensure any other name - * doesn't remove the iface. - */ -TEST_F(WifiChipHidlTest, RemoveApIface) { - if (!gEnv->isSoftApOn) return; - configureChipForIfaceType(IfaceType::AP, true); - - sp<IWifiApIface> ap_iface; - EXPECT_EQ(WifiStatusCode::SUCCESS, createApIface(&ap_iface)); - EXPECT_NE(nullptr, ap_iface.get()); - - std::string iface_name = getIfaceName(ap_iface); - std::string invalid_name = iface_name + "0"; - EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, removeApIface(invalid_name)); - EXPECT_EQ(WifiStatusCode::SUCCESS, removeApIface(iface_name)); - - // No such iface exists now. So, this should return failure. - EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, removeApIface(iface_name)); -} - -/* - * CreateNanIface - * Configures the chip in NAN mode and ensures that at least 1 iface creation - * succeeds. - */ -TEST_F(WifiChipHidlTest, CreateNanIface) { - if (!gEnv->isNanOn) return; - configureChipForIfaceType(IfaceType::NAN, gEnv->isNanOn); - - sp<IWifiNanIface> iface; - ASSERT_EQ(WifiStatusCode::SUCCESS, createNanIface(&iface)); - EXPECT_NE(nullptr, iface.get()); -} - -/* - * GetNanIfaceNames - * Configures the chip in NAN mode and ensures that the iface list is empty - * before creating the iface. Then, create the iface and ensure that - * iface name is returned via the list. - */ -TEST_F(WifiChipHidlTest, GetNanIfaceNames) { - if (!gEnv->isNanOn) return; - configureChipForIfaceType(IfaceType::NAN, gEnv->isNanOn); - - const auto& status_and_iface_names1 = - HIDL_INVOKE(wifi_chip_, getNanIfaceNames); - ASSERT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names1.first.code); - EXPECT_EQ(0u, status_and_iface_names1.second.size()); - - sp<IWifiNanIface> iface; - EXPECT_EQ(WifiStatusCode::SUCCESS, createNanIface(&iface)); - EXPECT_NE(nullptr, iface.get()); - - std::string iface_name = getIfaceName(iface); - const auto& status_and_iface_names2 = - HIDL_INVOKE(wifi_chip_, getNanIfaceNames); - EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names2.first.code); - EXPECT_EQ(1u, status_and_iface_names2.second.size()); - EXPECT_EQ(iface_name, status_and_iface_names2.second[0]); - - EXPECT_EQ(WifiStatusCode::SUCCESS, removeNanIface(iface_name)); - const auto& status_and_iface_names3 = - HIDL_INVOKE(wifi_chip_, getNanIfaceNames); - EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface_names3.first.code); - EXPECT_EQ(0u, status_and_iface_names3.second.size()); -} - -/* - * GetNanIface - * Configures the chip in NAN mode and create an iface. Then, retrieve - * the iface object using the correct name and ensure any other name - * doesn't retrieve an iface object. - */ -TEST_F(WifiChipHidlTest, GetNanIface) { - if (!gEnv->isNanOn) return; - configureChipForIfaceType(IfaceType::NAN, gEnv->isNanOn); - - sp<IWifiNanIface> nan_iface; - EXPECT_EQ(WifiStatusCode::SUCCESS, createNanIface(&nan_iface)); - EXPECT_NE(nullptr, nan_iface.get()); - - std::string iface_name = getIfaceName(nan_iface); - const auto& status_and_iface1 = - HIDL_INVOKE(wifi_chip_, getNanIface, iface_name); - EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_iface1.first.code); - EXPECT_NE(nullptr, status_and_iface1.second.get()); - - std::string invalid_name = iface_name + "0"; - const auto& status_and_iface2 = - HIDL_INVOKE(wifi_chip_, getNanIface, invalid_name); - EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, status_and_iface2.first.code); - EXPECT_EQ(nullptr, status_and_iface2.second.get()); -} - -/* - * RemoveNanIface - * Configures the chip in NAN mode and create an iface. Then, remove - * the iface object using the correct name and ensure any other name - * doesn't remove the iface. - */ -TEST_F(WifiChipHidlTest, RemoveNanIface) { - if (!gEnv->isNanOn) return; - configureChipForIfaceType(IfaceType::NAN, gEnv->isNanOn); - - sp<IWifiNanIface> nan_iface; - EXPECT_EQ(WifiStatusCode::SUCCESS, createNanIface(&nan_iface)); - EXPECT_NE(nullptr, nan_iface.get()); - - std::string iface_name = getIfaceName(nan_iface); - std::string invalid_name = iface_name + "0"; - EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, removeNanIface(invalid_name)); - - EXPECT_EQ(WifiStatusCode::SUCCESS, removeNanIface(iface_name)); - - // No such iface exists now. So, this should return failure. - EXPECT_EQ(WifiStatusCode::ERROR_INVALID_ARGS, removeNanIface(iface_name)); -} - -/* * CreateP2pIface * Configures the chip in P2P mode and ensures that at least 1 iface creation * succeeds. diff --git a/wifi/1.0/vts/functional/wifi_hidl_test_utils.h b/wifi/1.0/vts/functional/wifi_hidl_test_utils.h index d430ce0bea..7dacaf15a6 100644 --- a/wifi/1.0/vts/functional/wifi_hidl_test_utils.h +++ b/wifi/1.0/vts/functional/wifi_hidl_test_utils.h @@ -54,48 +54,4 @@ class WifiHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { stopWifi(); sleep(5); } - - public: - // Whether NaN feature is supported on the device. - bool isNanOn = false; - // Whether SoftAp feature is supported on the device. - bool isSoftApOn = false; - - void usage(char* me, char* arg) { - fprintf(stderr, - "unrecognized option: %s\n\n" - "usage: %s <gtest options> <test options>\n\n" - "test options are:\n\n" - "-N, --nan_on: Whether NAN feature is supported\n" - "-S, --softap_on: Whether SOFTAP feature is supported\n", - arg, me); - } - - int initFromOptions(int argc, char** argv) { - static struct option options[] = {{"nan_on", no_argument, 0, 'N'}, - {"softap_on", no_argument, 0, 'S'}, - {0, 0, 0, 0}}; - - int c; - while ((c = getopt_long(argc, argv, "NS", options, NULL)) >= 0) { - switch (c) { - case 'N': - isNanOn = true; - break; - case 'S': - isSoftApOn = true; - break; - default: - usage(argv[0], argv[optind]); - return 2; - } - } - - if (optind < argc) { - usage(argv[0], argv[optind]); - return 2; - } - - return 0; - } }; diff --git a/wifi/1.1/vts/functional/VtsHalWifiV1_1TargetTest.cpp b/wifi/1.1/vts/functional/VtsHalWifiV1_1TargetTest.cpp index a0f97f81e8..673fed3675 100644 --- a/wifi/1.1/vts/functional/VtsHalWifiV1_1TargetTest.cpp +++ b/wifi/1.1/vts/functional/VtsHalWifiV1_1TargetTest.cpp @@ -41,10 +41,7 @@ int main(int argc, char** argv) { ::testing::AddGlobalTestEnvironment(gEnv); ::testing::InitGoogleTest(&argc, argv); gEnv->init(&argc, argv); - int status = gEnv->initFromOptions(argc, argv); - if (status == 0) { - int status = RUN_ALL_TESTS(); - LOG(INFO) << "Test result = " << status; - } + int status = RUN_ALL_TESTS(); + LOG(INFO) << "Test result = " << status; return status; } diff --git a/wifi/1.3/vts/functional/wifi_sta_iface_hidl_test.cpp b/wifi/1.3/vts/functional/wifi_sta_iface_hidl_test.cpp index 71e90acb93..d382f30e0f 100644 --- a/wifi/1.3/vts/functional/wifi_sta_iface_hidl_test.cpp +++ b/wifi/1.3/vts/functional/wifi_sta_iface_hidl_test.cpp @@ -27,6 +27,8 @@ #include "wifi_hidl_test_utils.h" using ::android::sp; +using ::android::hardware::hidl_array; +using ::android::hardware::wifi::V1_0::WifiStatus; using ::android::hardware::wifi::V1_0::WifiStatusCode; using ::android::hardware::wifi::V1_3::IWifiStaIface; @@ -59,14 +61,11 @@ class WifiStaIfaceHidlTest : public ::testing::VtsHalHidlTargetTestBase { * and return a success status code. */ TEST_F(WifiStaIfaceHidlTest, GetFactoryMacAddress) { - const auto& status_and_mac = + std::pair<WifiStatus, hidl_array<uint8_t, 6> > status_and_mac = HIDL_INVOKE(wifi_sta_iface_, getFactoryMacAddress); EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_mac.first.code); - const int num_elements = sizeof(status_and_mac.second) / sizeof(uint8_t); - EXPECT_EQ(6, num_elements); - for (int i = 0; i < num_elements; i++) { - EXPECT_NE(0, status_and_mac.second[i]); - } + hidl_array<uint8_t, 6> all_zero{}; + EXPECT_NE(all_zero, status_and_mac.second); } /* diff --git a/wifi/1.4/Android.bp b/wifi/1.4/Android.bp index a6ac020cbc..aba8b4443a 100644 --- a/wifi/1.4/Android.bp +++ b/wifi/1.4/Android.bp @@ -8,6 +8,7 @@ hidl_interface { }, srcs: [ "IWifi.hal", + "IWifiApIface.hal", ], interfaces: [ "android.hardware.wifi@1.0", diff --git a/wifi/1.4/IWifi.hal b/wifi/1.4/IWifi.hal index f4bc618f10..765e09d16a 100644 --- a/wifi/1.4/IWifi.hal +++ b/wifi/1.4/IWifi.hal @@ -24,5 +24,4 @@ import @1.3::IWifi; * module loaded in the system. * IWifi.getChip() must return @1.2::IWifiChip */ -interface IWifi extends @1.3::IWifi { -}; +interface IWifi extends @1.3::IWifi {}; diff --git a/wifi/1.4/IWifiApIface.hal b/wifi/1.4/IWifiApIface.hal new file mode 100644 index 0000000000..af88afbea0 --- /dev/null +++ b/wifi/1.4/IWifiApIface.hal @@ -0,0 +1,53 @@ +/* + * Copyright 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.hardware.wifi@1.4; + +import @1.0::IWifiApIface; +import @1.0::MacAddress; +import @1.0::WifiStatus; + +/** + * Represents a network interface in AP mode. + * + * This can be obtained through @1.0::IWifiChip.getApIface() and casting + * IWifiApIface up to 1.4. + */ +interface IWifiApIface extends @1.0::IWifiApIface { + /** + * Changes the MAC address of the interface to the given MAC address. + * + * @param mac MAC address to change to. + * @return status WifiStatus of the operation. + * Possible status codes: + * |WifiStatusCode.SUCCESS|, + * |WifiStatusCode.ERROR_WIFI_IFACE_INVALID|, + * |WifiStatusCode.ERROR_UNKNOWN| + */ + setMacAddress(MacAddress mac) generates (WifiStatus status); + + /** + * Gets the factory MAC address of the interface. + * + * @return status WifiStatus of the operation + * Possible status codes: + * |WifiStatusCode.SUCCESS|, + * |WifiStatusCode.ERROR_WIFI_IFACE_INVALID|, + * |WifiStatusCode.ERROR_UNKNOWN| + * @return mac factory MAC address of the interface + */ + getFactoryMacAddress() generates (WifiStatus status, MacAddress mac); +}; diff --git a/wifi/1.4/default/Android.mk b/wifi/1.4/default/Android.mk index 43ff252894..efbdf230bb 100644 --- a/wifi/1.4/default/Android.mk +++ b/wifi/1.4/default/Android.mk @@ -18,7 +18,6 @@ LOCAL_PATH := $(call my-dir) ### include $(CLEAR_VARS) LOCAL_MODULE := android.hardware.wifi@1.0-service-lib -LOCAL_VINTF_FRAGMENTS := android.hardware.wifi@1.0-service.xml LOCAL_MODULE_RELATIVE_PATH := hw LOCAL_PROPRIETARY_MODULE := true LOCAL_CPPFLAGS := -Wall -Werror -Wextra @@ -81,6 +80,7 @@ include $(BUILD_STATIC_LIBRARY) ### include $(CLEAR_VARS) LOCAL_MODULE := android.hardware.wifi@1.0-service +LOCAL_VINTF_FRAGMENTS := android.hardware.wifi@1.0-service.xml LOCAL_MODULE_RELATIVE_PATH := hw LOCAL_PROPRIETARY_MODULE := true LOCAL_CPPFLAGS := -Wall -Werror -Wextra @@ -115,6 +115,7 @@ include $(BUILD_EXECUTABLE) ### include $(CLEAR_VARS) LOCAL_MODULE := android.hardware.wifi@1.0-service-lazy +LOCAL_VINTF_FRAGMENTS := android.hardware.wifi@1.0-service.xml LOCAL_OVERRIDES_MODULES := android.hardware.wifi@1.0-service LOCAL_CFLAGS := -DLAZY_SERVICE LOCAL_MODULE_RELATIVE_PATH := hw @@ -157,7 +158,6 @@ LOCAL_SRC_FILES := \ tests/mock_wifi_legacy_hal.cpp \ tests/mock_wifi_mode_controller.cpp \ tests/ringbuffer_unit_tests.cpp \ - tests/wifi_ap_iface_unit_tests.cpp \ tests/wifi_nan_iface_unit_tests.cpp \ tests/wifi_chip_unit_tests.cpp \ tests/wifi_iface_util_unit_tests.cpp diff --git a/wifi/1.4/default/tests/hidl_struct_util_unit_tests.cpp b/wifi/1.4/default/tests/hidl_struct_util_unit_tests.cpp index fde1df0383..14a15048fe 100644 --- a/wifi/1.4/default/tests/hidl_struct_util_unit_tests.cpp +++ b/wifi/1.4/default/tests/hidl_struct_util_unit_tests.cpp @@ -170,12 +170,14 @@ TEST_F(HidlStructUtilTest, canConvertLegacyLinkLayerStatsToHidl) { legacy_hal::wifi_channel_stat channel_stat1 = { .channel = {legacy_hal::WIFI_CHAN_WIDTH_20, 2437, 2437, 0}, + .on_time = 0x1111, .cca_busy_time = 0x55, - .on_time = 0x1111}; + }; legacy_hal::wifi_channel_stat channel_stat2 = { .channel = {legacy_hal::WIFI_CHAN_WIDTH_20, 5180, 5180, 0}, + .on_time = 0x2222, .cca_busy_time = 0x66, - .on_time = 0x2222}; + }; radio.channel_stats.push_back(channel_stat1); radio.channel_stats.push_back(channel_stat2); } diff --git a/wifi/1.4/default/tests/wifi_ap_iface_unit_tests.cpp b/wifi/1.4/default/tests/wifi_ap_iface_unit_tests.cpp deleted file mode 100644 index 230edd2ee3..0000000000 --- a/wifi/1.4/default/tests/wifi_ap_iface_unit_tests.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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. - */ - -#include <android-base/logging.h> -#include <android-base/macros.h> -#include <cutils/properties.h> -#include <gmock/gmock.h> - -#undef NAN // This is weird, NAN is defined in bionic/libc/include/math.h:38 -#include "wifi_ap_iface.h" - -#include "mock_interface_tool.h" -#include "mock_wifi_feature_flags.h" -#include "mock_wifi_iface_util.h" -#include "mock_wifi_legacy_hal.h" - -using testing::NiceMock; -using testing::Return; -using testing::Test; - -namespace { -constexpr char kIfaceName[] = "mockWlan0"; -} // namespace - -namespace android { -namespace hardware { -namespace wifi { -namespace V1_4 { -namespace implementation { - -class WifiApIfaceTest : public Test { - protected: - std::shared_ptr<NiceMock<wifi_system::MockInterfaceTool>> iface_tool_{ - new NiceMock<wifi_system::MockInterfaceTool>}; - std::shared_ptr<NiceMock<legacy_hal::MockWifiLegacyHal>> legacy_hal_{ - new NiceMock<legacy_hal::MockWifiLegacyHal>(iface_tool_)}; - std::shared_ptr<NiceMock<iface_util::MockWifiIfaceUtil>> iface_util_{ - new NiceMock<iface_util::MockWifiIfaceUtil>(iface_tool_)}; - std::shared_ptr<NiceMock<feature_flags::MockWifiFeatureFlags>> - feature_flags_{new NiceMock<feature_flags::MockWifiFeatureFlags>}; -}; - -TEST_F(WifiApIfaceTest, SetRandomMacAddressIfFeatureEnabled) { - EXPECT_CALL(*feature_flags_, isApMacRandomizationDisabled()) - .WillOnce(testing::Return(false)); - EXPECT_CALL(*iface_util_, getOrCreateRandomMacAddress()) - .WillOnce(testing::Return(std::array<uint8_t, 6>{0, 0, 0, 0, 0, 0})); - EXPECT_CALL(*iface_util_, setMacAddress(testing::_, testing::_)) - .WillOnce(testing::Return(true)); - sp<WifiApIface> ap_iface = - new WifiApIface(kIfaceName, legacy_hal_, iface_util_, feature_flags_); -} - -TEST_F(WifiApIfaceTest, DontSetRandomMacAddressIfFeatureDisabled) { - EXPECT_CALL(*feature_flags_, isApMacRandomizationDisabled()) - .WillOnce(testing::Return(true)); - EXPECT_CALL(*iface_util_, getOrCreateRandomMacAddress()).Times(0); - EXPECT_CALL(*iface_util_, setMacAddress(testing::_, testing::_)).Times(0); - sp<WifiApIface> ap_iface = - new WifiApIface(kIfaceName, legacy_hal_, iface_util_, feature_flags_); -} -} // namespace implementation -} // namespace V1_4 -} // namespace wifi -} // namespace hardware -} // namespace android diff --git a/wifi/1.4/default/tests/wifi_chip_unit_tests.cpp b/wifi/1.4/default/tests/wifi_chip_unit_tests.cpp index 2ad093ae69..b0357ba980 100644 --- a/wifi/1.4/default/tests/wifi_chip_unit_tests.cpp +++ b/wifi/1.4/default/tests/wifi_chip_unit_tests.cpp @@ -173,8 +173,9 @@ class WifiChipTest : public Test { std::string createIface(const IfaceType& type) { std::string iface_name; if (type == IfaceType::AP) { - chip_->createApIface([&iface_name](const WifiStatus& status, - const sp<IWifiApIface>& iface) { + chip_->createApIface([&iface_name]( + const WifiStatus& status, + const sp<V1_0::IWifiApIface>& iface) { if (WifiStatusCode::SUCCESS == status.code) { ASSERT_NE(iface.get(), nullptr); iface->getName([&iface_name](const WifiStatus& status, diff --git a/wifi/1.4/default/wifi_ap_iface.cpp b/wifi/1.4/default/wifi_ap_iface.cpp index 13ce2dddcb..e677f197b8 100644 --- a/wifi/1.4/default/wifi_ap_iface.cpp +++ b/wifi/1.4/default/wifi_ap_iface.cpp @@ -31,26 +31,11 @@ using hidl_return_util::validateAndCall; WifiApIface::WifiApIface( const std::string& ifname, const std::weak_ptr<legacy_hal::WifiLegacyHal> legacy_hal, - const std::weak_ptr<iface_util::WifiIfaceUtil> iface_util, - const std::weak_ptr<feature_flags::WifiFeatureFlags> feature_flags) + const std::weak_ptr<iface_util::WifiIfaceUtil> iface_util) : ifname_(ifname), legacy_hal_(legacy_hal), iface_util_(iface_util), - feature_flags_(feature_flags), - is_valid_(true) { - if (feature_flags_.lock()->isApMacRandomizationDisabled()) { - LOG(INFO) << "AP MAC randomization disabled"; - return; - } - LOG(INFO) << "AP MAC randomization enabled"; - // Set random MAC address - std::array<uint8_t, 6> randomized_mac = - iface_util_.lock()->getOrCreateRandomMacAddress(); - bool status = iface_util_.lock()->setMacAddress(ifname_, randomized_mac); - if (!status) { - LOG(ERROR) << "Failed to set random mac address"; - } -} + is_valid_(true) {} void WifiApIface::invalidate() { legacy_hal_.reset(); @@ -85,6 +70,20 @@ Return<void> WifiApIface::getValidFrequenciesForBand( hidl_status_cb, band); } +Return<void> WifiApIface::setMacAddress(const hidl_array<uint8_t, 6>& mac, + setMacAddress_cb hidl_status_cb) { + return validateAndCall(this, WifiStatusCode::ERROR_WIFI_IFACE_INVALID, + &WifiApIface::setMacAddressInternal, hidl_status_cb, + mac); +} + +Return<void> WifiApIface::getFactoryMacAddress( + getFactoryMacAddress_cb hidl_status_cb) { + return validateAndCall(this, WifiStatusCode::ERROR_WIFI_IFACE_INVALID, + &WifiApIface::getFactoryMacAddressInternal, + hidl_status_cb); +} + std::pair<WifiStatus, std::string> WifiApIface::getNameInternal() { return {createWifiStatus(WifiStatusCode::SUCCESS), ifname_}; } @@ -111,6 +110,26 @@ WifiApIface::getValidFrequenciesForBandInternal(WifiBand band) { ifname_, hidl_struct_util::convertHidlWifiBandToLegacy(band)); return {createWifiStatusFromLegacyError(legacy_status), valid_frequencies}; } + +WifiStatus WifiApIface::setMacAddressInternal( + const std::array<uint8_t, 6>& mac) { + bool status = iface_util_.lock()->setMacAddress(ifname_, mac); + if (!status) { + return createWifiStatus(WifiStatusCode::ERROR_UNKNOWN); + } + return createWifiStatus(WifiStatusCode::SUCCESS); +} + +std::pair<WifiStatus, std::array<uint8_t, 6>> +WifiApIface::getFactoryMacAddressInternal() { + std::array<uint8_t, 6> mac = + iface_util_.lock()->getFactoryMacAddress(ifname_); + if (mac[0] == 0 && mac[1] == 0 && mac[2] == 0 && mac[3] == 0 && + mac[4] == 0 && mac[5] == 0) { + return {createWifiStatus(WifiStatusCode::ERROR_UNKNOWN), mac}; + } + return {createWifiStatus(WifiStatusCode::SUCCESS), mac}; +} } // namespace implementation } // namespace V1_4 } // namespace wifi diff --git a/wifi/1.4/default/wifi_ap_iface.h b/wifi/1.4/default/wifi_ap_iface.h index 179acacdaa..4f3438c298 100644 --- a/wifi/1.4/default/wifi_ap_iface.h +++ b/wifi/1.4/default/wifi_ap_iface.h @@ -18,9 +18,8 @@ #define WIFI_AP_IFACE_H_ #include <android-base/macros.h> -#include <android/hardware/wifi/1.0/IWifiApIface.h> +#include <android/hardware/wifi/1.4/IWifiApIface.h> -#include "wifi_feature_flags.h" #include "wifi_iface_util.h" #include "wifi_legacy_hal.h" @@ -34,13 +33,11 @@ using namespace android::hardware::wifi::V1_0; /** * HIDL interface object used to control a AP Iface instance. */ -class WifiApIface : public V1_0::IWifiApIface { +class WifiApIface : public V1_4::IWifiApIface { public: - WifiApIface( - const std::string& ifname, - const std::weak_ptr<legacy_hal::WifiLegacyHal> legacy_hal, - const std::weak_ptr<iface_util::WifiIfaceUtil> iface_util, - const std::weak_ptr<feature_flags::WifiFeatureFlags> feature_flags); + WifiApIface(const std::string& ifname, + const std::weak_ptr<legacy_hal::WifiLegacyHal> legacy_hal, + const std::weak_ptr<iface_util::WifiIfaceUtil> iface_util); // Refer to |WifiChip::invalidate()|. void invalidate(); bool isValid(); @@ -53,6 +50,10 @@ class WifiApIface : public V1_0::IWifiApIface { setCountryCode_cb hidl_status_cb) override; Return<void> getValidFrequenciesForBand( WifiBand band, getValidFrequenciesForBand_cb hidl_status_cb) override; + Return<void> setMacAddress(const hidl_array<uint8_t, 6>& mac, + setMacAddress_cb hidl_status_cb) override; + Return<void> getFactoryMacAddress( + getFactoryMacAddress_cb hidl_status_cb) override; private: // Corresponding worker functions for the HIDL methods. @@ -61,11 +62,13 @@ class WifiApIface : public V1_0::IWifiApIface { WifiStatus setCountryCodeInternal(const std::array<int8_t, 2>& code); std::pair<WifiStatus, std::vector<WifiChannelInMhz>> getValidFrequenciesForBandInternal(WifiBand band); + WifiStatus setMacAddressInternal(const std::array<uint8_t, 6>& mac); + std::pair<WifiStatus, std::array<uint8_t, 6>> + getFactoryMacAddressInternal(); std::string ifname_; std::weak_ptr<legacy_hal::WifiLegacyHal> legacy_hal_; std::weak_ptr<iface_util::WifiIfaceUtil> iface_util_; - std::weak_ptr<feature_flags::WifiFeatureFlags> feature_flags_; bool is_valid_; DISALLOW_COPY_AND_ASSIGN(WifiApIface); diff --git a/wifi/1.4/default/wifi_chip.cpp b/wifi/1.4/default/wifi_chip.cpp index 170aa803cf..c7a67cdecd 100644 --- a/wifi/1.4/default/wifi_chip.cpp +++ b/wifi/1.4/default/wifi_chip.cpp @@ -322,7 +322,6 @@ WifiChip::WifiChip( legacy_hal_(legacy_hal), mode_controller_(mode_controller), iface_util_(iface_util), - feature_flags_(feature_flags), is_valid_(true), current_mode_id_(feature_flags::chip_mode_ids::kInvalid), modes_(feature_flags.lock()->getChipModes()), @@ -850,8 +849,7 @@ std::pair<WifiStatus, sp<IWifiApIface>> WifiChip::createApIfaceInternal() { iface_created = true; } iface_util_.lock()->setRandomMacAddressIndex(ap_ifaces_.size()); - sp<WifiApIface> iface = - new WifiApIface(ifname, legacy_hal_, iface_util_, feature_flags_); + sp<WifiApIface> iface = new WifiApIface(ifname, legacy_hal_, iface_util_); ap_ifaces_.push_back(iface); if (iface_created) created_ap_ifaces_.push_back(iface); for (const auto& callback : event_cb_handler_.getCallbacks()) { diff --git a/wifi/1.4/default/wifi_chip.h b/wifi/1.4/default/wifi_chip.h index 15908e9470..efbe07f04f 100644 --- a/wifi/1.4/default/wifi_chip.h +++ b/wifi/1.4/default/wifi_chip.h @@ -252,7 +252,6 @@ class WifiChip : public V1_3::IWifiChip { std::weak_ptr<legacy_hal::WifiLegacyHal> legacy_hal_; std::weak_ptr<mode_controller::WifiModeController> mode_controller_; std::weak_ptr<iface_util::WifiIfaceUtil> iface_util_; - std::weak_ptr<feature_flags::WifiFeatureFlags> feature_flags_; std::vector<sp<WifiApIface>> ap_ifaces_; std::vector<sp<WifiNanIface>> nan_ifaces_; std::vector<sp<WifiP2pIface>> p2p_ifaces_; diff --git a/wifi/1.4/default/wifi_feature_flags.cpp b/wifi/1.4/default/wifi_feature_flags.cpp index efbd00a917..f15ccb270d 100644 --- a/wifi/1.4/default/wifi_feature_flags.cpp +++ b/wifi/1.4/default/wifi_feature_flags.cpp @@ -159,10 +159,12 @@ static const std::vector<IWifiChip::ChipMode> kChipModes{ #undef NAN #ifdef WIFI_HIDL_FEATURE_DISABLE_AP_MAC_RANDOMIZATION -static const bool wifiHidlFeatureDisableApMacRandomization = true; -#else -static const bool wifiHidlFeatureDisableApMacRandomization = false; -#endif // WIFI_HIDL_FEATURE_DISABLE_AP +#pragma message \ + "WIFI_HIDL_FEATURE_DISABLE_AP_MAC_RANDOMIZATION is deprecated; override " \ + "'config_wifi_ap_randomization_supported' in " \ + "frameworks/base/core/res/res/values/config.xml in the device overlay " \ + "instead" +#endif // WIFI_HIDL_FEATURE_DISABLE_AP_MAC_RANDOMIZATION WifiFeatureFlags::WifiFeatureFlags() {} @@ -170,10 +172,6 @@ std::vector<IWifiChip::ChipMode> WifiFeatureFlags::getChipModes() { return kChipModes; } -bool WifiFeatureFlags::isApMacRandomizationDisabled() { - return wifiHidlFeatureDisableApMacRandomization; -} - } // namespace feature_flags } // namespace implementation } // namespace V1_4 diff --git a/wifi/1.4/default/wifi_feature_flags.h b/wifi/1.4/default/wifi_feature_flags.h index 4e146cdfc6..292dedfe19 100644 --- a/wifi/1.4/default/wifi_feature_flags.h +++ b/wifi/1.4/default/wifi_feature_flags.h @@ -43,7 +43,6 @@ class WifiFeatureFlags { virtual ~WifiFeatureFlags() = default; virtual std::vector<V1_0::IWifiChip::ChipMode> getChipModes(); - virtual bool isApMacRandomizationDisabled(); }; } // namespace feature_flags diff --git a/wifi/1.4/vts/OWNERS b/wifi/1.4/vts/OWNERS new file mode 100644 index 0000000000..8bfb14882c --- /dev/null +++ b/wifi/1.4/vts/OWNERS @@ -0,0 +1,2 @@ +rpius@google.com +etancohen@google.com diff --git a/wifi/1.4/vts/functional/Android.bp b/wifi/1.4/vts/functional/Android.bp new file mode 100644 index 0000000000..42c60f2f0e --- /dev/null +++ b/wifi/1.4/vts/functional/Android.bp @@ -0,0 +1,33 @@ +// +// 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. +// + +// SoftAP-specific tests, similar to VtsHalWifiApV1_0TargetTest. +cc_test { + name: "VtsHalWifiApV1_4TargetTest", + defaults: ["VtsHalTargetTestDefaults"], + srcs: [ + "VtsHalWifiV1_4TargetTest.cpp", + "wifi_ap_iface_hidl_test.cpp", + ], + static_libs: [ + "VtsHalWifiV1_0TargetTestUtil", + "android.hardware.wifi@1.0", + "android.hardware.wifi@1.1", + "android.hardware.wifi@1.2", + "android.hardware.wifi@1.3", + "android.hardware.wifi@1.4", + ], +} diff --git a/wifi/1.4/vts/functional/VtsHalWifiV1_4TargetTest.cpp b/wifi/1.4/vts/functional/VtsHalWifiV1_4TargetTest.cpp new file mode 100644 index 0000000000..deac0fa0cf --- /dev/null +++ b/wifi/1.4/vts/functional/VtsHalWifiV1_4TargetTest.cpp @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#include <android-base/logging.h> +#include <android/hardware/wifi/1.4/IWifi.h> + +#include "wifi_hidl_test_utils.h" + +using ::android::hardware::wifi::V1_4::IWifi; + +// Test environment for Wifi HIDL HAL. +class WifiHidlEnvironment_1_4 : public WifiHidlEnvironment { + public: + // get the test environment singleton + static WifiHidlEnvironment_1_4* Instance() { + static WifiHidlEnvironment_1_4* instance = new WifiHidlEnvironment_1_4; + return instance; + } + + virtual void registerTestServices() override { + registerTestService<android::hardware::wifi::V1_4::IWifi>(); + } + + private: + WifiHidlEnvironment_1_4() {} +}; + +WifiHidlEnvironment_1_4* gEnv = WifiHidlEnvironment_1_4::Instance(); + +int main(int argc, char** argv) { + ::testing::AddGlobalTestEnvironment(gEnv); + ::testing::InitGoogleTest(&argc, argv); + gEnv->init(&argc, argv); + int status = RUN_ALL_TESTS(); + LOG(INFO) << "Test result = " << status; + return status; +} diff --git a/wifi/1.4/vts/functional/wifi_ap_iface_hidl_test.cpp b/wifi/1.4/vts/functional/wifi_ap_iface_hidl_test.cpp new file mode 100644 index 0000000000..68e9bbbde2 --- /dev/null +++ b/wifi/1.4/vts/functional/wifi_ap_iface_hidl_test.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Staache 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. + */ + +#include <android/hardware/wifi/1.4/IWifiApIface.h> + +#include <VtsHalHidlTargetTestBase.h> + +#include "wifi_hidl_call_util.h" +#include "wifi_hidl_test_utils.h" + +using ::android::sp; +using ::android::hardware::hidl_array; +using ::android::hardware::wifi::V1_0::WifiStatus; +using ::android::hardware::wifi::V1_0::WifiStatusCode; +using ::android::hardware::wifi::V1_4::IWifiApIface; + +extern WifiHidlEnvironment* gEnv; + +/** + * Fixture to use for all STA Iface HIDL interface tests. + */ +class WifiApIfaceHidlTest : public ::testing::VtsHalHidlTargetTestBase { + public: + virtual void SetUp() override { + wifi_ap_iface_ = IWifiApIface::castFrom(getWifiApIface()); + ASSERT_NE(nullptr, wifi_ap_iface_.get()); + } + + virtual void TearDown() override { + stopWifi(); + } + + protected: + sp<IWifiApIface> wifi_ap_iface_; +}; + +/* + * SetMacAddress: + * Ensures that calls to set MAC address will return a success status + * code. + */ +TEST_F(WifiApIfaceHidlTest, SetMacAddress) { + const hidl_array<uint8_t, 6> kMac{{0x12, 0x22, 0x33, 0x52, 0x10, 0x41}}; + EXPECT_EQ(WifiStatusCode::SUCCESS, + HIDL_INVOKE(wifi_ap_iface_, setMacAddress, kMac).code); +} + +/* + * GetFactoryMacAddress: + * Ensures that calls to get factory MAC address will retrieve a non-zero MAC + * and return a success status code. + */ +TEST_F(WifiApIfaceHidlTest, GetFactoryMacAddress) { + std::pair<WifiStatus, hidl_array<uint8_t, 6> > status_and_mac = + HIDL_INVOKE(wifi_ap_iface_, getFactoryMacAddress); + EXPECT_EQ(WifiStatusCode::SUCCESS, status_and_mac.first.code); + hidl_array<uint8_t, 6> all_zero{}; + EXPECT_NE(all_zero, status_and_mac.second); +} diff --git a/wifi/supplicant/1.3/Android.bp b/wifi/supplicant/1.3/Android.bp index 6633d9d1e3..3f2053132b 100644 --- a/wifi/supplicant/1.3/Android.bp +++ b/wifi/supplicant/1.3/Android.bp @@ -9,6 +9,8 @@ hidl_interface { srcs: [ "types.hal", "ISupplicant.hal", + "ISupplicantStaIface.hal", + "ISupplicantStaIfaceCallback.hal", "ISupplicantStaNetwork.hal", ], interfaces: [ diff --git a/wifi/supplicant/1.3/ISupplicant.hal b/wifi/supplicant/1.3/ISupplicant.hal index 75b7e960ea..246ce1fec3 100644 --- a/wifi/supplicant/1.3/ISupplicant.hal +++ b/wifi/supplicant/1.3/ISupplicant.hal @@ -26,5 +26,4 @@ import @1.2::ISupplicant; * 1.2 HAL. For example V1_2::ISupplicant::addIface() adds V1_2::ISupplicantIface, * which can be cast to V1_3::ISupplicantStaIface. */ -interface ISupplicant extends @1.2::ISupplicant { -}; +interface ISupplicant extends @1.2::ISupplicant {}; diff --git a/wifi/supplicant/1.3/ISupplicantStaIface.hal b/wifi/supplicant/1.3/ISupplicantStaIface.hal new file mode 100644 index 0000000000..cb207d836e --- /dev/null +++ b/wifi/supplicant/1.3/ISupplicantStaIface.hal @@ -0,0 +1,57 @@ +/* + * Copyright 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.hardware.wifi.supplicant@1.3; + +import @1.0::SupplicantStatus; +import @1.2::ISupplicantStaIface; +import ISupplicantStaIfaceCallback; + +/** + * Interface exposed by the supplicant for each station mode network + * interface (e.g wlan0) it controls. + */ +interface ISupplicantStaIface extends @1.2::ISupplicantStaIface { + /** + * Register for callbacks from this interface. + * + * These callbacks are invoked for events that are specific to this interface. + * Registration of multiple callback objects is supported. These objects must + * be automatically deleted when the corresponding client process is dead or + * if this interface is removed. + * + * @param callback An instance of the |ISupplicantStaIfaceCallback| HIDL + * interface object. + * @return status Status of the operation. + * Possible status codes: + * |SupplicantStatusCode.SUCCESS|, + * |SupplicantStatusCode.FAILURE_UNKNOWN|, + * |SupplicantStatusCode.FAILURE_IFACE_INVALID| + */ + registerCallback_1_3(ISupplicantStaIfaceCallback callback) + generates (SupplicantStatus status); + + /** + * Get Connection capabilities + * + * @return status Status of the operation, and connection capabilities. + * Possible status codes: + * |SupplicantStatusCode.SUCCESS|, + * |SupplicantStatusCode.FAILURE_UNKNOWN|, + */ + getConnectionCapabilities() + generates (SupplicantStatus status, ConnectionCapabilities capabilities); +}; diff --git a/wifi/supplicant/1.3/ISupplicantStaIfaceCallback.hal b/wifi/supplicant/1.3/ISupplicantStaIfaceCallback.hal new file mode 100644 index 0000000000..107e0fc0f6 --- /dev/null +++ b/wifi/supplicant/1.3/ISupplicantStaIfaceCallback.hal @@ -0,0 +1,38 @@ +/* + * Copyright 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.hardware.wifi.supplicant@1.3; + +import @1.2::ISupplicantStaIfaceCallback; + +/** + * Callback Interface exposed by the supplicant service + * for each station mode interface (ISupplicantStaIface). + * + * Clients need to host an instance of this HIDL interface object and + * pass a reference of the object to the supplicant via the + * corresponding |ISupplicantStaIface.registerCallback_1_3| method. + */ +interface ISupplicantStaIfaceCallback extends @1.2::ISupplicantStaIfaceCallback { + /** + * Indicates PMK cache added event. + * + * @param expirationTimeInSec expiration time in seconds + * @param serializedEntry is serialized PMK cache entry, the content is + * opaque for the framework and depends on the native implementation. + */ + oneway onPmkCacheAdded(int64_t expirationTimeInSec, vec<uint8_t> serializedEntry); +}; diff --git a/wifi/supplicant/1.3/ISupplicantStaNetwork.hal b/wifi/supplicant/1.3/ISupplicantStaNetwork.hal index eb9de9a53e..ab08cff9c5 100644 --- a/wifi/supplicant/1.3/ISupplicantStaNetwork.hal +++ b/wifi/supplicant/1.3/ISupplicantStaNetwork.hal @@ -47,4 +47,18 @@ interface ISupplicantStaNetwork extends @1.2::ISupplicantStaNetwork { * @return ocspType ocsp type. */ getOcsp() generates (SupplicantStatus status, OcspType ocspType); + + /** + * Add a PMK into supplicant PMK cache. + * + * @param serializedEntry is serialized PMK cache entry, the content is + * opaque for the framework and depends on the native implementation. + * @return status Status of the operation + * Possible status codes: + * |SupplicantStatusCode.SUCCESS|, + * |SupplicantStatusCode.FAILURE_ARGS_INVALID|, + * |SupplicantStatusCode.FAILURE_UNKNOWN|, + * |SupplicantStatusCode.FAILURE_NETWORK_INVALID| + */ + setPmkCache(vec<uint8_t> serializedEntry) generates (SupplicantStatus status); }; diff --git a/wifi/supplicant/1.3/types.hal b/wifi/supplicant/1.3/types.hal index a782b49584..787439984b 100644 --- a/wifi/supplicant/1.3/types.hal +++ b/wifi/supplicant/1.3/types.hal @@ -25,3 +25,36 @@ enum OcspType : uint32_t { REQUIRE_CERT_STATUS, REQUIRE_ALL_CERTS_STATUS, }; + +/** + * Wifi Technologies + */ +enum WifiTechnology : uint32_t { + UNKNOWN = 0, + /** + * For 802.11a/b/g + */ + LEGACY = 1, + /** + * For 802.11n + */ + HT = 2, + /** + * For 802.11ac + */ + VHT = 3, + /** + * For 802.11ax + */ + HE = 4, +}; + +/** + * Connection Capabilities. + */ +struct ConnectionCapabilities { + /** + * Wifi Technology + */ + WifiTechnology technology; +}; diff --git a/wifi/supplicant/1.3/vts/functional/Android.bp b/wifi/supplicant/1.3/vts/functional/Android.bp index 67c73481f2..abb86008b5 100644 --- a/wifi/supplicant/1.3/vts/functional/Android.bp +++ b/wifi/supplicant/1.3/vts/functional/Android.bp @@ -42,6 +42,7 @@ cc_test { defaults: ["VtsHalTargetTestDefaults"], srcs: [ "VtsHalWifiSupplicantV1_3TargetTest.cpp", + "supplicant_sta_iface_hidl_test.cpp", "supplicant_sta_network_hidl_test.cpp", ], static_libs: [ diff --git a/wifi/supplicant/1.3/vts/functional/supplicant_hidl_test_utils_1_3.cpp b/wifi/supplicant/1.3/vts/functional/supplicant_hidl_test_utils_1_3.cpp index 86959eba97..308808deff 100644 --- a/wifi/supplicant/1.3/vts/functional/supplicant_hidl_test_utils_1_3.cpp +++ b/wifi/supplicant/1.3/vts/functional/supplicant_hidl_test_utils_1_3.cpp @@ -21,8 +21,13 @@ #include "supplicant_hidl_test_utils_1_3.h" using ::android::sp; +using ::android::hardware::wifi::supplicant::V1_3::ISupplicantStaIface; using ::android::hardware::wifi::supplicant::V1_3::ISupplicantStaNetwork; +sp<ISupplicantStaIface> getSupplicantStaIface_1_3() { + return ISupplicantStaIface::castFrom(getSupplicantStaIface()); +} + sp<ISupplicantStaNetwork> createSupplicantStaNetwork_1_3() { return ISupplicantStaNetwork::castFrom(createSupplicantStaNetwork()); } diff --git a/wifi/supplicant/1.3/vts/functional/supplicant_hidl_test_utils_1_3.h b/wifi/supplicant/1.3/vts/functional/supplicant_hidl_test_utils_1_3.h index 8e64162d04..39dbb8fc96 100644 --- a/wifi/supplicant/1.3/vts/functional/supplicant_hidl_test_utils_1_3.h +++ b/wifi/supplicant/1.3/vts/functional/supplicant_hidl_test_utils_1_3.h @@ -17,8 +17,11 @@ #ifndef SUPPLICANT_HIDL_TEST_UTILS_1_3_H #define SUPPLICANT_HIDL_TEST_UTILS_1_3_H +#include <android/hardware/wifi/supplicant/1.3/ISupplicantStaIface.h> #include <android/hardware/wifi/supplicant/1.3/ISupplicantStaNetwork.h> +android::sp<android::hardware::wifi::supplicant::V1_3::ISupplicantStaIface> +getSupplicantStaIface_1_3(); android::sp<android::hardware::wifi::supplicant::V1_3::ISupplicantStaNetwork> createSupplicantStaNetwork_1_3(); diff --git a/wifi/supplicant/1.3/vts/functional/supplicant_sta_iface_hidl_test.cpp b/wifi/supplicant/1.3/vts/functional/supplicant_sta_iface_hidl_test.cpp new file mode 100644 index 0000000000..62f3228c40 --- /dev/null +++ b/wifi/supplicant/1.3/vts/functional/supplicant_sta_iface_hidl_test.cpp @@ -0,0 +1,193 @@ +/* + * 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. + */ + +#include <VtsHalHidlTargetTestBase.h> +#include <android/hardware/wifi/supplicant/1.2/types.h> +#include <android/hardware/wifi/supplicant/1.3/ISupplicantStaIface.h> +#include <android/hardware/wifi/supplicant/1.3/ISupplicantStaIfaceCallback.h> +#include <android/hardware/wifi/supplicant/1.3/ISupplicantStaNetwork.h> +#include <android/hardware/wifi/supplicant/1.3/types.h> +#include <hidl/HidlSupport.h> +#include <hidl/Status.h> + +#include "supplicant_hidl_test_utils.h" +#include "supplicant_hidl_test_utils_1_3.h" + +using ::android::sp; +using ::android::hardware::hidl_array; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::wifi::supplicant::V1_0::SupplicantStatus; +using ::android::hardware::wifi::supplicant::V1_0::SupplicantStatusCode; +using ::android::hardware::wifi::supplicant::V1_2::DppAkm; +using ::android::hardware::wifi::supplicant::V1_2::DppFailureCode; +using ::android::hardware::wifi::supplicant::V1_2::DppProgressCode; +using ::android::hardware::wifi::supplicant::V1_3::ConnectionCapabilities; +using ::android::hardware::wifi::supplicant::V1_3::ISupplicantStaIface; +using ::android::hardware::wifi::supplicant::V1_3::ISupplicantStaIfaceCallback; +using ::android::hardware::wifi::supplicant::V1_3::ISupplicantStaNetwork; + +class SupplicantStaIfaceHidlTest : public ::testing::VtsHalHidlTargetTestBase { + public: + virtual void SetUp() override { + startSupplicantAndWaitForHidlService(); + EXPECT_TRUE(turnOnExcessiveLogging()); + sta_iface_ = getSupplicantStaIface_1_3(); + ASSERT_NE(sta_iface_.get(), nullptr); + } + + virtual void TearDown() override { stopSupplicant(); } + + int64_t pmkCacheExpirationTimeInSec; + std::vector<uint8_t> serializedPmkCacheEntry; + + protected: + // ISupplicantStaIface object used for all tests in this fixture. + sp<ISupplicantStaIface> sta_iface_; +}; + +class IfaceCallback : public ISupplicantStaIfaceCallback { + Return<void> onNetworkAdded(uint32_t /* id */) override { return Void(); } + Return<void> onNetworkRemoved(uint32_t /* id */) override { return Void(); } + Return<void> onStateChanged( + ISupplicantStaIfaceCallback::State /* newState */, + const hidl_array<uint8_t, 6>& /*bssid */, uint32_t /* id */, + const hidl_vec<uint8_t>& /* ssid */) override { + return Void(); + } + Return<void> onAnqpQueryDone( + const hidl_array<uint8_t, 6>& /* bssid */, + const ISupplicantStaIfaceCallback::AnqpData& /* data */, + const ISupplicantStaIfaceCallback::Hs20AnqpData& /* hs20Data */) + override { + return Void(); + } + virtual Return<void> onHs20IconQueryDone( + const hidl_array<uint8_t, 6>& /* bssid */, + const hidl_string& /* fileName */, + const hidl_vec<uint8_t>& /* data */) override { + return Void(); + } + virtual Return<void> onHs20SubscriptionRemediation( + const hidl_array<uint8_t, 6>& /* bssid */, + ISupplicantStaIfaceCallback::OsuMethod /* osuMethod */, + const hidl_string& /* url*/) override { + return Void(); + } + Return<void> onHs20DeauthImminentNotice( + const hidl_array<uint8_t, 6>& /* bssid */, uint32_t /* reasonCode */, + uint32_t /* reAuthDelayInSec */, + const hidl_string& /* url */) override { + return Void(); + } + Return<void> onDisconnected(const hidl_array<uint8_t, 6>& /* bssid */, + bool /* locallyGenerated */, + ISupplicantStaIfaceCallback::ReasonCode + /* reasonCode */) override { + return Void(); + } + Return<void> onAssociationRejected( + const hidl_array<uint8_t, 6>& /* bssid */, + ISupplicantStaIfaceCallback::StatusCode /* statusCode */, + bool /*timedOut */) override { + return Void(); + } + Return<void> onAuthenticationTimeout( + const hidl_array<uint8_t, 6>& /* bssid */) override { + return Void(); + } + Return<void> onBssidChanged( + ISupplicantStaIfaceCallback::BssidChangeReason /* reason */, + const hidl_array<uint8_t, 6>& /* bssid */) override { + return Void(); + } + Return<void> onEapFailure() override { return Void(); } + Return<void> onEapFailure_1_1( + ISupplicantStaIfaceCallback::EapErrorCode /* eapErrorCode */) override { + return Void(); + } + Return<void> onWpsEventSuccess() override { return Void(); } + Return<void> onWpsEventFail( + const hidl_array<uint8_t, 6>& /* bssid */, + ISupplicantStaIfaceCallback::WpsConfigError /* configError */, + ISupplicantStaIfaceCallback::WpsErrorIndication /* errorInd */) + override { + return Void(); + } + Return<void> onWpsEventPbcOverlap() override { return Void(); } + Return<void> onExtRadioWorkStart(uint32_t /* id */) override { + return Void(); + } + Return<void> onExtRadioWorkTimeout(uint32_t /* id*/) override { + return Void(); + } + Return<void> onDppSuccessConfigReceived( + const hidl_vec<uint8_t>& /* ssid */, const hidl_string& /* password */, + const hidl_array<uint8_t, 32>& /* psk */, + DppAkm /* securityAkm */) override { + return Void(); + } + Return<void> onDppSuccessConfigSent() override { return Void(); } + Return<void> onDppProgress(DppProgressCode /* code */) override { + return Void(); + } + Return<void> onDppFailure(DppFailureCode /* code */) override { + return Void(); + } + Return<void> onPmkCacheAdded( + int64_t /* expirationTimeInSec */, + const hidl_vec<uint8_t>& /* serializedEntry */) override { + return Void(); + } +}; + +class IfacePmkCacheCallback : public IfaceCallback { + SupplicantStaIfaceHidlTest& parent_; + Return<void> onPmkCacheAdded( + int64_t expirationTimeInSec, + const hidl_vec<uint8_t>& serializedEntry) override { + parent_.pmkCacheExpirationTimeInSec = expirationTimeInSec; + parent_.serializedPmkCacheEntry = serializedEntry; + return Void(); + } + + public: + IfacePmkCacheCallback(SupplicantStaIfaceHidlTest& parent) + : parent_(parent) {} +}; + +/* + * RegisterCallback_1_3 + */ +TEST_F(SupplicantStaIfaceHidlTest, RegisterCallback_1_3) { + sta_iface_->registerCallback_1_3( + new IfaceCallback(), [](const SupplicantStatus& status) { + EXPECT_EQ(SupplicantStatusCode::SUCCESS, status.code); + }); +} + +/* + * getConnectionCapabilities + */ +TEST_F(SupplicantStaIfaceHidlTest, GetConnectionCapabilities) { + sta_iface_->getConnectionCapabilities( + [&](const SupplicantStatus& status, + ConnectionCapabilities /* capabilities */) { + EXPECT_EQ(SupplicantStatusCode::SUCCESS, status.code); + }); +} diff --git a/wifi/supplicant/1.3/vts/functional/supplicant_sta_network_hidl_test.cpp b/wifi/supplicant/1.3/vts/functional/supplicant_sta_network_hidl_test.cpp index e5be0ccfef..07bc9d8103 100644 --- a/wifi/supplicant/1.3/vts/functional/supplicant_sta_network_hidl_test.cpp +++ b/wifi/supplicant/1.3/vts/functional/supplicant_sta_network_hidl_test.cpp @@ -71,3 +71,16 @@ TEST_F(SupplicantStaNetworkHidlTest, SetGetOcsp) { EXPECT_EQ(testOcspType, ocspType); }); } + +/* + * SetPmkCacheEntry + */ +TEST_F(SupplicantStaNetworkHidlTest, SetPmkCache) { + uint8_t bytes[128] = {0}; + std::vector<uint8_t> serializedEntry(bytes, bytes + sizeof(bytes)); + + sta_network_->setPmkCache( + serializedEntry, [](const SupplicantStatus &status) { + EXPECT_EQ(SupplicantStatusCode::SUCCESS, status.code); + }); +} |