diff --git a/Android.bp b/Android.bp index f64968f7b4..9e1df132a7 100644 --- a/Android.bp +++ b/Android.bp @@ -21,7 +21,7 @@ cc_defaults { // Lists all dependencies that can *not* be expected on the device. static_libs: [ - "VtsHalHidlTargetTestBase", + "VtsHalHidlTestUtils", "libhidl-gen-utils", ], @@ -47,3 +47,15 @@ cc_defaults { require_root: true, } + +// TODO: Remove this after all vts tests under vendor/qcom are converted to +// parameterized gtest. +cc_defaults { + name: "Vts10HalTargetTestDefaults", + defaults: [ + "VtsHalTargetTestDefaults", + ], + static_libs: [ + "VtsHalHidlTargetTestBase", + ], +} diff --git a/CleanSpec.mk b/CleanSpec.mk index 76594fb448..1eca2a1c21 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -84,3 +84,4 @@ $(call add-clean-step, rm -f $(PRODUCT_OUT)/etc/init/android.hardware.audio@2.0- $(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/bin/hw/android.hardware.cas@1.1*) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/android.hardware.cas@1.1*) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/vintf/manifest/android.hardware.cas@1.1*) +$(call add-clean-step, rm -rf $(OUT_DIR)/soong/.intermediates/hardware/interfaces/wifi/1.4/android.hardware.wifi@1.4-adapter_genc++/) diff --git a/TEST_MAPPING b/TEST_MAPPING index 543acf6db9..acae4f3a55 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -8,6 +8,9 @@ }, { "name": "hal_implementation_test" + }, + { + "name": "VtsHalTvInputV1_0TargetTest" } ] } diff --git a/audio/6.0/Android.bp b/audio/6.0/Android.bp index 5a22f3b53b..d7880b631d 100644 --- a/audio/6.0/Android.bp +++ b/audio/6.0/Android.bp @@ -12,6 +12,7 @@ hidl_interface { "IStreamIn.hal", "IStreamOut.hal", "IStreamOutCallback.hal", + "IStreamOutEventCallback.hal", ], interfaces: [ "android.hardware.audio.common@6.0", diff --git a/audio/6.0/IDevice.hal b/audio/6.0/IDevice.hal index 2347696035..2026d8ff8b 100644 --- a/audio/6.0/IDevice.hal +++ b/audio/6.0/IDevice.hal @@ -149,7 +149,10 @@ interface IDevice { AudioConfig suggestedConfig); /** - * Returns whether HAL supports audio patches. + * Returns whether HAL supports audio patches. Patch represents a connection + * between signal source(s) and signal sink(s). If HAL doesn't support + * patches natively (in hardware) then audio system will need to establish + * them in software. * * @return supports true if audio patches are supported. */ @@ -167,6 +170,25 @@ interface IDevice { createAudioPatch(vec sources, vec sinks) generates (Result retval, AudioPatchHandle patch); + /** + * Updates an audio patch. + * + * Use of this function is preferred to releasing and re-creating a patch + * as the HAL module can figure out a way of switching the route without + * causing audio disruption. + * + * @param previousPatch handle of the previous patch to update. + * @param sources new patch sources. + * @param sinks new patch sinks. + * @return retval operation completion status. + * @return patch updated patch handle. + */ + updateAudioPatch( + AudioPatchHandle previousPatch, + vec sources, + vec sinks) generates ( + Result retval, AudioPatchHandle patch); + /** * Release an audio patch. * @@ -297,7 +319,9 @@ interface IDevice { close() generates (Result retval); /** - * Applies an audio effect to an audio device. + * Applies an audio effect to an audio device. The effect is inserted + * according to its insertion preference specified by INSERT_... EffectFlags + * in the EffectDescriptor. * * @param device identifies the sink or source device this effect must be applied to. * "device" is the AudioPortHandle indicated for the device when the audio diff --git a/audio/6.0/IStream.hal b/audio/6.0/IStream.hal index d7d3c8437c..2ea1ab3680 100644 --- a/audio/6.0/IStream.hal +++ b/audio/6.0/IStream.hal @@ -274,6 +274,7 @@ interface IStream { * * @param minSizeFrames minimum buffer size requested. The actual buffer * size returned in struct MmapBufferInfo can be larger. + * The size must be a positive value. * @return retval OK in case the success. * NOT_SUPPORTED on non mmap mode streams * NOT_INITIALIZED in case of memory allocation error diff --git a/audio/6.0/IStreamOut.hal b/audio/6.0/IStreamOut.hal index 941ba61006..9da48fe02f 100644 --- a/audio/6.0/IStreamOut.hal +++ b/audio/6.0/IStreamOut.hal @@ -19,6 +19,7 @@ package android.hardware.audio@6.0; import android.hardware.audio.common@6.0; import IStream; import IStreamOutCallback; +import IStreamOutEventCallback; interface IStreamOut extends IStream { /** @@ -167,6 +168,18 @@ interface IStreamOut extends IStream { */ clearCallback() generates (Result retval); + /** + * Set the callback interface for notifying about an output stream event. + * + * Calling this method with a null pointer will result in releasing + * the local callback proxy on the server side and thus dereference + * the callback implementation on the client side. + * + * @return retval operation completion status. + */ + setEventCallback(IStreamOutEventCallback callback) + generates (Result retval); + /** * Returns whether HAL supports pausing and resuming of streams. * @@ -276,4 +289,90 @@ interface IStreamOut extends IStream { */ selectPresentation(int32_t presentationId, int32_t programId) generates (Result retval); + + /** + * Returns the Dual Mono mode presentation setting. + * + * Optional method + * + * @return retval operation completion status. + * @return mode current setting of Dual Mono mode. + */ + getDualMonoMode() generates (Result retval, DualMonoMode mode); + + /** + * Sets the Dual Mono mode presentation on the output device. + * + * The Dual Mono mode is generally applied to stereo audio streams + * where the left and right channels come from separate sources. + * + * Optional method + * + * @param mode selected Dual Mono mode. + * @return retval operation completion status. + */ + setDualMonoMode(DualMonoMode mode) generates (Result retval); + + /** + * Returns the Audio Description Mix level in dB. + * + * The level is applied to streams incorporating a secondary Audio + * Description stream. It specifies the relative level of mixing for + * the Audio Description with a reference to the Main Audio. + * + * Optional method + * + * The value of the relative level is in the range from negative infinity + * to +48. + * + * @return retval operation completion status. + * @return leveldB the current Audio Description Mix Level in dB. + */ + getAudioDescriptionMixLevel() generates (Result retval, float leveldB); + + /** + * Sets the Audio Description Mix level in dB. + * + * For streams incorporating a secondary Audio Description stream + * the relative level of mixing of the Audio Description to the Main Audio + * is controlled by this method. + * + * Optional method + * + * The value of the relative level must be in the range from negative + * infinity to +48. + * + * @param leveldB Audio Description Mix Level in dB + * @return retval operation completion status. + */ + setAudioDescriptionMixLevel(float leveldB) generates (Result retval); + + /** + * Retrieves current playback rate parameters. + * + * Optional method + * + * @return retval operation completion status. + * @return playbackRate current playback parameters + */ + getPlaybackRateParameters() + generates (Result retval, PlaybackRate playbackRate); + + /** + * Sets the playback rate parameters that control playback behavior. + * This is normally used when playing encoded content and decoding + * is performed in hardware. Otherwise, the framework can apply + * necessary transformations. + * + * Optional method + * + * If the HAL supports setting the playback rate, it is recommended + * to support speed and pitch values at least in the range + * from 0.5f to 2.0f, inclusive (see the definition of PlaybackRate struct). + * + * @param playbackRate playback parameters + * @return retval operation completion status. + */ + setPlaybackRateParameters(PlaybackRate playbackRate) + generates (Result retval); }; diff --git a/audio/6.0/IStreamOutEventCallback.hal b/audio/6.0/IStreamOutEventCallback.hal new file mode 100644 index 0000000000..9c8871306e --- /dev/null +++ b/audio/6.0/IStreamOutEventCallback.hal @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2020 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 stream out event callback interface. The interface provides + * a way for the HAL to notify platform when there are changes, e.g. codec + * format change, from the lower layer. + */ +interface IStreamOutEventCallback { + /** + * Codec format changed. + * + * onCodecFormatChanged returns an AudioMetadata object in read-only ByteString format. + * It represents the most recent codec format decoded by a HW audio decoder. + * + * Codec format is an optional message from HW audio decoders. It serves to + * notify the application about the codec format and audio objects contained + * within the compressed audio stream for control, informational, + * and display purposes. + * + * audioMetadata ByteString is convertible to an AudioMetadata object through + * both a C++ and a C API present in Metadata.h [1], or through a Java API present + * in AudioMetadata.java [2]. + * + * The ByteString format is a stable format used for parcelling (marshalling) across + * JNI, AIDL, and HIDL interfaces. The test for R compatibility for native marshalling + * is TEST(metadata_tests, compatibility_R) [3]. The test for R compatibility for JNI + * marshalling is android.media.cts.AudioMetadataTest#testCompatibilityR [4]. + * + * R (audio HAL 6.0) defined keys are as follows [2]: + * "bitrate", int32 + * "channel-mask", int32 + * "mime", string + * "sample-rate", int32 + * "bit-width", int32 + * "has-atmos", int32 + * "audio-encoding", int32 + * + * Parceling Format: + * All values are native endian order. [1] + * + * using type_size_t = uint32_t; + * using index_size_t = uint32_t; + * using datum_size_t = uint32_t; + * + * Permitted type indexes are + * TYPE_NONE = 0, // Reserved + * TYPE_INT32 = 1, + * TYPE_INT64 = 2, + * TYPE_FLOAT = 3, + * TYPE_DOUBLE = 4, + * TYPE_STRING = 5, + * TYPE_DATA = 6, // A data table of + * + * Datum = { + * (type_size_t) Type (the type index from type_as_value.) + * (datum_size_t) Size (size of the Payload) + * (byte string) Payload + * } + * + * The data is specified in native endian order. + * Since the size of the Payload is always present, unknown types may be skipped. + * + * Payload + * [ sizeof(Primitive_Value) in raw bytes ] + * + * Example of Payload of 123: + * Payload + * [ value of 123 ] = 0x7b 0x00 0x00 0x00 123 + * + * Payload + * [ (index_size_t) length, not including zero terminator.] + * [ (length) raw bytes ] + * + * Example of Payload of std::string("hi"): + * [ (index_size_t) length ] = 0x02 0x00 0x00 0x00 2 strlen("hi") + * [ raw bytes "hi" ] = 0x68 0x69 "hi" + * + * Payload + * [ (index_size_t) entries ] + * [ raw bytes (entry 1) Key (Payload) + * Value (Datum) + * ... (until #entries) ] + * + * Example of Payload of {{"hello", "world"}, + * {"value", (int32_t)1000}}; + * [ (index_size_t) #entries ] = 0x02 0x00 0x00 0x00 2 entries + * Key (Payload) + * [ index_size_t length ] = 0x05 0x00 0x00 0x00 5 strlen("hello") + * [ raw bytes "hello" ] = 0x68 0x65 0x6c 0x6c 0x6f "hello" + * Value (Datum) + * [ (type_size_t) type ] = 0x05 0x00 0x00 0x00 5 (TYPE_STRING) + * [ (datum_size_t) size ] = 0x09 0x00 0x00 0x00 sizeof(index_size_t) + + * strlen("world") + * Payload + * [ (index_size_t) length ] = 0x05 0x00 0x00 0x00 5 strlen("world") + * [ raw bytes "world" ] = 0x77 0x6f 0x72 0x6c 0x64 "world" + * Key (Payload) + * [ index_size_t length ] = 0x05 0x00 0x00 0x00 5 strlen("value") + * [ raw bytes "value" ] = 0x76 0x61 0x6c 0x75 0x65 "value" + * Value (Datum) + * [ (type_size_t) type ] = 0x01 0x00 0x00 0x00 1 (TYPE_INT32) + * [ (datum_size_t) size ] = 0x04 0x00 0x00 0x00 4 sizeof(int32_t) + * Payload + * [ raw bytes 1000 ] = 0xe8 0x03 0x00 0x00 1000 + * + * The contents of audioMetadata is a Payload. + * An implementation dependent detail is that the Keys are always + * stored sorted, so the byte string representation generated is unique. + * + * Vendor keys are allowed for informational and debugging purposes. + * Vendor keys should consist of the vendor company name followed + * by a dot; for example, "vendorCompany.someVolume" [2]. + * + * [1] system/media/audio_utils/include/audio_utils/Metadata.h + * [2] frameworks/base/media/java/android/media/AudioMetadata.java + * [3] system/media/audio_utils/tests/metadata_tests.cpp + * [4] cts/tests/tests/media/src/android/media/cts/AudioMetadataTest.java + * + * @param audioMetadata is a buffer containing decoded format changes + * reported by codec. The buffer contains data that can be transformed + * to audio metadata, which is a C++ object based map. + */ + oneway onCodecFormatChanged(vec audioMetadata); +}; diff --git a/audio/6.0/config/api/current.txt b/audio/6.0/config/api/current.txt index adab3d2657..6b49e5ed31 100644 --- a/audio/6.0/config/api/current.txt +++ b/audio/6.0/config/api/current.txt @@ -258,8 +258,10 @@ package audio.policy.configuration.V6_0 { public class GlobalConfiguration { ctor public GlobalConfiguration(); + method public boolean getCall_screen_mode_supported(); method public audio.policy.configuration.V6_0.EngineSuffix getEngine_library(); method public boolean getSpeaker_drc_enabled(); + method public void setCall_screen_mode_supported(boolean); method public void setEngine_library(audio.policy.configuration.V6_0.EngineSuffix); method public void setSpeaker_drc_enabled(boolean); } diff --git a/audio/6.0/config/audio_policy_configuration.xsd b/audio/6.0/config/audio_policy_configuration.xsd index 3fc60e2118..341c6b337a 100644 --- a/audio/6.0/config/audio_policy_configuration.xsd +++ b/audio/6.0/config/audio_policy_configuration.xsd @@ -66,6 +66,7 @@ + diff --git a/audio/6.0/types.hal b/audio/6.0/types.hal index 1a704f8b32..8ff618e58b 100644 --- a/audio/6.0/types.hal +++ b/audio/6.0/types.hal @@ -247,3 +247,111 @@ enum MicrophoneDirection : int32_t { */ EXTERNAL = 3, }; + + +/* Dual Mono handling is used when a stereo audio stream + * contains separate audio content on the left and right channels. + * Such information about the content of the stream may be found, for example, + * in ITU T-REC-J.94-201610 A.6.2.3 Component descriptor. + */ +@export(name="audio_dual_mono_mode_t", value_prefix="AUDIO_DUAL_MONO_MODE_") +enum DualMonoMode : int32_t { + // Need to be in sync with DUAL_MONO_MODE* constants in + // frameworks/base/media/java/android/media/AudioTrack.java + /** + * Disable any Dual Mono presentation effect. + * + */ + OFF = 0, + /** + * This mode indicates that a stereo stream should be presented + * with the left and right audio channels blended together + * and delivered to both channels. + * + * Behavior for non-stereo streams is implementation defined. + * A suggested guideline is that the left-right stereo symmetric + * channels are pairwise blended, the other channels such as center + * are left alone. + */ + LR = 1, + /** + * This mode indicates that a stereo stream should be presented + * with the left audio channel replicated into the right audio channel. + * + * Behavior for non-stereo streams is implementation defined. + * A suggested guideline is that all channels with left-right + * stereo symmetry will have the left channel position replicated + * into the right channel position. The center channels (with no + * left/right symmetry) or unbalanced channels are left alone. + */ + LL = 2, + /** + * This mode indicates that a stereo stream should be presented + * with the right audio channel replicated into the left audio channel. + * + * Behavior for non-stereo streams is implementation defined. + * A suggested guideline is that all channels with left-right + * stereo symmetry will have the right channel position replicated + * into the left channel position. The center channels (with no + * left/right symmetry) or unbalanced channels are left alone. + */ + RR = 3, +}; + +/** + * Algorithms used for timestretching (preserving pitch while playing audio + * content at different speed). + */ +@export(name="audio_timestretch_stretch_mode_t", value_prefix="AUDIO_TIMESTRETCH_STRETCH_") +enum TimestretchMode : int32_t { + // Need to be in sync with AUDIO_STRETCH_MODE_* constants in + // frameworks/base/media/java/android/media/PlaybackParams.java + DEFAULT = 0, + /** Selects timestretch algorithm best suitable for voice (speech) content. */ + VOICE = 1, +}; + +/** + * Behavior when the values for speed and / or pitch are out + * of applicable range. + */ +@export(name="audio_timestretch_fallback_mode_t", value_prefix="AUDIO_TIMESTRETCH_FALLBACK_") +enum TimestretchFallbackMode : int32_t { + // Need to be in sync with AUDIO_FALLBACK_MODE_* constants in + // frameworks/base/media/java/android/media/PlaybackParams.java + /** Play silence for parameter values that are out of range. */ + MUTE = 1, + /** Return an error while trying to set the parameters. */ + FAIL = 2, +}; + +/** + * Parameters determining playback behavior. They are used to speed up or + * slow down playback and / or change the tonal frequency of the audio content + * (pitch). + */ +struct PlaybackRate { + /** + * Speed factor (multiplier). Normal speed has the value of 1.0f. + * Values less than 1.0f slow down playback, value greater than 1.0f + * speed it up. + */ + float speed; + /** + * Pitch factor (multiplier). Setting pitch value to 1.0f together + * with changing playback speed preserves the pitch, this is often + * called "timestretching." Setting the pitch value equal to speed produces + * the same effect as playing audio content at different sampling rate. + */ + float pitch; + /** + * Selects the algorithm used for timestretching (preserving pitch while + * playing audio at different speed). + */ + TimestretchMode timestretchMode; + /** + * Selects the behavior when the specified values for speed and / or pitch + * are out of applicable range. + */ + TimestretchFallbackMode fallbackMode; +}; diff --git a/audio/README b/audio/README index abe979c0fa..afafbe32d2 100644 --- a/audio/README +++ b/audio/README @@ -1,5 +1,8 @@ Directory structure of the audio HIDL related code. +Run `common/all-versions/copyHAL.sh` to create a new version of the audio HAL +based on an existing one. + audio |-- 2.0 <== core 2.0 HIDL API. .hal can not be moved into the core directory | because that would change its namespace and include path @@ -11,13 +14,13 @@ audio | |-- 2.0 <== HIDL API of V2 | |-- 4.0 | |-- ... -| `-- all_versions <== code common to all version of both core and effect API +| `-- all-versions <== code common to all version of both core and effect API | |-- default <== implementation shared code between core and effect impl | |-- test <== utilities used by tests | `-- util <== utilities used by both implementation and tests | |-- core <== VTS and default implementation of the core API (not HIDL, see /audio/2.0)) -| `-- all_versions <== Code is version independent through #if and separate files +| `-- all-versions <== Code is version independent through #if and separate files | |-- default <== code that wraps the legacy API | `-- vts <== vts of core API | |-- 2.0 <== 2.0 specific tests and helpers @@ -28,6 +31,6 @@ audio |-- 2.0 |-- 4.0 |-- ... - `-- all_versions + `-- all-versions |-- default `-- vts diff --git a/audio/common/6.0/types.hal b/audio/common/6.0/types.hal index da506d66e0..67217ab503 100644 --- a/audio/common/6.0/types.hal +++ b/audio/common/6.0/types.hal @@ -91,37 +91,76 @@ struct Uuid { enum AudioStreamType : int32_t { // These values must kept in sync with // frameworks/base/media/java/android/media/AudioSystem.java + /** Used to identify the default audio stream volume. */ DEFAULT = -1, + /** Specifies the minimum value for use in checks and loops. */ MIN = 0, + /** Used to identify the volume of audio streams for phone calls. */ VOICE_CALL = 0, + /** Used to identify the volume of audio streams for system sounds. */ SYSTEM = 1, + /** + * Used to identify the volume of audio streams for the phone ring + * and message alerts. + */ RING = 2, + /** Used to identify the volume of audio streams for music playback. */ MUSIC = 3, + /** Used to identify the volume of audio streams for alarms. */ ALARM = 4, + /** Used to identify the volume of audio streams for notifications. */ NOTIFICATION = 5, + /** + * Used to identify the volume of audio streams for phone calls + * when connected on bluetooth. + */ BLUETOOTH_SCO = 6, - ENFORCED_AUDIBLE = 7, // Sounds that cannot be muted by user and must be - // routed to speaker + /** + * Used to identify the volume of audio streams for enforced system + * sounds in certain countries (e.g camera in Japan). */ + ENFORCED_AUDIBLE = 7, + /** Used to identify the volume of audio streams for DTMF tones. */ DTMF = 8, - TTS = 9, // Transmitted Through Speaker. Plays over speaker - // only, silent on other devices - ACCESSIBILITY = 10, // For accessibility talk back prompts - ASSISTANT = 11, // For virtual assistant service + /** + * Used to identify the volume of audio streams exclusively transmitted + * through the speaker (TTS) of the device. + */ + TTS = 9, + /** + * Used to identify the volume of audio streams for accessibility prompts. + */ + ACCESSIBILITY = 10, + /** Used to identify the volume of audio streams for virtual assistant. */ + ASSISTANT = 11, }; @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 audio source. */ DEFAULT = 0, + /** Microphone audio source. */ MIC = 1, + /** Voice call uplink (Tx) audio source. */ VOICE_UPLINK = 2, + /** Voice call downlink (Rx) audio source. */ VOICE_DOWNLINK = 3, + /** Voice call uplink + downlink audio source. */ VOICE_CALL = 4, + /** + * Microphone audio source tuned for video recording, with the same + * orientation as the camera if available. + */ CAMCORDER = 5, + /** Microphone audio source tuned for voice recognition. */ VOICE_RECOGNITION = 6, + /** + * Microphone audio source tuned for voice communications such as VoIP. It + * will for instance take advantage of echo cancellation or automatic gain + * control if available. + */ VOICE_COMMUNICATION = 7, /** * Source for the mix to be presented remotely. An example of remote @@ -146,7 +185,9 @@ enum AudioSource : int32_t { * to include all post processing applied to the playback path. */ ECHO_REFERENCE = 1997, + /** Virtual source for the built-in FM tuner. */ FM_TUNER = 1998, + /** Virtual source for the last captured hotword. */ HOTWORD = 1999, }; @@ -562,6 +603,8 @@ enum AudioMode : int32_t { IN_CALL = 2, /** Calls handled by apps (Eg: Hangout). */ IN_COMMUNICATION = 3, + /** Call screening in progress. */ + CALL_SCREEN = 4, }; @export(name="", value_prefix="AUDIO_DEVICE_") @@ -738,6 +781,7 @@ enum AudioInputFlag : int32_t { 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 + DIRECT = 0x80, // for acquiring encoded streams }; @export(name="audio_usage_t", value_prefix="AUDIO_USAGE_") @@ -745,19 +789,86 @@ 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 + /** + * Usage value to use when the usage is unknown. + */ UNKNOWN = 0, + /** + * Usage value to use when the usage is media, such as music, or movie + * soundtracks. + */ MEDIA = 1, + /** + * Usage value to use when the usage is voice communications, such as + * telephony or VoIP. + */ VOICE_COMMUNICATION = 2, + /** + * Usage value to use when the usage is in-call signalling, such as with + * a "busy" beep, or DTMF tones. + */ VOICE_COMMUNICATION_SIGNALLING = 3, + /** + * Usage value to use when the usage is an alarm (e.g. wake-up alarm). + */ ALARM = 4, + /** + * Usage value to use when the usage is a generic notification. + */ NOTIFICATION = 5, + /** + * Usage value to use when the usage is telephony ringtone. + */ NOTIFICATION_TELEPHONY_RINGTONE = 6, + /** + * Usage value to use when the usage is for accessibility, such as with + * a screen reader. + */ ASSISTANCE_ACCESSIBILITY = 11, + /** + * Usage value to use when the usage is driving or navigation directions. + */ ASSISTANCE_NAVIGATION_GUIDANCE = 12, + /** + * Usage value to use when the usage is sonification, such as with user + * interface sounds. + */ ASSISTANCE_SONIFICATION = 13, + /** + * Usage value to use when the usage is for game audio. + */ GAME = 14, + /** + * Usage value to use when feeding audio to the platform and replacing + * "traditional" audio source, such as audio capture devices. + */ VIRTUAL_SOURCE = 15, + /** + * Usage value to use for audio responses to user queries, audio + * instructions or help utterances. + */ ASSISTANT = 16, + /** + * Usage value to use for assistant voice interaction with remote caller + * on Cell and VoIP calls. + */ + CALL_ASSISTANT = 17, + /** + * Usage value to use when the usage is an emergency. + */ + EMERGENCY = 1000, + /** + * Usage value to use when the usage is a safety sound. + */ + SAFETY = 1001, + /** + * Usage value to use when the usage is a vehicle status. + */ + VEHICLE_STATUS = 1002, + /** + * Usage value to use when the usage is an announcement. + */ + ANNOUNCEMENT = 1003, }; /** Type of audio generated by an application. */ @@ -765,13 +876,52 @@ enum AudioUsage : int32_t { enum AudioContentType : uint32_t { // Do not change these values without updating their counterparts // in frameworks/base/media/java/android/media/AudioAttributes.java + /** + * Content type value to use when the content type is unknown, or other than + * the ones defined. + */ UNKNOWN = 0, + /** + * Content type value to use when the content type is speech. + */ SPEECH = 1, + /** + * Content type value to use when the content type is music. + */ MUSIC = 2, + /** + * Content type value to use when the content type is a soundtrack, + * typically accompanying a movie or TV program. + */ MOVIE = 3, + /** + * Content type value to use when the content type is a sound used to + * accompany a user action, such as a beep or sound effect expressing a key + * click, or event, such as the type of a sound for a bonus being received + * in a game. These sounds are mostly synthesized or short Foley sounds. + */ SONIFICATION = 4, }; +/** Encapsulation mode used for sending audio compressed data. */ +@export(name="audio_encapsulation_mode_t", value_prefix="AUDIO_ENCAPSULATION_MODE_") +enum AudioEncapsulationMode : int32_t { + // Do not change these values without updating their counterparts + // in frameworks/base/media/java/android/media/AudioTrack.java + /** + * No encapsulation mode for metadata. + */ + NONE = 0, + /** + * Elementary stream payload with metadata + */ + ELEMENTARY_STREAM = 1, + /** + * Handle-based payload with metadata + */ + HANDLE = 2, +}; + /** * Additional information about the stream passed to hardware decoders. */ @@ -787,6 +937,9 @@ struct AudioOffloadInfo { uint32_t bitWidth; uint32_t bufferSize; AudioUsage usage; + AudioEncapsulationMode encapsulationMode; + int32_t contentId; + int32_t syncId; }; /** diff --git a/audio/common/all-versions/default/HidlUtils.cpp b/audio/common/all-versions/default/HidlUtils.cpp index 08002c8788..a470c9cb78 100644 --- a/audio/common/all-versions/default/HidlUtils.cpp +++ b/audio/common/all-versions/default/HidlUtils.cpp @@ -28,12 +28,13 @@ namespace common { namespace CPP_VERSION { namespace implementation { -void HidlUtils::audioConfigFromHal(const audio_config_t& halConfig, AudioConfig* config) { +status_t HidlUtils::audioConfigFromHal(const audio_config_t& halConfig, AudioConfig* config) { config->sampleRateHz = halConfig.sample_rate; config->channelMask = EnumBitfield(halConfig.channel_mask); config->format = AudioFormat(halConfig.format); - audioOffloadInfoFromHal(halConfig.offload_info, &config->offloadInfo); + status_t status = audioOffloadInfoFromHal(halConfig.offload_info, &config->offloadInfo); config->frameCount = halConfig.frame_count; + return status; } void HidlUtils::audioConfigToHal(const AudioConfig& config, audio_config_t* halConfig) { @@ -106,8 +107,8 @@ audio_usage_t HidlUtils::audioUsageToHal(const AudioUsage usage) { return static_cast(usage); } -void HidlUtils::audioOffloadInfoFromHal(const audio_offload_info_t& halOffload, - AudioOffloadInfo* offload) { +status_t HidlUtils::audioOffloadInfoFromHal(const audio_offload_info_t& halOffload, + AudioOffloadInfo* offload) { offload->sampleRateHz = halOffload.sample_rate; offload->channelMask = EnumBitfield(halOffload.channel_mask); offload->format = AudioFormat(halOffload.format); @@ -119,6 +120,26 @@ void HidlUtils::audioOffloadInfoFromHal(const audio_offload_info_t& halOffload, offload->bitWidth = halOffload.bit_width; offload->bufferSize = halOffload.offload_buffer_size; offload->usage = audioUsageFromHal(halOffload.usage); +#if MAJOR_VERSION >= 6 + if (halOffload.version >= AUDIO_OFFLOAD_INFO_VERSION_0_2) { + offload->encapsulationMode = + static_cast(halOffload.encapsulation_mode); + offload->contentId = halOffload.content_id; + offload->syncId = halOffload.sync_id; + } else { + offload->encapsulationMode = AudioEncapsulationMode::NONE; + offload->contentId = 0; + offload->syncId = 0; + } +#else + // nonzero values here are not compatible with HAL versions below 6. + if (halOffload.version >= AUDIO_OFFLOAD_INFO_VERSION_0_2 && + (halOffload.encapsulation_mode != AUDIO_ENCAPSULATION_MODE_NONE || + halOffload.content_id != 0 || halOffload.sync_id != 0)) { + return BAD_VALUE; + } +#endif + return OK; } void HidlUtils::audioOffloadInfoToHal(const AudioOffloadInfo& offload, @@ -135,6 +156,14 @@ void HidlUtils::audioOffloadInfoToHal(const AudioOffloadInfo& offload, halOffload->bit_width = offload.bitWidth; halOffload->offload_buffer_size = offload.bufferSize; halOffload->usage = audioUsageToHal(offload.usage); +#if MAJOR_VERSION >= 6 + halOffload->encapsulation_mode = + static_cast(offload.encapsulationMode); + halOffload->content_id = offload.contentId; + halOffload->sync_id = offload.syncId; +#else + // offload doesn't contain encapsulationMode, contentId, syncId, so this is OK. +#endif } void HidlUtils::audioPortConfigFromHal(const struct audio_port_config& halConfig, diff --git a/audio/common/all-versions/default/HidlUtils.h b/audio/common/all-versions/default/HidlUtils.h index 758a7f43d1..ef6dee3706 100644 --- a/audio/common/all-versions/default/HidlUtils.h +++ b/audio/common/all-versions/default/HidlUtils.h @@ -35,8 +35,11 @@ namespace implementation { using namespace ::android::hardware::audio::common::CPP_VERSION; class HidlUtils { - public: - static void audioConfigFromHal(const audio_config_t& halConfig, AudioConfig* config); + public: + // A failure here indicates a platform config that is incompatible with + // the compiled HIDL interface version. + static status_t audioConfigFromHal(const audio_config_t& halConfig, AudioConfig* config); + static void audioConfigToHal(const AudioConfig& config, audio_config_t* halConfig); static void audioGainConfigFromHal(const struct audio_gain_config& halConfig, AudioGainConfig* config); @@ -46,8 +49,10 @@ class HidlUtils { static void audioGainToHal(const AudioGain& gain, struct audio_gain* halGain); static AudioUsage audioUsageFromHal(const audio_usage_t halUsage); static audio_usage_t audioUsageToHal(const AudioUsage usage); - static void audioOffloadInfoFromHal(const audio_offload_info_t& halOffload, - AudioOffloadInfo* offload); + // A failure here indicates a platform offload info that is incompatible with + // the compiled HIDL interface version. + static status_t audioOffloadInfoFromHal(const audio_offload_info_t& halOffload, + AudioOffloadInfo* offload); static void audioOffloadInfoToHal(const AudioOffloadInfo& offload, audio_offload_info_t* halOffload); static void audioPortConfigFromHal(const struct audio_port_config& halConfig, @@ -58,7 +63,7 @@ class HidlUtils { const struct audio_port_config* halConfigs, hidl_vec* configs); static std::unique_ptr audioPortConfigsToHal( - const hidl_vec& configs); + const hidl_vec& configs); static void audioPortFromHal(const struct audio_port& halPort, AudioPort* port); static void audioPortToHal(const AudioPort& port, struct audio_port* halPort); static void uuidFromHal(const audio_uuid_t& halUuid, Uuid* uuid); diff --git a/audio/common/all-versions/default/service/service.cpp b/audio/common/all-versions/default/service/service.cpp index 7331b0ac4c..147d062204 100644 --- a/audio/common/all-versions/default/service/service.cpp +++ b/audio/common/all-versions/default/service/service.cpp @@ -80,6 +80,7 @@ int main(int /* argc */, char* /* argv */ []) { const std::vector optionalInterfaces = { { "Soundtrigger API", + "android.hardware.soundtrigger@2.3::ISoundTriggerHw", "android.hardware.soundtrigger@2.2::ISoundTriggerHw", "android.hardware.soundtrigger@2.1::ISoundTriggerHw", "android.hardware.soundtrigger@2.0::ISoundTriggerHw", diff --git a/audio/core/all-versions/default/Device.cpp b/audio/core/all-versions/default/Device.cpp index ad841caf2e..6260ba1979 100644 --- a/audio/core/all-versions/default/Device.cpp +++ b/audio/core/all-versions/default/Device.cpp @@ -171,7 +171,8 @@ std::tuple> Device::openOutputStreamImpl(int32_t ioHandle streamOut = new StreamOut(this, halStream); ++mOpenedStreamsCount; } - HidlUtils::audioConfigFromHal(halConfig, suggestedConfig); + status_t convertStatus = HidlUtils::audioConfigFromHal(halConfig, suggestedConfig); + ALOGW_IF(convertStatus != OK, "%s: suggested config with incompatible fields", __func__); return {analyzeStatus("open_output_stream", status, {EINVAL} /*ignore*/), streamOut}; } @@ -198,7 +199,8 @@ std::tuple> Device::openInputStreamImpl( streamIn = new StreamIn(this, halStream); ++mOpenedStreamsCount; } - HidlUtils::audioConfigFromHal(halConfig, suggestedConfig); + status_t convertStatus = HidlUtils::audioConfigFromHal(halConfig, suggestedConfig); + ALOGW_IF(convertStatus != OK, "%s: suggested config with incompatible fields", __func__); return {analyzeStatus("open_input_stream", status, {EINVAL} /*ignore*/), streamIn}; } @@ -269,12 +271,21 @@ Return Device::supportsAudioPatches() { Return Device::createAudioPatch(const hidl_vec& sources, const hidl_vec& sinks, createAudioPatch_cb _hidl_cb) { + auto [retval, patch] = createOrUpdateAudioPatch( + static_cast(AudioHandleConsts::AUDIO_PATCH_HANDLE_NONE), sources, + sinks); + _hidl_cb(retval, patch); + return Void(); +} + +std::tuple Device::createOrUpdateAudioPatch( + AudioPatchHandle patch, const hidl_vec& sources, + const hidl_vec& sinks) { Result retval(Result::NOT_SUPPORTED); - AudioPatchHandle patch = 0; if (version() >= AUDIO_DEVICE_API_VERSION_3_0) { std::unique_ptr halSources(HidlUtils::audioPortConfigsToHal(sources)); std::unique_ptr halSinks(HidlUtils::audioPortConfigsToHal(sinks)); - audio_patch_handle_t halPatch = AUDIO_PATCH_HANDLE_NONE; + audio_patch_handle_t halPatch = static_cast(patch); retval = analyzeStatus("create_audio_patch", mDevice->create_audio_patch(mDevice, sources.size(), &halSources[0], sinks.size(), &halSinks[0], &halPatch)); @@ -282,8 +293,7 @@ Return Device::createAudioPatch(const hidl_vec& sources, patch = static_cast(halPatch); } } - _hidl_cb(retval, patch); - return Void(); + return {retval, patch}; } Return Device::releaseAudioPatch(int32_t patch) { @@ -438,6 +448,19 @@ Return Device::removeDeviceEffect(AudioPortHandle device, uint64_t effec } } +Return Device::updateAudioPatch(int32_t previousPatch, + const hidl_vec& sources, + const hidl_vec& sinks, + createAudioPatch_cb _hidl_cb) { + if (previousPatch != static_cast(AudioHandleConsts::AUDIO_PATCH_HANDLE_NONE)) { + auto [retval, patch] = createOrUpdateAudioPatch(previousPatch, sources, sinks); + _hidl_cb(retval, patch); + } else { + _hidl_cb(Result::INVALID_ARGUMENTS, previousPatch); + } + return Void(); +} + #endif } // namespace implementation diff --git a/audio/core/all-versions/default/PrimaryDevice.cpp b/audio/core/all-versions/default/PrimaryDevice.cpp index 0f1aba0f2f..11c1c5a4df 100644 --- a/audio/core/all-versions/default/PrimaryDevice.cpp +++ b/audio/core/all-versions/default/PrimaryDevice.cpp @@ -176,6 +176,13 @@ Return PrimaryDevice::addDeviceEffect(AudioPortHandle device, uint64_t e Return PrimaryDevice::removeDeviceEffect(AudioPortHandle device, uint64_t effectId) { return mDevice->removeDeviceEffect(device, effectId); } + +Return PrimaryDevice::updateAudioPatch(int32_t previousPatch, + const hidl_vec& sources, + const hidl_vec& sinks, + updateAudioPatch_cb _hidl_cb) { + return mDevice->updateAudioPatch(previousPatch, sources, sinks, _hidl_cb); +} #endif // Methods from ::android::hardware::audio::CPP_VERSION::IPrimaryDevice follow. @@ -196,6 +203,9 @@ Return PrimaryDevice::setMode(AudioMode mode) { case AudioMode::RINGTONE: case AudioMode::IN_CALL: case AudioMode::IN_COMMUNICATION: +#if MAJOR_VERSION >= 6 + case AudioMode::CALL_SCREEN: +#endif break; // Valid values default: return Result::INVALID_ARGUMENTS; diff --git a/audio/core/all-versions/default/StreamOut.cpp b/audio/core/all-versions/default/StreamOut.cpp index 1a2a764297..1519c48e12 100644 --- a/audio/core/all-versions/default/StreamOut.cpp +++ b/audio/core/all-versions/default/StreamOut.cpp @@ -22,6 +22,8 @@ //#define LOG_NDEBUG 0 #define ATRACE_TAG ATRACE_TAG_AUDIO +#include + #include #include @@ -453,20 +455,22 @@ int StreamOut::asyncCallback(stream_callback_event_t event, void*, void* cookie) sp callback = self->mCallback; if (callback.get() == nullptr) return 0; ALOGV("asyncCallback() event %d", event); + Return result; switch (event) { case STREAM_CBK_EVENT_WRITE_READY: - callback->onWriteReady(); + result = callback->onWriteReady(); break; case STREAM_CBK_EVENT_DRAIN_READY: - callback->onDrainReady(); + result = callback->onDrainReady(); break; case STREAM_CBK_EVENT_ERROR: - callback->onError(); + result = callback->onError(); break; default: ALOGW("asyncCallback() unknown event %d", event); break; } + ALOGW_IF(!result.isOk(), "Client callback failed: %s", result.description().c_str()); return 0; } @@ -582,6 +586,67 @@ Return StreamOut::selectPresentation(int32_t /*presentationId*/, int32_t } #endif +#if MAJOR_VERSION >= 6 +Return StreamOut::getDualMonoMode(getDualMonoMode_cb _hidl_cb) { + _hidl_cb(Result::NOT_SUPPORTED, DualMonoMode::OFF); + return Void(); +} + +Return StreamOut::setDualMonoMode(DualMonoMode /*mode*/) { + return Result::NOT_SUPPORTED; +} + +Return StreamOut::getAudioDescriptionMixLevel(getAudioDescriptionMixLevel_cb _hidl_cb) { + _hidl_cb(Result::NOT_SUPPORTED, -std::numeric_limits::infinity()); + return Void(); +} + +Return StreamOut::setAudioDescriptionMixLevel(float /*leveldB*/) { + return Result::NOT_SUPPORTED; +} + +Return StreamOut::getPlaybackRateParameters(getPlaybackRateParameters_cb _hidl_cb) { + _hidl_cb(Result::NOT_SUPPORTED, + // Same as AUDIO_PLAYBACK_RATE_INITIALIZER + PlaybackRate{1.0f, 1.0f, TimestretchMode::DEFAULT, TimestretchFallbackMode::FAIL}); + return Void(); +} + +Return StreamOut::setPlaybackRateParameters(const PlaybackRate& /*playbackRate*/) { + return Result::NOT_SUPPORTED; +} + +Return StreamOut::setEventCallback(const sp& callback) { + if (mStream->set_event_callback == nullptr) return Result::NOT_SUPPORTED; + int result = mStream->set_event_callback(mStream, StreamOut::asyncEventCallback, this); + if (result == 0) { + mEventCallback = callback; + } + return Stream::analyzeStatus("set_stream_out_callback", result, {ENOSYS} /*ignore*/); +} + +// static +int StreamOut::asyncEventCallback(stream_event_callback_type_t event, void* param, void* cookie) { + StreamOut* self = reinterpret_cast(cookie); + sp eventCallback = self->mEventCallback; + if (eventCallback.get() == nullptr) return 0; + ALOGV("%s event %d", __func__, event); + Return result; + switch (event) { + case STREAM_EVENT_CBK_TYPE_CODEC_FORMAT_CHANGED: { + hidl_vec audioMetadata; + audioMetadata.setToExternal((uint8_t*)param, strlen((char*)param)); + result = eventCallback->onCodecFormatChanged(audioMetadata); + } break; + default: + ALOGW("%s unknown event %d", __func__, event); + break; + } + ALOGW_IF(!result.isOk(), "Client callback failed: %s", result.description().c_str()); + return 0; +} +#endif + } // namespace implementation } // namespace CPP_VERSION } // namespace audio diff --git a/audio/core/all-versions/default/include/core/default/Device.h b/audio/core/all-versions/default/include/core/default/Device.h index 80a9638004..b0e72d9600 100644 --- a/audio/core/all-versions/default/include/core/default/Device.h +++ b/audio/core/all-versions/default/include/core/default/Device.h @@ -118,6 +118,9 @@ struct Device : public IDevice, public ParametersUtil { Return close() override; Return addDeviceEffect(AudioPortHandle device, uint64_t effectId) override; Return removeDeviceEffect(AudioPortHandle device, uint64_t effectId) override; + Return updateAudioPatch(int32_t previousPatch, const hidl_vec& sources, + const hidl_vec& sinks, + createAudioPatch_cb _hidl_cb) override; #endif Return debug(const hidl_handle& fd, const hidl_vec& options) override; @@ -136,6 +139,9 @@ struct Device : public IDevice, public ParametersUtil { virtual ~Device(); Result doClose(); + std::tuple createOrUpdateAudioPatch( + AudioPatchHandle patch, const hidl_vec& sources, + const hidl_vec& sinks); // Methods from ParametersUtil. char* halGetParameters(const char* keys) override; diff --git a/audio/core/all-versions/default/include/core/default/PrimaryDevice.h b/audio/core/all-versions/default/include/core/default/PrimaryDevice.h index 9fc90c39fe..ccdb7b26c1 100644 --- a/audio/core/all-versions/default/include/core/default/PrimaryDevice.h +++ b/audio/core/all-versions/default/include/core/default/PrimaryDevice.h @@ -100,6 +100,9 @@ struct PrimaryDevice : public IPrimaryDevice { Return close() override; Return addDeviceEffect(AudioPortHandle device, uint64_t effectId) override; Return removeDeviceEffect(AudioPortHandle device, uint64_t effectId) override; + Return updateAudioPatch(int32_t previousPatch, const hidl_vec& sources, + const hidl_vec& sinks, + updateAudioPatch_cb _hidl_cb) override; #endif Return debug(const hidl_handle& fd, const hidl_vec& options) override; diff --git a/audio/core/all-versions/default/include/core/default/Stream.h b/audio/core/all-versions/default/include/core/default/Stream.h index 91df0c7703..ce0003bfbf 100644 --- a/audio/core/all-versions/default/include/core/default/Stream.h +++ b/audio/core/all-versions/default/include/core/default/Stream.h @@ -157,6 +157,10 @@ Return StreamMmap::createMmapBuffer(int32_t minSizeFrames, size_t frame native_handle_t* hidlHandle = nullptr; if (mStream->create_mmap_buffer != NULL) { + if (minSizeFrames <= 0) { + retval = Result::INVALID_ARGUMENTS; + goto exit; + } struct audio_mmap_buffer_info halInfo; retval = Stream::analyzeStatus( "create_mmap_buffer", mStream->create_mmap_buffer(mStream, minSizeFrames, &halInfo)); @@ -184,6 +188,7 @@ Return StreamMmap::createMmapBuffer(int32_t minSizeFrames, size_t frame info.burstSizeFrames = halInfo.burst_size_frames; } } +exit: _hidl_cb(retval, info); if (hidlHandle != nullptr) { native_handle_delete(hidlHandle); diff --git a/audio/core/all-versions/default/include/core/default/StreamOut.h b/audio/core/all-versions/default/include/core/default/StreamOut.h index 6334785a03..e647da9b96 100644 --- a/audio/core/all-versions/default/include/core/default/StreamOut.h +++ b/audio/core/all-versions/default/include/core/default/StreamOut.h @@ -121,16 +121,31 @@ struct StreamOut : public IStreamOut { Return updateSourceMetadata(const SourceMetadata& sourceMetadata) override; Return selectPresentation(int32_t presentationId, int32_t programId) override; #endif +#if MAJOR_VERSION >= 6 + Return getDualMonoMode(getDualMonoMode_cb _hidl_cb) override; + Return setDualMonoMode(DualMonoMode mode) override; + Return getAudioDescriptionMixLevel(getAudioDescriptionMixLevel_cb _hidl_cb) override; + Return setAudioDescriptionMixLevel(float leveldB) override; + Return getPlaybackRateParameters(getPlaybackRateParameters_cb _hidl_cb) override; + Return setPlaybackRateParameters(const PlaybackRate& playbackRate) override; +#endif static Result getPresentationPositionImpl(audio_stream_out_t* stream, uint64_t* frames, TimeSpec* timeStamp); - private: +#if MAJOR_VERSION >= 6 + Return setEventCallback(const sp& callback) override; +#endif + + private: const sp mDevice; audio_stream_out_t* mStream; const sp mStreamCommon; const sp> mStreamMmap; - sp mCallback; + sp mCallback; // Callback for non-blocking write and drain +#if MAJOR_VERSION >= 6 + sp mEventCallback; +#endif std::unique_ptr mCommandMQ; std::unique_ptr mDataMQ; std::unique_ptr mStatusMQ; @@ -141,6 +156,10 @@ struct StreamOut : public IStreamOut { virtual ~StreamOut(); static int asyncCallback(stream_callback_event_t event, void* param, void* cookie); + +#if MAJOR_VERSION >= 6 + static int asyncEventCallback(stream_event_callback_type_t event, void* param, void* cookie); +#endif }; } // namespace implementation 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 709b7cd369..b0eb2e0cfb 100644 --- a/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp +++ b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp @@ -244,7 +244,13 @@ TEST_P(OutputStreamTest, updateSourceMetadata) { TEST_P(AudioPrimaryHidlTest, setMode) { doc::test("Make sure setMode always succeeds if mode is valid and fails otherwise"); // Test Invalid values - for (int mode : {-2, -1, int(AudioMode::IN_COMMUNICATION) + 1}) { +#if MAJOR_VERSION >= 6 + int maxMode = int(AudioMode::CALL_SCREEN); +#else + int maxMode = int(AudioMode::IN_COMMUNICATION); +#endif + + for (int mode : {-2, -1, maxMode + 1}) { ASSERT_RESULT(Result::INVALID_ARGUMENTS, getDevice()->setMode(AudioMode(mode))) << "mode=" << mode; } @@ -253,6 +259,10 @@ TEST_P(AudioPrimaryHidlTest, setMode) { AudioMode::NORMAL /* Make sure to leave the test in normal mode */}) { ASSERT_OK(getDevice()->setMode(mode)) << "mode=" << toString(mode); } + // AudioMode::CALL_SCREEN as support is optional +#if MAJOR_VERSION >= 6 + ASSERT_RESULT(okOrNotSupportedOrInvalidArgs, getDevice()->setMode(AudioMode::CALL_SCREEN)); +#endif } TEST_P(AudioPrimaryHidlTest, setBtHfpSampleRate) { diff --git a/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp index 5b585532bd..e09eeab4eb 100644 --- a/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp +++ b/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp @@ -133,3 +133,91 @@ TEST_P(AudioHidlDeviceTest, CloseDeviceWithOpenedInputStreams) { ASSERT_OK(getDevice()->close()); ASSERT_TRUE(resetDevice()); } + +TEST_P(AudioPatchHidlTest, UpdatePatchInvalidHandle) { + doc::test("Verify that passing an invalid handle to updateAudioPatch is checked"); + AudioPatchHandle ignored; + ASSERT_OK(getDevice()->updateAudioPatch( + static_cast(AudioHandleConsts::AUDIO_PATCH_HANDLE_NONE), + hidl_vec(), hidl_vec(), returnIn(res, ignored))); + ASSERT_RESULT(Result::INVALID_ARGUMENTS, res); +} + +using DualMonoModeAccessorHidlTest = AccessorHidlTest; +TEST_P(DualMonoModeAccessorHidlTest, DualMonoModeTest) { + doc::test("Check that dual mono mode can be set and retrieved"); + testAccessors(&OutputStreamTest::getStream, "dual mono mode", + Initial{DualMonoMode::OFF}, + {DualMonoMode::LR, DualMonoMode::LL, DualMonoMode::RR}, + &IStreamOut::setDualMonoMode, &IStreamOut::getDualMonoMode); +} + +INSTANTIATE_TEST_CASE_P(DualMonoModeHidl, DualMonoModeAccessorHidlTest, + ::testing::ValuesIn(getOutputDeviceConfigParameters()), + &DeviceConfigParameterToString); + +using AudioDescriptionMixLevelHidlTest = AccessorHidlTest; +TEST_P(AudioDescriptionMixLevelHidlTest, AudioDescriptionMixLevelTest) { + doc::test("Check that audio description mix level can be set and retrieved"); + testAccessors( + &OutputStreamTest::getStream, "audio description mix level", + Initial{-std::numeric_limits::infinity()}, {-48.0f, -1.0f, 0.0f, 1.0f, 48.0f}, + &IStreamOut::setAudioDescriptionMixLevel, &IStreamOut::getAudioDescriptionMixLevel, + {48.5f, 1000.0f, std::numeric_limits::infinity()}); +} + +INSTANTIATE_TEST_CASE_P(AudioDescriptionMixLevelHidl, AudioDescriptionMixLevelHidlTest, + ::testing::ValuesIn(getOutputDeviceConfigParameters()), + &DeviceConfigParameterToString); + +using PlaybackRateParametersHidlTest = AccessorHidlTest; +TEST_P(PlaybackRateParametersHidlTest, PlaybackRateParametersTest) { + doc::test("Check that playback rate parameters can be set and retrieved"); + testAccessors( + &OutputStreamTest::getStream, "playback rate parameters", + Initial{PlaybackRate{1.0f, 1.0f, TimestretchMode::DEFAULT, + TimestretchFallbackMode::FAIL}}, + {// Speed and pitch values in the range from 0.5f to 2.0f must be supported + // (see the definition of IStreamOut::setPlaybackRateParameters). + PlaybackRate{1.0f, 1.0f, TimestretchMode::DEFAULT, TimestretchFallbackMode::MUTE}, + PlaybackRate{2.0f, 2.0f, TimestretchMode::DEFAULT, TimestretchFallbackMode::MUTE}, + PlaybackRate{0.5f, 0.5f, TimestretchMode::DEFAULT, TimestretchFallbackMode::MUTE}, + // Gross speed / pitch values must not be rejected if the fallback mode is "mute" + PlaybackRate{1000.0f, 1000.0f, TimestretchMode::DEFAULT, + TimestretchFallbackMode::MUTE}, + // Default speed / pitch values must not be rejected in "fail" fallback mode + PlaybackRate{1.0f, 1.0f, TimestretchMode::DEFAULT, TimestretchFallbackMode::FAIL}, + // Same for "voice" mode + PlaybackRate{1.0f, 1.0f, TimestretchMode::VOICE, TimestretchFallbackMode::MUTE}, + PlaybackRate{2.0f, 2.0f, TimestretchMode::VOICE, TimestretchFallbackMode::MUTE}, + PlaybackRate{0.5f, 0.5f, TimestretchMode::VOICE, TimestretchFallbackMode::MUTE}, + PlaybackRate{1000.0f, 1000.0f, TimestretchMode::VOICE, TimestretchFallbackMode::MUTE}, + PlaybackRate{1.0f, 1.0f, TimestretchMode::VOICE, TimestretchFallbackMode::FAIL}}, + &IStreamOut::setPlaybackRateParameters, &IStreamOut::getPlaybackRateParameters, + {PlaybackRate{1000.0f, 1000.0f, TimestretchMode::DEFAULT, + TimestretchFallbackMode::FAIL}, + PlaybackRate{1000.0f, 1000.0f, TimestretchMode::VOICE, + TimestretchFallbackMode::FAIL}}); +} + +INSTANTIATE_TEST_CASE_P(PlaybackRateParametersHidl, PlaybackRateParametersHidlTest, + ::testing::ValuesIn(getOutputDeviceConfigParameters()), + &DeviceConfigParameterToString); + +/** Stub implementation of IStreamOutEventCallback **/ +class MockOutEventCallbacks : public IStreamOutEventCallback { + Return onCodecFormatChanged(const hidl_vec& audioMetadata __unused) override { + return {}; + } +}; + +TEST_P(OutputStreamTest, SetEventCallback) { + doc::test("If supported, set event callback for output stream should never fail"); + auto res = stream->setEventCallback(new MockOutEventCallbacks); + EXPECT_RESULT(okOrNotSupported, res); + if (res == Result::OK) { + ASSERT_OK(stream->setEventCallback(nullptr)); + } else { + doc::partialTest("The stream does not support event callback"); + } +} diff --git a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h index ae8e20fb8f..01bdd69408 100644 --- a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h +++ b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h @@ -109,6 +109,11 @@ static auto invalidStateOrNotSupported = {Result::INVALID_STATE, Result::NOT_SUP class HidlTest : public ::testing::Test { public: virtual ~HidlTest() = default; + // public access to avoid annoyances when using this method in template classes + // derived from test classes + sp getDevice() const { + return DeviceManager::getInstance().get(getFactoryName(), getDeviceName()); + } protected: // Factory and device name getters to be overridden in subclasses. @@ -118,9 +123,6 @@ class HidlTest : public ::testing::Test { sp getDevicesFactory() const { return DevicesFactoryManager::getInstance().get(getFactoryName()); } - sp getDevice() const { - return DeviceManager::getInstance().get(getFactoryName(), getDeviceName()); - } bool resetDevice() const { return DeviceManager::getInstance().reset(getFactoryName(), getDeviceName()); } @@ -411,7 +413,8 @@ class AudioPrimaryHidlTest : public AudioHidlDeviceTest { ASSERT_TRUE(getDevice() != nullptr); } - protected: + // public access to avoid annoyances when using this method in template classes + // derived from test classes sp getDevice() const { return DeviceManager::getInstance().getPrimary(getFactoryName()); } @@ -445,15 +448,15 @@ class AccessorHidlTest : public BaseTestClass { /** Test a property getter and setter. * The getter and/or the setter may return NOT_SUPPORTED if optionality == OPTIONAL. */ - template - void testAccessors(const string& propertyName, const Initial expectedInitial, - list valuesToTest, Setter setter, Getter getter, - const vector& invalidValues = {}) { + template + void testAccessors(IUTGetter iutGetter, const string& propertyName, + const Initial expectedInitial, list valuesToTest, Setter setter, + Getter getter, const vector& invalidValues = {}) { const auto expectedResults = {Result::OK, optionality == OPTIONAL ? Result::NOT_SUPPORTED : Result::OK}; Property initialValue = expectedInitial.value; - ASSERT_OK((BaseTestClass::getDevice().get()->*getter)(returnIn(res, initialValue))); + ASSERT_OK(((this->*iutGetter)().get()->*getter)(returnIn(res, initialValue))); ASSERT_RESULT(expectedResults, res); if (res == Result::OK && expectedInitial.check == REQUIRED) { EXPECT_EQ(expectedInitial.value, initialValue); @@ -464,7 +467,7 @@ class AccessorHidlTest : public BaseTestClass { for (Property setValue : valuesToTest) { SCOPED_TRACE("Test " + propertyName + " getter and setter for " + testing::PrintToString(setValue)); - auto ret = (BaseTestClass::getDevice().get()->*setter)(setValue); + auto ret = ((this->*iutGetter)().get()->*setter)(setValue); ASSERT_RESULT(expectedResults, ret); if (ret == Result::NOT_SUPPORTED) { doc::partialTest(propertyName + " setter is not supported"); @@ -472,7 +475,7 @@ class AccessorHidlTest : public BaseTestClass { } Property getValue; // Make sure the getter returns the same value just set - ASSERT_OK((BaseTestClass::getDevice().get()->*getter)(returnIn(res, getValue))); + ASSERT_OK(((this->*iutGetter)().get()->*getter)(returnIn(res, getValue))); ASSERT_RESULT(expectedResults, res); if (res == Result::NOT_SUPPORTED) { doc::partialTest(propertyName + " getter is not supported"); @@ -485,11 +488,18 @@ class AccessorHidlTest : public BaseTestClass { SCOPED_TRACE("Try to set " + propertyName + " with the invalid value " + testing::PrintToString(invalidValue)); EXPECT_RESULT(invalidArgsOrNotSupported, - (BaseTestClass::getDevice().get()->*setter)(invalidValue)); + ((this->*iutGetter)().get()->*setter)(invalidValue)); } // Restore initial value - EXPECT_RESULT(expectedResults, (BaseTestClass::getDevice().get()->*setter)(initialValue)); + EXPECT_RESULT(expectedResults, ((this->*iutGetter)().get()->*setter)(initialValue)); + } + template + void testAccessors(const string& propertyName, const Initial expectedInitial, + list valuesToTest, Setter setter, Getter getter, + const vector& invalidValues = {}) { + testAccessors(&BaseTestClass::getDevice, propertyName, expectedInitial, + valuesToTest, setter, getter, invalidValues); } }; @@ -881,6 +891,11 @@ class StreamHelper { template class OpenStreamTest : public AudioHidlTestWithDeviceConfigParameter { + public: + // public access to avoid annoyances when using this method in template classes + // derived from test classes + sp getStream() const { return stream; } + protected: OpenStreamTest() : AudioHidlTestWithDeviceConfigParameter(), helper(stream) {} template diff --git a/audio/effect/all-versions/default/Effect.cpp b/audio/effect/all-versions/default/Effect.cpp index 33ec996d57..406a571bef 100644 --- a/audio/effect/all-versions/default/Effect.cpp +++ b/audio/effect/all-versions/default/Effect.cpp @@ -307,12 +307,11 @@ void Effect::getConfigImpl(int commandCode, const char* commandName, GetConfigCa Result Effect::getCurrentConfigImpl(uint32_t featureId, uint32_t configSize, GetCurrentConfigSuccessCallback onSuccess) { uint32_t halCmd = featureId; - uint32_t halResult[alignedSizeIn(sizeof(uint32_t) + configSize)]; - memset(halResult, 0, sizeof(halResult)); + std::vector halResult(alignedSizeIn(sizeof(uint32_t) + configSize), 0); uint32_t halResultSize = 0; - return sendCommandReturningStatusAndData(EFFECT_CMD_GET_FEATURE_CONFIG, "GET_FEATURE_CONFIG", - sizeof(uint32_t), &halCmd, &halResultSize, halResult, - sizeof(uint32_t), [&] { onSuccess(&halResult[1]); }); + return sendCommandReturningStatusAndData( + EFFECT_CMD_GET_FEATURE_CONFIG, "GET_FEATURE_CONFIG", sizeof(uint32_t), &halCmd, + &halResultSize, &halResult[0], sizeof(uint32_t), [&] { onSuccess(&halResult[1]); }); } Result Effect::getParameterImpl(uint32_t paramSize, const void* paramData, @@ -339,8 +338,7 @@ Result Effect::getSupportedConfigsImpl(uint32_t featureId, uint32_t maxConfigs, GetSupportedConfigsSuccessCallback onSuccess) { uint32_t halCmd[2] = {featureId, maxConfigs}; uint32_t halResultSize = 2 * sizeof(uint32_t) + maxConfigs * sizeof(configSize); - uint8_t halResult[halResultSize]; - memset(&halResult[0], 0, halResultSize); + std::vector halResult(static_cast(halResultSize), 0); return sendCommandReturningStatusAndData( EFFECT_CMD_GET_FEATURE_SUPPORTED_CONFIGS, "GET_FEATURE_SUPPORTED_CONFIGS", sizeof(halCmd), halCmd, &halResultSize, &halResult[0], 2 * sizeof(uint32_t), [&] { @@ -519,9 +517,9 @@ Return Effect::setAndGetVolume(const hidl_vec& volumes, uint32_t halDataSize; std::unique_ptr halData = hidlVecToHal(volumes, &halDataSize); uint32_t halResultSize = halDataSize; - uint32_t halResult[volumes.size()]; + std::vector halResult(volumes.size(), 0); Result retval = sendCommandReturningData(EFFECT_CMD_SET_VOLUME, "SET_VOLUME", halDataSize, - &halData[0], &halResultSize, halResult); + &halData[0], &halResultSize, &halResult[0]); hidl_vec result; if (retval == Result::OK) { result.setToExternal(&halResult[0], halResultSize); @@ -581,8 +579,6 @@ Return Effect::getSupportedAuxChannelsConfigs(uint32_t maxConfigs, } Return Effect::getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) { - uint32_t halResult[alignedSizeIn(sizeof(uint32_t) + sizeof(channel_config_t))]; - memset(halResult, 0, sizeof(halResult)); EffectAuxChannelsConfig result; Result retval = getCurrentConfigImpl( EFFECT_FEATURE_AUX_CHANNELS, sizeof(channel_config_t), [&](void* configData) { @@ -594,11 +590,12 @@ Return Effect::getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) { } Return Effect::setAuxChannelsConfig(const EffectAuxChannelsConfig& config) { - uint32_t halCmd[alignedSizeIn(sizeof(uint32_t) + sizeof(channel_config_t))]; + std::vector halCmd( + alignedSizeIn(sizeof(uint32_t) + sizeof(channel_config_t)), 0); halCmd[0] = EFFECT_FEATURE_AUX_CHANNELS; effectAuxChannelsConfigToHal(config, reinterpret_cast(&halCmd[1])); return sendCommandReturningStatus(EFFECT_CMD_SET_FEATURE_CONFIG, - "SET_FEATURE_CONFIG AUX_CHANNELS", sizeof(halCmd), halCmd); + "SET_FEATURE_CONFIG AUX_CHANNELS", halCmd.size(), &halCmd[0]); } Return Effect::setAudioSource(AudioSource source) { @@ -692,12 +689,11 @@ Return Effect::getCurrentConfigForFeature(uint32_t featureId, uint32_t con Return Effect::setCurrentConfigForFeature(uint32_t featureId, const hidl_vec& configData) { - uint32_t halCmd[alignedSizeIn(sizeof(uint32_t) + configData.size())]; - memset(halCmd, 0, sizeof(halCmd)); + std::vector halCmd(alignedSizeIn(sizeof(uint32_t) + configData.size()), 0); halCmd[0] = featureId; memcpy(&halCmd[1], &configData[0], configData.size()); return sendCommandReturningStatus(EFFECT_CMD_SET_FEATURE_CONFIG, "SET_FEATURE_CONFIG", - sizeof(halCmd), halCmd); + halCmd.size(), &halCmd[0]); } Return Effect::close() { diff --git a/audio/policy/1.0/xml/api/current.txt b/audio/policy/1.0/xml/api/current.txt index ccbc828c37..29a9cd402d 100644 --- a/audio/policy/1.0/xml/api/current.txt +++ b/audio/policy/1.0/xml/api/current.txt @@ -129,6 +129,7 @@ package audio.policy.V1_0 { enum_constant public static final audio.policy.V1_0.FlagType AUDIO_FLAG_BEACON; enum_constant public static final audio.policy.V1_0.FlagType AUDIO_FLAG_BYPASS_INTERRUPTION_POLICY; enum_constant public static final audio.policy.V1_0.FlagType AUDIO_FLAG_BYPASS_MUTE; + enum_constant public static final audio.policy.V1_0.FlagType AUDIO_FLAG_CAPTURE_PRIVATE; enum_constant public static final audio.policy.V1_0.FlagType AUDIO_FLAG_DEEP_BUFFER; enum_constant public static final audio.policy.V1_0.FlagType AUDIO_FLAG_HW_AV_SYNC; enum_constant public static final audio.policy.V1_0.FlagType AUDIO_FLAG_HW_HOTWORD; @@ -212,6 +213,7 @@ package audio.policy.V1_0 { enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE; enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_ASSISTANCE_SONIFICATION; enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_ASSISTANT; + enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_CALL_ASSISTANT; enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_GAME; enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_MEDIA; enum_constant public static final audio.policy.V1_0.UsageEnumType AUDIO_USAGE_NOTIFICATION; diff --git a/audio/policy/1.0/xml/audio_policy_engine_configuration.xsd b/audio/policy/1.0/xml/audio_policy_engine_configuration.xsd index a23d9a8049..842e7246d6 100644 --- a/audio/policy/1.0/xml/audio_policy_engine_configuration.xsd +++ b/audio/policy/1.0/xml/audio_policy_engine_configuration.xsd @@ -342,6 +342,7 @@ + @@ -370,6 +371,7 @@ + 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/audiocontrol/1.0/IAudioControl.hal b/automotive/audiocontrol/1.0/IAudioControl.hal index 3c8b086bc6..2e7ef75380 100644 --- a/automotive/audiocontrol/1.0/IAudioControl.hal +++ b/automotive/audiocontrol/1.0/IAudioControl.hal @@ -29,6 +29,11 @@ interface IAudioControl { * * For every context, a valid bus number (0 - num busses-1) must be returned. If an * unrecognized contextNumber is encountered, then -1 shall be returned. + * + * Deprecated: usage of this API and car_volume_groups.xml has been replaced with + * car_audio_configuration.xml. If using car_audio_configuration.xml, then the framework + * will not call this method. If it doesn't, then it will load car_volume_groups.xml and + * call this method. */ getBusForContext(ContextNumber contextNumber) generates (int32_t busNumber); diff --git a/automotive/audiocontrol/1.0/default/Android.bp b/automotive/audiocontrol/1.0/default/Android.bp index 314830b92a..ae4b8057ae 100644 --- a/automotive/audiocontrol/1.0/default/Android.bp +++ b/automotive/audiocontrol/1.0/default/Android.bp @@ -29,10 +29,5 @@ cc_binary { "liblog", "libutils", ], - - cflags: [ - "-DLOG_TAG=\"AudCntrlDrv\"", - "-O0", - "-g", - ], + vintf_fragments: ["audiocontrol_manifest.xml"], } diff --git a/automotive/audiocontrol/1.0/default/audiocontrol_manifest.xml b/automotive/audiocontrol/1.0/default/audiocontrol_manifest.xml new file mode 100644 index 0000000000..0981eb71ad --- /dev/null +++ b/automotive/audiocontrol/1.0/default/audiocontrol_manifest.xml @@ -0,0 +1,11 @@ + + + android.hardware.automotive.audiocontrol + hwbinder + 1.0 + + IAudioControl + default + + + \ No newline at end of file diff --git a/automotive/audiocontrol/1.0/vts/functional/Android.bp b/automotive/audiocontrol/1.0/vts/functional/Android.bp index 3cb6340ee8..1bb8e88fbc 100644 --- a/automotive/audiocontrol/1.0/vts/functional/Android.bp +++ b/automotive/audiocontrol/1.0/vts/functional/Android.bp @@ -25,5 +25,8 @@ cc_test { static_libs: [ "android.hardware.automotive.audiocontrol@1.0", ], - test_suites: ["general-tests"], + test_suites: [ + "general-tests", + "vts", + ], } diff --git a/automotive/audiocontrol/1.0/vts/functional/VtsHalAudioControlV1_0TargetTest.cpp b/automotive/audiocontrol/1.0/vts/functional/VtsHalAudioControlV1_0TargetTest.cpp index fc0deb94c3..de1ec02072 100644 --- a/automotive/audiocontrol/1.0/vts/functional/VtsHalAudioControlV1_0TargetTest.cpp +++ b/automotive/audiocontrol/1.0/vts/functional/VtsHalAudioControlV1_0TargetTest.cpp @@ -25,11 +25,12 @@ #include #include -#include #include +#include #include - -#include +#include +#include +#include using namespace ::android::hardware::automotive::audiocontrol::V1_0; using ::android::hardware::Return; @@ -40,30 +41,12 @@ using ::android::hardware::hidl_string; using ::android::hardware::hidl_vec; using ::android::sp; - -// Boiler plate for test harness -class CarAudioControlHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static CarAudioControlHidlEnvironment* Instance() { - static CarAudioControlHidlEnvironment* instance = new CarAudioControlHidlEnvironment; - return instance; - } - - virtual void registerTestServices() override { registerTestService(); } - private: - CarAudioControlHidlEnvironment() {} -}; - - // The main test class for the automotive AudioControl HAL -class CarAudioControlHidlTest : public ::testing::VtsHalHidlTargetTestBase { -public: +class CarAudioControlHidlTest : public ::testing::TestWithParam { + public: virtual void SetUp() override { // Make sure we can connect to the driver - pAudioControl = ::testing::VtsHalHidlTargetTestBase::getService( - CarAudioControlHidlEnvironment::Instance()-> - getServiceName()); + pAudioControl = IAudioControl::getService(GetParam()); ASSERT_NE(pAudioControl.get(), nullptr); } @@ -82,7 +65,7 @@ public: * fader actually works. The only thing we can do is exercise the HAL and if the HAL crashes, * we _might_ get a test failure if that breaks the connection to the driver. */ -TEST_F(CarAudioControlHidlTest, FaderExercise) { +TEST_P(CarAudioControlHidlTest, FaderExercise) { ALOGI("Fader exercise test (silent)"); // Set the fader all the way to the back @@ -104,7 +87,7 @@ TEST_F(CarAudioControlHidlTest, FaderExercise) { /* * Balance exercise test. */ -TEST_F(CarAudioControlHidlTest, BalanceExercise) { +TEST_P(CarAudioControlHidlTest, BalanceExercise) { ALOGI("Balance exercise test (silent)"); // Set the balance all the way to the left @@ -126,7 +109,7 @@ TEST_F(CarAudioControlHidlTest, BalanceExercise) { /* * Context mapping test. */ -TEST_F(CarAudioControlHidlTest, ContextMapping) { +TEST_P(CarAudioControlHidlTest, ContextMapping) { ALOGI("Context mapping test"); int bus = -1; @@ -156,3 +139,8 @@ TEST_F(CarAudioControlHidlTest, ContextMapping) { bus = pAudioControl->getBusForContext((ContextNumber)~0); EXPECT_EQ(bus, -1); } + +INSTANTIATE_TEST_SUITE_P( + PerInstance, CarAudioControlHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IAudioControl::descriptor)), + android::hardware::PrintInstanceNameToString); \ No newline at end of file diff --git a/automotive/audiocontrol/2.0/Android.bp b/automotive/audiocontrol/2.0/Android.bp new file mode 100644 index 0000000000..e9ce638bae --- /dev/null +++ b/automotive/audiocontrol/2.0/Android.bp @@ -0,0 +1,18 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.automotive.audiocontrol@2.0", + root: "android.hardware", + srcs: [ + "types.hal", + "IAudioControl.hal", + "ICloseHandle.hal", + "IFocusListener.hal", + ], + interfaces: [ + "android.hardware.audio.common@6.0", + "android.hidl.base@1.0", + "android.hidl.safe_union@1.0", + ], + gen_java: true, +} diff --git a/automotive/audiocontrol/2.0/IAudioControl.hal b/automotive/audiocontrol/2.0/IAudioControl.hal new file mode 100644 index 0000000000..1073498f61 --- /dev/null +++ b/automotive/audiocontrol/2.0/IAudioControl.hal @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 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.audiocontrol@2.0; + +import ICloseHandle; +import IFocusListener; +import android.hardware.audio.common@6.0::AudioUsage; + +/** + * Interacts with the car's audio subsystem to manage audio sources and volumes + */ +interface IAudioControl { + /** + * Registers focus listener to be used by HAL for requesting and abandoning audio focus. + * + * It is expected that there will only ever be a single focus listener registered. If the + * observer dies, the HAL implementation must unregister observer automatically. If called when + * a listener is already registered, the existing one should be unregistered and replaced with + * the new listener. + * + * @param listener the listener interface + * @return closeHandle A handle to unregister observer. + */ + registerFocusListener(IFocusListener listener) generates (ICloseHandle closeHandle); + + /** + * Notifies HAL of changes in audio focus status for focuses requested or abandoned by the HAL. + * + * This will be called in response to IFocusListener's requestAudioFocus and + * abandonAudioFocus, as well as part of any change in focus being held by the HAL due focus + * request from other activities or services. + * + * The HAL is not required to wait for an callback of AUDIOFOCUS_GAIN before playing audio, nor + * is it required to stop playing audio in the event of a AUDIOFOCUS_LOSS callback is received. + * + * @param usage The audio usage associated with the focus change {@code AttributeUsage} + * @param zoneId The identifier for the audio zone that the HAL is playing the stream in + * @param focusChange the AudioFocusChange that has occurred + */ + oneway onAudioFocusChange(bitfield usage, int32_t zoneId, + bitfield focusChange); + + /** + * Control the right/left balance setting of the car speakers. + * + * This is intended to shift the speaker volume toward the right (+) or left (-) side of + * the car. 0.0 means "centered". +1.0 means fully right. -1.0 means fully left. + * + * A value outside the range -1 to 1 must be clamped by the implementation to the -1 to 1 + * range. + */ + oneway setBalanceTowardRight(float value); + + /** + * Control the fore/aft fade setting of the car speakers. + * + * This is intended to shift the speaker volume toward the front (+) or back (-) of the car. + * 0.0 means "centered". +1.0 means fully forward. -1.0 means fully rearward. + * + * A value outside the range -1 to 1 must be clamped by the implementation to the -1 to 1 + * range. + */ + oneway setFadeTowardFront(float value); +}; diff --git a/automotive/audiocontrol/2.0/ICloseHandle.hal b/automotive/audiocontrol/2.0/ICloseHandle.hal new file mode 100644 index 0000000000..537af6d635 --- /dev/null +++ b/automotive/audiocontrol/2.0/ICloseHandle.hal @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 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.audiocontrol@2.0; + +/** + * Represents a generic close handle to remove a callback that doesn't need + * active interface. + * + * When close() is called OR when the interface is released, the underlying + * resources must be freed. + */ +interface ICloseHandle { + /** + * Closes the handle. + * + * The call must not fail and must be issued by the client at most once. + * Otherwise, the server must ignore subsequent calls. + */ + close(); +}; diff --git a/automotive/audiocontrol/2.0/IFocusListener.hal b/automotive/audiocontrol/2.0/IFocusListener.hal new file mode 100644 index 0000000000..4fd5ef0425 --- /dev/null +++ b/automotive/audiocontrol/2.0/IFocusListener.hal @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 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.audiocontrol@2.0; + +import android.hardware.audio.common@6.0::AudioUsage; + +/** + * Callback interface for audio focus listener. + * + * For typical configuration, the listener the car audio service. + */ +interface IFocusListener { + /** + * Called whenever HAL is requesting focus as it is starting to play audio of a given usage in a + * specified zone. + * + * In response, IAudioControl#onAudioFocusChange will be called with focusChange status. This + * interaction is oneway to avoid blocking HAL so that it is not required to wait for a response + * before playing audio. + * + * @param usage The audio usage associated with the focus request {@code AttributeUsage} + * @param zoneId The identifier for the audio zone where the HAL is requesting focus + * @param focusGain The AudioFocusChange associated with this request. Should be one of the + * following: GAIN, GAIN_TRANSIENT, GAIN_TRANSIENT_MAY_DUCK, GAIN_TRANSIENT_EXCLUSIVE. + */ + oneway requestAudioFocus(bitfield usage, int32_t zoneId, + bitfield focusGain); + + /** + * Called whenever HAL is abandoning focus as it is finished playing audio of a given usage in a + * specific zone. + * + * In response, IAudioControl#onAudioFocusChange will be called with focusChange status. This + * interaction is oneway to avoid blocking HAL so that it is not required to wait for a response + * before stopping audio playback. + * + * @param usage The audio usage for which the HAL is abandoning focus {@code AttributeUsage} + * @param zoneId The identifier for the audio zone that the HAL abandoning focus + */ + oneway abandonAudioFocus(bitfield usage, int32_t zoneId); +}; diff --git a/automotive/audiocontrol/2.0/default/Android.bp b/automotive/audiocontrol/2.0/default/Android.bp new file mode 100644 index 0000000000..44ad0281fe --- /dev/null +++ b/automotive/audiocontrol/2.0/default/Android.bp @@ -0,0 +1,39 @@ +// Copyright (C) 2020 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_binary { + name: "android.hardware.automotive.audiocontrol@2.0-service", + defaults: ["hidl_defaults"], + vendor: true, + relative_install_path: "hw", + srcs: [ + "AudioControl.cpp", + "service.cpp", + "CloseHandle.cpp", + ], + init_rc: ["android.hardware.automotive.audiocontrol@2.0-service.rc"], + + shared_libs: [ + "android.hardware.automotive.audiocontrol@2.0", + "libbase", + "libhidlbase", + "liblog", + "libutils", + ], + vintf_fragments: ["audiocontrol2_manifest.xml"], + cflags: [ + "-O0", + "-g", + ], +} diff --git a/automotive/audiocontrol/2.0/default/AudioControl.cpp b/automotive/audiocontrol/2.0/default/AudioControl.cpp new file mode 100644 index 0000000000..b7c11cdf07 --- /dev/null +++ b/automotive/audiocontrol/2.0/default/AudioControl.cpp @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2020 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 "AudioControl.h" + +#include + +#include +#include +#include + +#include +#include +#include + +#include "CloseHandle.h" + +namespace android::hardware::automotive::audiocontrol::V2_0::implementation { + +using ::android::base::EqualsIgnoreCase; +using ::android::hardware::hidl_handle; +using ::android::hardware::hidl_string; + +static const float kLowerBound = -1.0f; +static const float kUpperBound = 1.0f; + +AudioControl::AudioControl() {} + +Return> AudioControl::registerFocusListener(const sp& listener) { + LOG(DEBUG) << "registering focus listener"; + sp closeHandle(nullptr); + + if (listener) { + mFocusListener = listener; + + closeHandle = new CloseHandle([this, listener]() { + if (mFocusListener == listener) { + mFocusListener = nullptr; + } + }); + } else { + LOG(ERROR) << "Unexpected nullptr for listener resulting in no-op."; + } + + return closeHandle; +} + +Return AudioControl::setBalanceTowardRight(float value) { + if (isValidValue(value)) { + // Just log in this default mock implementation + LOG(INFO) << "Balance set to " << value; + } else { + LOG(ERROR) << "Balance value out of range -1 to 1 at " << value; + } + return Void(); +} + +Return AudioControl::setFadeTowardFront(float value) { + if (isValidValue(value)) { + // Just log in this default mock implementation + LOG(INFO) << "Fader set to " << value; + } else { + LOG(ERROR) << "Fader value out of range -1 to 1 at " << value; + } + return Void(); +} + +bool AudioControl::isValidValue(float value) { + return (value > kLowerBound) && (value < kUpperBound); +} + +Return AudioControl::onAudioFocusChange(hidl_bitfield usage, int zoneId, + hidl_bitfield focusChange) { + LOG(INFO) << "Focus changed: " << static_cast(focusChange) << " for usage " + << static_cast(usage) << " in zone " << zoneId; + return Void(); +} + +Return AudioControl::debug(const hidl_handle& fd, const hidl_vec& options) { + if (fd.getNativeHandle() == nullptr || fd->numFds == 0) { + LOG(ERROR) << "Invalid parameters passed to debug()"; + return Void(); + } + + cmdDump(fd->data[0], options); + return Void(); +} + +void AudioControl::cmdDump(int fd, const hidl_vec& options) { + if (options.size() == 0) { + dump(fd); + return; + } + + std::string option = options[0]; + if (EqualsIgnoreCase(option, "--help")) { + cmdHelp(fd); + } else if (EqualsIgnoreCase(option, "--request")) { + cmdRequestFocus(fd, options); + } else if (EqualsIgnoreCase(option, "--abandon")) { + cmdAbandonFocus(fd, options); + } else { + dprintf(fd, "Invalid option: %s\n", option.c_str()); + } +} + +void AudioControl::dump(int fd) { + if (mFocusListener == nullptr) { + dprintf(fd, "No focus listener registered\n"); + } else { + dprintf(fd, "Focus listener registered\n"); + } +} + +void AudioControl::cmdHelp(int fd) const { + dprintf(fd, "Usage: \n\n"); + dprintf(fd, "[no args]: dumps focus listener status\n"); + dprintf(fd, "--help: shows this help\n"); + dprintf(fd, + "--request : requests audio focus for specified " + "usage (int), audio zone ID (int), and focus gain type (int)\n"); + dprintf(fd, + "--abandon : abandons audio focus for specified usage (int) and " + "audio zone ID (int)\n"); +} + +void AudioControl::cmdRequestFocus(int fd, const hidl_vec& options) { + if (!checkCallerHasWritePermissions(fd) || !checkArgumentsSize(fd, options, 3)) return; + + hidl_bitfield usage; + if (!safelyParseInt(options[1], &usage)) { + dprintf(fd, "Non-integer usage provided with request: %s\n", options[1].c_str()); + return; + } + int zoneId; + if (!safelyParseInt(options[2], &zoneId)) { + dprintf(fd, "Non-integer zoneId provided with request: %s\n", options[2].c_str()); + return; + } + hidl_bitfield focusGain; + if (!safelyParseInt(options[3], &focusGain)) { + dprintf(fd, "Non-integer focusGain provided with request: %s\n", options[3].c_str()); + return; + } + + if (mFocusListener == nullptr) { + dprintf(fd, "Unable to request focus - no focus listener registered\n"); + return; + } + + mFocusListener->requestAudioFocus(usage, zoneId, focusGain); + dprintf(fd, "Requested focus for usage %d, zoneId %d, and focusGain %d\n", usage, zoneId, + focusGain); +} + +void AudioControl::cmdAbandonFocus(int fd, const hidl_vec& options) { + if (!checkCallerHasWritePermissions(fd) || !checkArgumentsSize(fd, options, 2)) return; + + hidl_bitfield usage; + if (!safelyParseInt(options[1], &usage)) { + dprintf(fd, "Non-integer usage provided with abandon: %s\n", options[1].c_str()); + return; + } + int zoneId; + if (!safelyParseInt(options[2], &zoneId)) { + dprintf(fd, "Non-integer zoneId provided with abandon: %s\n", options[2].c_str()); + return; + } + + if (mFocusListener == nullptr) { + dprintf(fd, "Unable to abandon focus - no focus listener registered\n"); + return; + } + + mFocusListener->abandonAudioFocus(usage, zoneId); + dprintf(fd, "Abandoned focus for usage %d and zoneId %d\n", usage, zoneId); +} + +bool AudioControl::checkCallerHasWritePermissions(int fd) { + // Double check that's only called by root - it should be be blocked at the HIDL debug() level, + // but it doesn't hurt to make sure... + if (hardware::IPCThreadState::self()->getCallingUid() != AID_ROOT) { + dprintf(fd, "Must be root\n"); + return false; + } + return true; +} + +bool AudioControl::checkArgumentsSize(int fd, const hidl_vec& options, + size_t expectedSize) { + // options includes the command, so reducing size by one + size_t size = options.size() - 1; + if (size == expectedSize) { + return true; + } + dprintf(fd, "Invalid number of arguments: required %zu, got %zu\n", expectedSize, size); + return false; +} + +bool AudioControl::safelyParseInt(std::string s, int* out) { + if (!android::base::ParseInt(s, out)) { + return false; + } + return true; +} + +} // namespace android::hardware::automotive::audiocontrol::V2_0::implementation diff --git a/automotive/audiocontrol/2.0/default/AudioControl.h b/automotive/audiocontrol/2.0/default/AudioControl.h new file mode 100644 index 0000000000..d66458ec05 --- /dev/null +++ b/automotive/audiocontrol/2.0/default/AudioControl.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 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_AUTOMOTIVE_AUDIOCONTROL_V2_0_AUDIOCONTROL_H +#define ANDROID_HARDWARE_AUTOMOTIVE_AUDIOCONTROL_V2_0_AUDIOCONTROL_H + +#include +#include +#include +#include +#include + +using android::hardware::audio::common::V6_0::AudioUsage; + +namespace android::hardware::automotive::audiocontrol::V2_0::implementation { + +class AudioControl : public IAudioControl { + public: + // Methods from ::android::hardware::automotive::audiocontrol::V2_0::IAudioControl follow. + Return> registerFocusListener(const sp& listener); + Return onAudioFocusChange(hidl_bitfield usage, int zoneId, + hidl_bitfield focusChange); + Return setBalanceTowardRight(float value) override; + Return setFadeTowardFront(float value) override; + Return debug(const hidl_handle& fd, const hidl_vec& options) override; + + // Implementation details + AudioControl(); + + private: + sp mFocusListener; + + static bool checkArgumentsSize(int fd, const hidl_vec& options, size_t minSize); + static bool checkCallerHasWritePermissions(int fd); + static bool isValidValue(float value); + static bool safelyParseInt(std::string s, int* out); + + void cmdDump(int fd, const hidl_vec& options); + void cmdHelp(int fd) const; + void cmdRequestFocus(int fd, const hidl_vec& options); + void cmdAbandonFocus(int fd, const hidl_vec& options); + void dump(int fd); +}; + +} // namespace android::hardware::automotive::audiocontrol::V2_0::implementation + +#endif // ANDROID_HARDWARE_AUTOMOTIVE_AUDIOCONTROL_V2_0_AUDIOCONTROL_H diff --git a/automotive/audiocontrol/2.0/default/CloseHandle.cpp b/automotive/audiocontrol/2.0/default/CloseHandle.cpp new file mode 100644 index 0000000000..bc47931535 --- /dev/null +++ b/automotive/audiocontrol/2.0/default/CloseHandle.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 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 "CloseHandle.h" + +namespace android::hardware::automotive::audiocontrol::V2_0::implementation { + +CloseHandle::CloseHandle(Callback callback) : mCallback(callback) {} + +CloseHandle::~CloseHandle() { + close(); +} + +Return CloseHandle::close() { + const auto wasClosed = mIsClosed.exchange(true); + if (wasClosed) return {}; + + if (mCallback) mCallback(); + return {}; +} + +} // namespace android::hardware::automotive::audiocontrol::V2_0::implementation diff --git a/automotive/audiocontrol/2.0/default/CloseHandle.h b/automotive/audiocontrol/2.0/default/CloseHandle.h new file mode 100644 index 0000000000..6caf0bfa7e --- /dev/null +++ b/automotive/audiocontrol/2.0/default/CloseHandle.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 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 +#include + +namespace android::hardware::automotive::audiocontrol::V2_0::implementation { + +/** Generic ICloseHandle implementation ignoring double-close events. */ +class CloseHandle : public ICloseHandle { + public: + using Callback = std::function; + + /** + * Create a handle with a callback. + * + * The callback is guaranteed to be called exactly once. + * + * \param callback Called on the first close() call, or on destruction of the handle + */ + CloseHandle(Callback callback = nullptr); + virtual ~CloseHandle(); + + Return close() override; + + private: + const Callback mCallback; + std::atomic mIsClosed = false; + + DISALLOW_COPY_AND_ASSIGN(CloseHandle); +}; + +} // namespace android::hardware::automotive::audiocontrol::V2_0::implementation diff --git a/automotive/audiocontrol/2.0/default/android.hardware.automotive.audiocontrol@2.0-service.rc b/automotive/audiocontrol/2.0/default/android.hardware.automotive.audiocontrol@2.0-service.rc new file mode 100644 index 0000000000..81c9be4460 --- /dev/null +++ b/automotive/audiocontrol/2.0/default/android.hardware.automotive.audiocontrol@2.0-service.rc @@ -0,0 +1,4 @@ +service vendor.audiocontrol-hal-2.0 /vendor/bin/hw/android.hardware.automotive.audiocontrol@2.0-service + class hal + user audioserver + group system diff --git a/automotive/audiocontrol/2.0/default/audiocontrol2_manifest.xml b/automotive/audiocontrol/2.0/default/audiocontrol2_manifest.xml new file mode 100644 index 0000000000..42d23ed62e --- /dev/null +++ b/automotive/audiocontrol/2.0/default/audiocontrol2_manifest.xml @@ -0,0 +1,11 @@ + + + android.hardware.automotive.audiocontrol + hwbinder + 2.0 + + IAudioControl + default + + + \ No newline at end of file diff --git a/automotive/audiocontrol/2.0/default/service.cpp b/automotive/audiocontrol/2.0/default/service.cpp new file mode 100644 index 0000000000..dcc46c31aa --- /dev/null +++ b/automotive/audiocontrol/2.0/default/service.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 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 + +#include +#include +#include +#include + +#include "AudioControl.h" + +// libhidl: +using android::hardware::configureRpcThreadpool; +using android::hardware::joinRpcThreadpool; + +// Generated HIDL files +using android::hardware::automotive::audiocontrol::V2_0::IAudioControl; + +// The namespace in which all our implementation code lives +using namespace android::hardware::automotive::audiocontrol::V2_0::implementation; +using namespace android; + +// Main service entry point +int main() { + // Create an instance of our service class + android::sp service = new AudioControl(); + configureRpcThreadpool(1, true /*callerWillJoin*/); + + if (service->registerAsService() != OK) { + LOG(ERROR) << "registerAsService failed"; + return 1; + } + + // Join (forever) the thread pool we created for the service above + joinRpcThreadpool(); + + // We don't ever actually expect to return, so return an error if we do get here + return 2; +} \ No newline at end of file diff --git a/automotive/audiocontrol/2.0/types.hal b/automotive/audiocontrol/2.0/types.hal new file mode 100644 index 0000000000..80d9ee1867 --- /dev/null +++ b/automotive/audiocontrol/2.0/types.hal @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 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.audiocontrol@2.0; + +/** + * Changes in audio focus that can be experienced + */ +enum AudioFocusChange : int32_t { + NONE = 0, + GAIN = 1, + GAIN_TRANSIENT = 2, + GAIN_TRANSIENT_MAY_DUCK = 3, + GAIN_TRANSIENT_EXCLUSIVE = 4, + LOSS = -1 * GAIN, + LOSS_TRANSIENT = -1 * GAIN_TRANSIENT, + LOSS_TRANSIENT_CAN_DUCK = -1 * GAIN_TRANSIENT_MAY_DUCK, +}; diff --git a/automotive/audiocontrol/2.0/vts/functional/Android.bp b/automotive/audiocontrol/2.0/vts/functional/Android.bp new file mode 100644 index 0000000000..520b042e37 --- /dev/null +++ b/automotive/audiocontrol/2.0/vts/functional/Android.bp @@ -0,0 +1,29 @@ +// +// Copyright (C) 2020 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: "VtsHalAudioControlV2_0TargetTest", + defaults: ["VtsHalTargetTestDefaults"], + srcs: ["VtsHalAudioControlV2_0TargetTest.cpp"], + static_libs: [ + "android.hardware.automotive.audiocontrol@2.0", + "libgmock", + ], + test_suites: [ + "general-tests", + "vts-core", + ], +} diff --git a/automotive/audiocontrol/2.0/vts/functional/VtsHalAudioControlV2_0TargetTest.cpp b/automotive/audiocontrol/2.0/vts/functional/VtsHalAudioControlV2_0TargetTest.cpp new file mode 100644 index 0000000000..0c106647c5 --- /dev/null +++ b/automotive/audiocontrol/2.0/vts/functional/VtsHalAudioControlV2_0TargetTest.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2020 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 "VtsHalAudioControlTest" + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace ::android::hardware::automotive::audiocontrol::V2_0; +using ::android::sp; +using ::android::hardware::hidl_bitfield; +using ::android::hardware::hidl_enum_range; +using ::android::hardware::hidl_handle; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::audio::common::V6_0::AudioUsage; + +// The main test class for the automotive AudioControl HAL +class CarAudioControlHidlTest : public testing::TestWithParam { + public: + virtual void SetUp() override { + // Make sure we can connect to the driver + pAudioControl = IAudioControl::getService(GetParam()); + ASSERT_NE(pAudioControl.get(), nullptr); + } + + virtual void TearDown() override {} + + protected: + sp pAudioControl; // Every test needs access to the service +}; + +// +// Tests start here... +// + +/* + * Fader exercise test. Note that only a subjective observer could determine if the + * fader actually works. The only thing we can do is exercise the HAL and if the HAL crashes, + * we _might_ get a test failure if that breaks the connection to the driver. + */ +TEST_P(CarAudioControlHidlTest, FaderExercise) { + ALOGI("Fader exercise test (silent)"); + + // Set the fader all the way to the back + pAudioControl->setFadeTowardFront(-1.0f); + + // Set the fader all the way to the front + pAudioControl->setFadeTowardFront(1.0f); + + // Set the fader part way toward the back + pAudioControl->setFadeTowardFront(-0.333f); + + // Set the fader to a out of bounds value (driver should clamp) + pAudioControl->setFadeTowardFront(99999.9f); + + // Set the fader back to the middle + pAudioControl->setFadeTowardFront(0.0f); +} + +/* + * Balance exercise test. + */ +TEST_P(CarAudioControlHidlTest, BalanceExercise) { + ALOGI("Balance exercise test (silent)"); + + // Set the balance all the way to the left + pAudioControl->setBalanceTowardRight(-1.0f); + + // Set the balance all the way to the right + pAudioControl->setBalanceTowardRight(1.0f); + + // Set the balance part way toward the left + pAudioControl->setBalanceTowardRight(-0.333f); + + // Set the balance to a out of bounds value (driver should clamp) + pAudioControl->setBalanceTowardRight(99999.9f); + + // Set the balance back to the middle + pAudioControl->setBalanceTowardRight(0.0f); +} + +struct FocusListenerMock : public IFocusListener { + MOCK_METHOD(Return, requestAudioFocus, + (hidl_bitfield usage, int zoneId, + hidl_bitfield focusGain)); + MOCK_METHOD(Return, abandonAudioFocus, (hidl_bitfield usage, int zoneId)); +}; + +/* + * Test focus listener registration. + * + * Verifies that: + * - registerFocusListener succeeds; + * - registering a second listener succeeds in replacing the first; + * - closing handle does not crash; + */ +TEST_P(CarAudioControlHidlTest, FocusListenerRegistration) { + ALOGI("Focus listener test"); + + sp listener = new FocusListenerMock(); + + auto hidlResult = pAudioControl->registerFocusListener(listener); + ASSERT_TRUE(hidlResult.isOk()); + + sp listener2 = new FocusListenerMock(); + + auto hidlResult2 = pAudioControl->registerFocusListener(listener2); + ASSERT_TRUE(hidlResult2.isOk()); + + const sp& closeHandle = hidlResult2; + closeHandle->close(); +}; + +TEST_P(CarAudioControlHidlTest, FocusChangeExercise) { + ALOGI("Focus Change test"); + + pAudioControl->onAudioFocusChange(AudioUsage::MEDIA | 0, 0, + AudioFocusChange::GAIN_TRANSIENT | 0); +}; + +INSTANTIATE_TEST_SUITE_P( + PerInstance, CarAudioControlHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IAudioControl::descriptor)), + android::hardware::PrintInstanceNameToString); \ No newline at end of file diff --git a/automotive/can/1.0/Android.bp b/automotive/can/1.0/Android.bp new file mode 100644 index 0000000000..2ddfaf93b8 --- /dev/null +++ b/automotive/can/1.0/Android.bp @@ -0,0 +1,18 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.automotive.can@1.0", + root: "android.hardware", + srcs: [ + "types.hal", + "ICanBus.hal", + "ICanController.hal", + "ICanErrorListener.hal", + "ICanMessageListener.hal", + "ICloseHandle.hal", + ], + interfaces: [ + "android.hidl.base@1.0", + ], + gen_java: true, +} diff --git a/automotive/can/1.0/ICanBus.hal b/automotive/can/1.0/ICanBus.hal new file mode 100644 index 0000000000..e68f16c6fc --- /dev/null +++ b/automotive/can/1.0/ICanBus.hal @@ -0,0 +1,72 @@ +/* + * 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.can@1.0; + +import ICanErrorListener; +import ICanMessageListener; +import ICloseHandle; + +/** + * Represents a CAN bus interface that's up and configured. + * + * Configuration part is done in ICanController. + */ +interface ICanBus { + /** + * Send CAN message. + * + * @param message CAN message to send out + * @return result OK in the case of success + * PAYLOAD_TOO_LONG if the payload is too long + * INTERFACE_DOWN if the bus is down + * TRANSMISSION_FAILURE in case of transmission failure + */ + send(CanMessage message) generates (Result result); + + /** + * Requests HAL implementation to listen for specific CAN messages. + * + * HAL is responsible for maintaining listener set and sending out messages + * to each listener that matches given filter against received message. + * + * Empty filter list means no filtering. If two or more listeners requested + * different filters, HAL server must merge these to fulfill the superset of + * these filters. HAL must not send out a message to a listener which filter + * doesn't match given message id. + * + * If filtering is not supported at the hardware level (what's strongly + * recommended), it must be covered in the HAL. + * + * @param filter The set of requested filters + * @param listener The interface to receive the messages on + * @return result OK in the case of success + * INTERFACE_DOWN if the bus is down + * @return close A handle to call in order to remove the listener + */ + listen(vec filter, ICanMessageListener listener) + generates (Result result, ICloseHandle close); + + /** + * Adds a new listener for CAN bus or interface errors. + * + * If the error is fatal, the client is supposed to drop any references to + * this specific ICanBus instance (see ICanErrorListener). + * + * @param listener The interface to receive the error events on + * @return close A handle to call in order to remove the listener + */ + listenForErrors(ICanErrorListener listener) generates (ICloseHandle close); +}; diff --git a/automotive/can/1.0/ICanController.hal b/automotive/can/1.0/ICanController.hal new file mode 100644 index 0000000000..aaf85e94c9 --- /dev/null +++ b/automotive/can/1.0/ICanController.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.automotive.can@1.0; + +/** + * Represents a CAN controller that's capable of configuring CAN bus interfaces. + * + * The goal of this service is to configure CAN interfaces and bring up HIDL + * server instances of ICanBus for each one that's up. + * + * Providing an ICanController interface to configure CAN buses is optional. + * A system can elect to publish only ICanBus if the hardware is hardcoded + * for a specific application. + */ +interface ICanController { + /** + * Type of an interface, an equivalent to BusConfig::InterfaceId + * union discriminator. Defines a number of specific standard hardware + * families and a generic catch-all type of {@see INDEXED}. + */ + enum InterfaceType : uint8_t { + /** Virtual SocketCAN interface. */ + VIRTUAL, + + /** Native SocketCAN interface. */ + SOCKETCAN, + + /** Serial line CAN interface. */ + SLCAN, + + /** Proprietary, device-specific interface. */ + INDEXED, + }; + + enum Result : uint8_t { + OK, + + /** + * General error class, if others are not applicable. + */ + UNKNOWN_ERROR, + + /** + * Up request was called out of order (i.e. trying to up the + * interface twice). + */ + INVALID_STATE, + + /** Interface type is not supported. */ + NOT_SUPPORTED, + + /** + * Provided interface ID (index, name, device path) doesn't exist or + * there is no device with a given serial number. + */ + BAD_INTERFACE_ID, + + /** Provided bit rate is not supported by the hardware. */ + BAD_BITRATE, + + /** + * Provided service name ({@see BusConfig#name}) either has invalid + * format or is not listed in device manifest file. + */ + BAD_SERVICE_NAME, + }; + + /** + * Configuration of the (physical or virtual) CAN bus. + * + * ISO TP and CAN FD are currently not supported. + */ + struct BusConfig { + /** + * Name under which ICanBus HIDL service should be published. + * + * It must consist of only alphanumeric characters and underscore + * (a-z, A-Z, 0-9, '_'), at least 1 and at most 32 characters long. + * + * This field is *not* meant to distinguish between hardware interfaces + * nor preselect parameters like bitrate. The only intended side-effect + * of changing it should be a different ICanBus HIDL service name and + * the HIDL service should make no assumptions on its contents. + */ + string name; + + /** + * Hardware interface configuration. + * + * This union's discriminator has an equivalent enum + * {@see InterfaceType} to express compatibility via + * getSupportedInterfaceTypes(). + */ + safe_union InterfaceId { + /** Virtual SocketCAN interface. */ + struct Virtual { + /** Interface name, such as vcan0. If the interface doesn't + * exist, HAL server must create it. + */ + string ifname; + } virtualif; + + /** Native SocketCAN interface. */ + safe_union Socketcan { + /** Interface name, such as can0. */ + string ifname; + /** + * Alternatively to providing {@see ifname}, one may provide a + * list of interface serial number suffixes. If there happens to + * be a device (like USB2CAN) with a matching serial number + * suffix, the HAL service will have to select it. + * + * Client may utilize this in two ways: by matching against the + * entire serial number, or the last few characters (usually + * one). The former is better for small-scale test deployments + * (with just a handful of vehicles), the latter is good for + * larger scale (where a small suffix list may support large + * test fleet). + */ + vec serialno; + } socketcan; + + /** Serial line CAN interface. */ + safe_union Slcan { + /** Path to a device, such as /dev/ttyUSB0. */ + string ttyname; + /** + * List of interface serial number suffixes. + * {@see Socketcan::serialno} + */ + vec serialno; + } slcan; + + /** + * Proprietary, device-specific interface. + * + * Non-SocketCAN interfaces should use this variant. + */ + struct Indexed { + /** Interface number, 0-based. */ + uint8_t index; + } indexed; + } interfaceId; + + /** + * Bit rate for CAN communication. + * + * Typical bit rates are: 100000, 125000, 250000, 500000. + * + * For {@see interfaceId#virtual} and pre-configured + * {@see interfaceId#indexed} interfaces this value is ignored. + */ + uint32_t bitrate; + }; + + /** + * Fetches the list of interface types supported by this HAL server. + * + * @return iftypes The list of supported interface types. + */ + getSupportedInterfaceTypes() generates (vec iftypes); + + /** + * Bring up the CAN interface and publish ICanBus server instance. + * + * @param config Configuration of the CAN interface. + * @return result OK if the operation succeeded; error code otherwise. + */ + upInterface(BusConfig config) generates (Result result); + + /** + * Unpublish ICanBus server instance and bring down the CAN interface. + * + * In case of failure, at least the ICanBus server instance must be + * unpublished and resources freed on best-effort basis. + * + * @param name Name of the interface (@see BusConfig#name} to + * bring down. + * @return success true in case of success, false otherwise. + */ + downInterface(string name) generates (bool success); +}; diff --git a/automotive/can/1.0/ICanErrorListener.hal b/automotive/can/1.0/ICanErrorListener.hal new file mode 100644 index 0000000000..8a6ba054bc --- /dev/null +++ b/automotive/can/1.0/ICanErrorListener.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.automotive.can@1.0; + +/** + * CAN error listener. + */ +interface ICanErrorListener { + /** + * Called on error event. + * + * If the error is fatal, the client is supposed to drop any references to + * this specific ICanBus instance. + * + * @param error Error type + * @param isFatal Whether an error would result with ICanBus instance being unusable. + */ + onError(ErrorEvent error, bool isFatal); +}; diff --git a/automotive/can/1.0/ICanMessageListener.hal b/automotive/can/1.0/ICanMessageListener.hal new file mode 100644 index 0000000000..28161fa747 --- /dev/null +++ b/automotive/can/1.0/ICanMessageListener.hal @@ -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. + */ +package android.hardware.automotive.can@1.0; + +/** + * CAN message listener. + */ +interface ICanMessageListener { + /** + * Called on received CAN message. + * + * The timestamp field of message struct is set to time when the message + * was received by the hardware. If it's not possible to fetch exact + * hardware time, this field should be set as early as possible to decrease + * potential time delta. + * + * @param message Received CAN message + */ + onReceive(CanMessage message); +}; diff --git a/automotive/can/1.0/ICloseHandle.hal b/automotive/can/1.0/ICloseHandle.hal new file mode 100644 index 0000000000..924c58bfff --- /dev/null +++ b/automotive/can/1.0/ICloseHandle.hal @@ -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. + */ +package android.hardware.automotive.can@1.0; + +/** + * Represents a generic close handle to remove a callback that doesn't need + * active interface. + * + * When close() is called OR when the interface is released, the underlying + * resources must be freed. + */ +interface ICloseHandle { + /** + * Closes the handle. + * + * The call must not fail and must be issued by the client at most once. + * Otherwise, the server must ignore subsequent calls. + */ + close(); +}; diff --git a/automotive/can/1.0/default/Android.bp b/automotive/can/1.0/default/Android.bp new file mode 100644 index 0000000000..f5cf425887 --- /dev/null +++ b/automotive/can/1.0/default/Android.bp @@ -0,0 +1,56 @@ +// +// 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_defaults { + name: "android.hardware.automotive.can@defaults", + cpp_std: "experimental", + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION=1", + ], + shared_libs: [ + "libbase", + "libutils", + ], +} + +cc_binary { + name: "android.hardware.automotive.can@1.0-service", + init_rc: ["android.hardware.automotive.can@1.0-service.rc"], + defaults: ["android.hardware.automotive.can@defaults"], + vendor: true, + relative_install_path: "hw", + srcs: [ + "CanBus.cpp", + "CanBusNative.cpp", + "CanBusVirtual.cpp", + "CanBusSlcan.cpp", + "CanController.cpp", + "CanSocket.cpp", + "CloseHandle.cpp", + "service.cpp", + ], + shared_libs: [ + "android.hardware.automotive.can@1.0", + "libhidlbase", + ], + static_libs: [ + "android.hardware.automotive.can@libnetdevice", + "android.hardware.automotive@libc++fs", + ], +} diff --git a/automotive/can/1.0/default/CanBus.cpp b/automotive/can/1.0/default/CanBus.cpp new file mode 100644 index 0000000000..8b98e5ee2b --- /dev/null +++ b/automotive/can/1.0/default/CanBus.cpp @@ -0,0 +1,344 @@ +/* + * 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 "CanBus.h" + +#include "CloseHandle.h" + +#include +#include +#include +#include +#include +#include + +namespace android::hardware::automotive::can::V1_0::implementation { + +/** Whether to log sent/received packets. */ +static constexpr bool kSuperVerbose = false; + +Return CanBus::send(const CanMessage& message) { + std::lock_guard lck(mIsUpGuard); + if (!mIsUp) return Result::INTERFACE_DOWN; + + if (UNLIKELY(kSuperVerbose)) { + LOG(VERBOSE) << "Sending " << toString(message); + } + + if (message.payload.size() > CAN_MAX_DLEN) return Result::PAYLOAD_TOO_LONG; + + struct canfd_frame frame = {}; + frame.can_id = message.id; + if (message.isExtendedId) frame.can_id |= CAN_EFF_FLAG; + if (message.remoteTransmissionRequest) frame.can_id |= CAN_RTR_FLAG; + frame.len = message.payload.size(); + memcpy(frame.data, message.payload.data(), message.payload.size()); + + if (!mSocket->send(frame)) return Result::TRANSMISSION_FAILURE; + + return Result::OK; +} + +Return CanBus::listen(const hidl_vec& filter, + const sp& listenerCb, listen_cb _hidl_cb) { + std::lock_guard lck(mIsUpGuard); + + if (listenerCb == nullptr) { + _hidl_cb(Result::INVALID_ARGUMENTS, nullptr); + return {}; + } + if (!mIsUp) { + _hidl_cb(Result::INTERFACE_DOWN, nullptr); + return {}; + } + + std::lock_guard lckListeners(mMsgListenersGuard); + + sp closeHandle = new CloseHandle([this, listenerCb]() { + std::lock_guard lck(mMsgListenersGuard); + std::erase_if(mMsgListeners, [&](const auto& e) { return e.callback == listenerCb; }); + }); + mMsgListeners.emplace_back(CanMessageListener{listenerCb, filter, closeHandle}); + auto& listener = mMsgListeners.back(); + + // fix message IDs to have all zeros on bits not covered by mask + std::for_each(listener.filter.begin(), listener.filter.end(), + [](auto& rule) { rule.id &= rule.mask; }); + + _hidl_cb(Result::OK, closeHandle); + return {}; +} + +CanBus::CanBus() {} + +CanBus::CanBus(const std::string& ifname) : mIfname(ifname) {} + +CanBus::~CanBus() { + std::lock_guard lck(mIsUpGuard); + CHECK(!mIsUp) << "Interface is still up while being destroyed"; + + std::lock_guard lckListeners(mMsgListenersGuard); + CHECK(mMsgListeners.empty()) << "Listener list is not empty while interface is being destroyed"; +} + +void CanBus::setErrorCallback(ErrorCallback errcb) { + CHECK(!mIsUp) << "Can't set error callback while interface is up"; + CHECK(mErrCb == nullptr) << "Error callback is already set"; + mErrCb = errcb; + CHECK(!mIsUp) << "Can't set error callback while interface is up"; +} + +ICanController::Result CanBus::preUp() { + return ICanController::Result::OK; +} + +bool CanBus::postDown() { + return true; +} + +ICanController::Result CanBus::up() { + std::lock_guard lck(mIsUpGuard); + + if (mIsUp) { + LOG(WARNING) << "Interface is already up"; + return ICanController::Result::INVALID_STATE; + } + + const auto preResult = preUp(); + if (preResult != ICanController::Result::OK) return preResult; + + const auto isUp = netdevice::isUp(mIfname); + if (!isUp.has_value()) { + // preUp() should prepare the interface (either create or make sure it's there) + LOG(ERROR) << "Interface " << mIfname << " didn't get prepared"; + return ICanController::Result::BAD_INTERFACE_ID; + } + + if (!*isUp && !netdevice::up(mIfname)) { + LOG(ERROR) << "Can't bring " << mIfname << " up"; + return ICanController::Result::UNKNOWN_ERROR; + } + mDownAfterUse = !*isUp; + + using namespace std::placeholders; + CanSocket::ReadCallback rdcb = std::bind(&CanBus::onRead, this, _1, _2); + CanSocket::ErrorCallback errcb = std::bind(&CanBus::onError, this, _1); + mSocket = CanSocket::open(mIfname, rdcb, errcb); + if (!mSocket) { + if (mDownAfterUse) netdevice::down(mIfname); + return ICanController::Result::UNKNOWN_ERROR; + } + + mIsUp = true; + return ICanController::Result::OK; +} + +void CanBus::clearMsgListeners() { + std::vector> listenersToClose; + { + std::lock_guard lck(mMsgListenersGuard); + std::transform(mMsgListeners.begin(), mMsgListeners.end(), + std::back_inserter(listenersToClose), + [](const auto& e) { return e.closeHandle; }); + } + + for (auto& weakListener : listenersToClose) { + /* Between populating listenersToClose and calling close method here, some listeners might + * have been already removed from the original mMsgListeners list (resulting in a dangling + * weak pointer here). It's fine - we just want to clean them up. */ + auto listener = weakListener.promote(); + if (listener != nullptr) listener->close(); + } + + std::lock_guard lck(mMsgListenersGuard); + CHECK(mMsgListeners.empty()) << "Listeners list wasn't emptied"; +} + +void CanBus::clearErrListeners() { + std::lock_guard lck(mErrListenersGuard); + mErrListeners.clear(); +} + +Return> CanBus::listenForErrors(const sp& listener) { + if (listener == nullptr) { + return new CloseHandle(); + } + + std::lock_guard upLck(mIsUpGuard); + if (!mIsUp) { + listener->onError(ErrorEvent::INTERFACE_DOWN, true); + return new CloseHandle(); + } + + std::lock_guard errLck(mErrListenersGuard); + mErrListeners.emplace_back(listener); + + return new CloseHandle([this, listener]() { + std::lock_guard lck(mErrListenersGuard); + std::erase(mErrListeners, listener); + }); +} + +bool CanBus::down() { + std::lock_guard lck(mIsUpGuard); + + if (!mIsUp) { + LOG(WARNING) << "Interface is already down"; + return false; + } + mIsUp = false; + + clearMsgListeners(); + clearErrListeners(); + mSocket.reset(); + + bool success = true; + + if (mDownAfterUse && !netdevice::down(mIfname)) { + LOG(ERROR) << "Can't bring " << mIfname << " down"; + // don't return yet, let's try to do best-effort cleanup + success = false; + } + + if (!postDown()) success = false; + + return success; +} + +/** + * Helper function to determine if a flag meets the requirements of a + * FilterFlag. See definition of FilterFlag in types.hal + * + * \param filterFlag FilterFlag object to match flag against + * \param flag bool object from CanMessage object + */ +static bool satisfiesFilterFlag(FilterFlag filterFlag, bool flag) { + if (filterFlag == FilterFlag::DONT_CARE) return true; + if (filterFlag == FilterFlag::SET) return flag; + if (filterFlag == FilterFlag::NOT_SET) return !flag; + return false; +} + +/** + * Match the filter set against message id. + * + * For details on the filters syntax, please see CanMessageFilter at + * the HAL definition (types.hal). + * + * \param filter Filter to match against + * \param id Message id to filter + * \return true if the message id matches the filter, false otherwise + */ +static bool match(const hidl_vec& filter, CanMessageId id, bool isRtr, + bool isExtendedId) { + if (filter.size() == 0) return true; + + bool anyNonExcludeRulePresent = false; + bool anyNonExcludeRuleSatisfied = false; + for (auto& rule : filter) { + const bool satisfied = ((id & rule.mask) == rule.id) && + satisfiesFilterFlag(rule.rtr, isRtr) && + satisfiesFilterFlag(rule.extendedFormat, isExtendedId); + + if (rule.exclude) { + // Any excluded (blacklist) rule not being satisfied invalidates the whole filter set. + if (satisfied) return false; + } else { + anyNonExcludeRulePresent = true; + if (satisfied) anyNonExcludeRuleSatisfied = true; + } + } + return !anyNonExcludeRulePresent || anyNonExcludeRuleSatisfied; +} + +void CanBus::notifyErrorListeners(ErrorEvent err, bool isFatal) { + std::lock_guard lck(mErrListenersGuard); + for (auto& listener : mErrListeners) { + if (!listener->onError(err, isFatal).isOk()) { + LOG(WARNING) << "Failed to notify listener about error"; + } + } +} + +static ErrorEvent parseErrorFrame(const struct canfd_frame& frame) { + // decode error frame (to a degree) + if ((frame.can_id & (CAN_ERR_BUSERROR | CAN_ERR_BUSOFF)) != 0) { + return ErrorEvent::BUS_ERROR; + } + if ((frame.data[1] & CAN_ERR_CRTL_TX_OVERFLOW) != 0) { + return ErrorEvent::TX_OVERFLOW; + } + if ((frame.data[1] & CAN_ERR_CRTL_RX_OVERFLOW) != 0) { + return ErrorEvent::RX_OVERFLOW; + } + if ((frame.data[2] & CAN_ERR_PROT_OVERLOAD) != 0) { + return ErrorEvent::BUS_OVERLOAD; + } + if ((frame.can_id & CAN_ERR_PROT) != 0) { + return ErrorEvent::MALFORMED_INPUT; + } + if ((frame.can_id & (CAN_ERR_CRTL | CAN_ERR_TRX | CAN_ERR_RESTARTED)) != 0) { + // "controller restarted" constitutes a HARDWARE_ERROR imo + return ErrorEvent::HARDWARE_ERROR; + } + return ErrorEvent::UNKNOWN_ERROR; +} + +void CanBus::onRead(const struct canfd_frame& frame, std::chrono::nanoseconds timestamp) { + if ((frame.can_id & CAN_ERR_FLAG) != 0) { + // error bit is set + LOG(WARNING) << "CAN Error frame received"; + notifyErrorListeners(parseErrorFrame(frame), false); + return; + } + + CanMessage message = {}; + message.id = frame.can_id & CAN_EFF_MASK; // mask out eff/rtr/err flags + message.payload = hidl_vec(frame.data, frame.data + frame.len); + message.timestamp = timestamp.count(); + message.isExtendedId = (frame.can_id & CAN_EFF_FLAG) != 0; + message.remoteTransmissionRequest = (frame.can_id & CAN_RTR_FLAG) != 0; + + if (UNLIKELY(kSuperVerbose)) { + LOG(VERBOSE) << "Got message " << toString(message); + } + + std::lock_guard lck(mMsgListenersGuard); + for (auto& listener : mMsgListeners) { + if (!match(listener.filter, message.id, message.remoteTransmissionRequest, + message.isExtendedId)) + continue; + if (!listener.callback->onReceive(message).isOk() && !listener.failedOnce) { + listener.failedOnce = true; + LOG(WARNING) << "Failed to notify listener about message"; + } + } +} + +void CanBus::onError(int errnoVal) { + auto eventType = ErrorEvent::HARDWARE_ERROR; + + if (errnoVal == ENODEV || errnoVal == ENETDOWN) { + mDownAfterUse = false; + eventType = ErrorEvent::INTERFACE_DOWN; + } + notifyErrorListeners(eventType, true); + + const auto errcb = mErrCb; + if (errcb != nullptr) errcb(); +} + +} // namespace android::hardware::automotive::can::V1_0::implementation diff --git a/automotive/can/1.0/default/CanBus.h b/automotive/can/1.0/default/CanBus.h new file mode 100644 index 0000000000..8b73258318 --- /dev/null +++ b/automotive/can/1.0/default/CanBus.h @@ -0,0 +1,112 @@ +/* + * 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 "CanSocket.h" + +#include +#include +#include +#include + +#include +#include + +namespace android::hardware::automotive::can::V1_0::implementation { + +struct CanBus : public ICanBus { + using ErrorCallback = std::function; + + virtual ~CanBus(); + + Return send(const CanMessage& message) override; + Return listen(const hidl_vec& filter, + const sp& listener, listen_cb _hidl_cb) override; + Return> listenForErrors(const sp& listener) override; + + void setErrorCallback(ErrorCallback errcb); + ICanController::Result up(); + bool down(); + + protected: + /** + * Blank constructor, since some interface types (such as SLCAN) don't get a name until after + * being initialized. + * + * If using this constructor, you MUST initialize mIfname prior to the completion of preUp(). + */ + CanBus(); + + CanBus(const std::string& ifname); + + /** + * Prepare the SocketCAN interface. + * + * After calling this method, mIfname network interface is available and ready to be brought up. + * + * \return OK on success, or an error state on failure. See ICanController::Result + */ + virtual ICanController::Result preUp(); + + /** + * Cleanup after bringing the interface down. + * + * This is a counterpart to preUp(). + * + * \return true upon success and false upon failure + */ + virtual bool postDown(); + + /** Network interface name. */ + std::string mIfname; + + private: + struct CanMessageListener { + sp callback; + hidl_vec filter; + wp closeHandle; + bool failedOnce = false; + }; + void clearMsgListeners(); + void clearErrListeners(); + + void notifyErrorListeners(ErrorEvent err, bool isFatal); + + void onRead(const struct canfd_frame& frame, std::chrono::nanoseconds timestamp); + void onError(int errnoVal); + + std::mutex mMsgListenersGuard; + std::vector mMsgListeners GUARDED_BY(mMsgListenersGuard); + + std::mutex mErrListenersGuard; + std::vector> mErrListeners GUARDED_BY(mErrListenersGuard); + + std::unique_ptr mSocket; + bool mDownAfterUse; + + /** + * Guard for up flag is required to be held for entire time when the interface is being used + * (i.e. message being sent), because we don't want the interface to be torn down while + * executing that operation. + */ + std::mutex mIsUpGuard; + bool mIsUp GUARDED_BY(mIsUpGuard) = false; + + ErrorCallback mErrCb; +}; + +} // namespace android::hardware::automotive::can::V1_0::implementation diff --git a/automotive/can/1.0/default/CanBusNative.cpp b/automotive/can/1.0/default/CanBusNative.cpp new file mode 100644 index 0000000000..aafbeccdb8 --- /dev/null +++ b/automotive/can/1.0/default/CanBusNative.cpp @@ -0,0 +1,52 @@ +/* + * 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 "CanBusNative.h" + +#include +#include +#include + +namespace android::hardware::automotive::can::V1_0::implementation { + +CanBusNative::CanBusNative(const std::string& ifname, uint32_t bitrate) + : CanBus(ifname), mBitrate(bitrate) {} + +ICanController::Result CanBusNative::preUp() { + if (!netdevice::exists(mIfname)) { + LOG(ERROR) << "Interface " << mIfname << " doesn't exist"; + return ICanController::Result::BAD_INTERFACE_ID; + } + + if (mBitrate == 0) { + // interface is already up and we just want to register it + return ICanController::Result::OK; + } + + if (!netdevice::down(mIfname)) { + LOG(ERROR) << "Can't bring " << mIfname << " down (to configure it)"; + return ICanController::Result::UNKNOWN_ERROR; + } + + if (!netdevice::can::setBitrate(mIfname, mBitrate)) { + LOG(ERROR) << "Can't set bitrate " << mBitrate << " for " << mIfname; + return ICanController::Result::BAD_BITRATE; + } + + return ICanController::Result::OK; +} + +} // namespace android::hardware::automotive::can::V1_0::implementation diff --git a/automotive/can/1.0/default/CanBusNative.h b/automotive/can/1.0/default/CanBusNative.h new file mode 100644 index 0000000000..04d7194e37 --- /dev/null +++ b/automotive/can/1.0/default/CanBusNative.h @@ -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. + */ + +#pragma once + +#include "CanBus.h" + +namespace android::hardware::automotive::can::V1_0::implementation { + +struct CanBusNative : public CanBus { + CanBusNative(const std::string& ifname, uint32_t bitrate); + + protected: + virtual ICanController::Result preUp() override; + + private: + const uint32_t mBitrate; +}; + +} // namespace android::hardware::automotive::can::V1_0::implementation diff --git a/automotive/can/1.0/default/CanBusSlcan.cpp b/automotive/can/1.0/default/CanBusSlcan.cpp new file mode 100644 index 0000000000..f08566ca5f --- /dev/null +++ b/automotive/can/1.0/default/CanBusSlcan.cpp @@ -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. + */ + +#include "CanBusSlcan.h" + +#include +#include +#include + +#include +#include +#include +#include + +namespace android::hardware::automotive::can::V1_0::implementation { + +namespace slcanprotocol { +static const std::string kOpenCommand = "O\r"; +static const std::string kCloseCommand = "C\r"; +static constexpr int kSlcanDiscipline = N_SLCAN; +static constexpr int kDefaultDiscipline = N_TTY; + +static const std::map kBitrateCommands = { + {10000, "C\rS0\r"}, {20000, "C\rS1\r"}, {50000, "C\rS2\r"}, + {100000, "C\rS3\r"}, {125000, "C\rS4\r"}, {250000, "C\rS5\r"}, + {500000, "C\rS6\r"}, {800000, "C\rS7\r"}, {1000000, "C\rS8\r"}}; +} // namespace slcanprotocol + +/** + * Serial Line CAN constructor + * \param string uartName - name of slcan device (e.x. /dev/ttyUSB0) + * \param uint32_t bitrate - speed of the CAN bus (125k = MSCAN, 500k = HSCAN) + */ +CanBusSlcan::CanBusSlcan(const std::string& uartName, uint32_t bitrate) + : CanBus(), mUartName(uartName), kBitrate(bitrate) {} + +/** helper function to update CanBusSlcan object's iface name */ +ICanController::Result CanBusSlcan::updateIfaceName(base::unique_fd& uartFd) { + struct ifreq ifrequest = {}; + /* + * Fetching the iface name with an ioctl won't interfere with an open socketCAN iface attached + * to this tty. This is important in the event we are trying to register a SLCAN based iface + * that has already been configured and brought up. + */ + if (ioctl(uartFd.get(), SIOCGIFNAME, ifrequest.ifr_name) < 0) { + PLOG(ERROR) << "Failed to get the name of the created device"; + return ICanController::Result::UNKNOWN_ERROR; + } + + // Update the CanBus object with name that was assigned to it + mIfname = ifrequest.ifr_name; + return ICanController::Result::OK; +} + +ICanController::Result CanBusSlcan::preUp() { + // verify valid bitrate and translate to serial command format + std::optional canBitrateCommand = std::nullopt; + if (kBitrate != 0) { + const auto lookupIt = slcanprotocol::kBitrateCommands.find(kBitrate); + if (lookupIt == slcanprotocol::kBitrateCommands.end()) { + return ICanController::Result::BAD_BITRATE; + } + canBitrateCommand = lookupIt->second; + } + + /* Attempt to open the uart in r/w without blocking or becoming the + * controlling terminal */ + mFd = base::unique_fd(open(mUartName.c_str(), O_RDWR | O_NONBLOCK | O_NOCTTY)); + if (!mFd.ok()) { + PLOG(ERROR) << "SLCAN Failed to open " << mUartName; + return ICanController::Result::BAD_INTERFACE_ID; + } + + // If the device is already up, update the iface name in our CanBusSlcan object + if (kBitrate == 0) { + return updateIfaceName(mFd); + } + + // blank terminal settings and pull them from the device + struct termios terminalSettings = {}; + if (tcgetattr(mFd.get(), &terminalSettings) < 0) { + PLOG(ERROR) << "Failed to read attrs of" << mUartName; + return ICanController::Result::UNKNOWN_ERROR; + } + + // change settings to raw mode + cfmakeraw(&terminalSettings); + + // disable software flow control + terminalSettings.c_iflag &= ~IXOFF; + // enable hardware flow control + terminalSettings.c_cflag |= CRTSCTS; + + struct serial_struct serialSettings; + // get serial settings + if (ioctl(mFd.get(), TIOCGSERIAL, &serialSettings) < 0) { + PLOG(ERROR) << "Failed to read serial settings from " << mUartName; + return ICanController::Result::UNKNOWN_ERROR; + } + // set low latency mode + serialSettings.flags |= ASYNC_LOW_LATENCY; + // apply serial settings + if (ioctl(mFd.get(), TIOCSSERIAL, &serialSettings) < 0) { + PLOG(ERROR) << "Failed to set low latency mode on " << mUartName; + return ICanController::Result::UNKNOWN_ERROR; + } + + /* TCSADRAIN applies settings after we finish writing the rest of our + * changes (as opposed to TCSANOW, which changes immediately) */ + if (tcsetattr(mFd.get(), TCSADRAIN, &terminalSettings) < 0) { + PLOG(ERROR) << "Failed to apply terminal settings to " << mUartName; + return ICanController::Result::UNKNOWN_ERROR; + } + + // apply speed setting for CAN + if (write(mFd.get(), canBitrateCommand->c_str(), canBitrateCommand->length()) <= 0) { + PLOG(ERROR) << "Failed to apply CAN bitrate"; + return ICanController::Result::UNKNOWN_ERROR; + } + + // TODO(b/144775286): set open flag & support listen only + if (write(mFd.get(), slcanprotocol::kOpenCommand.c_str(), + slcanprotocol::kOpenCommand.length()) <= 0) { + PLOG(ERROR) << "Failed to set open flag"; + return ICanController::Result::UNKNOWN_ERROR; + } + + // set line discipline to slcan + if (ioctl(mFd.get(), TIOCSETD, &slcanprotocol::kSlcanDiscipline) < 0) { + PLOG(ERROR) << "Failed to set line discipline to slcan"; + return ICanController::Result::UNKNOWN_ERROR; + } + + // Update the CanBus object with name that was assigned to it + return updateIfaceName(mFd); +} + +bool CanBusSlcan::postDown() { + // reset the line discipline to TTY mode + if (ioctl(mFd.get(), TIOCSETD, &slcanprotocol::kDefaultDiscipline) < 0) { + LOG(ERROR) << "Failed to reset line discipline!"; + return false; + } + + // issue the close command + if (write(mFd.get(), slcanprotocol::kCloseCommand.c_str(), + slcanprotocol::kCloseCommand.length()) <= 0) { + LOG(ERROR) << "Failed to close tty!"; + return false; + } + + // close our unique_fd + mFd.reset(); + + return true; +} + +} // namespace android::hardware::automotive::can::V1_0::implementation diff --git a/automotive/can/1.0/default/CanBusSlcan.h b/automotive/can/1.0/default/CanBusSlcan.h new file mode 100644 index 0000000000..2328a2c65d --- /dev/null +++ b/automotive/can/1.0/default/CanBusSlcan.h @@ -0,0 +1,42 @@ +/* + * 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 +#include +#include +#include +#include "CanBus.h" + +namespace android::hardware::automotive::can::V1_0::implementation { + +struct CanBusSlcan : public CanBus { + CanBusSlcan(const std::string& uartName, uint32_t bitrate); + + protected: + virtual ICanController::Result preUp() override; + virtual bool postDown() override; + + private: + ICanController::Result updateIfaceName(base::unique_fd& uartFd); + + const std::string mUartName; + const uint32_t kBitrate; + base::unique_fd mFd; +}; + +} // namespace android::hardware::automotive::can::V1_0::implementation diff --git a/automotive/can/1.0/default/CanBusVirtual.cpp b/automotive/can/1.0/default/CanBusVirtual.cpp new file mode 100644 index 0000000000..32fe8d6340 --- /dev/null +++ b/automotive/can/1.0/default/CanBusVirtual.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 "CanBusVirtual.h" + +#include +#include + +namespace android::hardware::automotive::can::V1_0::implementation { + +CanBusVirtual::CanBusVirtual(const std::string& ifname) : CanBus(ifname) {} + +ICanController::Result CanBusVirtual::preUp() { + if (netdevice::exists(mIfname)) return ICanController::Result::OK; + + LOG(DEBUG) << "Virtual interface " << mIfname << " doesn't exist, creating..."; + mWasCreated = true; + if (!netdevice::add(mIfname, "vcan")) { + LOG(ERROR) << "Can't create vcan interface " << mIfname; + return ICanController::Result::UNKNOWN_ERROR; + } + + return ICanController::Result::OK; +} + +bool CanBusVirtual::postDown() { + if (mWasCreated) { + mWasCreated = false; + if (!netdevice::del(mIfname)) { + LOG(ERROR) << "Couldn't remove vcan interface " << mIfname; + return false; + } + } + return true; +} + +} // namespace android::hardware::automotive::can::V1_0::implementation diff --git a/automotive/can/1.0/default/CanBusVirtual.h b/automotive/can/1.0/default/CanBusVirtual.h new file mode 100644 index 0000000000..3990b20a0f --- /dev/null +++ b/automotive/can/1.0/default/CanBusVirtual.h @@ -0,0 +1,34 @@ +/* + * 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 "CanBus.h" + +namespace android::hardware::automotive::can::V1_0::implementation { + +struct CanBusVirtual : public CanBus { + CanBusVirtual(const std::string& ifname); + + protected: + virtual ICanController::Result preUp() override; + virtual bool postDown() override; + + private: + bool mWasCreated = false; +}; + +} // namespace android::hardware::automotive::can::V1_0::implementation diff --git a/automotive/can/1.0/default/CanController.cpp b/automotive/can/1.0/default/CanController.cpp new file mode 100644 index 0000000000..9c0f2c57a2 --- /dev/null +++ b/automotive/can/1.0/default/CanController.cpp @@ -0,0 +1,330 @@ +/* + * 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 "CanController.h" + +#include "CanBusNative.h" +#include "CanBusSlcan.h" +#include "CanBusVirtual.h" + +#include +#include + +#include +#include +#include + +namespace android::hardware::automotive::can::V1_0::implementation { + +using IfId = ICanController::BusConfig::InterfaceId; +using IfIdDisc = ICanController::BusConfig::InterfaceId::hidl_discriminator; +namespace fs = android::hardware::automotive::filesystem; + +namespace fsErrors { +static const std::error_code ok; +static const std::error_code eperm(EPERM, std::generic_category()); +static const std::error_code enoent(ENOENT, std::generic_category()); +static const std::error_code eacces(EACCES, std::generic_category()); +} // namespace fsErrors + +/* In the /sys/devices tree, there are files called "serial", which contain the serial numbers + * for various devices. The exact location inside of this directory is dependent upon the + * hardware we are running on, so we have to start from /sys/devices and work our way down. */ +static const fs::path kDevPath("/sys/devices/"); +static const std::regex kTtyRe("^tty[A-Z]+[0-9]+$"); +static constexpr auto kOpts = ~(fs::directory_options::follow_directory_symlink | + fs::directory_options::skip_permission_denied); + +/** + * A helper object to associate the interface name and type of a USB to CAN adapter. + */ +struct UsbCanIface { + ICanController::InterfaceType iftype; + std::string ifaceName; +}; + +Return CanController::getSupportedInterfaceTypes(getSupportedInterfaceTypes_cb _hidl_cb) { + _hidl_cb({ICanController::InterfaceType::VIRTUAL, ICanController::InterfaceType::SOCKETCAN, + ICanController::InterfaceType::SLCAN}); + return {}; +} + +static bool isValidName(const std::string& name) { + static const std::regex nameRE("^[a-zA-Z0-9_]{1,32}$"); + return std::regex_match(name, nameRE); +} + +/** + * Given a UsbCanIface object, get the ifaceName given the serialPath. + * + * \param serialPath - Absolute path to a "serial" file for a given device in /sys. + * \return A populated UsbCanIface. On failure, nullopt is returned. + */ +static std::optional getIfaceName(fs::path serialPath) { + std::error_code fsStatus; + // Since the path is to a file called "serial", we need to search its parent directory. + fs::recursive_directory_iterator fsItr(serialPath.parent_path(), kOpts, fsStatus); + if (fsStatus != fsErrors::ok) { + LOG(ERROR) << "Failed to open " << serialPath.parent_path(); + return std::nullopt; + } + + for (; fsStatus == fsErrors::ok && fsItr != fs::recursive_directory_iterator(); + fsItr.increment(fsStatus)) { + /* We want either a directory called "net" or a directory that looks like tty, so + * skip files. */ + bool isDir = fsItr->is_directory(fsStatus); + if (fsStatus != fsErrors::ok || !isDir) continue; + + /* path() returns an iterator that steps through directories from / to the leaf. + * end() returns one past the leaf of the path, but we want the leaf. Decrementing the + * path gives us a pointer to the leaf, which we then dereference.*/ + std::string currentDir = *(--(fsItr->path().end())); + if (currentDir == "net") { + /* This device is a SocketCAN device. The iface name is the only directory under + * net/. Multiple directories under net/ is an error.*/ + fs::directory_iterator netItr(fsItr->path(), kOpts, fsStatus); + if (fsStatus != fsErrors::ok) { + LOG(ERROR) << "Failed to open " << fsItr->path() << " to get net name!"; + return std::nullopt; + } + + // Get the leaf of the path. This is the interface name, assuming it's the only leaf. + std::string netName = *(--(netItr->path().end())); + + // Check if there is more than one item in net/ + netItr.increment(fsStatus); + if (fsStatus != fsErrors::ok) { + // It's possible we have a valid net name, but this is most likely an error. + LOG(ERROR) << "Failed to verify " << fsItr->path() << " has valid net name!"; + return std::nullopt; + } + if (netItr != fs::directory_iterator()) { + // There should never be more than one name under net/ + LOG(ERROR) << "Found more than one net name in " << fsItr->path() << "!"; + return std::nullopt; + } + return {{ICanController::InterfaceType::SOCKETCAN, netName}}; + } else if (std::regex_match(currentDir, kTtyRe)) { + // This device is a USB serial device, and currentDir is the tty name. + return {{ICanController::InterfaceType::SLCAN, "/dev/" + currentDir}}; + } + } + + // check if the loop above exited due to a c++fs error. + if (fsStatus != fsErrors::ok) { + LOG(ERROR) << "Failed search filesystem: " << fsStatus; + } + return std::nullopt; +} + +/** + * A helper function to read the serial number from a "serial" file in /sys/devices/ + * + * \param serialnoPath - path to the file to read. + * \return the serial number, or nullopt on failure. + */ +static std::optional readSerialNo(const std::string& serialnoPath) { + std::ifstream serialnoStream(serialnoPath); + std::string serialno; + if (!serialnoStream.good()) { + LOG(ERROR) << "Failed to read serial number from " << serialnoPath; + return std::nullopt; + } + std::getline(serialnoStream, serialno); + return serialno; +} + +/** + * Searches for USB devices found in /sys/devices/, and attempts to find a device matching the + * provided list of serial numbers. + * + * \param configSerialnos - a list of serial number (suffixes) from the HAL config. + * \param iftype - the type of the interface to be located. + * \return a matching USB device. On failure, std::nullopt is returned. + */ +static std::optional findUsbDevice(const hidl_vec& configSerialnos) { + std::error_code fsStatus; + fs::recursive_directory_iterator fsItr(kDevPath, kOpts, fsStatus); + if (fsStatus != fsErrors::ok) { + LOG(ERROR) << "Failed to open " << kDevPath; + return std::nullopt; + } + + for (; fsStatus == fsErrors::ok && fsItr != fs::recursive_directory_iterator(); + fsItr.increment(fsStatus)) { + // We want to find a file called "serial", which is in a directory somewhere. Skip files. + bool isDir = fsItr->is_directory(fsStatus); + if (fsStatus != fsErrors::ok) { + LOG(ERROR) << "Failed check if " << fsStatus; + return std::nullopt; + } + if (!isDir) continue; + + auto serialnoPath = fsItr->path() / "serial"; + bool isReg = fs::is_regular_file(serialnoPath, fsStatus); + + /* Make sure we have permissions to this directory, ignore enoent, since the file + * "serial" may not exist, which is ok. */ + if (fsStatus == fsErrors::eperm || fsStatus == fsErrors::eacces) { + /* This means we don't have access to this directory. If we recurse into it, this + * will cause the iterator to loose its state and we'll crash. */ + fsItr.disable_recursion_pending(); + continue; + } + if (fsStatus == fsErrors::enoent) continue; + if (fsStatus != fsErrors::ok) { + LOG(WARNING) << "An unexpected error occurred while checking for serialno: " + << fsStatus; + continue; + } + if (!isReg) continue; + + // we found a serial number + auto serialno = readSerialNo(serialnoPath); + if (!serialno.has_value()) continue; + + // see if the serial number exists in the config + for (auto&& cfgSn : configSerialnos) { + if (serialno->ends_with(std::string(cfgSn))) { + auto ifaceInfo = getIfaceName(serialnoPath); + if (!ifaceInfo.has_value()) break; + return ifaceInfo; + } + } + } + if (fsStatus != fsErrors::ok) { + LOG(ERROR) << "Error searching filesystem: " << fsStatus; + return std::nullopt; + } + return std::nullopt; +} + +Return CanController::upInterface(const ICanController::BusConfig& config) { + LOG(VERBOSE) << "Attempting to bring interface up: " << toString(config); + + std::lock_guard lck(mCanBusesGuard); + + if (!isValidName(config.name)) { + LOG(ERROR) << "Bus name " << config.name << " is invalid"; + return ICanController::Result::BAD_SERVICE_NAME; + } + + if (mCanBuses.find(config.name) != mCanBuses.end()) { + LOG(ERROR) << "Bus " << config.name << " is already up"; + return ICanController::Result::INVALID_STATE; + } + + sp busService; + + // SocketCAN native type interface. + if (config.interfaceId.getDiscriminator() == IfIdDisc::socketcan) { + auto& socketcan = config.interfaceId.socketcan(); + std::string ifaceName; + if (socketcan.getDiscriminator() == IfId::Socketcan::hidl_discriminator::serialno) { + // Configure by serial number. + auto selectedDevice = findUsbDevice(socketcan.serialno()); + // verify the returned device is the correct one + if (!selectedDevice.has_value() || + selectedDevice->iftype != ICanController::InterfaceType::SOCKETCAN) { + return ICanController::Result::BAD_INTERFACE_ID; + } + ifaceName = selectedDevice->ifaceName; + } else { + // configure by iface name. + ifaceName = socketcan.ifname(); + } + busService = new CanBusNative(ifaceName, config.bitrate); + } + // Virtual interface. + else if (config.interfaceId.getDiscriminator() == IfIdDisc::virtualif) { + busService = new CanBusVirtual(config.interfaceId.virtualif().ifname); + } + // SLCAN interface. + else if (config.interfaceId.getDiscriminator() == IfIdDisc::slcan) { + auto& slcan = config.interfaceId.slcan(); + std::string ttyName; + if (slcan.getDiscriminator() == IfId::Slcan::hidl_discriminator::serialno) { + // Configure by serial number. + auto selectedDevice = findUsbDevice(slcan.serialno()); + if (!selectedDevice.has_value() || + selectedDevice->iftype != ICanController::InterfaceType::SLCAN) { + return ICanController::Result::BAD_INTERFACE_ID; + } + ttyName = selectedDevice->ifaceName; + } else { + // Configure by tty name. + ttyName = slcan.ttyname(); + } + busService = new CanBusSlcan(ttyName, config.bitrate); + } else { + return ICanController::Result::NOT_SUPPORTED; + } + + busService->setErrorCallback([this, name = config.name]() { downInterface(name); }); + + const auto result = busService->up(); + if (result != ICanController::Result::OK) return result; + + if (busService->registerAsService(config.name) != OK) { + LOG(ERROR) << "Failed to register ICanBus/" << config.name; + if (!busService->down()) { + LOG(WARNING) << "Failed to bring down CAN bus that failed to register"; + } + return ICanController::Result::BAD_SERVICE_NAME; + } + + mCanBuses[config.name] = busService; + + return ICanController::Result::OK; +} + +static bool unregisterCanBusService(const hidl_string& name, sp busService) { + auto manager = hidl::manager::V1_2::IServiceManager::getService(); + if (!manager) return false; + const auto res = manager->tryUnregister(ICanBus::descriptor, name, busService); + if (!res.isOk()) return false; + return res; +} + +Return CanController::downInterface(const hidl_string& name) { + LOG(VERBOSE) << "Attempting to bring interface down: " << name; + + std::lock_guard lck(mCanBusesGuard); + + auto busEntry = mCanBuses.extract(name); + if (!busEntry) { + LOG(WARNING) << "Interface " << name << " is not up"; + return false; + } + + auto success = true; + + if (!unregisterCanBusService(name, busEntry.mapped())) { + LOG(ERROR) << "Couldn't unregister " << name; + // don't return yet, let's try to do best-effort cleanup + success = false; + } + + if (!busEntry.mapped()->down()) { + LOG(ERROR) << "Couldn't bring " << name << " down"; + success = false; + } + + return success; +} + +} // namespace android::hardware::automotive::can::V1_0::implementation diff --git a/automotive/can/1.0/default/CanController.h b/automotive/can/1.0/default/CanController.h new file mode 100644 index 0000000000..27e82f3f3f --- /dev/null +++ b/automotive/can/1.0/default/CanController.h @@ -0,0 +1,36 @@ +/* + * 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 "CanBus.h" + +#include + +namespace android::hardware::automotive::can::V1_0::implementation { + +struct CanController : public ICanController { + Return getSupportedInterfaceTypes(getSupportedInterfaceTypes_cb _hidl_cb) override; + + Return upInterface(const ICanController::BusConfig& config) override; + Return downInterface(const hidl_string& name) override; + + private: + std::mutex mCanBusesGuard; + std::map> mCanBuses GUARDED_BY(mCanBusesGuard); +}; + +} // namespace android::hardware::automotive::can::V1_0::implementation diff --git a/automotive/can/1.0/default/CanSocket.cpp b/automotive/can/1.0/default/CanSocket.cpp new file mode 100644 index 0000000000..f379d5a070 --- /dev/null +++ b/automotive/can/1.0/default/CanSocket.cpp @@ -0,0 +1,150 @@ +/* + * 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 "CanSocket.h" + +#include +#include +#include +#include +#include + +#include + +namespace android::hardware::automotive::can::V1_0::implementation { + +using namespace std::chrono_literals; + +/* How frequently the read thread checks whether the interface was asked to be down. + * + * Note: This does *not* affect read timing or bandwidth, just CPU load vs time to + * down the interface. */ +static constexpr auto kReadPooling = 100ms; + +std::unique_ptr CanSocket::open(const std::string& ifname, ReadCallback rdcb, + ErrorCallback errcb) { + auto sock = netdevice::can::socket(ifname); + if (!sock.ok()) { + LOG(ERROR) << "Can't open CAN socket on " << ifname; + return nullptr; + } + + // Can't use std::make_unique due to private CanSocket constructor. + return std::unique_ptr(new CanSocket(std::move(sock), rdcb, errcb)); +} + +CanSocket::CanSocket(base::unique_fd socket, ReadCallback rdcb, ErrorCallback errcb) + : mReadCallback(rdcb), + mErrorCallback(errcb), + mSocket(std::move(socket)), + mReaderThread(&CanSocket::readerThread, this) {} + +CanSocket::~CanSocket() { + mStopReaderThread = true; + + /* CanSocket can be brought down as a result of read failure, from the same thread, + * so let's just detach and let it finish on its own. */ + if (mReaderThreadFinished) { + mReaderThread.detach(); + } else { + mReaderThread.join(); + } +} + +bool CanSocket::send(const struct canfd_frame& frame) { + const auto res = write(mSocket.get(), &frame, CAN_MTU); + if (res < 0) { + PLOG(DEBUG) << "CanSocket send failed"; + return false; + } + if (res != CAN_MTU) { + LOG(DEBUG) << "CanSocket sent wrong number of bytes: " << res; + return false; + } + return true; +} + +static struct timeval toTimeval(std::chrono::microseconds t) { + struct timeval tv; + tv.tv_sec = t / 1s; + tv.tv_usec = (t % 1s) / 1us; + return tv; +} + +static int selectRead(const base::unique_fd& fd, std::chrono::microseconds timeout) { + auto timeouttv = toTimeval(timeout); + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(fd.get(), &readfds); + return select(fd.get() + 1, &readfds, nullptr, nullptr, &timeouttv); +} + +void CanSocket::readerThread() { + LOG(VERBOSE) << "Reader thread started"; + int errnoCopy = 0; + + while (!mStopReaderThread) { + /* The ideal would be to have a blocking read(3) call and interrupt it with shutdown(3). + * This is unfortunately not supported for SocketCAN, so we need to rely on select(3). */ + const auto sel = selectRead(mSocket, kReadPooling); + if (sel == 0) continue; // timeout + if (sel == -1) { + PLOG(ERROR) << "Select failed"; + break; + } + + struct canfd_frame frame; + const auto nbytes = read(mSocket.get(), &frame, CAN_MTU); + + /* We could use SIOCGSTAMP to get a precise UNIX timestamp for a given packet, but what + * we really need is a time since boot. There is no direct way to convert between these + * clocks. We could implement a class to calculate the difference between the clocks + * (querying both several times and picking the smallest difference); apply the difference + * to a SIOCGSTAMP returned value; re-synchronize if the elapsed time is too much in the + * past (indicating the UNIX timestamp might have been adjusted). + * + * Apart from the added complexity, it's possible the added calculations and system calls + * would add so much time to the processing pipeline so the precision of the reported time + * was buried under the subsystem latency. Let's just use a local time since boot here and + * leave precise hardware timestamps for custom proprietary implementations (if needed). */ + const std::chrono::nanoseconds ts(elapsedRealtimeNano()); + + if (nbytes != CAN_MTU) { + if (nbytes >= 0) { + LOG(ERROR) << "Failed to read CAN packet, got " << nbytes << " bytes"; + break; + } + if (errno == EAGAIN) continue; + + errnoCopy = errno; + PLOG(ERROR) << "Failed to read CAN packet"; + break; + } + + mReadCallback(frame, ts); + } + + bool failed = !mStopReaderThread; + auto errCb = mErrorCallback; + mReaderThreadFinished = true; + + // Don't access any fields from here, see CanSocket::~CanSocket comment about detached thread + if (failed) errCb(errnoCopy); + + LOG(VERBOSE) << "Reader thread stopped"; +} + +} // namespace android::hardware::automotive::can::V1_0::implementation diff --git a/automotive/can/1.0/default/CanSocket.h b/automotive/can/1.0/default/CanSocket.h new file mode 100644 index 0000000000..fd956b50f6 --- /dev/null +++ b/automotive/can/1.0/default/CanSocket.h @@ -0,0 +1,69 @@ +/* + * 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 +#include +#include + +#include +#include +#include + +namespace android::hardware::automotive::can::V1_0::implementation { + +/** Wrapper around SocketCAN socket. */ +struct CanSocket { + using ReadCallback = std::function; + using ErrorCallback = std::function; + + /** + * Open and bind SocketCAN socket. + * + * \param ifname SocketCAN network interface name (such as can0) + * \param rdcb Callback on received messages + * \param errcb Callback on socket failure + * \return Socket instance, or nullptr if it wasn't possible to open one + */ + static std::unique_ptr open(const std::string& ifname, ReadCallback rdcb, + ErrorCallback errcb); + virtual ~CanSocket(); + + /** + * Send CAN frame. + * + * \param frame Frame to send + * \return true in case of success, false otherwise + */ + bool send(const struct canfd_frame& frame); + + private: + CanSocket(base::unique_fd socket, ReadCallback rdcb, ErrorCallback errcb); + void readerThread(); + + ReadCallback mReadCallback; + ErrorCallback mErrorCallback; + + const base::unique_fd mSocket; + std::thread mReaderThread; + std::atomic mStopReaderThread = false; + std::atomic mReaderThreadFinished = false; + + DISALLOW_COPY_AND_ASSIGN(CanSocket); +}; + +} // namespace android::hardware::automotive::can::V1_0::implementation diff --git a/automotive/can/1.0/default/CloseHandle.cpp b/automotive/can/1.0/default/CloseHandle.cpp new file mode 100644 index 0000000000..e1ffe2b8cf --- /dev/null +++ b/automotive/can/1.0/default/CloseHandle.cpp @@ -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. + */ + +#include "CloseHandle.h" + +namespace android::hardware::automotive::can::V1_0::implementation { + +CloseHandle::CloseHandle(Callback callback) : mCallback(callback) {} + +CloseHandle::~CloseHandle() { + close(); +} + +Return CloseHandle::close() { + const auto wasClosed = mIsClosed.exchange(true); + if (wasClosed) return {}; + + if (mCallback != nullptr) mCallback(); + return {}; +} + +} // namespace android::hardware::automotive::can::V1_0::implementation diff --git a/automotive/can/1.0/default/CloseHandle.h b/automotive/can/1.0/default/CloseHandle.h new file mode 100644 index 0000000000..c332d7493f --- /dev/null +++ b/automotive/can/1.0/default/CloseHandle.h @@ -0,0 +1,47 @@ +/* + * 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 +#include + +namespace android::hardware::automotive::can::V1_0::implementation { + +/** Generic ICloseHandle implementation ignoring double-close events. */ +struct CloseHandle : public ICloseHandle { + using Callback = std::function; + + /** + * Create a handle with a callback. + * + * The callback is guaranteed to be called exactly once. + * + * \param callback Called on the first close() call, or on destruction of the handle + */ + CloseHandle(Callback callback = nullptr); + virtual ~CloseHandle(); + + Return close() override; + + private: + const Callback mCallback; + std::atomic mIsClosed = false; + + DISALLOW_COPY_AND_ASSIGN(CloseHandle); +}; + +} // namespace android::hardware::automotive::can::V1_0::implementation diff --git a/automotive/can/1.0/default/android.hardware.automotive.can@1.0-service.rc b/automotive/can/1.0/default/android.hardware.automotive.can@1.0-service.rc new file mode 100644 index 0000000000..a629bdae00 --- /dev/null +++ b/automotive/can/1.0/default/android.hardware.automotive.can@1.0-service.rc @@ -0,0 +1,5 @@ +service can-hal-1.0-service /vendor/bin/hw/android.hardware.automotive.can@1.0-service + class hal + capabilities NET_ADMIN + user vehicle_network + group system inet diff --git a/automotive/can/1.0/default/libc++fs/.clang-format b/automotive/can/1.0/default/libc++fs/.clang-format new file mode 100644 index 0000000000..dd596813fb --- /dev/null +++ b/automotive/can/1.0/default/libc++fs/.clang-format @@ -0,0 +1,13 @@ +BasedOnStyle: LLVM + +--- +Language: Cpp +Standard: Cpp03 + +AlwaysBreakTemplateDeclarations: true +PointerAlignment: Left + +# Disable formatting options which may break tests. +SortIncludes: false +ReflowComments: false +--- diff --git a/automotive/can/1.0/default/libc++fs/Android.bp b/automotive/can/1.0/default/libc++fs/Android.bp new file mode 100644 index 0000000000..7ab1c28b3b --- /dev/null +++ b/automotive/can/1.0/default/libc++fs/Android.bp @@ -0,0 +1,61 @@ +// +// Copyright (C) 2020 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(152067309): Stop building this yourself once it's ABI stable and has +// been made vendor available. Just use libc++fs instead of this. + +cc_defaults { + name: "android.hardware.automotive@libc++fsdefaults", + local_include_dirs: ["include"], + export_include_dirs: ["include"], + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + ], + cppflags: [ + "-std=c++17", + "-fexceptions", + "-DLIBCXX_BUILDING_LIBCXXABI", + "-D_LIBCPP_BUILDING_LIBRARY", + ], + rtti: true, +} + +cc_library_static { + name: "android.hardware.automotive@libc++fs", + recovery_available: true, + vendor: true, + defaults: ["android.hardware.automotive@libc++fsdefaults"], + srcs: [ + "src/filesystem/directory_iterator.cpp", + "src/filesystem/operations.cpp", + ], + multilib: { + lib32: { + // off_t usage is constrained to within the libc++ source (not the + // headers), so we can build the filesystem library with a 64-bit + // off_t on LP32 to get large file support without needing all users + // of the library to match. + cflags: ["-D_FILE_OFFSET_BITS=64"], + }, + }, + target: { + windows: { + enabled: false, + }, + }, +} diff --git a/automotive/can/1.0/default/libc++fs/include/automotive/filesystem b/automotive/can/1.0/default/libc++fs/include/automotive/filesystem new file mode 100644 index 0000000000..660ad09745 --- /dev/null +++ b/automotive/can/1.0/default/libc++fs/include/automotive/filesystem @@ -0,0 +1,2699 @@ +// -*- C++ -*- +//===--------------------------- filesystem -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#ifndef _LIBAUTO_FILESYSTEM +#define _LIBAUTO_FILESYSTEM +/* + filesystem synopsis + + namespace android::hardware::automotive { namespace filesystem { + + class path; + + void swap(path& lhs, path& rhs) noexcept; + size_t hash_value(const path& p) noexcept; + + bool operator==(const path& lhs, const path& rhs) noexcept; + bool operator!=(const path& lhs, const path& rhs) noexcept; + bool operator< (const path& lhs, const path& rhs) noexcept; + bool operator<=(const path& lhs, const path& rhs) noexcept; + bool operator> (const path& lhs, const path& rhs) noexcept; + bool operator>=(const path& lhs, const path& rhs) noexcept; + + path operator/ (const path& lhs, const path& rhs); + + // fs.path.io operators are friends of path. + template + friend basic_ostream& + operator<<(basic_ostream& os, const path& p); + + template + friend basic_istream& + operator>>(basic_istream& is, path& p); + + template + path u8path(const Source& source); + template + path u8path(InputIterator first, InputIterator last); + + class filesystem_error; + class directory_entry; + + class directory_iterator; + + // enable directory_iterator range-based for statements + directory_iterator begin(directory_iterator iter) noexcept; + directory_iterator end(const directory_iterator&) noexcept; + + class recursive_directory_iterator; + + // enable recursive_directory_iterator range-based for statements + recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept; + recursive_directory_iterator end(const recursive_directory_iterator&) noexcept; + + class file_status; + + struct space_info + { + uintmax_t capacity; + uintmax_t free; + uintmax_t available; + }; + + enum class file_type; + enum class perms; + enum class perm_options; + enum class copy_options; + enum class directory_options; + + typedef chrono::time_point file_time_type; + + // operational functions + + path absolute(const path& p); + path absolute(const path& p, error_code &ec); + + path canonical(const path& p); + path canonical(const path& p, error_code& ec); + + void copy(const path& from, const path& to); + void copy(const path& from, const path& to, error_code& ec); + void copy(const path& from, const path& to, copy_options options); + void copy(const path& from, const path& to, copy_options options, + error_code& ec); + + bool copy_file(const path& from, const path& to); + bool copy_file(const path& from, const path& to, error_code& ec); + bool copy_file(const path& from, const path& to, copy_options option); + bool copy_file(const path& from, const path& to, copy_options option, + error_code& ec); + + void copy_symlink(const path& existing_symlink, const path& new_symlink); + void copy_symlink(const path& existing_symlink, const path& new_symlink, + error_code& ec) noexcept; + + bool create_directories(const path& p); + bool create_directories(const path& p, error_code& ec); + + bool create_directory(const path& p); + bool create_directory(const path& p, error_code& ec) noexcept; + + bool create_directory(const path& p, const path& attributes); + bool create_directory(const path& p, const path& attributes, + error_code& ec) noexcept; + + void create_directory_symlink(const path& to, const path& new_symlink); + void create_directory_symlink(const path& to, const path& new_symlink, + error_code& ec) noexcept; + + void create_hard_link(const path& to, const path& new_hard_link); + void create_hard_link(const path& to, const path& new_hard_link, + error_code& ec) noexcept; + + void create_symlink(const path& to, const path& new_symlink); + void create_symlink(const path& to, const path& new_symlink, + error_code& ec) noexcept; + + path current_path(); + path current_path(error_code& ec); + void current_path(const path& p); + void current_path(const path& p, error_code& ec) noexcept; + + bool exists(file_status s) noexcept; + bool exists(const path& p); + bool exists(const path& p, error_code& ec) noexcept; + + bool equivalent(const path& p1, const path& p2); + bool equivalent(const path& p1, const path& p2, error_code& ec) noexcept; + + uintmax_t file_size(const path& p); + uintmax_t file_size(const path& p, error_code& ec) noexcept; + + uintmax_t hard_link_count(const path& p); + uintmax_t hard_link_count(const path& p, error_code& ec) noexcept; + + bool is_block_file(file_status s) noexcept; + bool is_block_file(const path& p); + bool is_block_file(const path& p, error_code& ec) noexcept; + + bool is_character_file(file_status s) noexcept; + bool is_character_file(const path& p); + bool is_character_file(const path& p, error_code& ec) noexcept; + + bool is_directory(file_status s) noexcept; + bool is_directory(const path& p); + bool is_directory(const path& p, error_code& ec) noexcept; + + bool is_empty(const path& p); + bool is_empty(const path& p, error_code& ec) noexcept; + + bool is_fifo(file_status s) noexcept; + bool is_fifo(const path& p); + bool is_fifo(const path& p, error_code& ec) noexcept; + + bool is_other(file_status s) noexcept; + bool is_other(const path& p); + bool is_other(const path& p, error_code& ec) noexcept; + + bool is_regular_file(file_status s) noexcept; + bool is_regular_file(const path& p); + bool is_regular_file(const path& p, error_code& ec) noexcept; + + bool is_socket(file_status s) noexcept; + bool is_socket(const path& p); + bool is_socket(const path& p, error_code& ec) noexcept; + + bool is_symlink(file_status s) noexcept; + bool is_symlink(const path& p); + bool is_symlink(const path& p, error_code& ec) noexcept; + + file_time_type last_write_time(const path& p); + file_time_type last_write_time(const path& p, error_code& ec) noexcept; + void last_write_time(const path& p, file_time_type new_time); + void last_write_time(const path& p, file_time_type new_time, + error_code& ec) noexcept; + + void permissions(const path& p, perms prms, + perm_options opts=perm_options::replace); + void permissions(const path& p, perms prms, error_code& ec) noexcept; + void permissions(const path& p, perms prms, perm_options opts, + error_code& ec); + + path proximate(const path& p, error_code& ec); + path proximate(const path& p, const path& base = current_path()); + path proximate(const path& p, const path& base, error_code &ec); + + path read_symlink(const path& p); + path read_symlink(const path& p, error_code& ec); + + path relative(const path& p, error_code& ec); + path relative(const path& p, const path& base=current_path()); + path relative(const path& p, const path& base, error_code& ec); + + bool remove(const path& p); + bool remove(const path& p, error_code& ec) noexcept; + + uintmax_t remove_all(const path& p); + uintmax_t remove_all(const path& p, error_code& ec); + + void rename(const path& from, const path& to); + void rename(const path& from, const path& to, error_code& ec) noexcept; + + void resize_file(const path& p, uintmax_t size); + void resize_file(const path& p, uintmax_t size, error_code& ec) noexcept; + + space_info space(const path& p); + space_info space(const path& p, error_code& ec) noexcept; + + file_status status(const path& p); + file_status status(const path& p, error_code& ec) noexcept; + + bool status_known(file_status s) noexcept; + + file_status symlink_status(const path& p); + file_status symlink_status(const path& p, error_code& ec) noexcept; + + path temp_directory_path(); + path temp_directory_path(error_code& ec); + + path weakly_canonical(path const& p); + path weakly_canonical(path const& p, error_code& ec); + + +} } // namespace android::hardware::automotive::filesystem + +*/ + +#include <__config> +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // for quoted +#include +#include + +#include <__debug> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +#ifndef _LIBCPP_CXX03_LANG + +namespace android::hardware::automotive::filesystem { +using namespace std; +using namespace std::chrono; + +using std::basic_string; +using std::enable_if; +using std::error_code; +using std::false_type; + +#ifndef _VSTD +#define _LIBAUTO_UNDEF_VSTD +#define _VSTD std +#endif + +#ifdef _VSTD_FS +#pragma push_macro("_VSTD_FS") +#else +#define _LIBAUTO_UNDEF_VSTD_FS +#endif +#define _VSTD_FS android::hardware::automotive::filesystem + +/* Begin copy of _FilesystemClock from include/chrono */ +struct _FilesystemClock { +#if !defined(_LIBCPP_HAS_NO_INT128) + typedef __int128_t rep; + typedef nano period; +#else + typedef long long rep; + typedef nano period; +#endif + + typedef chrono::duration duration; + typedef chrono::time_point<_FilesystemClock> time_point; + + static _LIBCPP_CONSTEXPR_AFTER_CXX11 const bool is_steady = false; + + _LIBCPP_FUNC_VIS static time_point now() noexcept; + + _LIBCPP_INLINE_VISIBILITY + static time_t to_time_t(const time_point& __t) noexcept { + typedef chrono::duration __secs; + return time_t( + chrono::duration_cast<__secs>(__t.time_since_epoch()).count()); + } + + _LIBCPP_INLINE_VISIBILITY + static time_point from_time_t(time_t __t) noexcept { + typedef chrono::duration __secs; + return time_point(__secs(__t)); + } +}; +/* End copy of _FilesystemClock from include/chrono */ + +typedef chrono::time_point<_FilesystemClock> file_time_type; + +struct _LIBCPP_TYPE_VIS space_info { + uintmax_t capacity; + uintmax_t free; + uintmax_t available; +}; + +enum class _LIBCPP_ENUM_VIS file_type : signed char { + none = 0, + not_found = -1, + regular = 1, + directory = 2, + symlink = 3, + block = 4, + character = 5, + fifo = 6, + socket = 7, + unknown = 8 +}; + +enum class _LIBCPP_ENUM_VIS perms : unsigned { + none = 0, + + owner_read = 0400, + owner_write = 0200, + owner_exec = 0100, + owner_all = 0700, + + group_read = 040, + group_write = 020, + group_exec = 010, + group_all = 070, + + others_read = 04, + others_write = 02, + others_exec = 01, + others_all = 07, + + all = 0777, + + set_uid = 04000, + set_gid = 02000, + sticky_bit = 01000, + mask = 07777, + unknown = 0xFFFF, +}; + +_LIBCPP_INLINE_VISIBILITY +inline constexpr perms operator&(perms _LHS, perms _RHS) { + return static_cast(static_cast(_LHS) & + static_cast(_RHS)); +} + +_LIBCPP_INLINE_VISIBILITY +inline constexpr perms operator|(perms _LHS, perms _RHS) { + return static_cast(static_cast(_LHS) | + static_cast(_RHS)); +} + +_LIBCPP_INLINE_VISIBILITY +inline constexpr perms operator^(perms _LHS, perms _RHS) { + return static_cast(static_cast(_LHS) ^ + static_cast(_RHS)); +} + +_LIBCPP_INLINE_VISIBILITY +inline constexpr perms operator~(perms _LHS) { + return static_cast(~static_cast(_LHS)); +} + +_LIBCPP_INLINE_VISIBILITY +inline perms& operator&=(perms& _LHS, perms _RHS) { return _LHS = _LHS & _RHS; } + +_LIBCPP_INLINE_VISIBILITY +inline perms& operator|=(perms& _LHS, perms _RHS) { return _LHS = _LHS | _RHS; } + +_LIBCPP_INLINE_VISIBILITY +inline perms& operator^=(perms& _LHS, perms _RHS) { return _LHS = _LHS ^ _RHS; } + +enum class _LIBCPP_ENUM_VIS perm_options : unsigned char { + replace = 1, + add = 2, + remove = 4, + nofollow = 8 +}; + +_LIBCPP_INLINE_VISIBILITY +inline constexpr perm_options operator&(perm_options _LHS, perm_options _RHS) { + return static_cast(static_cast(_LHS) & + static_cast(_RHS)); +} + +_LIBCPP_INLINE_VISIBILITY +inline constexpr perm_options operator|(perm_options _LHS, perm_options _RHS) { + return static_cast(static_cast(_LHS) | + static_cast(_RHS)); +} + +_LIBCPP_INLINE_VISIBILITY +inline constexpr perm_options operator^(perm_options _LHS, perm_options _RHS) { + return static_cast(static_cast(_LHS) ^ + static_cast(_RHS)); +} + +_LIBCPP_INLINE_VISIBILITY +inline constexpr perm_options operator~(perm_options _LHS) { + return static_cast(~static_cast(_LHS)); +} + +_LIBCPP_INLINE_VISIBILITY +inline perm_options& operator&=(perm_options& _LHS, perm_options _RHS) { + return _LHS = _LHS & _RHS; +} + +_LIBCPP_INLINE_VISIBILITY +inline perm_options& operator|=(perm_options& _LHS, perm_options _RHS) { + return _LHS = _LHS | _RHS; +} + +_LIBCPP_INLINE_VISIBILITY +inline perm_options& operator^=(perm_options& _LHS, perm_options _RHS) { + return _LHS = _LHS ^ _RHS; +} + +enum class _LIBCPP_ENUM_VIS copy_options : unsigned short { + none = 0, + skip_existing = 1, + overwrite_existing = 2, + update_existing = 4, + recursive = 8, + copy_symlinks = 16, + skip_symlinks = 32, + directories_only = 64, + create_symlinks = 128, + create_hard_links = 256, + __in_recursive_copy = 512, +}; + +_LIBCPP_INLINE_VISIBILITY +inline constexpr copy_options operator&(copy_options _LHS, copy_options _RHS) { + return static_cast(static_cast(_LHS) & + static_cast(_RHS)); +} + +_LIBCPP_INLINE_VISIBILITY +inline constexpr copy_options operator|(copy_options _LHS, copy_options _RHS) { + return static_cast(static_cast(_LHS) | + static_cast(_RHS)); +} + +_LIBCPP_INLINE_VISIBILITY +inline constexpr copy_options operator^(copy_options _LHS, copy_options _RHS) { + return static_cast(static_cast(_LHS) ^ + static_cast(_RHS)); +} + +_LIBCPP_INLINE_VISIBILITY +inline constexpr copy_options operator~(copy_options _LHS) { + return static_cast(~static_cast(_LHS)); +} + +_LIBCPP_INLINE_VISIBILITY +inline copy_options& operator&=(copy_options& _LHS, copy_options _RHS) { + return _LHS = _LHS & _RHS; +} + +_LIBCPP_INLINE_VISIBILITY +inline copy_options& operator|=(copy_options& _LHS, copy_options _RHS) { + return _LHS = _LHS | _RHS; +} + +_LIBCPP_INLINE_VISIBILITY +inline copy_options& operator^=(copy_options& _LHS, copy_options _RHS) { + return _LHS = _LHS ^ _RHS; +} + +enum class _LIBCPP_ENUM_VIS directory_options : unsigned char { + none = 0, + follow_directory_symlink = 1, + skip_permission_denied = 2 +}; + +_LIBCPP_INLINE_VISIBILITY +inline constexpr directory_options operator&(directory_options _LHS, + directory_options _RHS) { + return static_cast(static_cast(_LHS) & + static_cast(_RHS)); +} + +_LIBCPP_INLINE_VISIBILITY +inline constexpr directory_options operator|(directory_options _LHS, + directory_options _RHS) { + return static_cast(static_cast(_LHS) | + static_cast(_RHS)); +} + +_LIBCPP_INLINE_VISIBILITY +inline constexpr directory_options operator^(directory_options _LHS, + directory_options _RHS) { + return static_cast(static_cast(_LHS) ^ + static_cast(_RHS)); +} + +_LIBCPP_INLINE_VISIBILITY +inline constexpr directory_options operator~(directory_options _LHS) { + return static_cast(~static_cast(_LHS)); +} + +_LIBCPP_INLINE_VISIBILITY +inline directory_options& operator&=(directory_options& _LHS, + directory_options _RHS) { + return _LHS = _LHS & _RHS; +} + +_LIBCPP_INLINE_VISIBILITY +inline directory_options& operator|=(directory_options& _LHS, + directory_options _RHS) { + return _LHS = _LHS | _RHS; +} + +_LIBCPP_INLINE_VISIBILITY +inline directory_options& operator^=(directory_options& _LHS, + directory_options _RHS) { + return _LHS = _LHS ^ _RHS; +} + +class _LIBCPP_TYPE_VIS file_status { +public: + // constructors + _LIBCPP_INLINE_VISIBILITY + file_status() noexcept : file_status(file_type::none) {} + _LIBCPP_INLINE_VISIBILITY + explicit file_status(file_type __ft, perms __prms = perms::unknown) noexcept + : __ft_(__ft), + __prms_(__prms) {} + + file_status(const file_status&) noexcept = default; + file_status(file_status&&) noexcept = default; + + _LIBCPP_INLINE_VISIBILITY + ~file_status() {} + + file_status& operator=(const file_status&) noexcept = default; + file_status& operator=(file_status&&) noexcept = default; + + // observers + _LIBCPP_INLINE_VISIBILITY + file_type type() const noexcept { return __ft_; } + + _LIBCPP_INLINE_VISIBILITY + perms permissions() const noexcept { return __prms_; } + + // modifiers + _LIBCPP_INLINE_VISIBILITY + void type(file_type __ft) noexcept { __ft_ = __ft; } + + _LIBCPP_INLINE_VISIBILITY + void permissions(perms __p) noexcept { __prms_ = __p; } + +private: + file_type __ft_; + perms __prms_; +}; + +class _LIBCPP_TYPE_VIS directory_entry; + +template +struct __can_convert_char { + static const bool value = false; +}; +template +struct __can_convert_char : public __can_convert_char<_Tp> {}; +template <> +struct __can_convert_char { + static const bool value = true; + using __char_type = char; +}; +template <> +struct __can_convert_char { + static const bool value = true; + using __char_type = wchar_t; +}; +template <> +struct __can_convert_char { + static const bool value = true; + using __char_type = char16_t; +}; +template <> +struct __can_convert_char { + static const bool value = true; + using __char_type = char32_t; +}; + +template +typename enable_if<__can_convert_char<_ECharT>::value, bool>::type +__is_separator(_ECharT __e) { + return __e == _ECharT('/'); +} + +struct _NullSentinal {}; + +template +using _Void = void; + +template +struct __is_pathable_string : public false_type {}; + +template +struct __is_pathable_string< + basic_string<_ECharT, _Traits, _Alloc>, + _Void::__char_type> > + : public __can_convert_char<_ECharT> { + using _Str = basic_string<_ECharT, _Traits, _Alloc>; + using _Base = __can_convert_char<_ECharT>; + static _ECharT const* __range_begin(_Str const& __s) { return __s.data(); } + static _ECharT const* __range_end(_Str const& __s) { + return __s.data() + __s.length(); + } + static _ECharT __first_or_null(_Str const& __s) { + return __s.empty() ? _ECharT{} : __s[0]; + } +}; + +template +struct __is_pathable_string< + basic_string_view<_ECharT, _Traits>, + _Void::__char_type> > + : public __can_convert_char<_ECharT> { + using _Str = basic_string_view<_ECharT, _Traits>; + using _Base = __can_convert_char<_ECharT>; + static _ECharT const* __range_begin(_Str const& __s) { return __s.data(); } + static _ECharT const* __range_end(_Str const& __s) { + return __s.data() + __s.length(); + } + static _ECharT __first_or_null(_Str const& __s) { + return __s.empty() ? _ECharT{} : __s[0]; + } +}; + +template ::type, + class _UnqualPtrType = + typename remove_const::type>::type, + bool _IsCharPtr = is_pointer<_DS>::value&& + __can_convert_char<_UnqualPtrType>::value> +struct __is_pathable_char_array : false_type {}; + +template +struct __is_pathable_char_array<_Source, _ECharT*, _UPtr, true> + : __can_convert_char::type> { + using _Base = __can_convert_char::type>; + + static _ECharT const* __range_begin(const _ECharT* __b) { return __b; } + static _ECharT const* __range_end(const _ECharT* __b) { + using _Iter = const _ECharT*; + const _ECharT __sentinal = _ECharT{}; + _Iter __e = __b; + for (; *__e != __sentinal; ++__e) + ; + return __e; + } + + static _ECharT __first_or_null(const _ECharT* __b) { return *__b; } +}; + +template ::value, + class = void> +struct __is_pathable_iter : false_type {}; + +template +struct __is_pathable_iter< + _Iter, true, + _Void::value_type>::__char_type> > + : __can_convert_char::value_type> { + using _ECharT = typename iterator_traits<_Iter>::value_type; + using _Base = __can_convert_char<_ECharT>; + + static _Iter __range_begin(_Iter __b) { return __b; } + static _NullSentinal __range_end(_Iter) { return _NullSentinal{}; } + + static _ECharT __first_or_null(_Iter __b) { return *__b; } +}; + +template ::value, + bool _IsCharIterT = __is_pathable_char_array<_Tp>::value, + bool _IsIterT = !_IsCharIterT && __is_pathable_iter<_Tp>::value> +struct __is_pathable : false_type { + static_assert(!_IsStringT && !_IsCharIterT && !_IsIterT, "Must all be false"); +}; + +template +struct __is_pathable<_Tp, true, false, false> : __is_pathable_string<_Tp> {}; + +template +struct __is_pathable<_Tp, false, true, false> : __is_pathable_char_array<_Tp> { +}; + +template +struct __is_pathable<_Tp, false, false, true> : __is_pathable_iter<_Tp> {}; + +template +struct _PathCVT { + static_assert(__can_convert_char<_ECharT>::value, + "Char type not convertible"); + + typedef __narrow_to_utf8 _Narrower; + + static void __append_range(string& __dest, _ECharT const* __b, + _ECharT const* __e) { + _Narrower()(back_inserter(__dest), __b, __e); + } + + template + static void __append_range(string& __dest, _Iter __b, _Iter __e) { + static_assert(!is_same<_Iter, _ECharT*>::value, "Call const overload"); + if (__b == __e) + return; + basic_string<_ECharT> __tmp(__b, __e); + _Narrower()(back_inserter(__dest), __tmp.data(), + __tmp.data() + __tmp.length()); + } + + template + static void __append_range(string& __dest, _Iter __b, _NullSentinal) { + static_assert(!is_same<_Iter, _ECharT*>::value, "Call const overload"); + const _ECharT __sentinal = _ECharT{}; + if (*__b == __sentinal) + return; + basic_string<_ECharT> __tmp; + for (; *__b != __sentinal; ++__b) + __tmp.push_back(*__b); + _Narrower()(back_inserter(__dest), __tmp.data(), + __tmp.data() + __tmp.length()); + } + + template + static void __append_source(string& __dest, _Source const& __s) { + using _Traits = __is_pathable<_Source>; + __append_range(__dest, _Traits::__range_begin(__s), + _Traits::__range_end(__s)); + } +}; + +template <> +struct _PathCVT { + + template + static typename enable_if<__is_exactly_input_iterator<_Iter>::value>::type + __append_range(string& __dest, _Iter __b, _Iter __e) { + for (; __b != __e; ++__b) + __dest.push_back(*__b); + } + + template + static typename enable_if<__is_forward_iterator<_Iter>::value>::type + __append_range(string& __dest, _Iter __b, _Iter __e) { + __dest.__append_forward_unsafe(__b, __e); + } + + template + static void __append_range(string& __dest, _Iter __b, _NullSentinal) { + const char __sentinal = char{}; + for (; *__b != __sentinal; ++__b) + __dest.push_back(*__b); + } + + template + static void __append_source(string& __dest, _Source const& __s) { + using _Traits = __is_pathable<_Source>; + __append_range(__dest, _Traits::__range_begin(__s), + _Traits::__range_end(__s)); + } +}; + +class _LIBCPP_TYPE_VIS path { + template + using _EnableIfPathable = + typename enable_if<__is_pathable<_SourceOrIter>::value, _Tp>::type; + + template + using _SourceChar = typename __is_pathable<_Tp>::__char_type; + + template + using _SourceCVT = _PathCVT<_SourceChar<_Tp> >; + +public: + typedef char value_type; + typedef basic_string string_type; + typedef _VSTD::string_view __string_view; + static constexpr value_type preferred_separator = '/'; + + enum class _LIBCPP_ENUM_VIS format : unsigned char { + auto_format, + native_format, + generic_format + }; + + // constructors and destructor + _LIBCPP_INLINE_VISIBILITY path() noexcept {} + _LIBCPP_INLINE_VISIBILITY path(const path& __p) : __pn_(__p.__pn_) {} + _LIBCPP_INLINE_VISIBILITY path(path&& __p) noexcept + : __pn_(_VSTD::move(__p.__pn_)) {} + + _LIBCPP_INLINE_VISIBILITY + path(string_type&& __s, format = format::auto_format) noexcept + : __pn_(_VSTD::move(__s)) {} + + template > + path(const _Source& __src, format = format::auto_format) { + _SourceCVT<_Source>::__append_source(__pn_, __src); + } + + template + path(_InputIt __first, _InputIt __last, format = format::auto_format) { + typedef typename iterator_traits<_InputIt>::value_type _ItVal; + _PathCVT<_ItVal>::__append_range(__pn_, __first, __last); + } + + // TODO Implement locale conversions. + template > + path(const _Source& __src, const locale& __loc, format = format::auto_format); + template + path(_InputIt __first, _InputIt _last, const locale& __loc, + format = format::auto_format); + + _LIBCPP_INLINE_VISIBILITY + ~path() = default; + + // assignments + _LIBCPP_INLINE_VISIBILITY + path& operator=(const path& __p) { + __pn_ = __p.__pn_; + return *this; + } + + _LIBCPP_INLINE_VISIBILITY + path& operator=(path&& __p) noexcept { + __pn_ = _VSTD::move(__p.__pn_); + return *this; + } + + template + _LIBCPP_INLINE_VISIBILITY path& operator=(string_type&& __s) noexcept { + __pn_ = _VSTD::move(__s); + return *this; + } + + _LIBCPP_INLINE_VISIBILITY + path& assign(string_type&& __s) noexcept { + __pn_ = _VSTD::move(__s); + return *this; + } + + template + _LIBCPP_INLINE_VISIBILITY _EnableIfPathable<_Source> + operator=(const _Source& __src) { + return this->assign(__src); + } + + template + _EnableIfPathable<_Source> assign(const _Source& __src) { + __pn_.clear(); + _SourceCVT<_Source>::__append_source(__pn_, __src); + return *this; + } + + template + path& assign(_InputIt __first, _InputIt __last) { + typedef typename iterator_traits<_InputIt>::value_type _ItVal; + __pn_.clear(); + _PathCVT<_ItVal>::__append_range(__pn_, __first, __last); + return *this; + } + +private: + template + static bool __source_is_absolute(_ECharT __first_or_null) { + return __is_separator(__first_or_null); + } + +public: + // appends + path& operator/=(const path& __p) { + if (__p.is_absolute()) { + __pn_ = __p.__pn_; + return *this; + } + if (has_filename()) + __pn_ += preferred_separator; + __pn_ += __p.native(); + return *this; + } + + // FIXME: Use _LIBCPP_DIAGNOSE_WARNING to produce a diagnostic when __src + // is known at compile time to be "/' since the user almost certainly intended + // to append a separator instead of overwriting the path with "/" + template + _LIBCPP_INLINE_VISIBILITY _EnableIfPathable<_Source> + operator/=(const _Source& __src) { + return this->append(__src); + } + + template + _EnableIfPathable<_Source> append(const _Source& __src) { + using _Traits = __is_pathable<_Source>; + using _CVT = _PathCVT<_SourceChar<_Source> >; + if (__source_is_absolute(_Traits::__first_or_null(__src))) + __pn_.clear(); + else if (has_filename()) + __pn_ += preferred_separator; + _CVT::__append_source(__pn_, __src); + return *this; + } + + template + path& append(_InputIt __first, _InputIt __last) { + typedef typename iterator_traits<_InputIt>::value_type _ItVal; + static_assert(__can_convert_char<_ItVal>::value, "Must convertible"); + using _CVT = _PathCVT<_ItVal>; + if (__first != __last && __source_is_absolute(*__first)) + __pn_.clear(); + else if (has_filename()) + __pn_ += preferred_separator; + _CVT::__append_range(__pn_, __first, __last); + return *this; + } + + // concatenation + _LIBCPP_INLINE_VISIBILITY + path& operator+=(const path& __x) { + __pn_ += __x.__pn_; + return *this; + } + + _LIBCPP_INLINE_VISIBILITY + path& operator+=(const string_type& __x) { + __pn_ += __x; + return *this; + } + + _LIBCPP_INLINE_VISIBILITY + path& operator+=(__string_view __x) { + __pn_ += __x; + return *this; + } + + _LIBCPP_INLINE_VISIBILITY + path& operator+=(const value_type* __x) { + __pn_ += __x; + return *this; + } + + _LIBCPP_INLINE_VISIBILITY + path& operator+=(value_type __x) { + __pn_ += __x; + return *this; + } + + template + typename enable_if<__can_convert_char<_ECharT>::value, path&>::type + operator+=(_ECharT __x) { + basic_string<_ECharT> __tmp; + __tmp += __x; + _PathCVT<_ECharT>::__append_source(__pn_, __tmp); + return *this; + } + + template + _EnableIfPathable<_Source> operator+=(const _Source& __x) { + return this->concat(__x); + } + + template + _EnableIfPathable<_Source> concat(const _Source& __x) { + _SourceCVT<_Source>::__append_source(__pn_, __x); + return *this; + } + + template + path& concat(_InputIt __first, _InputIt __last) { + typedef typename iterator_traits<_InputIt>::value_type _ItVal; + _PathCVT<_ItVal>::__append_range(__pn_, __first, __last); + return *this; + } + + // modifiers + _LIBCPP_INLINE_VISIBILITY + void clear() noexcept { __pn_.clear(); } + + path& make_preferred() { return *this; } + + _LIBCPP_INLINE_VISIBILITY + path& remove_filename() { + auto __fname = __filename(); + if (!__fname.empty()) + __pn_.erase(__fname.data() - __pn_.data()); + return *this; + } + + path& replace_filename(const path& __replacement) { + remove_filename(); + return (*this /= __replacement); + } + + path& replace_extension(const path& __replacement = path()); + + _LIBCPP_INLINE_VISIBILITY + void swap(path& __rhs) noexcept { __pn_.swap(__rhs.__pn_); } + + // private helper to allow reserving memory in the path + _LIBCPP_INLINE_VISIBILITY + void __reserve(size_t __s) { __pn_.reserve(__s); } + + // native format observers + _LIBCPP_INLINE_VISIBILITY + const string_type& native() const noexcept { return __pn_; } + + _LIBCPP_INLINE_VISIBILITY + const value_type* c_str() const noexcept { return __pn_.c_str(); } + + _LIBCPP_INLINE_VISIBILITY operator string_type() const { return __pn_; } + + template , + class _Allocator = allocator<_ECharT> > + basic_string<_ECharT, _Traits, _Allocator> + string(const _Allocator& __a = _Allocator()) const { + using _CVT = __widen_from_utf8; + using _Str = basic_string<_ECharT, _Traits, _Allocator>; + _Str __s(__a); + __s.reserve(__pn_.size()); + _CVT()(back_inserter(__s), __pn_.data(), __pn_.data() + __pn_.size()); + return __s; + } + + _LIBCPP_INLINE_VISIBILITY std::string string() const { return __pn_; } + _LIBCPP_INLINE_VISIBILITY std::wstring wstring() const { + return string(); + } + _LIBCPP_INLINE_VISIBILITY std::string u8string() const { return __pn_; } + _LIBCPP_INLINE_VISIBILITY std::u16string u16string() const { + return string(); + } + _LIBCPP_INLINE_VISIBILITY std::u32string u32string() const { + return string(); + } + + // generic format observers + template , + class _Allocator = allocator<_ECharT> > + basic_string<_ECharT, _Traits, _Allocator> + generic_string(const _Allocator& __a = _Allocator()) const { + return string<_ECharT, _Traits, _Allocator>(__a); + } + + std::string generic_string() const { return __pn_; } + std::wstring generic_wstring() const { return string(); } + std::string generic_u8string() const { return __pn_; } + std::u16string generic_u16string() const { return string(); } + std::u32string generic_u32string() const { return string(); } + +private: + int __compare(__string_view) const; + __string_view __root_name() const; + __string_view __root_directory() const; + __string_view __root_path_raw() const; + __string_view __relative_path() const; + __string_view __parent_path() const; + __string_view __filename() const; + __string_view __stem() const; + __string_view __extension() const; + +public: + // compare + _LIBCPP_INLINE_VISIBILITY int compare(const path& __p) const noexcept { + return __compare(__p.__pn_); + } + _LIBCPP_INLINE_VISIBILITY int compare(const string_type& __s) const { + return __compare(__s); + } + _LIBCPP_INLINE_VISIBILITY int compare(__string_view __s) const { + return __compare(__s); + } + _LIBCPP_INLINE_VISIBILITY int compare(const value_type* __s) const { + return __compare(__s); + } + + // decomposition + _LIBCPP_INLINE_VISIBILITY path root_name() const { + return string_type(__root_name()); + } + _LIBCPP_INLINE_VISIBILITY path root_directory() const { + return string_type(__root_directory()); + } + _LIBCPP_INLINE_VISIBILITY path root_path() const { + return root_name().append(string_type(__root_directory())); + } + _LIBCPP_INLINE_VISIBILITY path relative_path() const { + return string_type(__relative_path()); + } + _LIBCPP_INLINE_VISIBILITY path parent_path() const { + return string_type(__parent_path()); + } + _LIBCPP_INLINE_VISIBILITY path filename() const { + return string_type(__filename()); + } + _LIBCPP_INLINE_VISIBILITY path stem() const { return string_type(__stem()); } + _LIBCPP_INLINE_VISIBILITY path extension() const { + return string_type(__extension()); + } + + // query + _LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_INLINE_VISIBILITY bool + empty() const noexcept { + return __pn_.empty(); + } + + _LIBCPP_INLINE_VISIBILITY bool has_root_name() const { + return !__root_name().empty(); + } + _LIBCPP_INLINE_VISIBILITY bool has_root_directory() const { + return !__root_directory().empty(); + } + _LIBCPP_INLINE_VISIBILITY bool has_root_path() const { + return !__root_path_raw().empty(); + } + _LIBCPP_INLINE_VISIBILITY bool has_relative_path() const { + return !__relative_path().empty(); + } + _LIBCPP_INLINE_VISIBILITY bool has_parent_path() const { + return !__parent_path().empty(); + } + _LIBCPP_INLINE_VISIBILITY bool has_filename() const { + return !__filename().empty(); + } + _LIBCPP_INLINE_VISIBILITY bool has_stem() const { return !__stem().empty(); } + _LIBCPP_INLINE_VISIBILITY bool has_extension() const { + return !__extension().empty(); + } + + _LIBCPP_INLINE_VISIBILITY bool is_absolute() const { + return has_root_directory(); + } + _LIBCPP_INLINE_VISIBILITY bool is_relative() const { return !is_absolute(); } + + // relative paths + path lexically_normal() const; + path lexically_relative(const path& __base) const; + + _LIBCPP_INLINE_VISIBILITY path lexically_proximate(const path& __base) const { + path __result = this->lexically_relative(__base); + if (__result.native().empty()) + return *this; + return __result; + } + + // iterators + class _LIBCPP_TYPE_VIS iterator; + typedef iterator const_iterator; + + iterator begin() const; + iterator end() const; + + template + _LIBCPP_INLINE_VISIBILITY friend + typename enable_if::value && + is_same<_Traits, char_traits >::value, + basic_ostream<_CharT, _Traits>&>::type + operator<<(basic_ostream<_CharT, _Traits>& __os, const path& __p) { + __os << std::__quoted(__p.native()); + return __os; + } + + template + _LIBCPP_INLINE_VISIBILITY friend + typename enable_if::value || + !is_same<_Traits, char_traits >::value, + basic_ostream<_CharT, _Traits>&>::type + operator<<(basic_ostream<_CharT, _Traits>& __os, const path& __p) { + __os << std::__quoted(__p.string<_CharT, _Traits>()); + return __os; + } + + template + _LIBCPP_INLINE_VISIBILITY friend basic_istream<_CharT, _Traits>& + operator>>(basic_istream<_CharT, _Traits>& __is, path& __p) { + basic_string<_CharT, _Traits> __tmp; + __is >> __quoted(__tmp); + __p = __tmp; + return __is; + } + + friend _LIBCPP_INLINE_VISIBILITY bool operator==(const path& __lhs, const path& __rhs) noexcept { + return __lhs.compare(__rhs) == 0; + } + friend _LIBCPP_INLINE_VISIBILITY bool operator!=(const path& __lhs, const path& __rhs) noexcept { + return __lhs.compare(__rhs) != 0; + } + friend _LIBCPP_INLINE_VISIBILITY bool operator<(const path& __lhs, const path& __rhs) noexcept { + return __lhs.compare(__rhs) < 0; + } + friend _LIBCPP_INLINE_VISIBILITY bool operator<=(const path& __lhs, const path& __rhs) noexcept { + return __lhs.compare(__rhs) <= 0; + } + friend _LIBCPP_INLINE_VISIBILITY bool operator>(const path& __lhs, const path& __rhs) noexcept { + return __lhs.compare(__rhs) > 0; + } + friend _LIBCPP_INLINE_VISIBILITY bool operator>=(const path& __lhs, const path& __rhs) noexcept { + return __lhs.compare(__rhs) >= 0; + } + + friend _LIBCPP_INLINE_VISIBILITY path operator/(const path& __lhs, + const path& __rhs) { + path __result(__lhs); + __result /= __rhs; + return __result; + } +private: + inline _LIBCPP_INLINE_VISIBILITY path& + __assign_view(__string_view const& __s) noexcept { + __pn_ = string_type(__s); + return *this; + } + string_type __pn_; +}; + +inline _LIBCPP_INLINE_VISIBILITY void swap(path& __lhs, path& __rhs) noexcept { + __lhs.swap(__rhs); +} + +_LIBCPP_FUNC_VIS +size_t hash_value(const path& __p) noexcept; + +template +_LIBCPP_INLINE_VISIBILITY + typename enable_if<__is_pathable<_Source>::value, path>::type + u8path(const _Source& __s) { + static_assert( + is_same::__char_type, char>::value, + "u8path(Source const&) requires Source have a character type of type " + "'char'"); + return path(__s); +} + +template +_LIBCPP_INLINE_VISIBILITY + typename enable_if<__is_pathable<_InputIt>::value, path>::type + u8path(_InputIt __f, _InputIt __l) { + static_assert( + is_same::__char_type, char>::value, + "u8path(Iter, Iter) requires Iter have a value_type of type 'char'"); + return path(__f, __l); +} + +class _LIBCPP_TYPE_VIS path::iterator { +public: + enum _ParserState : unsigned char { + _Singular, + _BeforeBegin, + _InRootName, + _InRootDir, + _InFilenames, + _InTrailingSep, + _AtEnd + }; + +public: + typedef bidirectional_iterator_tag iterator_category; + + typedef path value_type; + typedef std::ptrdiff_t difference_type; + typedef const path* pointer; + typedef const path& reference; + + typedef void + __stashing_iterator_tag; // See reverse_iterator and __is_stashing_iterator + +public: + _LIBCPP_INLINE_VISIBILITY + iterator() + : __stashed_elem_(), __path_ptr_(nullptr), __entry_(), + __state_(_Singular) {} + + iterator(const iterator&) = default; + ~iterator() = default; + + iterator& operator=(const iterator&) = default; + + _LIBCPP_INLINE_VISIBILITY + reference operator*() const { return __stashed_elem_; } + + _LIBCPP_INLINE_VISIBILITY + pointer operator->() const { return &__stashed_elem_; } + + _LIBCPP_INLINE_VISIBILITY + iterator& operator++() { + _LIBCPP_ASSERT(__state_ != _Singular, + "attempting to increment a singular iterator"); + _LIBCPP_ASSERT(__state_ != _AtEnd, + "attempting to increment the end iterator"); + return __increment(); + } + + _LIBCPP_INLINE_VISIBILITY + iterator operator++(int) { + iterator __it(*this); + this->operator++(); + return __it; + } + + _LIBCPP_INLINE_VISIBILITY + iterator& operator--() { + _LIBCPP_ASSERT(__state_ != _Singular, + "attempting to decrement a singular iterator"); + _LIBCPP_ASSERT(__entry_.data() != __path_ptr_->native().data(), + "attempting to decrement the begin iterator"); + return __decrement(); + } + + _LIBCPP_INLINE_VISIBILITY + iterator operator--(int) { + iterator __it(*this); + this->operator--(); + return __it; + } + +private: + friend class path; + + inline _LIBCPP_INLINE_VISIBILITY friend bool operator==(const iterator&, + const iterator&); + + iterator& __increment(); + iterator& __decrement(); + + path __stashed_elem_; + const path* __path_ptr_; + path::__string_view __entry_; + _ParserState __state_; +}; + +inline _LIBCPP_INLINE_VISIBILITY bool operator==(const path::iterator& __lhs, + const path::iterator& __rhs) { + return __lhs.__path_ptr_ == __rhs.__path_ptr_ && + __lhs.__entry_.data() == __rhs.__entry_.data(); +} + +inline _LIBCPP_INLINE_VISIBILITY bool operator!=(const path::iterator& __lhs, + const path::iterator& __rhs) { + return !(__lhs == __rhs); +} + +class _LIBCPP_EXCEPTION_ABI filesystem_error : public system_error { +public: + _LIBCPP_INLINE_VISIBILITY + filesystem_error(const string& __what, error_code __ec) + : system_error(__ec, __what), + __storage_(make_shared<_Storage>(path(), path())) { + __create_what(0); + } + + _LIBCPP_INLINE_VISIBILITY + filesystem_error(const string& __what, const path& __p1, error_code __ec) + : system_error(__ec, __what), + __storage_(make_shared<_Storage>(__p1, path())) { + __create_what(1); + } + + _LIBCPP_INLINE_VISIBILITY + filesystem_error(const string& __what, const path& __p1, const path& __p2, + error_code __ec) + : system_error(__ec, __what), + __storage_(make_shared<_Storage>(__p1, __p2)) { + __create_what(2); + } + + _LIBCPP_INLINE_VISIBILITY + const path& path1() const noexcept { return __storage_->__p1_; } + + _LIBCPP_INLINE_VISIBILITY + const path& path2() const noexcept { return __storage_->__p2_; } + + ~filesystem_error() override; // key function + + _LIBCPP_INLINE_VISIBILITY + const char* what() const noexcept override { + return __storage_->__what_.c_str(); + } + + _LIBCPP_FUNC_VIS + void __create_what(int __num_paths); + +private: + struct _Storage { + _LIBCPP_INLINE_VISIBILITY + _Storage(const path& __p1, const path& __p2) : __p1_(__p1), __p2_(__p2) {} + + path __p1_; + path __p2_; + string __what_; + }; + shared_ptr<_Storage> __storage_; +}; + +template +_LIBCPP_NORETURN inline _LIBCPP_INLINE_VISIBILITY +#ifndef _LIBCPP_NO_EXCEPTIONS + void + __throw_filesystem_error(_Args&&... __args) { + throw filesystem_error(std::forward<_Args>(__args)...); +} +#else + void + __throw_filesystem_error(_Args&&...) { + _VSTD::abort(); +} +#endif + +// operational functions + +_LIBCPP_FUNC_VIS +path __absolute(const path&, error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +path __canonical(const path&, error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +void __copy(const path& __from, const path& __to, copy_options __opt, + error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +bool __copy_file(const path& __from, const path& __to, copy_options __opt, + error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +void __copy_symlink(const path& __existing_symlink, const path& __new_symlink, + error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +bool __create_directories(const path& p, error_code* ec = nullptr); +_LIBCPP_FUNC_VIS +bool __create_directory(const path& p, error_code* ec = nullptr); +_LIBCPP_FUNC_VIS +bool __create_directory(const path& p, const path& attributes, + error_code* ec = nullptr); +_LIBCPP_FUNC_VIS +void __create_directory_symlink(const path& __to, const path& __new_symlink, + error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +void __create_hard_link(const path& __to, const path& __new_hard_link, + error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +void __create_symlink(const path& __to, const path& __new_symlink, + error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +path __current_path(error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +void __current_path(const path&, error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +bool __equivalent(const path&, const path&, error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +uintmax_t __file_size(const path&, error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +uintmax_t __hard_link_count(const path&, error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +bool __fs_is_empty(const path& p, error_code* ec = nullptr); +_LIBCPP_FUNC_VIS +file_time_type __last_write_time(const path& p, error_code* ec = nullptr); +_LIBCPP_FUNC_VIS +void __last_write_time(const path& p, file_time_type new_time, + error_code* ec = nullptr); +_LIBCPP_FUNC_VIS +void __permissions(const path&, perms, perm_options, error_code* = nullptr); +_LIBCPP_FUNC_VIS +path __read_symlink(const path& p, error_code* ec = nullptr); +_LIBCPP_FUNC_VIS +bool __remove(const path& p, error_code* ec = nullptr); +_LIBCPP_FUNC_VIS +uintmax_t __remove_all(const path& p, error_code* ec = nullptr); +_LIBCPP_FUNC_VIS +void __rename(const path& from, const path& to, error_code* ec = nullptr); +_LIBCPP_FUNC_VIS +void __resize_file(const path& p, uintmax_t size, error_code* ec = nullptr); +_LIBCPP_FUNC_VIS +space_info __space(const path&, error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +file_status __status(const path&, error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +file_status __symlink_status(const path&, error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +path __system_complete(const path&, error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +path __temp_directory_path(error_code* __ec = nullptr); +_LIBCPP_FUNC_VIS +path __weakly_canonical(path const& __p, error_code* __ec = nullptr); + +inline _LIBCPP_INLINE_VISIBILITY path current_path() { + return __current_path(); +} + +inline _LIBCPP_INLINE_VISIBILITY path current_path(error_code& __ec) { + return __current_path(&__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY void current_path(const path& __p) { + __current_path(__p); +} + +inline _LIBCPP_INLINE_VISIBILITY void current_path(const path& __p, + error_code& __ec) noexcept { + __current_path(__p, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY path absolute(const path& __p) { + return __absolute(__p); +} + +inline _LIBCPP_INLINE_VISIBILITY path absolute(const path& __p, + error_code& __ec) { + return __absolute(__p, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY path canonical(const path& __p) { + return __canonical(__p); +} + +inline _LIBCPP_INLINE_VISIBILITY path canonical(const path& __p, + error_code& __ec) { + return __canonical(__p, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY void copy(const path& __from, + const path& __to) { + __copy(__from, __to, copy_options::none); +} + +inline _LIBCPP_INLINE_VISIBILITY void copy(const path& __from, const path& __to, + error_code& __ec) { + __copy(__from, __to, copy_options::none, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY void copy(const path& __from, const path& __to, + copy_options __opt) { + __copy(__from, __to, __opt); +} + +inline _LIBCPP_INLINE_VISIBILITY void copy(const path& __from, const path& __to, + copy_options __opt, + error_code& __ec) { + __copy(__from, __to, __opt, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY bool copy_file(const path& __from, + const path& __to) { + return __copy_file(__from, __to, copy_options::none); +} + +inline _LIBCPP_INLINE_VISIBILITY bool +copy_file(const path& __from, const path& __to, error_code& __ec) { + return __copy_file(__from, __to, copy_options::none, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY bool +copy_file(const path& __from, const path& __to, copy_options __opt) { + return __copy_file(__from, __to, __opt); +} + +inline _LIBCPP_INLINE_VISIBILITY bool copy_file(const path& __from, + const path& __to, + copy_options __opt, + error_code& __ec) { + return __copy_file(__from, __to, __opt, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY void copy_symlink(const path& __existing, + const path& __new) { + __copy_symlink(__existing, __new); +} + +inline _LIBCPP_INLINE_VISIBILITY void +copy_symlink(const path& __ext, const path& __new, error_code& __ec) noexcept { + __copy_symlink(__ext, __new, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY bool create_directories(const path& __p) { + return __create_directories(__p); +} + +inline _LIBCPP_INLINE_VISIBILITY bool create_directories(const path& __p, + error_code& __ec) { + return __create_directories(__p, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY bool create_directory(const path& __p) { + return __create_directory(__p); +} + +inline _LIBCPP_INLINE_VISIBILITY bool +create_directory(const path& __p, error_code& __ec) noexcept { + return __create_directory(__p, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY bool create_directory(const path& __p, + const path& __attrs) { + return __create_directory(__p, __attrs); +} + +inline _LIBCPP_INLINE_VISIBILITY bool +create_directory(const path& __p, const path& __attrs, + error_code& __ec) noexcept { + return __create_directory(__p, __attrs, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY void +create_directory_symlink(const path& __to, const path& __new) { + __create_directory_symlink(__to, __new); +} + +inline _LIBCPP_INLINE_VISIBILITY void +create_directory_symlink(const path& __to, const path& __new, + error_code& __ec) noexcept { + __create_directory_symlink(__to, __new, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY void create_hard_link(const path& __to, + const path& __new) { + __create_hard_link(__to, __new); +} + +inline _LIBCPP_INLINE_VISIBILITY void +create_hard_link(const path& __to, const path& __new, + error_code& __ec) noexcept { + __create_hard_link(__to, __new, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY void create_symlink(const path& __to, + const path& __new) { + __create_symlink(__to, __new); +} + +inline _LIBCPP_INLINE_VISIBILITY void +create_symlink(const path& __to, const path& __new, error_code& __ec) noexcept { + return __create_symlink(__to, __new, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY bool status_known(file_status __s) noexcept { + return __s.type() != file_type::none; +} + +inline _LIBCPP_INLINE_VISIBILITY bool exists(file_status __s) noexcept { + return status_known(__s) && __s.type() != file_type::not_found; +} + +inline _LIBCPP_INLINE_VISIBILITY bool exists(const path& __p) { + return exists(__status(__p)); +} + +inline _LIBCPP_INLINE_VISIBILITY bool exists(const path& __p, + error_code& __ec) noexcept { + auto __s = __status(__p, &__ec); + if (status_known(__s)) + __ec.clear(); + return exists(__s); +} + +inline _LIBCPP_INLINE_VISIBILITY bool equivalent(const path& __p1, + const path& __p2) { + return __equivalent(__p1, __p2); +} + +inline _LIBCPP_INLINE_VISIBILITY bool +equivalent(const path& __p1, const path& __p2, error_code& __ec) noexcept { + return __equivalent(__p1, __p2, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY uintmax_t file_size(const path& __p) { + return __file_size(__p); +} + +inline _LIBCPP_INLINE_VISIBILITY uintmax_t +file_size(const path& __p, error_code& __ec) noexcept { + return __file_size(__p, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY uintmax_t hard_link_count(const path& __p) { + return __hard_link_count(__p); +} + +inline _LIBCPP_INLINE_VISIBILITY uintmax_t +hard_link_count(const path& __p, error_code& __ec) noexcept { + return __hard_link_count(__p, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_block_file(file_status __s) noexcept { + return __s.type() == file_type::block; +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_block_file(const path& __p) { + return is_block_file(__status(__p)); +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_block_file(const path& __p, + error_code& __ec) noexcept { + return is_block_file(__status(__p, &__ec)); +} + +inline _LIBCPP_INLINE_VISIBILITY bool +is_character_file(file_status __s) noexcept { + return __s.type() == file_type::character; +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_character_file(const path& __p) { + return is_character_file(__status(__p)); +} + +inline _LIBCPP_INLINE_VISIBILITY bool +is_character_file(const path& __p, error_code& __ec) noexcept { + return is_character_file(__status(__p, &__ec)); +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_directory(file_status __s) noexcept { + return __s.type() == file_type::directory; +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_directory(const path& __p) { + return is_directory(__status(__p)); +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_directory(const path& __p, + error_code& __ec) noexcept { + return is_directory(__status(__p, &__ec)); +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_empty(const path& __p) { + return __fs_is_empty(__p); +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_empty(const path& __p, + error_code& __ec) { + return __fs_is_empty(__p, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_fifo(file_status __s) noexcept { + return __s.type() == file_type::fifo; +} +inline _LIBCPP_INLINE_VISIBILITY bool is_fifo(const path& __p) { + return is_fifo(__status(__p)); +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_fifo(const path& __p, + error_code& __ec) noexcept { + return is_fifo(__status(__p, &__ec)); +} + +inline _LIBCPP_INLINE_VISIBILITY bool +is_regular_file(file_status __s) noexcept { + return __s.type() == file_type::regular; +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_regular_file(const path& __p) { + return is_regular_file(__status(__p)); +} + +inline _LIBCPP_INLINE_VISIBILITY bool +is_regular_file(const path& __p, error_code& __ec) noexcept { + return is_regular_file(__status(__p, &__ec)); +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_socket(file_status __s) noexcept { + return __s.type() == file_type::socket; +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_socket(const path& __p) { + return is_socket(__status(__p)); +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_socket(const path& __p, + error_code& __ec) noexcept { + return is_socket(__status(__p, &__ec)); +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_symlink(file_status __s) noexcept { + return __s.type() == file_type::symlink; +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_symlink(const path& __p) { + return is_symlink(__symlink_status(__p)); +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_symlink(const path& __p, + error_code& __ec) noexcept { + return is_symlink(__symlink_status(__p, &__ec)); +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_other(file_status __s) noexcept { + return exists(__s) && !is_regular_file(__s) && !is_directory(__s) && + !is_symlink(__s); +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_other(const path& __p) { + return is_other(__status(__p)); +} + +inline _LIBCPP_INLINE_VISIBILITY bool is_other(const path& __p, + error_code& __ec) noexcept { + return is_other(__status(__p, &__ec)); +} + +inline _LIBCPP_INLINE_VISIBILITY file_time_type +last_write_time(const path& __p) { + return __last_write_time(__p); +} + +inline _LIBCPP_INLINE_VISIBILITY file_time_type +last_write_time(const path& __p, error_code& __ec) noexcept { + return __last_write_time(__p, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY void last_write_time(const path& __p, + file_time_type __t) { + __last_write_time(__p, __t); +} + +inline _LIBCPP_INLINE_VISIBILITY void +last_write_time(const path& __p, file_time_type __t, + error_code& __ec) noexcept { + __last_write_time(__p, __t, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY void +permissions(const path& __p, perms __prms, + perm_options __opts = perm_options::replace) { + __permissions(__p, __prms, __opts); +} + +inline _LIBCPP_INLINE_VISIBILITY void permissions(const path& __p, perms __prms, + error_code& __ec) noexcept { + __permissions(__p, __prms, perm_options::replace, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY void permissions(const path& __p, perms __prms, + perm_options __opts, + error_code& __ec) { + __permissions(__p, __prms, __opts, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY path proximate(const path& __p, + const path& __base, + error_code& __ec) { + path __tmp = __weakly_canonical(__p, &__ec); + if (__ec) + return {}; + path __tmp_base = __weakly_canonical(__base, &__ec); + if (__ec) + return {}; + return __tmp.lexically_proximate(__tmp_base); +} + +inline _LIBCPP_INLINE_VISIBILITY path proximate(const path& __p, + error_code& __ec) { + return proximate(__p, current_path(), __ec); +} + +inline _LIBCPP_INLINE_VISIBILITY path +proximate(const path& __p, const path& __base = current_path()) { + return __weakly_canonical(__p).lexically_proximate( + __weakly_canonical(__base)); +} + +inline _LIBCPP_INLINE_VISIBILITY path read_symlink(const path& __p) { + return __read_symlink(__p); +} + +inline _LIBCPP_INLINE_VISIBILITY path read_symlink(const path& __p, + error_code& __ec) { + return __read_symlink(__p, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY path relative(const path& __p, + const path& __base, + error_code& __ec) { + path __tmp = __weakly_canonical(__p, &__ec); + if (__ec) + return path(); + path __tmpbase = __weakly_canonical(__base, &__ec); + if (__ec) + return path(); + return __tmp.lexically_relative(__tmpbase); +} + +inline _LIBCPP_INLINE_VISIBILITY path relative(const path& __p, + error_code& __ec) { + return relative(__p, current_path(), __ec); +} + +inline _LIBCPP_INLINE_VISIBILITY path +relative(const path& __p, const path& __base = current_path()) { + return __weakly_canonical(__p).lexically_relative(__weakly_canonical(__base)); +} + +inline _LIBCPP_INLINE_VISIBILITY bool remove(const path& __p) { + return __remove(__p); +} + +inline _LIBCPP_INLINE_VISIBILITY bool remove(const path& __p, + error_code& __ec) noexcept { + return __remove(__p, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY uintmax_t remove_all(const path& __p) { + return __remove_all(__p); +} + +inline _LIBCPP_INLINE_VISIBILITY uintmax_t remove_all(const path& __p, + error_code& __ec) { + return __remove_all(__p, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY void rename(const path& __from, + const path& __to) { + return __rename(__from, __to); +} + +inline _LIBCPP_INLINE_VISIBILITY void +rename(const path& __from, const path& __to, error_code& __ec) noexcept { + return __rename(__from, __to, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY void resize_file(const path& __p, + uintmax_t __ns) { + return __resize_file(__p, __ns); +} + +inline _LIBCPP_INLINE_VISIBILITY void +resize_file(const path& __p, uintmax_t __ns, error_code& __ec) noexcept { + return __resize_file(__p, __ns, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY space_info space(const path& __p) { + return __space(__p); +} + +inline _LIBCPP_INLINE_VISIBILITY space_info space(const path& __p, + error_code& __ec) noexcept { + return __space(__p, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY file_status status(const path& __p) { + return __status(__p); +} + +inline _LIBCPP_INLINE_VISIBILITY file_status status(const path& __p, + error_code& __ec) noexcept { + return __status(__p, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY file_status symlink_status(const path& __p) { + return __symlink_status(__p); +} + +inline _LIBCPP_INLINE_VISIBILITY file_status +symlink_status(const path& __p, error_code& __ec) noexcept { + return __symlink_status(__p, &__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY path temp_directory_path() { + return __temp_directory_path(); +} + +inline _LIBCPP_INLINE_VISIBILITY path temp_directory_path(error_code& __ec) { + return __temp_directory_path(&__ec); +} + +inline _LIBCPP_INLINE_VISIBILITY path weakly_canonical(path const& __p) { + return __weakly_canonical(__p); +} + +inline _LIBCPP_INLINE_VISIBILITY path weakly_canonical(path const& __p, + error_code& __ec) { + return __weakly_canonical(__p, &__ec); +} + +class directory_iterator; +class recursive_directory_iterator; +class __dir_stream; + +class directory_entry { + typedef _VSTD_FS::path _Path; + +public: + // constructors and destructors + directory_entry() noexcept = default; + directory_entry(directory_entry const&) = default; + directory_entry(directory_entry&&) noexcept = default; + + _LIBCPP_INLINE_VISIBILITY + explicit directory_entry(_Path const& __p) : __p_(__p) { + error_code __ec; + __refresh(&__ec); + } + + _LIBCPP_INLINE_VISIBILITY + directory_entry(_Path const& __p, error_code& __ec) : __p_(__p) { + __refresh(&__ec); + } + + ~directory_entry() {} + + directory_entry& operator=(directory_entry const&) = default; + directory_entry& operator=(directory_entry&&) noexcept = default; + + _LIBCPP_INLINE_VISIBILITY + void assign(_Path const& __p) { + __p_ = __p; + error_code __ec; + __refresh(&__ec); + } + + _LIBCPP_INLINE_VISIBILITY + void assign(_Path const& __p, error_code& __ec) { + __p_ = __p; + __refresh(&__ec); + } + + _LIBCPP_INLINE_VISIBILITY + void replace_filename(_Path const& __p) { + __p_.replace_filename(__p); + error_code __ec; + __refresh(&__ec); + } + + _LIBCPP_INLINE_VISIBILITY + void replace_filename(_Path const& __p, error_code& __ec) { + __p_ = __p_.parent_path() / __p; + __refresh(&__ec); + } + + _LIBCPP_INLINE_VISIBILITY + void refresh() { __refresh(); } + + _LIBCPP_INLINE_VISIBILITY + void refresh(error_code& __ec) noexcept { __refresh(&__ec); } + + _LIBCPP_INLINE_VISIBILITY + _Path const& path() const noexcept { return __p_; } + + _LIBCPP_INLINE_VISIBILITY + operator const _Path&() const noexcept { return __p_; } + + _LIBCPP_INLINE_VISIBILITY + bool exists() const { return _VSTD_FS::exists(file_status{__get_ft()}); } + + _LIBCPP_INLINE_VISIBILITY + bool exists(error_code& __ec) const noexcept { + return _VSTD_FS::exists(file_status{__get_ft(&__ec)}); + } + + _LIBCPP_INLINE_VISIBILITY + bool is_block_file() const { return __get_ft() == file_type::block; } + + _LIBCPP_INLINE_VISIBILITY + bool is_block_file(error_code& __ec) const noexcept { + return __get_ft(&__ec) == file_type::block; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_character_file() const { return __get_ft() == file_type::character; } + + _LIBCPP_INLINE_VISIBILITY + bool is_character_file(error_code& __ec) const noexcept { + return __get_ft(&__ec) == file_type::character; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_directory() const { return __get_ft() == file_type::directory; } + + _LIBCPP_INLINE_VISIBILITY + bool is_directory(error_code& __ec) const noexcept { + return __get_ft(&__ec) == file_type::directory; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_fifo() const { return __get_ft() == file_type::fifo; } + + _LIBCPP_INLINE_VISIBILITY + bool is_fifo(error_code& __ec) const noexcept { + return __get_ft(&__ec) == file_type::fifo; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_other() const { return _VSTD_FS::is_other(file_status{__get_ft()}); } + + _LIBCPP_INLINE_VISIBILITY + bool is_other(error_code& __ec) const noexcept { + return _VSTD_FS::is_other(file_status{__get_ft(&__ec)}); + } + + _LIBCPP_INLINE_VISIBILITY + bool is_regular_file() const { return __get_ft() == file_type::regular; } + + _LIBCPP_INLINE_VISIBILITY + bool is_regular_file(error_code& __ec) const noexcept { + return __get_ft(&__ec) == file_type::regular; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_socket() const { return __get_ft() == file_type::socket; } + + _LIBCPP_INLINE_VISIBILITY + bool is_socket(error_code& __ec) const noexcept { + return __get_ft(&__ec) == file_type::socket; + } + + _LIBCPP_INLINE_VISIBILITY + bool is_symlink() const { return __get_sym_ft() == file_type::symlink; } + + _LIBCPP_INLINE_VISIBILITY + bool is_symlink(error_code& __ec) const noexcept { + return __get_sym_ft(&__ec) == file_type::symlink; + } + _LIBCPP_INLINE_VISIBILITY + uintmax_t file_size() const { return __get_size(); } + + _LIBCPP_INLINE_VISIBILITY + uintmax_t file_size(error_code& __ec) const noexcept { + return __get_size(&__ec); + } + + _LIBCPP_INLINE_VISIBILITY + uintmax_t hard_link_count() const { return __get_nlink(); } + + _LIBCPP_INLINE_VISIBILITY + uintmax_t hard_link_count(error_code& __ec) const noexcept { + return __get_nlink(&__ec); + } + + _LIBCPP_INLINE_VISIBILITY + file_time_type last_write_time() const { return __get_write_time(); } + + _LIBCPP_INLINE_VISIBILITY + file_time_type last_write_time(error_code& __ec) const noexcept { + return __get_write_time(&__ec); + } + + _LIBCPP_INLINE_VISIBILITY + file_status status() const { return __get_status(); } + + _LIBCPP_INLINE_VISIBILITY + file_status status(error_code& __ec) const noexcept { + return __get_status(&__ec); + } + + _LIBCPP_INLINE_VISIBILITY + file_status symlink_status() const { return __get_symlink_status(); } + + _LIBCPP_INLINE_VISIBILITY + file_status symlink_status(error_code& __ec) const noexcept { + return __get_symlink_status(&__ec); + } + + _LIBCPP_INLINE_VISIBILITY + bool operator<(directory_entry const& __rhs) const noexcept { + return __p_ < __rhs.__p_; + } + + _LIBCPP_INLINE_VISIBILITY + bool operator==(directory_entry const& __rhs) const noexcept { + return __p_ == __rhs.__p_; + } + + _LIBCPP_INLINE_VISIBILITY + bool operator!=(directory_entry const& __rhs) const noexcept { + return __p_ != __rhs.__p_; + } + + _LIBCPP_INLINE_VISIBILITY + bool operator<=(directory_entry const& __rhs) const noexcept { + return __p_ <= __rhs.__p_; + } + + _LIBCPP_INLINE_VISIBILITY + bool operator>(directory_entry const& __rhs) const noexcept { + return __p_ > __rhs.__p_; + } + + _LIBCPP_INLINE_VISIBILITY + bool operator>=(directory_entry const& __rhs) const noexcept { + return __p_ >= __rhs.__p_; + } + +private: + friend class directory_iterator; + friend class recursive_directory_iterator; + friend class __dir_stream; + + enum _CacheType : unsigned char { + _Empty, + _IterSymlink, + _IterNonSymlink, + _RefreshSymlink, + _RefreshSymlinkUnresolved, + _RefreshNonSymlink + }; + + struct __cached_data { + uintmax_t __size_; + uintmax_t __nlink_; + file_time_type __write_time_; + perms __sym_perms_; + perms __non_sym_perms_; + file_type __type_; + _CacheType __cache_type_; + + _LIBCPP_INLINE_VISIBILITY + __cached_data() noexcept { __reset(); } + + _LIBCPP_INLINE_VISIBILITY + void __reset() { + __cache_type_ = _Empty; + __type_ = file_type::none; + __sym_perms_ = __non_sym_perms_ = perms::unknown; + __size_ = __nlink_ = uintmax_t(-1); + __write_time_ = file_time_type::min(); + } + }; + + _LIBCPP_INLINE_VISIBILITY + static __cached_data __create_iter_result(file_type __ft) { + __cached_data __data; + __data.__type_ = __ft; + __data.__cache_type_ = [&]() { + switch (__ft) { + case file_type::none: + return _Empty; + case file_type::symlink: + return _IterSymlink; + default: + return _IterNonSymlink; + } + }(); + return __data; + } + + _LIBCPP_INLINE_VISIBILITY + void __assign_iter_entry(_Path&& __p, __cached_data __dt) { + __p_ = std::move(__p); + __data_ = __dt; + } + + _LIBCPP_FUNC_VIS + error_code __do_refresh() noexcept; + + _LIBCPP_INLINE_VISIBILITY + static bool __is_dne_error(error_code const& __ec) { + if (!__ec) + return true; + switch (static_cast(__ec.value())) { + case errc::no_such_file_or_directory: + case errc::not_a_directory: + return true; + default: + return false; + } + } + + _LIBCPP_INLINE_VISIBILITY + void __handle_error(const char* __msg, error_code* __dest_ec, + error_code const& __ec, bool __allow_dne = false) const { + if (__dest_ec) { + *__dest_ec = __ec; + return; + } + if (__ec && (!__allow_dne || !__is_dne_error(__ec))) + __throw_filesystem_error(__msg, __p_, __ec); + } + + _LIBCPP_INLINE_VISIBILITY + void __refresh(error_code* __ec = nullptr) { + __handle_error("in directory_entry::refresh", __ec, __do_refresh(), + /*allow_dne*/ true); + } + + _LIBCPP_INLINE_VISIBILITY + file_type __get_sym_ft(error_code* __ec = nullptr) const { + switch (__data_.__cache_type_) { + case _Empty: + return __symlink_status(__p_, __ec).type(); + case _IterSymlink: + case _RefreshSymlink: + case _RefreshSymlinkUnresolved: + if (__ec) + __ec->clear(); + return file_type::symlink; + case _IterNonSymlink: + case _RefreshNonSymlink: + file_status __st(__data_.__type_); + if (__ec && !_VSTD_FS::exists(__st)) + *__ec = make_error_code(errc::no_such_file_or_directory); + else if (__ec) + __ec->clear(); + return __data_.__type_; + } + _LIBCPP_UNREACHABLE(); + } + + _LIBCPP_INLINE_VISIBILITY + file_type __get_ft(error_code* __ec = nullptr) const { + switch (__data_.__cache_type_) { + case _Empty: + case _IterSymlink: + case _RefreshSymlinkUnresolved: + return __status(__p_, __ec).type(); + case _IterNonSymlink: + case _RefreshNonSymlink: + case _RefreshSymlink: { + file_status __st(__data_.__type_); + if (__ec && !_VSTD_FS::exists(__st)) + *__ec = make_error_code(errc::no_such_file_or_directory); + else if (__ec) + __ec->clear(); + return __data_.__type_; + } + } + _LIBCPP_UNREACHABLE(); + } + + _LIBCPP_INLINE_VISIBILITY + file_status __get_status(error_code* __ec = nullptr) const { + switch (__data_.__cache_type_) { + case _Empty: + case _IterNonSymlink: + case _IterSymlink: + case _RefreshSymlinkUnresolved: + return __status(__p_, __ec); + case _RefreshNonSymlink: + case _RefreshSymlink: + return file_status(__get_ft(__ec), __data_.__non_sym_perms_); + } + _LIBCPP_UNREACHABLE(); + } + + _LIBCPP_INLINE_VISIBILITY + file_status __get_symlink_status(error_code* __ec = nullptr) const { + switch (__data_.__cache_type_) { + case _Empty: + case _IterNonSymlink: + case _IterSymlink: + return __symlink_status(__p_, __ec); + case _RefreshNonSymlink: + return file_status(__get_sym_ft(__ec), __data_.__non_sym_perms_); + case _RefreshSymlink: + case _RefreshSymlinkUnresolved: + return file_status(__get_sym_ft(__ec), __data_.__sym_perms_); + } + _LIBCPP_UNREACHABLE(); + } + + _LIBCPP_INLINE_VISIBILITY + uintmax_t __get_size(error_code* __ec = nullptr) const { + switch (__data_.__cache_type_) { + case _Empty: + case _IterNonSymlink: + case _IterSymlink: + case _RefreshSymlinkUnresolved: + return _VSTD_FS::__file_size(__p_, __ec); + case _RefreshSymlink: + case _RefreshNonSymlink: { + error_code __m_ec; + file_status __st(__get_ft(&__m_ec)); + __handle_error("in directory_entry::file_size", __ec, __m_ec); + if (_VSTD_FS::exists(__st) && !_VSTD_FS::is_regular_file(__st)) { + errc __err_kind = _VSTD_FS::is_directory(__st) ? errc::is_a_directory + : errc::not_supported; + __handle_error("in directory_entry::file_size", __ec, + make_error_code(__err_kind)); + } + return __data_.__size_; + } + } + _LIBCPP_UNREACHABLE(); + } + + _LIBCPP_INLINE_VISIBILITY + uintmax_t __get_nlink(error_code* __ec = nullptr) const { + switch (__data_.__cache_type_) { + case _Empty: + case _IterNonSymlink: + case _IterSymlink: + case _RefreshSymlinkUnresolved: + return _VSTD_FS::__hard_link_count(__p_, __ec); + case _RefreshSymlink: + case _RefreshNonSymlink: { + error_code __m_ec; + (void)__get_ft(&__m_ec); + __handle_error("in directory_entry::hard_link_count", __ec, __m_ec); + return __data_.__nlink_; + } + } + _LIBCPP_UNREACHABLE(); + } + + _LIBCPP_INLINE_VISIBILITY + file_time_type __get_write_time(error_code* __ec = nullptr) const { + switch (__data_.__cache_type_) { + case _Empty: + case _IterNonSymlink: + case _IterSymlink: + case _RefreshSymlinkUnresolved: + return _VSTD_FS::__last_write_time(__p_, __ec); + case _RefreshSymlink: + case _RefreshNonSymlink: { + error_code __m_ec; + file_status __st(__get_ft(&__m_ec)); + __handle_error("in directory_entry::last_write_time", __ec, __m_ec); + if (_VSTD_FS::exists(__st) && + __data_.__write_time_ == file_time_type::min()) + __handle_error("in directory_entry::last_write_time", __ec, + make_error_code(errc::value_too_large)); + return __data_.__write_time_; + } + } + _LIBCPP_UNREACHABLE(); + } + +private: + _Path __p_; + __cached_data __data_; +}; + +class __dir_element_proxy { +public: + inline _LIBCPP_INLINE_VISIBILITY directory_entry operator*() { + return _VSTD::move(__elem_); + } + +private: + friend class directory_iterator; + friend class recursive_directory_iterator; + explicit __dir_element_proxy(directory_entry const& __e) : __elem_(__e) {} + __dir_element_proxy(__dir_element_proxy&& __o) + : __elem_(_VSTD::move(__o.__elem_)) {} + directory_entry __elem_; +}; + +class directory_iterator { +public: + typedef directory_entry value_type; + typedef ptrdiff_t difference_type; + typedef value_type const* pointer; + typedef value_type const& reference; + typedef input_iterator_tag iterator_category; + +public: + //ctor & dtor + directory_iterator() noexcept {} + + explicit directory_iterator(const path& __p) + : directory_iterator(__p, nullptr) {} + + directory_iterator(const path& __p, directory_options __opts) + : directory_iterator(__p, nullptr, __opts) {} + + directory_iterator(const path& __p, error_code& __ec) + : directory_iterator(__p, &__ec) {} + + directory_iterator(const path& __p, directory_options __opts, + error_code& __ec) + : directory_iterator(__p, &__ec, __opts) {} + + directory_iterator(const directory_iterator&) = default; + directory_iterator(directory_iterator&&) = default; + directory_iterator& operator=(const directory_iterator&) = default; + + directory_iterator& operator=(directory_iterator&& __o) noexcept { + // non-default implementation provided to support self-move assign. + if (this != &__o) { + __imp_ = _VSTD::move(__o.__imp_); + } + return *this; + } + + ~directory_iterator() = default; + + const directory_entry& operator*() const { + _LIBCPP_ASSERT(__imp_, "The end iterator cannot be dereferenced"); + return __dereference(); + } + + const directory_entry* operator->() const { return &**this; } + + directory_iterator& operator++() { return __increment(); } + + __dir_element_proxy operator++(int) { + __dir_element_proxy __p(**this); + __increment(); + return __p; + } + + directory_iterator& increment(error_code& __ec) { return __increment(&__ec); } + +private: + inline _LIBCPP_INLINE_VISIBILITY friend bool + operator==(const directory_iterator& __lhs, + const directory_iterator& __rhs) noexcept; + + // construct the dir_stream + _LIBCPP_FUNC_VIS + directory_iterator(const path&, error_code*, + directory_options = directory_options::none); + + _LIBCPP_FUNC_VIS + directory_iterator& __increment(error_code* __ec = nullptr); + + _LIBCPP_FUNC_VIS + const directory_entry& __dereference() const; + +private: + shared_ptr<__dir_stream> __imp_; +}; + +inline _LIBCPP_INLINE_VISIBILITY bool +operator==(const directory_iterator& __lhs, + const directory_iterator& __rhs) noexcept { + return __lhs.__imp_ == __rhs.__imp_; +} + +inline _LIBCPP_INLINE_VISIBILITY bool +operator!=(const directory_iterator& __lhs, + const directory_iterator& __rhs) noexcept { + return !(__lhs == __rhs); +} + +// enable directory_iterator range-based for statements +inline _LIBCPP_INLINE_VISIBILITY directory_iterator +begin(directory_iterator __iter) noexcept { + return __iter; +} + +inline _LIBCPP_INLINE_VISIBILITY directory_iterator +end(const directory_iterator&) noexcept { + return directory_iterator(); +} + +class recursive_directory_iterator { +public: + using value_type = directory_entry; + using difference_type = std::ptrdiff_t; + using pointer = directory_entry const*; + using reference = directory_entry const&; + using iterator_category = std::input_iterator_tag; + +public: + // constructors and destructor + _LIBCPP_INLINE_VISIBILITY + recursive_directory_iterator() noexcept : __rec_(false) {} + + _LIBCPP_INLINE_VISIBILITY + explicit recursive_directory_iterator( + const path& __p, directory_options __xoptions = directory_options::none) + : recursive_directory_iterator(__p, __xoptions, nullptr) {} + + _LIBCPP_INLINE_VISIBILITY + recursive_directory_iterator(const path& __p, directory_options __xoptions, + error_code& __ec) + : recursive_directory_iterator(__p, __xoptions, &__ec) {} + + _LIBCPP_INLINE_VISIBILITY + recursive_directory_iterator(const path& __p, error_code& __ec) + : recursive_directory_iterator(__p, directory_options::none, &__ec) {} + + recursive_directory_iterator(const recursive_directory_iterator&) = default; + recursive_directory_iterator(recursive_directory_iterator&&) = default; + + recursive_directory_iterator& + operator=(const recursive_directory_iterator&) = default; + + _LIBCPP_INLINE_VISIBILITY + recursive_directory_iterator& + operator=(recursive_directory_iterator&& __o) noexcept { + // non-default implementation provided to support self-move assign. + if (this != &__o) { + __imp_ = _VSTD::move(__o.__imp_); + __rec_ = __o.__rec_; + } + return *this; + } + + ~recursive_directory_iterator() = default; + + _LIBCPP_INLINE_VISIBILITY + const directory_entry& operator*() const { return __dereference(); } + + _LIBCPP_INLINE_VISIBILITY + const directory_entry* operator->() const { return &__dereference(); } + + recursive_directory_iterator& operator++() { return __increment(); } + + _LIBCPP_INLINE_VISIBILITY + __dir_element_proxy operator++(int) { + __dir_element_proxy __p(**this); + __increment(); + return __p; + } + + _LIBCPP_INLINE_VISIBILITY + recursive_directory_iterator& increment(error_code& __ec) { + return __increment(&__ec); + } + + _LIBCPP_FUNC_VIS directory_options options() const; + _LIBCPP_FUNC_VIS int depth() const; + + _LIBCPP_INLINE_VISIBILITY + void pop() { __pop(); } + + _LIBCPP_INLINE_VISIBILITY + void pop(error_code& __ec) { __pop(&__ec); } + + _LIBCPP_INLINE_VISIBILITY + bool recursion_pending() const { return __rec_; } + + _LIBCPP_INLINE_VISIBILITY + void disable_recursion_pending() { __rec_ = false; } + +private: + recursive_directory_iterator(const path& __p, directory_options __opt, + error_code* __ec); + + _LIBCPP_FUNC_VIS + const directory_entry& __dereference() const; + + _LIBCPP_FUNC_VIS + bool __try_recursion(error_code* __ec); + + _LIBCPP_FUNC_VIS + void __advance(error_code* __ec = nullptr); + + _LIBCPP_FUNC_VIS + recursive_directory_iterator& __increment(error_code* __ec = nullptr); + + _LIBCPP_FUNC_VIS + void __pop(error_code* __ec = nullptr); + + inline _LIBCPP_INLINE_VISIBILITY friend bool + operator==(const recursive_directory_iterator&, + const recursive_directory_iterator&) noexcept; + + struct __shared_imp; + shared_ptr<__shared_imp> __imp_; + bool __rec_; +}; // class recursive_directory_iterator + +inline _LIBCPP_INLINE_VISIBILITY bool +operator==(const recursive_directory_iterator& __lhs, + const recursive_directory_iterator& __rhs) noexcept { + return __lhs.__imp_ == __rhs.__imp_; +} + +_LIBCPP_INLINE_VISIBILITY +inline bool operator!=(const recursive_directory_iterator& __lhs, + const recursive_directory_iterator& __rhs) noexcept { + return !(__lhs == __rhs); +} +// enable recursive_directory_iterator range-based for statements +inline _LIBCPP_INLINE_VISIBILITY recursive_directory_iterator +begin(recursive_directory_iterator __iter) noexcept { + return __iter; +} + +inline _LIBCPP_INLINE_VISIBILITY recursive_directory_iterator +end(const recursive_directory_iterator&) noexcept { + return recursive_directory_iterator(); +} + +} // namespace android::hardware::automotive::filesystem +#ifdef _LIBAUTO_UNDEF_VSTD +#undef _VSTD +#undef _LIBAUTO_UNDEF_VSTD +#endif + +#ifndef _LIBAUTO_UNDEF_VSTD_FS +#pragma pop_macro("_VSTD_FS") +#else +#undef _VSTD +#undef _LIBAUTO_UNDEF_VSTD_FS +#endif + +#endif // !_LIBCPP_CXX03_LANG + +_LIBCPP_POP_MACROS + +#endif // _LIBAUTO_FILESYSTEM diff --git a/automotive/can/1.0/default/libc++fs/src/filesystem/directory_iterator.cpp b/automotive/can/1.0/default/libc++fs/src/filesystem/directory_iterator.cpp new file mode 100644 index 0000000000..624538bd77 --- /dev/null +++ b/automotive/can/1.0/default/libc++fs/src/filesystem/directory_iterator.cpp @@ -0,0 +1,397 @@ +//===------------------ directory_iterator.cpp ----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/* clang-format off */ +#include "automotive/filesystem" +#include <__config> +#if defined(_LIBCPP_WIN32API) +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#endif +#include + +#include "filesystem_common.h" + +namespace android::hardware::automotive::filesystem { + +namespace detail { +namespace { + +#if !defined(_LIBCPP_WIN32API) +template +static file_type get_file_type(DirEntT* ent, int) { + switch (ent->d_type) { + case DT_BLK: + return file_type::block; + case DT_CHR: + return file_type::character; + case DT_DIR: + return file_type::directory; + case DT_FIFO: + return file_type::fifo; + case DT_LNK: + return file_type::symlink; + case DT_REG: + return file_type::regular; + case DT_SOCK: + return file_type::socket; + // Unlike in lstat, hitting "unknown" here simply means that the underlying + // filesystem doesn't support d_type. Report is as 'none' so we correctly + // set the cache to empty. + case DT_UNKNOWN: + break; + } + return file_type::none; +} + +template +static file_type get_file_type(DirEntT* ent, long) { + return file_type::none; +} + +static pair posix_readdir(DIR* dir_stream, + error_code& ec) { + struct dirent* dir_entry_ptr = nullptr; + errno = 0; // zero errno in order to detect errors + ec.clear(); + if ((dir_entry_ptr = ::readdir(dir_stream)) == nullptr) { + if (errno) + ec = capture_errno(); + return {}; + } else { + return {dir_entry_ptr->d_name, get_file_type(dir_entry_ptr, 0)}; + } +} +#else + +static file_type get_file_type(const WIN32_FIND_DATA& data) { + //auto attrs = data.dwFileAttributes; + // FIXME(EricWF) + return file_type::unknown; +} +static uintmax_t get_file_size(const WIN32_FIND_DATA& data) { + return (data.nFileSizeHight * (MAXDWORD + 1)) + data.nFileSizeLow; +} +static file_time_type get_write_time(const WIN32_FIND_DATA& data) { + ULARGE_INTEGER tmp; + FILETIME& time = data.ftLastWriteTime; + tmp.u.LowPart = time.dwLowDateTime; + tmp.u.HighPart = time.dwHighDateTime; + return file_time_type(file_time_type::duration(time.QuadPart)); +} + +#endif + +} // namespace +} // namespace detail + +using detail::ErrorHandler; + +#if defined(_LIBCPP_WIN32API) +class __dir_stream { +public: + __dir_stream() = delete; + __dir_stream& operator=(const __dir_stream&) = delete; + + __dir_stream(__dir_stream&& __ds) noexcept : __stream_(__ds.__stream_), + __root_(move(__ds.__root_)), + __entry_(move(__ds.__entry_)) { + __ds.__stream_ = INVALID_HANDLE_VALUE; + } + + __dir_stream(const path& root, directory_options opts, error_code& ec) + : __stream_(INVALID_HANDLE_VALUE), __root_(root) { + __stream_ = ::FindFirstFileEx(root.c_str(), &__data_); + if (__stream_ == INVALID_HANDLE_VALUE) { + ec = error_code(::GetLastError(), generic_category()); + const bool ignore_permission_denied = + bool(opts & directory_options::skip_permission_denied); + if (ignore_permission_denied && ec.value() == ERROR_ACCESS_DENIED) + ec.clear(); + return; + } + } + + ~__dir_stream() noexcept { + if (__stream_ == INVALID_HANDLE_VALUE) + return; + close(); + } + + bool good() const noexcept { return __stream_ != INVALID_HANDLE_VALUE; } + + bool advance(error_code& ec) { + while (::FindNextFile(__stream_, &__data_)) { + if (!strcmp(__data_.cFileName, ".") || strcmp(__data_.cFileName, "..")) + continue; + // FIXME: Cache more of this + //directory_entry::__cached_data cdata; + //cdata.__type_ = get_file_type(__data_); + //cdata.__size_ = get_file_size(__data_); + //cdata.__write_time_ = get_write_time(__data_); + __entry_.__assign_iter_entry( + __root_ / __data_.cFileName, + directory_entry::__create_iter_result(get_file_type(__data))); + return true; + } + ec = error_code(::GetLastError(), generic_category()); + close(); + return false; + } + +private: + error_code close() noexcept { + error_code ec; + if (!::FindClose(__stream_)) + ec = error_code(::GetLastError(), generic_category()); + __stream_ = INVALID_HANDLE_VALUE; + return ec; + } + + HANDLE __stream_{INVALID_HANDLE_VALUE}; + WIN32_FIND_DATA __data_; + +public: + path __root_; + directory_entry __entry_; +}; +#else +class __dir_stream { +public: + __dir_stream() = delete; + __dir_stream& operator=(const __dir_stream&) = delete; + + __dir_stream(__dir_stream&& other) noexcept : __stream_(other.__stream_), + __root_(move(other.__root_)), + __entry_(move(other.__entry_)) { + other.__stream_ = nullptr; + } + + __dir_stream(const path& root, directory_options opts, error_code& ec) + : __stream_(nullptr), __root_(root) { + if ((__stream_ = ::opendir(root.c_str())) == nullptr) { + ec = detail::capture_errno(); + const bool allow_eacess = + bool(opts & directory_options::skip_permission_denied); + if (allow_eacess && ec.value() == EACCES) + ec.clear(); + return; + } + advance(ec); + } + + ~__dir_stream() noexcept { + if (__stream_) + close(); + } + + bool good() const noexcept { return __stream_ != nullptr; } + + bool advance(error_code& ec) { + while (true) { + auto str_type_pair = detail::posix_readdir(__stream_, ec); + auto& str = str_type_pair.first; + if (str == "." || str == "..") { + continue; + } else if (ec || str.empty()) { + close(); + return false; + } else { + __entry_.__assign_iter_entry( + __root_ / str, + directory_entry::__create_iter_result(str_type_pair.second)); + return true; + } + } + } + +private: + error_code close() noexcept { + error_code m_ec; + if (::closedir(__stream_) == -1) + m_ec = detail::capture_errno(); + __stream_ = nullptr; + return m_ec; + } + + DIR* __stream_{nullptr}; + +public: + path __root_; + directory_entry __entry_; +}; +#endif + +// directory_iterator + +directory_iterator::directory_iterator(const path& p, error_code* ec, + directory_options opts) { + ErrorHandler err("directory_iterator::directory_iterator(...)", ec, &p); + + error_code m_ec; + __imp_ = make_shared<__dir_stream>(p, opts, m_ec); + if (ec) + *ec = m_ec; + if (!__imp_->good()) { + __imp_.reset(); + if (m_ec) + err.report(m_ec); + } +} + +directory_iterator& directory_iterator::__increment(error_code* ec) { + _LIBCPP_ASSERT(__imp_, "Attempting to increment an invalid iterator"); + ErrorHandler err("directory_iterator::operator++()", ec); + + error_code m_ec; + if (!__imp_->advance(m_ec)) { + path root = move(__imp_->__root_); + __imp_.reset(); + if (m_ec) + err.report(m_ec, "at root \"%s\"", root); + } + return *this; +} + +directory_entry const& directory_iterator::__dereference() const { + _LIBCPP_ASSERT(__imp_, "Attempting to dereference an invalid iterator"); + return __imp_->__entry_; +} + +// recursive_directory_iterator + +struct recursive_directory_iterator::__shared_imp { + stack<__dir_stream> __stack_; + directory_options __options_; +}; + +recursive_directory_iterator::recursive_directory_iterator( + const path& p, directory_options opt, error_code* ec) + : __imp_(nullptr), __rec_(true) { + ErrorHandler err("recursive_directory_iterator", ec, &p); + + error_code m_ec; + __dir_stream new_s(p, opt, m_ec); + if (m_ec) + err.report(m_ec); + if (m_ec || !new_s.good()) + return; + + __imp_ = make_shared<__shared_imp>(); + __imp_->__options_ = opt; + __imp_->__stack_.push(move(new_s)); +} + +void recursive_directory_iterator::__pop(error_code* ec) { + _LIBCPP_ASSERT(__imp_, "Popping the end iterator"); + if (ec) + ec->clear(); + __imp_->__stack_.pop(); + if (__imp_->__stack_.size() == 0) + __imp_.reset(); + else + __advance(ec); +} + +directory_options recursive_directory_iterator::options() const { + return __imp_->__options_; +} + +int recursive_directory_iterator::depth() const { + return __imp_->__stack_.size() - 1; +} + +const directory_entry& recursive_directory_iterator::__dereference() const { + return __imp_->__stack_.top().__entry_; +} + +recursive_directory_iterator& +recursive_directory_iterator::__increment(error_code* ec) { + if (ec) + ec->clear(); + if (recursion_pending()) { + if (__try_recursion(ec) || (ec && *ec)) + return *this; + } + __rec_ = true; + __advance(ec); + return *this; +} + +void recursive_directory_iterator::__advance(error_code* ec) { + ErrorHandler err("recursive_directory_iterator::operator++()", ec); + + const directory_iterator end_it; + auto& stack = __imp_->__stack_; + error_code m_ec; + while (stack.size() > 0) { + if (stack.top().advance(m_ec)) + return; + if (m_ec) + break; + stack.pop(); + } + + if (m_ec) { + path root = move(stack.top().__root_); + __imp_.reset(); + err.report(m_ec, "at root \"%s\"", root); + } else { + __imp_.reset(); + } +} + +bool recursive_directory_iterator::__try_recursion(error_code* ec) { + ErrorHandler err("recursive_directory_iterator::operator++()", ec); + + bool rec_sym = bool(options() & directory_options::follow_directory_symlink); + + auto& curr_it = __imp_->__stack_.top(); + + bool skip_rec = false; + error_code m_ec; + if (!rec_sym) { + file_status st(curr_it.__entry_.__get_sym_ft(&m_ec)); + if (m_ec && status_known(st)) + m_ec.clear(); + if (m_ec || is_symlink(st) || !is_directory(st)) + skip_rec = true; + } else { + file_status st(curr_it.__entry_.__get_ft(&m_ec)); + if (m_ec && status_known(st)) + m_ec.clear(); + if (m_ec || !is_directory(st)) + skip_rec = true; + } + + if (!skip_rec) { + __dir_stream new_it(curr_it.__entry_.path(), __imp_->__options_, m_ec); + if (new_it.good()) { + __imp_->__stack_.push(move(new_it)); + return true; + } + } + if (m_ec) { + const bool allow_eacess = + bool(__imp_->__options_ & directory_options::skip_permission_denied); + if (m_ec.value() == EACCES && allow_eacess) { + if (ec) + ec->clear(); + } else { + path at_ent = move(curr_it.__entry_.__p_); + __imp_.reset(); + err.report(m_ec, "attempting recursion into \"%s\"", at_ent); + } + } + return false; +} + +} // namespace android::hardware::automotive::filesystem +/* clang-format on */ diff --git a/automotive/can/1.0/default/libc++fs/src/filesystem/filesystem_common.h b/automotive/can/1.0/default/libc++fs/src/filesystem/filesystem_common.h new file mode 100644 index 0000000000..4f4466120a --- /dev/null +++ b/automotive/can/1.0/default/libc++fs/src/filesystem/filesystem_common.h @@ -0,0 +1,441 @@ +//===----------------------------------------------------------------------===//// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===//// +/* clang-format off */ +#ifndef AUTO_FILESYSTEM_COMMON_H +#define AUTO_FILESYSTEM_COMMON_H + +#include "automotive/filesystem" +#include +#include +#include +#include + +#include +#include +#include +#include // for ::utimes as used in __last_write_time +#include /* values for fchmodat */ + +#if !defined(__APPLE__) +// We can use the presence of UTIME_OMIT to detect platforms that provide +// utimensat. +#if defined(UTIME_OMIT) +#define _LIBCPP_USE_UTIMENSAT +#endif +#endif + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +namespace android::hardware::automotive::filesystem { +using namespace std::chrono; + +using std::error_code; +using std::is_floating_point; +using std::micro; +using std::nano; +using std::ratio; + +namespace detail { +namespace { + +static string format_string_imp(const char* msg, ...) { + // we might need a second shot at this, so pre-emptivly make a copy + struct GuardVAList { + va_list& target; + bool active = true; + GuardVAList(va_list& target) : target(target), active(true) {} + void clear() { + if (active) + va_end(target); + active = false; + } + ~GuardVAList() { + if (active) + va_end(target); + } + }; + va_list args; + va_start(args, msg); + GuardVAList args_guard(args); + + va_list args_cp; + va_copy(args_cp, args); + GuardVAList args_copy_guard(args_cp); + + std::string result; + + array local_buff; + size_t size_with_null = local_buff.size(); + auto ret = ::vsnprintf(local_buff.data(), size_with_null, msg, args_cp); + + args_copy_guard.clear(); + + // handle empty expansion + if (ret == 0) + return result; + if (static_cast(ret) < size_with_null) { + result.assign(local_buff.data(), static_cast(ret)); + return result; + } + + // we did not provide a long enough buffer on our first attempt. The + // return value is the number of bytes (excluding the null byte) that are + // needed for formatting. + size_with_null = static_cast(ret) + 1; + result.__resize_default_init(size_with_null - 1); + ret = ::vsnprintf(&result[0], size_with_null, msg, args); + _LIBCPP_ASSERT(static_cast(ret) == (size_with_null - 1), "TODO"); + + return result; +} + +const char* unwrap(string const& s) { return s.c_str(); } +const char* unwrap(path const& p) { return p.native().c_str(); } +template +Arg const& unwrap(Arg const& a) { + static_assert(!is_class::value, "cannot pass class here"); + return a; +} + +template +string format_string(const char* fmt, Args const&... args) { + return format_string_imp(fmt, unwrap(args)...); +} + +error_code capture_errno() { + _LIBCPP_ASSERT(errno, "Expected errno to be non-zero"); + return error_code(errno, generic_category()); +} + +template +T error_value(); +template <> +_LIBCPP_CONSTEXPR_AFTER_CXX11 void error_value() {} +template <> +bool error_value() { + return false; +} +template <> +uintmax_t error_value() { + return uintmax_t(-1); +} +template <> +_LIBCPP_CONSTEXPR_AFTER_CXX11 file_time_type error_value() { + return file_time_type::min(); +} +template <> +path error_value() { + return {}; +} + +template +struct ErrorHandler { + const char* func_name; + error_code* ec = nullptr; + const path* p1 = nullptr; + const path* p2 = nullptr; + + ErrorHandler(const char* fname, error_code* ec, const path* p1 = nullptr, + const path* p2 = nullptr) + : func_name(fname), ec(ec), p1(p1), p2(p2) { + if (ec) + ec->clear(); + } + + T report(const error_code& m_ec) const { + if (ec) { + *ec = m_ec; + return error_value(); + } + string what = string("in ") + func_name; + switch (bool(p1) + bool(p2)) { + case 0: + __throw_filesystem_error(what, m_ec); + case 1: + __throw_filesystem_error(what, *p1, m_ec); + case 2: + __throw_filesystem_error(what, *p1, *p2, m_ec); + } + _LIBCPP_UNREACHABLE(); + } + + template + T report(const error_code& m_ec, const char* msg, Args const&... args) const { + if (ec) { + *ec = m_ec; + return error_value(); + } + string what = + string("in ") + func_name + ": " + format_string(msg, args...); + switch (bool(p1) + bool(p2)) { + case 0: + __throw_filesystem_error(what, m_ec); + case 1: + __throw_filesystem_error(what, *p1, m_ec); + case 2: + __throw_filesystem_error(what, *p1, *p2, m_ec); + } + _LIBCPP_UNREACHABLE(); + } + + T report(errc const& err) const { return report(make_error_code(err)); } + + template + T report(errc const& err, const char* msg, Args const&... args) const { + return report(make_error_code(err), msg, args...); + } + +private: + ErrorHandler(ErrorHandler const&) = delete; + ErrorHandler& operator=(ErrorHandler const&) = delete; +}; + +using chrono::duration; +using chrono::duration_cast; + +using TimeSpec = struct ::timespec; +using StatT = struct ::stat; + +template ::value> +struct time_util_base { + using rep = typename FileTimeT::rep; + using fs_duration = typename FileTimeT::duration; + using fs_seconds = duration; + using fs_nanoseconds = duration; + using fs_microseconds = duration; + + static constexpr rep max_seconds = + duration_cast(FileTimeT::duration::max()).count(); + + static constexpr rep max_nsec = + duration_cast(FileTimeT::duration::max() - + fs_seconds(max_seconds)) + .count(); + + static constexpr rep min_seconds = + duration_cast(FileTimeT::duration::min()).count(); + + static constexpr rep min_nsec_timespec = + duration_cast( + (FileTimeT::duration::min() - fs_seconds(min_seconds)) + + fs_seconds(1)) + .count(); + +private: +#if _LIBCPP_STD_VER > 11 && !defined(_LIBCPP_HAS_NO_CXX14_CONSTEXPR) + static constexpr fs_duration get_min_nsecs() { + return duration_cast( + fs_nanoseconds(min_nsec_timespec) - + duration_cast(fs_seconds(1))); + } + // Static assert that these values properly round trip. + static_assert(fs_seconds(min_seconds) + get_min_nsecs() == + FileTimeT::duration::min(), + "value doesn't roundtrip"); + + static constexpr bool check_range() { + // This kinda sucks, but it's what happens when we don't have __int128_t. + if (sizeof(TimeT) == sizeof(rep)) { + typedef duration > Years; + return duration_cast(fs_seconds(max_seconds)) > Years(250) && + duration_cast(fs_seconds(min_seconds)) < Years(-250); + } + return max_seconds >= numeric_limits::max() && + min_seconds <= numeric_limits::min(); + } + static_assert(check_range(), "the representable range is unacceptable small"); +#endif +}; + +template +struct time_util_base { + using rep = typename FileTimeT::rep; + using fs_duration = typename FileTimeT::duration; + using fs_seconds = duration; + using fs_nanoseconds = duration; + using fs_microseconds = duration; + + static const rep max_seconds; + static const rep max_nsec; + static const rep min_seconds; + static const rep min_nsec_timespec; +}; + +template +const typename FileTimeT::rep + time_util_base::max_seconds = + duration_cast(FileTimeT::duration::max()).count(); + +template +const typename FileTimeT::rep time_util_base::max_nsec = + duration_cast(FileTimeT::duration::max() - + fs_seconds(max_seconds)) + .count(); + +template +const typename FileTimeT::rep + time_util_base::min_seconds = + duration_cast(FileTimeT::duration::min()).count(); + +template +const typename FileTimeT::rep + time_util_base::min_nsec_timespec = + duration_cast((FileTimeT::duration::min() - + fs_seconds(min_seconds)) + + fs_seconds(1)) + .count(); + +template +struct time_util : time_util_base { + using Base = time_util_base; + using Base::max_nsec; + using Base::max_seconds; + using Base::min_nsec_timespec; + using Base::min_seconds; + + using typename Base::fs_duration; + using typename Base::fs_microseconds; + using typename Base::fs_nanoseconds; + using typename Base::fs_seconds; + +public: + template + static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool checked_set(CType* out, + ChronoType time) { + using Lim = numeric_limits; + if (time > Lim::max() || time < Lim::min()) + return false; + *out = static_cast(time); + return true; + } + + static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool is_representable(TimeSpecT tm) { + if (tm.tv_sec >= 0) { + return tm.tv_sec < max_seconds || + (tm.tv_sec == max_seconds && tm.tv_nsec <= max_nsec); + } else if (tm.tv_sec == (min_seconds - 1)) { + return tm.tv_nsec >= min_nsec_timespec; + } else { + return tm.tv_sec >= min_seconds; + } + } + + static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool is_representable(FileTimeT tm) { + auto secs = duration_cast(tm.time_since_epoch()); + auto nsecs = duration_cast(tm.time_since_epoch() - secs); + if (nsecs.count() < 0) { + secs = secs + fs_seconds(1); + nsecs = nsecs + fs_seconds(1); + } + using TLim = numeric_limits; + if (secs.count() >= 0) + return secs.count() <= TLim::max(); + return secs.count() >= TLim::min(); + } + + static _LIBCPP_CONSTEXPR_AFTER_CXX11 FileTimeT + convert_from_timespec(TimeSpecT tm) { + if (tm.tv_sec >= 0 || tm.tv_nsec == 0) { + return FileTimeT(fs_seconds(tm.tv_sec) + + duration_cast(fs_nanoseconds(tm.tv_nsec))); + } else { // tm.tv_sec < 0 + auto adj_subsec = duration_cast(fs_seconds(1) - + fs_nanoseconds(tm.tv_nsec)); + auto Dur = fs_seconds(tm.tv_sec + 1) - adj_subsec; + return FileTimeT(Dur); + } + } + + template + static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool + set_times_checked(TimeT* sec_out, SubSecT* subsec_out, FileTimeT tp) { + auto dur = tp.time_since_epoch(); + auto sec_dur = duration_cast(dur); + auto subsec_dur = duration_cast(dur - sec_dur); + // The tv_nsec and tv_usec fields must not be negative so adjust accordingly + if (subsec_dur.count() < 0) { + if (sec_dur.count() > min_seconds) { + sec_dur = sec_dur - fs_seconds(1); + subsec_dur = subsec_dur + fs_seconds(1); + } else { + subsec_dur = fs_nanoseconds::zero(); + } + } + return checked_set(sec_out, sec_dur.count()) && + checked_set(subsec_out, subsec_dur.count()); + } + static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool convert_to_timespec(TimeSpecT& dest, + FileTimeT tp) { + if (!is_representable(tp)) + return false; + return set_times_checked(&dest.tv_sec, &dest.tv_nsec, tp); + } +}; + +using fs_time = time_util; + +#if defined(__APPLE__) +TimeSpec extract_mtime(StatT const& st) { return st.st_mtimespec; } +TimeSpec extract_atime(StatT const& st) { return st.st_atimespec; } +#else +TimeSpec extract_mtime(StatT const& st) { return st.st_mtim; } +TimeSpec extract_atime(StatT const& st) { return st.st_atim; } +#endif + +// allow the utimes implementation to compile even it we're not going +// to use it. + +bool posix_utimes(const path& p, std::array const& TS, + error_code& ec) { + using namespace chrono; + auto Convert = [](long nsec) { + using int_type = decltype(std::declval< ::timeval>().tv_usec); + auto dur = duration_cast(nanoseconds(nsec)).count(); + return static_cast(dur); + }; + struct ::timeval ConvertedTS[2] = {{TS[0].tv_sec, Convert(TS[0].tv_nsec)}, + {TS[1].tv_sec, Convert(TS[1].tv_nsec)}}; + if (::utimes(p.c_str(), ConvertedTS) == -1) { + ec = capture_errno(); + return true; + } + return false; +} + +#if defined(_LIBCPP_USE_UTIMENSAT) +bool posix_utimensat(const path& p, std::array const& TS, + error_code& ec) { + if (::utimensat(AT_FDCWD, p.c_str(), TS.data(), 0) == -1) { + ec = capture_errno(); + return true; + } + return false; +} +#endif + +bool set_file_times(const path& p, std::array const& TS, + error_code& ec) { +#if !defined(_LIBCPP_USE_UTIMENSAT) + return posix_utimes(p, TS, ec); +#else + return posix_utimensat(p, TS, ec); +#endif +} + +} // namespace +} // end namespace detail + +} // namespace android::hardware::automotive::filesystem + +#endif // AUTO_FILESYSTEM_COMMON_H +/* clang-format on */ diff --git a/automotive/can/1.0/default/libc++fs/src/filesystem/operations.cpp b/automotive/can/1.0/default/libc++fs/src/filesystem/operations.cpp new file mode 100644 index 0000000000..404c0bd4c9 --- /dev/null +++ b/automotive/can/1.0/default/libc++fs/src/filesystem/operations.cpp @@ -0,0 +1,1773 @@ +//===--------------------- filesystem/ops.cpp -----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/* clang-format off */ +#include "automotive/filesystem" +#include +#include +#include +#include /* for unique_path */ +#include +#include +#include +#include +#include + +#include "filesystem_common.h" + +#include +#include +#include +#include +#include /* values for fchmodat */ + +#if defined(__linux__) +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33) +#include +#define _LIBCPP_USE_SENDFILE +#endif +#elif defined(__APPLE__) || __has_include() +#include +#define _LIBCPP_USE_COPYFILE +#endif + +#if !defined(__APPLE__) +#define _LIBCPP_USE_CLOCK_GETTIME +#endif + +#if !defined(CLOCK_REALTIME) || !defined(_LIBCPP_USE_CLOCK_GETTIME) +#include // for gettimeofday and timeval +#endif // !defined(CLOCK_REALTIME) + +#if defined(_LIBCPP_COMPILER_GCC) +#if _GNUC_VER < 500 +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif +#endif + +namespace android::hardware::automotive::filesystem { + +#ifdef _VSTD_FS +#pragma push_macro("_VSTD_FS") +#else +#define _LIBAUTO_UNDEF_VSTD_FS +#endif +#define _VSTD_FS android::hardware::automotive::filesystem + +namespace { +namespace parser { + +using string_view_t = path::__string_view; +using string_view_pair = pair; +using PosPtr = path::value_type const*; + +struct PathParser { + enum ParserState : unsigned char { + // Zero is a special sentinel value used by default constructed iterators. + PS_BeforeBegin = path::iterator::_BeforeBegin, + PS_InRootName = path::iterator::_InRootName, + PS_InRootDir = path::iterator::_InRootDir, + PS_InFilenames = path::iterator::_InFilenames, + PS_InTrailingSep = path::iterator::_InTrailingSep, + PS_AtEnd = path::iterator::_AtEnd + }; + + const string_view_t Path; + string_view_t RawEntry; + ParserState State; + +private: + PathParser(string_view_t P, ParserState State) noexcept : Path(P), + State(State) {} + +public: + PathParser(string_view_t P, string_view_t E, unsigned char S) + : Path(P), RawEntry(E), State(static_cast(S)) { + // S cannot be '0' or PS_BeforeBegin. + } + + static PathParser CreateBegin(string_view_t P) noexcept { + PathParser PP(P, PS_BeforeBegin); + PP.increment(); + return PP; + } + + static PathParser CreateEnd(string_view_t P) noexcept { + PathParser PP(P, PS_AtEnd); + return PP; + } + + PosPtr peek() const noexcept { + auto TkEnd = getNextTokenStartPos(); + auto End = getAfterBack(); + return TkEnd == End ? nullptr : TkEnd; + } + + void increment() noexcept { + const PosPtr End = getAfterBack(); + const PosPtr Start = getNextTokenStartPos(); + if (Start == End) + return makeState(PS_AtEnd); + + switch (State) { + case PS_BeforeBegin: { + PosPtr TkEnd = consumeSeparator(Start, End); + if (TkEnd) + return makeState(PS_InRootDir, Start, TkEnd); + else + return makeState(PS_InFilenames, Start, consumeName(Start, End)); + } + case PS_InRootDir: + return makeState(PS_InFilenames, Start, consumeName(Start, End)); + + case PS_InFilenames: { + PosPtr SepEnd = consumeSeparator(Start, End); + if (SepEnd != End) { + PosPtr TkEnd = consumeName(SepEnd, End); + if (TkEnd) + return makeState(PS_InFilenames, SepEnd, TkEnd); + } + return makeState(PS_InTrailingSep, Start, SepEnd); + } + + case PS_InTrailingSep: + return makeState(PS_AtEnd); + + case PS_InRootName: + case PS_AtEnd: + _LIBCPP_UNREACHABLE(); + } + } + + void decrement() noexcept { + const PosPtr REnd = getBeforeFront(); + const PosPtr RStart = getCurrentTokenStartPos() - 1; + if (RStart == REnd) // we're decrementing the begin + return makeState(PS_BeforeBegin); + + switch (State) { + case PS_AtEnd: { + // Try to consume a trailing separator or root directory first. + if (PosPtr SepEnd = consumeSeparator(RStart, REnd)) { + if (SepEnd == REnd) + return makeState(PS_InRootDir, Path.data(), RStart + 1); + return makeState(PS_InTrailingSep, SepEnd + 1, RStart + 1); + } else { + PosPtr TkStart = consumeName(RStart, REnd); + return makeState(PS_InFilenames, TkStart + 1, RStart + 1); + } + } + case PS_InTrailingSep: + return makeState(PS_InFilenames, consumeName(RStart, REnd) + 1, + RStart + 1); + case PS_InFilenames: { + PosPtr SepEnd = consumeSeparator(RStart, REnd); + if (SepEnd == REnd) + return makeState(PS_InRootDir, Path.data(), RStart + 1); + PosPtr TkEnd = consumeName(SepEnd, REnd); + return makeState(PS_InFilenames, TkEnd + 1, SepEnd + 1); + } + case PS_InRootDir: + // return makeState(PS_InRootName, Path.data(), RStart + 1); + case PS_InRootName: + case PS_BeforeBegin: + _LIBCPP_UNREACHABLE(); + } + } + + /// \brief Return a view with the "preferred representation" of the current + /// element. For example trailing separators are represented as a '.' + string_view_t operator*() const noexcept { + switch (State) { + case PS_BeforeBegin: + case PS_AtEnd: + return ""; + case PS_InRootDir: + return "/"; + case PS_InTrailingSep: + return ""; + case PS_InRootName: + case PS_InFilenames: + return RawEntry; + } + _LIBCPP_UNREACHABLE(); + } + + explicit operator bool() const noexcept { + return State != PS_BeforeBegin && State != PS_AtEnd; + } + + PathParser& operator++() noexcept { + increment(); + return *this; + } + + PathParser& operator--() noexcept { + decrement(); + return *this; + } + + bool atEnd() const noexcept { + return State == PS_AtEnd; + } + + bool inRootDir() const noexcept { + return State == PS_InRootDir; + } + + bool inRootName() const noexcept { + return State == PS_InRootName; + } + + bool inRootPath() const noexcept { + return inRootName() || inRootDir(); + } + +private: + void makeState(ParserState NewState, PosPtr Start, PosPtr End) noexcept { + State = NewState; + RawEntry = string_view_t(Start, End - Start); + } + void makeState(ParserState NewState) noexcept { + State = NewState; + RawEntry = {}; + } + + PosPtr getAfterBack() const noexcept { return Path.data() + Path.size(); } + + PosPtr getBeforeFront() const noexcept { return Path.data() - 1; } + + /// \brief Return a pointer to the first character after the currently + /// lexed element. + PosPtr getNextTokenStartPos() const noexcept { + switch (State) { + case PS_BeforeBegin: + return Path.data(); + case PS_InRootName: + case PS_InRootDir: + case PS_InFilenames: + return &RawEntry.back() + 1; + case PS_InTrailingSep: + case PS_AtEnd: + return getAfterBack(); + } + _LIBCPP_UNREACHABLE(); + } + + /// \brief Return a pointer to the first character in the currently lexed + /// element. + PosPtr getCurrentTokenStartPos() const noexcept { + switch (State) { + case PS_BeforeBegin: + case PS_InRootName: + return &Path.front(); + case PS_InRootDir: + case PS_InFilenames: + case PS_InTrailingSep: + return &RawEntry.front(); + case PS_AtEnd: + return &Path.back() + 1; + } + _LIBCPP_UNREACHABLE(); + } + + PosPtr consumeSeparator(PosPtr P, PosPtr End) const noexcept { + if (P == End || *P != '/') + return nullptr; + const int Inc = P < End ? 1 : -1; + P += Inc; + while (P != End && *P == '/') + P += Inc; + return P; + } + + PosPtr consumeName(PosPtr P, PosPtr End) const noexcept { + if (P == End || *P == '/') + return nullptr; + const int Inc = P < End ? 1 : -1; + P += Inc; + while (P != End && *P != '/') + P += Inc; + return P; + } +}; + +string_view_pair separate_filename(string_view_t const& s) { + if (s == "." || s == ".." || s.empty()) + return string_view_pair{s, ""}; + auto pos = s.find_last_of('.'); + if (pos == string_view_t::npos || pos == 0) + return string_view_pair{s, string_view_t{}}; + return string_view_pair{s.substr(0, pos), s.substr(pos)}; +} + +string_view_t createView(PosPtr S, PosPtr E) noexcept { + return {S, static_cast(E - S) + 1}; +} + +} // namespace parser +} // namespace + +// POSIX HELPERS + +namespace detail { +namespace { + +using value_type = path::value_type; +using string_type = path::string_type; + +struct FileDescriptor { + const path& name; + int fd = -1; + StatT m_stat; + file_status m_status; + + template + static FileDescriptor create(const path* p, error_code& ec, Args... args) { + ec.clear(); + int fd; + if ((fd = ::open(p->c_str(), args...)) == -1) { + ec = capture_errno(); + return FileDescriptor{p}; + } + return FileDescriptor(p, fd); + } + + template + static FileDescriptor create_with_status(const path* p, error_code& ec, + Args... args) { + FileDescriptor fd = create(p, ec, args...); + if (!ec) + fd.refresh_status(ec); + + return fd; + } + + file_status get_status() const { return m_status; } + StatT const& get_stat() const { return m_stat; } + + bool status_known() const { return _VSTD_FS::status_known(m_status); } + + file_status refresh_status(error_code& ec); + + void close() noexcept { + if (fd != -1) + ::close(fd); + fd = -1; + } + + FileDescriptor(FileDescriptor&& other) + : name(other.name), fd(other.fd), m_stat(other.m_stat), + m_status(other.m_status) { + other.fd = -1; + other.m_status = file_status{}; + } + + ~FileDescriptor() { close(); } + + FileDescriptor(FileDescriptor const&) = delete; + FileDescriptor& operator=(FileDescriptor const&) = delete; + +private: + explicit FileDescriptor(const path* p, int fd = -1) : name(*p), fd(fd) {} +}; + +perms posix_get_perms(const StatT& st) noexcept { + return static_cast(st.st_mode) & perms::mask; +} + +::mode_t posix_convert_perms(perms prms) { + return static_cast< ::mode_t>(prms & perms::mask); +} + +file_status create_file_status(error_code& m_ec, path const& p, + const StatT& path_stat, error_code* ec) { + if (ec) + *ec = m_ec; + if (m_ec && (m_ec.value() == ENOENT || m_ec.value() == ENOTDIR)) { + return file_status(file_type::not_found); + } else if (m_ec) { + ErrorHandler err("posix_stat", ec, &p); + err.report(m_ec, "failed to determine attributes for the specified path"); + return file_status(file_type::none); + } + // else + + file_status fs_tmp; + auto const mode = path_stat.st_mode; + if (S_ISLNK(mode)) + fs_tmp.type(file_type::symlink); + else if (S_ISREG(mode)) + fs_tmp.type(file_type::regular); + else if (S_ISDIR(mode)) + fs_tmp.type(file_type::directory); + else if (S_ISBLK(mode)) + fs_tmp.type(file_type::block); + else if (S_ISCHR(mode)) + fs_tmp.type(file_type::character); + else if (S_ISFIFO(mode)) + fs_tmp.type(file_type::fifo); + else if (S_ISSOCK(mode)) + fs_tmp.type(file_type::socket); + else + fs_tmp.type(file_type::unknown); + + fs_tmp.permissions(detail::posix_get_perms(path_stat)); + return fs_tmp; +} + +file_status posix_stat(path const& p, StatT& path_stat, error_code* ec) { + error_code m_ec; + if (::stat(p.c_str(), &path_stat) == -1) + m_ec = detail::capture_errno(); + return create_file_status(m_ec, p, path_stat, ec); +} + +file_status posix_stat(path const& p, error_code* ec) { + StatT path_stat; + return posix_stat(p, path_stat, ec); +} + +file_status posix_lstat(path const& p, StatT& path_stat, error_code* ec) { + error_code m_ec; + if (::lstat(p.c_str(), &path_stat) == -1) + m_ec = detail::capture_errno(); + return create_file_status(m_ec, p, path_stat, ec); +} + +file_status posix_lstat(path const& p, error_code* ec) { + StatT path_stat; + return posix_lstat(p, path_stat, ec); +} + +bool posix_ftruncate(const FileDescriptor& fd, size_t to_size, error_code& ec) { + if (::ftruncate(fd.fd, to_size) == -1) { + ec = capture_errno(); + return true; + } + ec.clear(); + return false; +} + +bool posix_fchmod(const FileDescriptor& fd, const StatT& st, error_code& ec) { + if (::fchmod(fd.fd, st.st_mode) == -1) { + ec = capture_errno(); + return true; + } + ec.clear(); + return false; +} + +bool stat_equivalent(const StatT& st1, const StatT& st2) { + return (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino); +} + +file_status FileDescriptor::refresh_status(error_code& ec) { + // FD must be open and good. + m_status = file_status{}; + m_stat = {}; + error_code m_ec; + if (::fstat(fd, &m_stat) == -1) + m_ec = capture_errno(); + m_status = create_file_status(m_ec, name, m_stat, &ec); + return m_status; +} +} // namespace +} // end namespace detail + +using detail::capture_errno; +using detail::ErrorHandler; +using detail::StatT; +using detail::TimeSpec; +using parser::createView; +using parser::PathParser; +using parser::string_view_t; + +const bool _FilesystemClock::is_steady; + +_FilesystemClock::time_point _FilesystemClock::now() noexcept { + typedef chrono::duration __secs; +#if defined(_LIBCPP_USE_CLOCK_GETTIME) && defined(CLOCK_REALTIME) + typedef chrono::duration __nsecs; + struct timespec tp; + if (0 != clock_gettime(CLOCK_REALTIME, &tp)) + __throw_system_error(errno, "clock_gettime(CLOCK_REALTIME) failed"); + return time_point(__secs(tp.tv_sec) + + chrono::duration_cast(__nsecs(tp.tv_nsec))); +#else + typedef chrono::duration __microsecs; + timeval tv; + gettimeofday(&tv, 0); + return time_point(__secs(tv.tv_sec) + __microsecs(tv.tv_usec)); +#endif // _LIBCPP_USE_CLOCK_GETTIME && CLOCK_REALTIME +} + +filesystem_error::~filesystem_error() {} + +void filesystem_error::__create_what(int __num_paths) { + const char* derived_what = system_error::what(); + __storage_->__what_ = [&]() -> string { + const char* p1 = path1().native().empty() ? "\"\"" : path1().c_str(); + const char* p2 = path2().native().empty() ? "\"\"" : path2().c_str(); + switch (__num_paths) { + default: + return detail::format_string("filesystem error: %s", derived_what); + case 1: + return detail::format_string("filesystem error: %s [%s]", derived_what, + p1); + case 2: + return detail::format_string("filesystem error: %s [%s] [%s]", + derived_what, p1, p2); + } + }(); +} + +static path __do_absolute(const path& p, path* cwd, error_code* ec) { + if (ec) + ec->clear(); + if (p.is_absolute()) + return p; + *cwd = __current_path(ec); + if (ec && *ec) + return {}; + return (*cwd) / p; +} + +path __absolute(const path& p, error_code* ec) { + path cwd; + return __do_absolute(p, &cwd, ec); +} + +path __canonical(path const& orig_p, error_code* ec) { + path cwd; + ErrorHandler err("canonical", ec, &orig_p, &cwd); + + path p = __do_absolute(orig_p, &cwd, ec); + char buff[PATH_MAX + 1]; + char* ret; + if ((ret = ::realpath(p.c_str(), buff)) == nullptr) + return err.report(capture_errno()); + return {ret}; +} + +void __copy(const path& from, const path& to, copy_options options, + error_code* ec) { + ErrorHandler err("copy", ec, &from, &to); + + const bool sym_status = bool( + options & (copy_options::create_symlinks | copy_options::skip_symlinks)); + + const bool sym_status2 = bool(options & copy_options::copy_symlinks); + + error_code m_ec1; + StatT f_st = {}; + const file_status f = sym_status || sym_status2 + ? detail::posix_lstat(from, f_st, &m_ec1) + : detail::posix_stat(from, f_st, &m_ec1); + if (m_ec1) + return err.report(m_ec1); + + StatT t_st = {}; + const file_status t = sym_status ? detail::posix_lstat(to, t_st, &m_ec1) + : detail::posix_stat(to, t_st, &m_ec1); + + if (not status_known(t)) + return err.report(m_ec1); + + if (!exists(f) || is_other(f) || is_other(t) || + (is_directory(f) && is_regular_file(t)) || + detail::stat_equivalent(f_st, t_st)) { + return err.report(errc::function_not_supported); + } + + if (ec) + ec->clear(); + + if (is_symlink(f)) { + if (bool(copy_options::skip_symlinks & options)) { + // do nothing + } else if (not exists(t)) { + __copy_symlink(from, to, ec); + } else { + return err.report(errc::file_exists); + } + return; + } else if (is_regular_file(f)) { + if (bool(copy_options::directories_only & options)) { + // do nothing + } else if (bool(copy_options::create_symlinks & options)) { + __create_symlink(from, to, ec); + } else if (bool(copy_options::create_hard_links & options)) { + __create_hard_link(from, to, ec); + } else if (is_directory(t)) { + __copy_file(from, to / from.filename(), options, ec); + } else { + __copy_file(from, to, options, ec); + } + return; + } else if (is_directory(f) && bool(copy_options::create_symlinks & options)) { + return err.report(errc::is_a_directory); + } else if (is_directory(f) && (bool(copy_options::recursive & options) || + copy_options::none == options)) { + + if (!exists(t)) { + // create directory to with attributes from 'from'. + __create_directory(to, from, ec); + if (ec && *ec) { + return; + } + } + directory_iterator it = + ec ? directory_iterator(from, *ec) : directory_iterator(from); + if (ec && *ec) { + return; + } + error_code m_ec2; + for (; it != directory_iterator(); it.increment(m_ec2)) { + if (m_ec2) { + return err.report(m_ec2); + } + __copy(it->path(), to / it->path().filename(), + options | copy_options::__in_recursive_copy, ec); + if (ec && *ec) { + return; + } + } + } +} + +namespace detail { +namespace { + +#ifdef _LIBCPP_USE_SENDFILE +bool copy_file_impl_sendfile(FileDescriptor& read_fd, FileDescriptor& write_fd, + error_code& ec) { + + size_t count = read_fd.get_stat().st_size; + do { + ssize_t res; + if ((res = ::sendfile(write_fd.fd, read_fd.fd, nullptr, count)) == -1) { + ec = capture_errno(); + return false; + } + count -= res; + } while (count > 0); + + ec.clear(); + + return true; +} +#elif defined(_LIBCPP_USE_COPYFILE) +bool copy_file_impl_copyfile(FileDescriptor& read_fd, FileDescriptor& write_fd, + error_code& ec) { + struct CopyFileState { + copyfile_state_t state; + CopyFileState() { state = copyfile_state_alloc(); } + ~CopyFileState() { copyfile_state_free(state); } + + private: + CopyFileState(CopyFileState const&) = delete; + CopyFileState& operator=(CopyFileState const&) = delete; + }; + + CopyFileState cfs; + if (fcopyfile(read_fd.fd, write_fd.fd, cfs.state, COPYFILE_DATA) < 0) { + ec = capture_errno(); + return false; + } + + ec.clear(); + return true; +} +#endif + +// Note: This function isn't guarded by ifdef's even though it may be unused +// in order to assure it still compiles. +__attribute__((unused)) bool copy_file_impl_default(FileDescriptor& read_fd, + FileDescriptor& write_fd, + error_code& ec) { + ifstream in; + in.__open(read_fd.fd, ios::binary); + if (!in.is_open()) { + // This assumes that __open didn't reset the error code. + ec = capture_errno(); + return false; + } + ofstream out; + out.__open(write_fd.fd, ios::binary); + if (!out.is_open()) { + ec = capture_errno(); + return false; + } + + if (in.good() && out.good()) { + using InIt = istreambuf_iterator; + using OutIt = ostreambuf_iterator; + InIt bin(in); + InIt ein; + OutIt bout(out); + copy(bin, ein, bout); + } + if (out.fail() || in.fail()) { + ec = make_error_code(errc::io_error); + return false; + } + + ec.clear(); + return true; +} + +bool copy_file_impl(FileDescriptor& from, FileDescriptor& to, error_code& ec) { +#if defined(_LIBCPP_USE_SENDFILE) + return copy_file_impl_sendfile(from, to, ec); +#elif defined(_LIBCPP_USE_COPYFILE) + return copy_file_impl_copyfile(from, to, ec); +#else + return copy_file_impl_default(from, to, ec); +#endif +} + +} // namespace +} // namespace detail + +bool __copy_file(const path& from, const path& to, copy_options options, + error_code* ec) { + using detail::FileDescriptor; + ErrorHandler err("copy_file", ec, &to, &from); + + error_code m_ec; + FileDescriptor from_fd = + FileDescriptor::create_with_status(&from, m_ec, O_RDONLY | O_NONBLOCK); + if (m_ec) + return err.report(m_ec); + + auto from_st = from_fd.get_status(); + StatT const& from_stat = from_fd.get_stat(); + if (!is_regular_file(from_st)) { + if (not m_ec) + m_ec = make_error_code(errc::not_supported); + return err.report(m_ec); + } + + const bool skip_existing = bool(copy_options::skip_existing & options); + const bool update_existing = bool(copy_options::update_existing & options); + const bool overwrite_existing = + bool(copy_options::overwrite_existing & options); + + StatT to_stat_path; + file_status to_st = detail::posix_stat(to, to_stat_path, &m_ec); + if (!status_known(to_st)) + return err.report(m_ec); + + const bool to_exists = exists(to_st); + if (to_exists && !is_regular_file(to_st)) + return err.report(errc::not_supported); + + if (to_exists && detail::stat_equivalent(from_stat, to_stat_path)) + return err.report(errc::file_exists); + + if (to_exists && skip_existing) + return false; + + bool ShouldCopy = [&]() { + if (to_exists && update_existing) { + auto from_time = detail::extract_mtime(from_stat); + auto to_time = detail::extract_mtime(to_stat_path); + if (from_time.tv_sec < to_time.tv_sec) + return false; + if (from_time.tv_sec == to_time.tv_sec && + from_time.tv_nsec <= to_time.tv_nsec) + return false; + return true; + } + if (!to_exists || overwrite_existing) + return true; + return err.report(errc::file_exists); + }(); + if (!ShouldCopy) + return false; + + // Don't truncate right away. We may not be opening the file we originally + // looked at; we'll check this later. + int to_open_flags = O_WRONLY; + if (!to_exists) + to_open_flags |= O_CREAT; + FileDescriptor to_fd = FileDescriptor::create_with_status( + &to, m_ec, to_open_flags, from_stat.st_mode); + if (m_ec) + return err.report(m_ec); + + if (to_exists) { + // Check that the file we initially stat'ed is equivalent to the one + // we opened. + // FIXME: report this better. + if (!detail::stat_equivalent(to_stat_path, to_fd.get_stat())) + return err.report(errc::bad_file_descriptor); + + // Set the permissions and truncate the file we opened. + if (detail::posix_fchmod(to_fd, from_stat, m_ec)) + return err.report(m_ec); + if (detail::posix_ftruncate(to_fd, 0, m_ec)) + return err.report(m_ec); + } + + if (!copy_file_impl(from_fd, to_fd, m_ec)) { + // FIXME: Remove the dest file if we failed, and it didn't exist previously. + return err.report(m_ec); + } + + return true; +} + +void __copy_symlink(const path& existing_symlink, const path& new_symlink, + error_code* ec) { + const path real_path(__read_symlink(existing_symlink, ec)); + if (ec && *ec) { + return; + } + // NOTE: proposal says you should detect if you should call + // create_symlink or create_directory_symlink. I don't think this + // is needed with POSIX + __create_symlink(real_path, new_symlink, ec); +} + +bool __create_directories(const path& p, error_code* ec) { + ErrorHandler err("create_directories", ec, &p); + + error_code m_ec; + auto const st = detail::posix_stat(p, &m_ec); + if (!status_known(st)) + return err.report(m_ec); + else if (is_directory(st)) + return false; + else if (exists(st)) + return err.report(errc::file_exists); + + const path parent = p.parent_path(); + if (!parent.empty()) { + const file_status parent_st = status(parent, m_ec); + if (not status_known(parent_st)) + return err.report(m_ec); + if (not exists(parent_st)) { + __create_directories(parent, ec); + if (ec && *ec) { + return false; + } + } + } + return __create_directory(p, ec); +} + +bool __create_directory(const path& p, error_code* ec) { + ErrorHandler err("create_directory", ec, &p); + + if (::mkdir(p.c_str(), static_cast(perms::all)) == 0) + return true; + if (errno != EEXIST) + err.report(capture_errno()); + return false; +} + +bool __create_directory(path const& p, path const& attributes, error_code* ec) { + ErrorHandler err("create_directory", ec, &p, &attributes); + + StatT attr_stat; + error_code mec; + auto st = detail::posix_stat(attributes, attr_stat, &mec); + if (!status_known(st)) + return err.report(mec); + if (!is_directory(st)) + return err.report(errc::not_a_directory, + "the specified attribute path is invalid"); + + if (::mkdir(p.c_str(), attr_stat.st_mode) == 0) + return true; + if (errno != EEXIST) + err.report(capture_errno()); + return false; +} + +void __create_directory_symlink(path const& from, path const& to, + error_code* ec) { + ErrorHandler err("create_directory_symlink", ec, &from, &to); + if (::symlink(from.c_str(), to.c_str()) != 0) + return err.report(capture_errno()); +} + +void __create_hard_link(const path& from, const path& to, error_code* ec) { + ErrorHandler err("create_hard_link", ec, &from, &to); + if (::link(from.c_str(), to.c_str()) == -1) + return err.report(capture_errno()); +} + +void __create_symlink(path const& from, path const& to, error_code* ec) { + ErrorHandler err("create_symlink", ec, &from, &to); + if (::symlink(from.c_str(), to.c_str()) == -1) + return err.report(capture_errno()); +} + +path __current_path(error_code* ec) { + ErrorHandler err("current_path", ec); + + auto size = ::pathconf(".", _PC_PATH_MAX); + _LIBCPP_ASSERT(size >= 0, "pathconf returned a 0 as max size"); + + auto buff = unique_ptr(new char[size + 1]); + char* ret; + if ((ret = ::getcwd(buff.get(), static_cast(size))) == nullptr) + return err.report(capture_errno(), "call to getcwd failed"); + + return {buff.get()}; +} + +void __current_path(const path& p, error_code* ec) { + ErrorHandler err("current_path", ec, &p); + if (::chdir(p.c_str()) == -1) + err.report(capture_errno()); +} + +bool __equivalent(const path& p1, const path& p2, error_code* ec) { + ErrorHandler err("equivalent", ec, &p1, &p2); + + error_code ec1, ec2; + StatT st1 = {}, st2 = {}; + auto s1 = detail::posix_stat(p1.native(), st1, &ec1); + if (!exists(s1)) + return err.report(errc::not_supported); + auto s2 = detail::posix_stat(p2.native(), st2, &ec2); + if (!exists(s2)) + return err.report(errc::not_supported); + + return detail::stat_equivalent(st1, st2); +} + +uintmax_t __file_size(const path& p, error_code* ec) { + ErrorHandler err("file_size", ec, &p); + + error_code m_ec; + StatT st; + file_status fst = detail::posix_stat(p, st, &m_ec); + if (!exists(fst) || !is_regular_file(fst)) { + errc error_kind = + is_directory(fst) ? errc::is_a_directory : errc::not_supported; + if (!m_ec) + m_ec = make_error_code(error_kind); + return err.report(m_ec); + } + // is_regular_file(p) == true + return static_cast(st.st_size); +} + +uintmax_t __hard_link_count(const path& p, error_code* ec) { + ErrorHandler err("hard_link_count", ec, &p); + + error_code m_ec; + StatT st; + detail::posix_stat(p, st, &m_ec); + if (m_ec) + return err.report(m_ec); + return static_cast(st.st_nlink); +} + +bool __fs_is_empty(const path& p, error_code* ec) { + ErrorHandler err("is_empty", ec, &p); + + error_code m_ec; + StatT pst; + auto st = detail::posix_stat(p, pst, &m_ec); + if (m_ec) + return err.report(m_ec); + else if (!is_directory(st) && !is_regular_file(st)) + return err.report(errc::not_supported); + else if (is_directory(st)) { + auto it = ec ? directory_iterator(p, *ec) : directory_iterator(p); + if (ec && *ec) + return false; + return it == directory_iterator{}; + } else if (is_regular_file(st)) + return static_cast(pst.st_size) == 0; + + _LIBCPP_UNREACHABLE(); +} + +static file_time_type __extract_last_write_time(const path& p, const StatT& st, + error_code* ec) { + using detail::fs_time; + ErrorHandler err("last_write_time", ec, &p); + + auto ts = detail::extract_mtime(st); + if (!fs_time::is_representable(ts)) + return err.report(errc::value_too_large); + + return fs_time::convert_from_timespec(ts); +} + +file_time_type __last_write_time(const path& p, error_code* ec) { + using namespace chrono; + ErrorHandler err("last_write_time", ec, &p); + + error_code m_ec; + StatT st; + detail::posix_stat(p, st, &m_ec); + if (m_ec) + return err.report(m_ec); + return __extract_last_write_time(p, st, ec); +} + +void __last_write_time(const path& p, file_time_type new_time, error_code* ec) { + using detail::fs_time; + ErrorHandler err("last_write_time", ec, &p); + + error_code m_ec; + array tbuf; +#if !defined(_LIBCPP_USE_UTIMENSAT) + // This implementation has a race condition between determining the + // last access time and attempting to set it to the same value using + // ::utimes + StatT st; + file_status fst = detail::posix_stat(p, st, &m_ec); + if (m_ec) + return err.report(m_ec); + tbuf[0] = detail::extract_atime(st); +#else + tbuf[0].tv_sec = 0; + tbuf[0].tv_nsec = UTIME_OMIT; +#endif + if (!fs_time::convert_to_timespec(tbuf[1], new_time)) + return err.report(errc::value_too_large); + + detail::set_file_times(p, tbuf, m_ec); + if (m_ec) + return err.report(m_ec); +} + +void __permissions(const path& p, perms prms, perm_options opts, + error_code* ec) { + ErrorHandler err("permissions", ec, &p); + + auto has_opt = [&](perm_options o) { return bool(o & opts); }; + const bool resolve_symlinks = !has_opt(perm_options::nofollow); + const bool add_perms = has_opt(perm_options::add); + const bool remove_perms = has_opt(perm_options::remove); + _LIBCPP_ASSERT( + (add_perms + remove_perms + has_opt(perm_options::replace)) == 1, + "One and only one of the perm_options constants replace, add, or remove " + "is present in opts"); + + bool set_sym_perms = false; + prms &= perms::mask; + if (!resolve_symlinks || (add_perms || remove_perms)) { + error_code m_ec; + file_status st = resolve_symlinks ? detail::posix_stat(p, &m_ec) + : detail::posix_lstat(p, &m_ec); + set_sym_perms = is_symlink(st); + if (m_ec) + return err.report(m_ec); + _LIBCPP_ASSERT(st.permissions() != perms::unknown, + "Permissions unexpectedly unknown"); + if (add_perms) + prms |= st.permissions(); + else if (remove_perms) + prms = st.permissions() & ~prms; + } + const auto real_perms = detail::posix_convert_perms(prms); + +#if defined(AT_SYMLINK_NOFOLLOW) && defined(AT_FDCWD) + const int flags = set_sym_perms ? AT_SYMLINK_NOFOLLOW : 0; + if (::fchmodat(AT_FDCWD, p.c_str(), real_perms, flags) == -1) { + return err.report(capture_errno()); + } +#else + if (set_sym_perms) + return err.report(errc::operation_not_supported); + if (::chmod(p.c_str(), real_perms) == -1) { + return err.report(capture_errno()); + } +#endif +} + +path __read_symlink(const path& p, error_code* ec) { + ErrorHandler err("read_symlink", ec, &p); + + char buff[PATH_MAX + 1]; + error_code m_ec; + ::ssize_t ret; + if ((ret = ::readlink(p.c_str(), buff, PATH_MAX)) == -1) { + return err.report(capture_errno()); + } + _LIBCPP_ASSERT(ret <= PATH_MAX, "TODO"); + _LIBCPP_ASSERT(ret > 0, "TODO"); + buff[ret] = 0; + return {buff}; +} + +bool __remove(const path& p, error_code* ec) { + ErrorHandler err("remove", ec, &p); + if (::remove(p.c_str()) == -1) { + if (errno != ENOENT) + err.report(capture_errno()); + return false; + } + return true; +} + +namespace { + +uintmax_t remove_all_impl(path const& p, error_code& ec) { + const auto npos = static_cast(-1); + const file_status st = __symlink_status(p, &ec); + if (ec) + return npos; + uintmax_t count = 1; + if (is_directory(st)) { + for (directory_iterator it(p, ec); !ec && it != directory_iterator(); + it.increment(ec)) { + auto other_count = remove_all_impl(it->path(), ec); + if (ec) + return npos; + count += other_count; + } + if (ec) + return npos; + } + if (!__remove(p, &ec)) + return npos; + return count; +} + +} // end namespace + +uintmax_t __remove_all(const path& p, error_code* ec) { + ErrorHandler err("remove_all", ec, &p); + + error_code mec; + auto count = remove_all_impl(p, mec); + if (mec) { + if (mec == errc::no_such_file_or_directory) + return 0; + return err.report(mec); + } + return count; +} + +void __rename(const path& from, const path& to, error_code* ec) { + ErrorHandler err("rename", ec, &from, &to); + if (::rename(from.c_str(), to.c_str()) == -1) + err.report(capture_errno()); +} + +void __resize_file(const path& p, uintmax_t size, error_code* ec) { + ErrorHandler err("resize_file", ec, &p); + if (::truncate(p.c_str(), static_cast< ::off_t>(size)) == -1) + return err.report(capture_errno()); +} + +space_info __space(const path& p, error_code* ec) { + ErrorHandler err("space", ec, &p); + space_info si; + struct statvfs m_svfs = {}; + if (::statvfs(p.c_str(), &m_svfs) == -1) { + err.report(capture_errno()); + si.capacity = si.free = si.available = static_cast(-1); + return si; + } + // Multiply with overflow checking. + auto do_mult = [&](uintmax_t& out, uintmax_t other) { + out = other * m_svfs.f_frsize; + if (other == 0 || out / other != m_svfs.f_frsize) + out = static_cast(-1); + }; + do_mult(si.capacity, m_svfs.f_blocks); + do_mult(si.free, m_svfs.f_bfree); + do_mult(si.available, m_svfs.f_bavail); + return si; +} + +file_status __status(const path& p, error_code* ec) { + return detail::posix_stat(p, ec); +} + +file_status __symlink_status(const path& p, error_code* ec) { + return detail::posix_lstat(p, ec); +} + +path __temp_directory_path(error_code* ec) { + ErrorHandler err("temp_directory_path", ec); + + const char* env_paths[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR"}; + const char* ret = nullptr; + + for (auto& ep : env_paths) + if ((ret = getenv(ep))) + break; + if (ret == nullptr) + ret = "/tmp"; + + path p(ret); + error_code m_ec; + file_status st = detail::posix_stat(p, &m_ec); + if (!status_known(st)) + return err.report(m_ec, "cannot access path \"%s\"", p); + + if (!exists(st) || !is_directory(st)) + return err.report(errc::not_a_directory, "path \"%s\" is not a directory", + p); + + return p; +} + +path __weakly_canonical(const path& p, error_code* ec) { + ErrorHandler err("weakly_canonical", ec, &p); + + if (p.empty()) + return __canonical("", ec); + + path result; + path tmp; + tmp.__reserve(p.native().size()); + auto PP = PathParser::CreateEnd(p.native()); + --PP; + vector DNEParts; + + while (PP.State != PathParser::PS_BeforeBegin) { + tmp.assign(createView(p.native().data(), &PP.RawEntry.back())); + error_code m_ec; + file_status st = __status(tmp, &m_ec); + if (!status_known(st)) { + return err.report(m_ec); + } else if (exists(st)) { + result = __canonical(tmp, ec); + break; + } + DNEParts.push_back(*PP); + --PP; + } + if (PP.State == PathParser::PS_BeforeBegin) + result = __canonical("", ec); + if (ec) + ec->clear(); + if (DNEParts.empty()) + return result; + for (auto It = DNEParts.rbegin(); It != DNEParts.rend(); ++It) + result /= *It; + return result.lexically_normal(); +} + +/////////////////////////////////////////////////////////////////////////////// +// path definitions +/////////////////////////////////////////////////////////////////////////////// + +constexpr path::value_type path::preferred_separator; + +path& path::replace_extension(path const& replacement) { + path p = extension(); + if (not p.empty()) { + __pn_.erase(__pn_.size() - p.native().size()); + } + if (!replacement.empty()) { + if (replacement.native()[0] != '.') { + __pn_ += "."; + } + __pn_.append(replacement.__pn_); + } + return *this; +} + +/////////////////////////////////////////////////////////////////////////////// +// path.decompose + +string_view_t path::__root_name() const { + auto PP = PathParser::CreateBegin(__pn_); + if (PP.State == PathParser::PS_InRootName) + return *PP; + return {}; +} + +string_view_t path::__root_directory() const { + auto PP = PathParser::CreateBegin(__pn_); + if (PP.State == PathParser::PS_InRootName) + ++PP; + if (PP.State == PathParser::PS_InRootDir) + return *PP; + return {}; +} + +string_view_t path::__root_path_raw() const { + auto PP = PathParser::CreateBegin(__pn_); + if (PP.State == PathParser::PS_InRootName) { + auto NextCh = PP.peek(); + if (NextCh && *NextCh == '/') { + ++PP; + return createView(__pn_.data(), &PP.RawEntry.back()); + } + return PP.RawEntry; + } + if (PP.State == PathParser::PS_InRootDir) + return *PP; + return {}; +} + +static bool ConsumeRootName(PathParser *PP) { + static_assert(PathParser::PS_BeforeBegin == 1 && + PathParser::PS_InRootName == 2, + "Values for enums are incorrect"); + while (PP->State <= PathParser::PS_InRootName) + ++(*PP); + return PP->State == PathParser::PS_AtEnd; +} + +static bool ConsumeRootDir(PathParser* PP) { + static_assert(PathParser::PS_BeforeBegin == 1 && + PathParser::PS_InRootName == 2 && + PathParser::PS_InRootDir == 3, "Values for enums are incorrect"); + while (PP->State <= PathParser::PS_InRootDir) + ++(*PP); + return PP->State == PathParser::PS_AtEnd; +} + +string_view_t path::__relative_path() const { + auto PP = PathParser::CreateBegin(__pn_); + if (ConsumeRootDir(&PP)) + return {}; + return createView(PP.RawEntry.data(), &__pn_.back()); +} + +string_view_t path::__parent_path() const { + if (empty()) + return {}; + // Determine if we have a root path but not a relative path. In that case + // return *this. + { + auto PP = PathParser::CreateBegin(__pn_); + if (ConsumeRootDir(&PP)) + return __pn_; + } + // Otherwise remove a single element from the end of the path, and return + // a string representing that path + { + auto PP = PathParser::CreateEnd(__pn_); + --PP; + if (PP.RawEntry.data() == __pn_.data()) + return {}; + --PP; + return createView(__pn_.data(), &PP.RawEntry.back()); + } +} + +string_view_t path::__filename() const { + if (empty()) + return {}; + { + PathParser PP = PathParser::CreateBegin(__pn_); + if (ConsumeRootDir(&PP)) + return {}; + } + return *(--PathParser::CreateEnd(__pn_)); +} + +string_view_t path::__stem() const { + return parser::separate_filename(__filename()).first; +} + +string_view_t path::__extension() const { + return parser::separate_filename(__filename()).second; +} + +//////////////////////////////////////////////////////////////////////////// +// path.gen + +enum PathPartKind : unsigned char { + PK_None, + PK_RootSep, + PK_Filename, + PK_Dot, + PK_DotDot, + PK_TrailingSep +}; + +static PathPartKind ClassifyPathPart(string_view_t Part) { + if (Part.empty()) + return PK_TrailingSep; + if (Part == ".") + return PK_Dot; + if (Part == "..") + return PK_DotDot; + if (Part == "/") + return PK_RootSep; + return PK_Filename; +} + +path path::lexically_normal() const { + if (__pn_.empty()) + return *this; + + using PartKindPair = pair; + vector Parts; + // Guess as to how many elements the path has to avoid reallocating. + Parts.reserve(32); + + // Track the total size of the parts as we collect them. This allows the + // resulting path to reserve the correct amount of memory. + size_t NewPathSize = 0; + auto AddPart = [&](PathPartKind K, string_view_t P) { + NewPathSize += P.size(); + Parts.emplace_back(P, K); + }; + auto LastPartKind = [&]() { + if (Parts.empty()) + return PK_None; + return Parts.back().second; + }; + + bool MaybeNeedTrailingSep = false; + // Build a stack containing the remaining elements of the path, popping off + // elements which occur before a '..' entry. + for (auto PP = PathParser::CreateBegin(__pn_); PP; ++PP) { + auto Part = *PP; + PathPartKind Kind = ClassifyPathPart(Part); + switch (Kind) { + case PK_Filename: + case PK_RootSep: { + // Add all non-dot and non-dot-dot elements to the stack of elements. + AddPart(Kind, Part); + MaybeNeedTrailingSep = false; + break; + } + case PK_DotDot: { + // Only push a ".." element if there are no elements preceding the "..", + // or if the preceding element is itself "..". + auto LastKind = LastPartKind(); + if (LastKind == PK_Filename) { + NewPathSize -= Parts.back().first.size(); + Parts.pop_back(); + } else if (LastKind != PK_RootSep) + AddPart(PK_DotDot, ".."); + MaybeNeedTrailingSep = LastKind == PK_Filename; + break; + } + case PK_Dot: + case PK_TrailingSep: { + MaybeNeedTrailingSep = true; + break; + } + case PK_None: + _LIBCPP_UNREACHABLE(); + } + } + // [fs.path.generic]p6.8: If the path is empty, add a dot. + if (Parts.empty()) + return "."; + + // [fs.path.generic]p6.7: If the last filename is dot-dot, remove any + // trailing directory-separator. + bool NeedTrailingSep = MaybeNeedTrailingSep && LastPartKind() == PK_Filename; + + path Result; + Result.__pn_.reserve(Parts.size() + NewPathSize + NeedTrailingSep); + for (auto& PK : Parts) + Result /= PK.first; + + if (NeedTrailingSep) + Result /= ""; + + return Result; +} + +static int DetermineLexicalElementCount(PathParser PP) { + int Count = 0; + for (; PP; ++PP) { + auto Elem = *PP; + if (Elem == "..") + --Count; + else if (Elem != "." && Elem != "") + ++Count; + } + return Count; +} + +path path::lexically_relative(const path& base) const { + { // perform root-name/root-directory mismatch checks + auto PP = PathParser::CreateBegin(__pn_); + auto PPBase = PathParser::CreateBegin(base.__pn_); + auto CheckIterMismatchAtBase = [&]() { + return PP.State != PPBase.State && + (PP.inRootPath() || PPBase.inRootPath()); + }; + if (PP.inRootName() && PPBase.inRootName()) { + if (*PP != *PPBase) + return {}; + } else if (CheckIterMismatchAtBase()) + return {}; + + if (PP.inRootPath()) + ++PP; + if (PPBase.inRootPath()) + ++PPBase; + if (CheckIterMismatchAtBase()) + return {}; + } + + // Find the first mismatching element + auto PP = PathParser::CreateBegin(__pn_); + auto PPBase = PathParser::CreateBegin(base.__pn_); + while (PP && PPBase && PP.State == PPBase.State && *PP == *PPBase) { + ++PP; + ++PPBase; + } + + // If there is no mismatch, return ".". + if (!PP && !PPBase) + return "."; + + // Otherwise, determine the number of elements, 'n', which are not dot or + // dot-dot minus the number of dot-dot elements. + int ElemCount = DetermineLexicalElementCount(PPBase); + if (ElemCount < 0) + return {}; + + // if n == 0 and (a == end() || a->empty()), returns path("."); otherwise + if (ElemCount == 0 && (PP.atEnd() || *PP == "")) + return "."; + + // return a path constructed with 'n' dot-dot elements, followed by the + // elements of '*this' after the mismatch. + path Result; + // FIXME: Reserve enough room in Result that it won't have to re-allocate. + while (ElemCount--) + Result /= ".."; + for (; PP; ++PP) + Result /= *PP; + return Result; +} + +//////////////////////////////////////////////////////////////////////////// +// path.comparisons +static int CompareRootName(PathParser *LHS, PathParser *RHS) { + if (!LHS->inRootName() && !RHS->inRootName()) + return 0; + + auto GetRootName = [](PathParser *Parser) -> string_view_t { + return Parser->inRootName() ? **Parser : ""; + }; + int res = GetRootName(LHS).compare(GetRootName(RHS)); + ConsumeRootName(LHS); + ConsumeRootName(RHS); + return res; +} + +static int CompareRootDir(PathParser *LHS, PathParser *RHS) { + if (!LHS->inRootDir() && RHS->inRootDir()) + return -1; + else if (LHS->inRootDir() && !RHS->inRootDir()) + return 1; + else { + ConsumeRootDir(LHS); + ConsumeRootDir(RHS); + return 0; + } +} + +static int CompareRelative(PathParser *LHSPtr, PathParser *RHSPtr) { + auto &LHS = *LHSPtr; + auto &RHS = *RHSPtr; + + int res; + while (LHS && RHS) { + if ((res = (*LHS).compare(*RHS)) != 0) + return res; + ++LHS; + ++RHS; + } + return 0; +} + +static int CompareEndState(PathParser *LHS, PathParser *RHS) { + if (LHS->atEnd() && !RHS->atEnd()) + return -1; + else if (!LHS->atEnd() && RHS->atEnd()) + return 1; + return 0; +} + +int path::__compare(string_view_t __s) const { + auto LHS = PathParser::CreateBegin(__pn_); + auto RHS = PathParser::CreateBegin(__s); + int res; + + if ((res = CompareRootName(&LHS, &RHS)) != 0) + return res; + + if ((res = CompareRootDir(&LHS, &RHS)) != 0) + return res; + + if ((res = CompareRelative(&LHS, &RHS)) != 0) + return res; + + return CompareEndState(&LHS, &RHS); +} + +//////////////////////////////////////////////////////////////////////////// +// path.nonmembers +size_t hash_value(const path& __p) noexcept { + auto PP = PathParser::CreateBegin(__p.native()); + size_t hash_value = 0; + hash hasher; + while (PP) { + hash_value = __hash_combine(hash_value, hasher(*PP)); + ++PP; + } + return hash_value; +} + +//////////////////////////////////////////////////////////////////////////// +// path.itr +path::iterator path::begin() const { + auto PP = PathParser::CreateBegin(__pn_); + iterator it; + it.__path_ptr_ = this; + it.__state_ = static_cast(PP.State); + it.__entry_ = PP.RawEntry; + it.__stashed_elem_.__assign_view(*PP); + return it; +} + +path::iterator path::end() const { + iterator it{}; + it.__state_ = path::iterator::_AtEnd; + it.__path_ptr_ = this; + return it; +} + +path::iterator& path::iterator::__increment() { + PathParser PP(__path_ptr_->native(), __entry_, __state_); + ++PP; + __state_ = static_cast<_ParserState>(PP.State); + __entry_ = PP.RawEntry; + __stashed_elem_.__assign_view(*PP); + return *this; +} + +path::iterator& path::iterator::__decrement() { + PathParser PP(__path_ptr_->native(), __entry_, __state_); + --PP; + __state_ = static_cast<_ParserState>(PP.State); + __entry_ = PP.RawEntry; + __stashed_elem_.__assign_view(*PP); + return *this; +} + +/////////////////////////////////////////////////////////////////////////////// +// directory entry definitions +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _LIBCPP_WIN32API +error_code directory_entry::__do_refresh() noexcept { + __data_.__reset(); + error_code failure_ec; + + StatT full_st; + file_status st = detail::posix_lstat(__p_, full_st, &failure_ec); + if (!status_known(st)) { + __data_.__reset(); + return failure_ec; + } + + if (!_VSTD_FS::exists(st) || !_VSTD_FS::is_symlink(st)) { + __data_.__cache_type_ = directory_entry::_RefreshNonSymlink; + __data_.__type_ = st.type(); + __data_.__non_sym_perms_ = st.permissions(); + } else { // we have a symlink + __data_.__sym_perms_ = st.permissions(); + // Get the information about the linked entity. + // Ignore errors from stat, since we don't want errors regarding symlink + // resolution to be reported to the user. + error_code ignored_ec; + st = detail::posix_stat(__p_, full_st, &ignored_ec); + + __data_.__type_ = st.type(); + __data_.__non_sym_perms_ = st.permissions(); + + // If we failed to resolve the link, then only partially populate the + // cache. + if (!status_known(st)) { + __data_.__cache_type_ = directory_entry::_RefreshSymlinkUnresolved; + return error_code{}; + } + // Otherwise, we resolved the link, potentially as not existing. + // That's OK. + __data_.__cache_type_ = directory_entry::_RefreshSymlink; + } + + if (_VSTD_FS::is_regular_file(st)) + __data_.__size_ = static_cast(full_st.st_size); + + if (_VSTD_FS::exists(st)) { + __data_.__nlink_ = static_cast(full_st.st_nlink); + + // Attempt to extract the mtime, and fail if it's not representable using + // file_time_type. For now we ignore the error, as we'll report it when + // the value is actually used. + error_code ignored_ec; + __data_.__write_time_ = + __extract_last_write_time(__p_, full_st, &ignored_ec); + } + + return failure_ec; +} +#else +error_code directory_entry::__do_refresh() noexcept { + __data_.__reset(); + error_code failure_ec; + + file_status st = _VSTD_FS::symlink_status(__p_, failure_ec); + if (!status_known(st)) { + __data_.__reset(); + return failure_ec; + } + + if (!_VSTD_FS::exists(st) || !_VSTD_FS::is_symlink(st)) { + __data_.__cache_type_ = directory_entry::_RefreshNonSymlink; + __data_.__type_ = st.type(); + __data_.__non_sym_perms_ = st.permissions(); + } else { // we have a symlink + __data_.__sym_perms_ = st.permissions(); + // Get the information about the linked entity. + // Ignore errors from stat, since we don't want errors regarding symlink + // resolution to be reported to the user. + error_code ignored_ec; + st = _VSTD_FS::status(__p_, ignored_ec); + + __data_.__type_ = st.type(); + __data_.__non_sym_perms_ = st.permissions(); + + // If we failed to resolve the link, then only partially populate the + // cache. + if (!status_known(st)) { + __data_.__cache_type_ = directory_entry::_RefreshSymlinkUnresolved; + return error_code{}; + } + __data_.__cache_type_ = directory_entry::_RefreshSymlink; + } + + // FIXME: This is currently broken, and the implementation only a placeholder. + // We need to cache last_write_time, file_size, and hard_link_count here before + // the implementation actually works. + + return failure_ec; +} +#endif + +#ifndef _LIBAUTO_UNDEF_VSTD_FS +#pragma pop_macro("_VSTD_FS") +#else +#undef _VSTD +#undef _LIBAUTO_UNDEF_VSTD_FS +#endif +} // namespace android::hardware::automotive::filesystem +/* clang-format on */ diff --git a/automotive/can/1.0/default/libnetdevice/Android.bp b/automotive/can/1.0/default/libnetdevice/Android.bp new file mode 100644 index 0000000000..31e5ad0376 --- /dev/null +++ b/automotive/can/1.0/default/libnetdevice/Android.bp @@ -0,0 +1,30 @@ +// +// 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: "android.hardware.automotive.can@libnetdevice", + defaults: ["android.hardware.automotive.can@defaults"], + vendor_available: true, + relative_install_path: "hw", + srcs: [ + "NetlinkRequest.cpp", + "NetlinkSocket.cpp", + "can.cpp", + "common.cpp", + "libnetdevice.cpp", + ], + export_include_dirs: ["include"], +} diff --git a/automotive/can/1.0/default/libnetdevice/NetlinkRequest.cpp b/automotive/can/1.0/default/libnetdevice/NetlinkRequest.cpp new file mode 100644 index 0000000000..556debfc09 --- /dev/null +++ b/automotive/can/1.0/default/libnetdevice/NetlinkRequest.cpp @@ -0,0 +1,54 @@ +/* + * 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 "NetlinkRequest.h" + +#include + +namespace android::netdevice::impl { + +static struct rtattr* nlmsg_tail(struct nlmsghdr* n) { + return reinterpret_cast( // + reinterpret_cast(n) + NLMSG_ALIGN(n->nlmsg_len)); +} + +struct rtattr* addattr_l(struct nlmsghdr* n, size_t maxLen, rtattrtype_t type, const void* data, + size_t dataLen) { + size_t newLen = NLMSG_ALIGN(n->nlmsg_len) + RTA_SPACE(dataLen); + if (newLen > maxLen) { + LOG(ERROR) << "addattr_l failed - exceeded maxLen: " << newLen << " > " << maxLen; + return nullptr; + } + + auto attr = nlmsg_tail(n); + attr->rta_len = RTA_SPACE(dataLen); + attr->rta_type = type; + if (dataLen > 0) memcpy(RTA_DATA(attr), data, dataLen); + + n->nlmsg_len = newLen; + return attr; +} + +struct rtattr* addattr_nest(struct nlmsghdr* n, size_t maxLen, rtattrtype_t type) { + return addattr_l(n, maxLen, type, nullptr, 0); +} + +void addattr_nest_end(struct nlmsghdr* n, struct rtattr* nest) { + size_t nestLen = reinterpret_cast(nlmsg_tail(n)) - reinterpret_cast(nest); + nest->rta_len = nestLen; +} + +} // namespace android::netdevice::impl diff --git a/automotive/can/1.0/default/libnetdevice/NetlinkRequest.h b/automotive/can/1.0/default/libnetdevice/NetlinkRequest.h new file mode 100644 index 0000000000..3e28d78485 --- /dev/null +++ b/automotive/can/1.0/default/libnetdevice/NetlinkRequest.h @@ -0,0 +1,153 @@ +/* + * 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 +#include + +#include + +namespace android::netdevice { + +typedef unsigned short rtattrtype_t; // as in rtnetlink.h +typedef __u16 nlmsgtype_t; // as in netlink.h + +/** Implementation details, do not use outside NetlinkRequest template. */ +namespace impl { + +struct rtattr* addattr_l(struct nlmsghdr* n, size_t maxLen, rtattrtype_t type, const void* data, + size_t dataLen); +struct rtattr* addattr_nest(struct nlmsghdr* n, size_t maxLen, rtattrtype_t type); +void addattr_nest_end(struct nlmsghdr* n, struct rtattr* nest); + +} // namespace impl + +/** + * Wrapper around NETLINK_ROUTE messages, to build them in C++ style. + * + * \param T specific message header (such as struct ifinfomsg) + * \param BUFSIZE how much space to reserve for payload (not counting the header size) + */ +template +struct NetlinkRequest { + /** + * Create empty message. + * + * \param type Message type (such as RTM_NEWLINK) + * \param flags Message flags (such as NLM_F_REQUEST) + */ + NetlinkRequest(nlmsgtype_t type, uint16_t flags) { + mRequest.nlmsg.nlmsg_len = NLMSG_LENGTH(sizeof(mRequest.data)); + mRequest.nlmsg.nlmsg_type = type; + mRequest.nlmsg.nlmsg_flags = flags; + } + + /** \return pointer to raw netlink message header. */ + struct nlmsghdr* header() { + return &mRequest.nlmsg; + } + /** Reference to message-specific header. */ + T& data() { return mRequest.data; } + + /** + * Adds an attribute of a simple type. + * + * If this method fails (i.e. due to insufficient space), the message will be marked + * as bad (\see isGood). + * + * \param type attribute type (such as IFLA_IFNAME) + * \param attr attribute data + */ + template + void addattr(rtattrtype_t type, const A& attr) { + if (!mIsGood) return; + auto ap = impl::addattr_l(&mRequest.nlmsg, sizeof(mRequest), type, &attr, sizeof(attr)); + if (ap == nullptr) mIsGood = false; + } + + template <> + void addattr(rtattrtype_t type, const std::string& s) { + if (!mIsGood) return; + auto ap = impl::addattr_l(&mRequest.nlmsg, sizeof(mRequest), type, s.c_str(), s.size() + 1); + if (ap == nullptr) mIsGood = false; + } + + /** Guard class to frame nested attributes. See nest(int). */ + struct Nest { + Nest(NetlinkRequest& req, rtattrtype_t type) : mReq(req), mAttr(req.nestStart(type)) {} + ~Nest() { mReq.nestEnd(mAttr); } + + private: + NetlinkRequest& mReq; + struct rtattr* mAttr; + + DISALLOW_COPY_AND_ASSIGN(Nest); + }; + + /** + * Add nested attribute. + * + * The returned object is a guard for auto-nesting children inside the argument attribute. + * When the Nest object goes out of scope, the nesting attribute is closed. + * + * Example usage nesting IFLA_CAN_BITTIMING inside IFLA_INFO_DATA, which is nested + * inside IFLA_LINKINFO: + * NetlinkRequest req(RTM_NEWLINK, NLM_F_REQUEST); + * { + * auto linkinfo = req.nest(IFLA_LINKINFO); + * req.addattr(IFLA_INFO_KIND, "can"); + * { + * auto infodata = req.nest(IFLA_INFO_DATA); + * req.addattr(IFLA_CAN_BITTIMING, bitTimingStruct); + * } + * } + * // use req + * + * \param type attribute type (such as IFLA_LINKINFO) + */ + Nest nest(int type) { return Nest(*this, type); } + + /** + * Indicates, whether the message is in a good state. + * + * The bad state is usually a result of payload buffer being too small. + * You can modify BUFSIZE template parameter to fix this. + */ + bool isGood() const { return mIsGood; } + + private: + bool mIsGood = true; + + struct { + struct nlmsghdr nlmsg; + T data; + char buf[BUFSIZE]; + } mRequest = {}; + + struct rtattr* nestStart(rtattrtype_t type) { + if (!mIsGood) return nullptr; + auto attr = impl::addattr_nest(&mRequest.nlmsg, sizeof(mRequest), type); + if (attr == nullptr) mIsGood = false; + return attr; + } + + void nestEnd(struct rtattr* nest) { + if (mIsGood && nest != nullptr) impl::addattr_nest_end(&mRequest.nlmsg, nest); + } +}; + +} // namespace android::netdevice diff --git a/automotive/can/1.0/default/libnetdevice/NetlinkSocket.cpp b/automotive/can/1.0/default/libnetdevice/NetlinkSocket.cpp new file mode 100644 index 0000000000..7817169876 --- /dev/null +++ b/automotive/can/1.0/default/libnetdevice/NetlinkSocket.cpp @@ -0,0 +1,112 @@ +/* + * 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 "NetlinkSocket.h" + +#include + +namespace android::netdevice { + +NetlinkSocket::NetlinkSocket(int protocol) { + mFd.reset(socket(AF_NETLINK, SOCK_RAW, protocol)); + if (!mFd.ok()) { + PLOG(ERROR) << "Can't open Netlink socket"; + mFailed = true; + return; + } + + struct sockaddr_nl sa = {}; + sa.nl_family = AF_NETLINK; + + if (bind(mFd.get(), reinterpret_cast(&sa), sizeof(sa)) < 0) { + PLOG(ERROR) << "Can't bind Netlink socket"; + mFd.reset(); + mFailed = true; + } +} + +bool NetlinkSocket::send(struct nlmsghdr* nlmsg) { + if (mFailed) return false; + + nlmsg->nlmsg_pid = 0; // kernel + nlmsg->nlmsg_seq = mSeq++; + nlmsg->nlmsg_flags |= NLM_F_ACK; + + struct iovec iov = {nlmsg, nlmsg->nlmsg_len}; + + struct sockaddr_nl sa = {}; + sa.nl_family = AF_NETLINK; + + struct msghdr msg = {}; + msg.msg_name = &sa; + msg.msg_namelen = sizeof(sa); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + if (sendmsg(mFd.get(), &msg, 0) < 0) { + PLOG(ERROR) << "Can't send Netlink message"; + return false; + } + return true; +} + +bool NetlinkSocket::receiveAck() { + if (mFailed) return false; + + char buf[8192]; + + struct sockaddr_nl sa; + struct iovec iov = {buf, sizeof(buf)}; + + struct msghdr msg = {}; + msg.msg_name = &sa; + msg.msg_namelen = sizeof(sa); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + const ssize_t status = recvmsg(mFd.get(), &msg, 0); + if (status < 0) { + PLOG(ERROR) << "Failed to receive Netlink message"; + return false; + } + size_t remainingLen = status; + + if (msg.msg_flags & MSG_TRUNC) { + LOG(ERROR) << "Failed to receive Netlink message: truncated"; + return false; + } + + for (auto nlmsg = reinterpret_cast(buf); NLMSG_OK(nlmsg, remainingLen); + nlmsg = NLMSG_NEXT(nlmsg, remainingLen)) { + // We're looking for error/ack message only, ignoring others. + if (nlmsg->nlmsg_type != NLMSG_ERROR) { + LOG(WARNING) << "Received unexpected Netlink message (ignored): " << nlmsg->nlmsg_type; + continue; + } + + // Found error/ack message, return status. + auto nlerr = reinterpret_cast(NLMSG_DATA(nlmsg)); + if (nlerr->error != 0) { + LOG(ERROR) << "Received Netlink error message: " << nlerr->error; + return false; + } + return true; + } + // Couldn't find any error/ack messages. + return false; +} + +} // namespace android::netdevice diff --git a/automotive/can/1.0/default/libnetdevice/NetlinkSocket.h b/automotive/can/1.0/default/libnetdevice/NetlinkSocket.h new file mode 100644 index 0000000000..2b40ea20c0 --- /dev/null +++ b/automotive/can/1.0/default/libnetdevice/NetlinkSocket.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. + */ + +#pragma once + +#include "NetlinkRequest.h" + +#include +#include + +#include + +namespace android::netdevice { + +/** + * A wrapper around AF_NETLINK sockets. + * + * This class is not thread safe to use a single instance between multiple threads, but it's fine to + * use multiple instances over multiple threads. + */ +struct NetlinkSocket { + NetlinkSocket(int protocol); + + /** + * Send Netlink message to Kernel. + * + * \param msg Message to send, nlmsg_seq will be set to next sequence number + * \return true, if succeeded + */ + template + bool send(NetlinkRequest& req) { + if (!req.isGood()) return false; + return send(req.header()); + } + + /** + * Receive Netlink ACK message from Kernel. + * + * \return true if received ACK message, false in case of error + */ + bool receiveAck(); + + private: + uint32_t mSeq = 0; + base::unique_fd mFd; + bool mFailed = false; + + bool send(struct nlmsghdr* msg); + + DISALLOW_COPY_AND_ASSIGN(NetlinkSocket); +}; + +} // namespace android::netdevice diff --git a/automotive/can/1.0/default/libnetdevice/can.cpp b/automotive/can/1.0/default/libnetdevice/can.cpp new file mode 100644 index 0000000000..a2a85dcae8 --- /dev/null +++ b/automotive/can/1.0/default/libnetdevice/can.cpp @@ -0,0 +1,96 @@ +/* + * 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 + +#include "NetlinkRequest.h" +#include "NetlinkSocket.h" +#include "common.h" + +#include +#include + +#include +#include +#include +#include + +namespace android::netdevice::can { + +static constexpr can_err_mask_t kErrMask = CAN_ERR_MASK; + +base::unique_fd socket(const std::string& ifname) { + struct sockaddr_can addr = {}; + addr.can_family = AF_CAN; + addr.can_ifindex = nametoindex(ifname); + if (addr.can_ifindex == 0) { + LOG(ERROR) << "Interface " << ifname << " doesn't exists"; + return {}; + } + + base::unique_fd sock(::socket(PF_CAN, SOCK_RAW, CAN_RAW)); + if (!sock.ok()) { + LOG(ERROR) << "Failed to create CAN socket"; + return {}; + } + + if (setsockopt(sock.get(), SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &kErrMask, sizeof(kErrMask)) < 0) { + PLOG(ERROR) << "Can't receive error frames, CAN setsockpt failed"; + return {}; + } + + if (0 != fcntl(sock.get(), F_SETFL, O_RDWR | O_NONBLOCK)) { + LOG(ERROR) << "Couldn't put CAN socket in non-blocking mode"; + return {}; + } + + if (0 != bind(sock.get(), reinterpret_cast(&addr), sizeof(addr))) { + LOG(ERROR) << "Can't bind to CAN interface " << ifname; + return {}; + } + + return sock; +} + +bool setBitrate(std::string ifname, uint32_t bitrate) { + struct can_bittiming bt = {}; + bt.bitrate = bitrate; + + NetlinkRequest req(RTM_NEWLINK, NLM_F_REQUEST); + + const auto ifidx = nametoindex(ifname); + if (ifidx == 0) { + LOG(ERROR) << "Can't find interface " << ifname; + return false; + } + req.data().ifi_index = ifidx; + + { + auto linkinfo = req.nest(IFLA_LINKINFO); + req.addattr(IFLA_INFO_KIND, "can"); + { + auto infodata = req.nest(IFLA_INFO_DATA); + /* For CAN FD, it would require to add IFLA_CAN_DATA_BITTIMING + * and IFLA_CAN_CTRLMODE as well. */ + req.addattr(IFLA_CAN_BITTIMING, bt); + } + } + + NetlinkSocket sock(NETLINK_ROUTE); + return sock.send(req) && sock.receiveAck(); +} + +} // namespace android::netdevice::can diff --git a/automotive/can/1.0/default/libnetdevice/common.cpp b/automotive/can/1.0/default/libnetdevice/common.cpp new file mode 100644 index 0000000000..5c624439cd --- /dev/null +++ b/automotive/can/1.0/default/libnetdevice/common.cpp @@ -0,0 +1,36 @@ +/* + * 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 "common.h" + +#include + +#include + +namespace android::netdevice { + +unsigned int nametoindex(const std::string& ifname) { + const auto ifidx = if_nametoindex(ifname.c_str()); + if (ifidx != 0) return ifidx; + + const auto err = errno; + if (err != ENODEV) { + LOG(ERROR) << "if_nametoindex(" << ifname << ") failed: " << err; + } + return 0; +} + +} // namespace android::netdevice diff --git a/automotive/can/1.0/default/libnetdevice/common.h b/automotive/can/1.0/default/libnetdevice/common.h new file mode 100644 index 0000000000..8097f374ad --- /dev/null +++ b/automotive/can/1.0/default/libnetdevice/common.h @@ -0,0 +1,34 @@ +/* + * 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 + +namespace android::netdevice { + +/** + * Returns the index of a given network interface. + * + * If the syscall to check the index fails with other error than ENODEV, it gets logged and the + * return value indicates the interface doesn't exists. + * + * \param ifname Interface to check + * \return Interface index, or 0 if the interface doesn't exist + */ +unsigned int nametoindex(const std::string& ifname); + +} // namespace android::netdevice diff --git a/automotive/can/1.0/default/libnetdevice/include/libnetdevice/can.h b/automotive/can/1.0/default/libnetdevice/include/libnetdevice/can.h new file mode 100644 index 0000000000..3886acf1cd --- /dev/null +++ b/automotive/can/1.0/default/libnetdevice/include/libnetdevice/can.h @@ -0,0 +1,41 @@ +/* + * 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 + +#include + +namespace android::netdevice::can { + +/** + * Opens and binds SocketCAN socket. + * + * \param ifname Interface to open a socket against + * \return Socket's FD or -1 in case of failure + */ +base::unique_fd socket(const std::string& ifname); + +/** + * Sets CAN interface bitrate. + * + * \param ifname Interface for which the bitrate is to be set + * \return true on success, false on failure + */ +bool setBitrate(std::string ifname, uint32_t bitrate); + +} // namespace android::netdevice::can diff --git a/automotive/can/1.0/default/libnetdevice/include/libnetdevice/libnetdevice.h b/automotive/can/1.0/default/libnetdevice/include/libnetdevice/libnetdevice.h new file mode 100644 index 0000000000..3818a31c4c --- /dev/null +++ b/automotive/can/1.0/default/libnetdevice/include/libnetdevice/libnetdevice.h @@ -0,0 +1,73 @@ +/* + * 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 +#include + +namespace android::netdevice { + +/** + * Checks, if the network interface exists. + * + * \param ifname Interface to check + * \return true if it exists, false otherwise + */ +bool exists(std::string ifname); + +/** + * Checks if network interface is up. + * + * \param ifname Interface to check + * \return true/false if the check succeeded, nullopt otherwise + */ +std::optional isUp(std::string ifname); + +/** + * Brings network interface up. + * + * \param ifname Interface to bring up + * \return true in case of success, false otherwise + */ +bool up(std::string ifname); + +/** + * Brings network interface down. + * + * \param ifname Interface to bring down + * \return true in case of success, false otherwise + */ +bool down(std::string ifname); + +/** + * Adds virtual link. + * + * \param dev the name of the new virtual device + * \param type the type of the new device + * \return true in case of success, false otherwise + */ +bool add(std::string dev, std::string type); + +/** + * Deletes virtual link. + * + * \param dev the name of the device to remove + * \return true in case of success, false otherwise + */ +bool del(std::string dev); + +} // namespace android::netdevice diff --git a/automotive/can/1.0/default/libnetdevice/libnetdevice.cpp b/automotive/can/1.0/default/libnetdevice/libnetdevice.cpp new file mode 100644 index 0000000000..b05144209e --- /dev/null +++ b/automotive/can/1.0/default/libnetdevice/libnetdevice.cpp @@ -0,0 +1,98 @@ +/* + * 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 + +#include "NetlinkRequest.h" +#include "NetlinkSocket.h" +#include "common.h" + +#include + +#include +#include + +namespace android::netdevice { + +bool exists(std::string ifname) { + return nametoindex(ifname) != 0; +} + +static bool sendIfreq(unsigned long request, struct ifreq& ifr) { + /* For general interfaces it would be socket(AF_INET, SOCK_DGRAM, 0), + * but SEPolicy forces us to limit our flexibility here. */ + base::unique_fd sock(socket(PF_CAN, SOCK_RAW, CAN_RAW)); + if (!sock.ok()) { + LOG(ERROR) << "Can't create socket"; + return false; + } + + if (ioctl(sock.get(), request, &ifr) < 0) { + PLOG(ERROR) << "ioctl(" << std::hex << request << std::dec << ") failed"; + return false; + } + + return true; +} + +static struct ifreq ifreqFromName(const std::string& ifname) { + struct ifreq ifr = {}; + strlcpy(ifr.ifr_name, ifname.c_str(), IF_NAMESIZE); + return ifr; +} + +std::optional isUp(std::string ifname) { + struct ifreq ifr = ifreqFromName(ifname); + if (!sendIfreq(SIOCGIFFLAGS, ifr)) return std::nullopt; + return ifr.ifr_flags & IFF_UP; +} + +bool up(std::string ifname) { + struct ifreq ifr = ifreqFromName(ifname); + if (!sendIfreq(SIOCGIFFLAGS, ifr)) return false; + ifr.ifr_flags |= IFF_UP; + return sendIfreq(SIOCSIFFLAGS, ifr); +} + +bool down(std::string ifname) { + struct ifreq ifr = ifreqFromName(ifname); + if (!sendIfreq(SIOCGIFFLAGS, ifr)) return false; + ifr.ifr_flags &= ~IFF_UP; + return sendIfreq(SIOCSIFFLAGS, ifr); +} + +bool add(std::string dev, std::string type) { + NetlinkRequest req(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL); + req.addattr(IFLA_IFNAME, dev); + + { + auto linkinfo = req.nest(IFLA_LINKINFO); + req.addattr(IFLA_INFO_KIND, type); + } + + NetlinkSocket sock(NETLINK_ROUTE); + return sock.send(req) && sock.receiveAck(); +} + +bool del(std::string dev) { + NetlinkRequest req(RTM_DELLINK, NLM_F_REQUEST); + req.addattr(IFLA_IFNAME, dev); + + NetlinkSocket sock(NETLINK_ROUTE); + return sock.send(req) && sock.receiveAck(); +} + +} // namespace android::netdevice diff --git a/automotive/can/1.0/default/service.cpp b/automotive/can/1.0/default/service.cpp new file mode 100644 index 0000000000..b52a54a5b7 --- /dev/null +++ b/automotive/can/1.0/default/service.cpp @@ -0,0 +1,45 @@ +/* + * 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 "CanController.h" + +#include +#include + +namespace android::hardware::automotive::can::V1_0::implementation { + +static void canControllerService() { + base::SetDefaultTag("CanController"); + base::SetMinimumLogSeverity(android::base::VERBOSE); + configureRpcThreadpool(16, true); + LOG(DEBUG) << "CAN controller service starting..."; + + sp canController(new CanController); + if (canController->registerAsService("socketcan") != OK) { + LOG(FATAL) << "Failed to register CAN controller"; + return; + } + + LOG(INFO) << "CAN controller service ready"; + joinRpcThreadpool(); +} + +} // namespace android::hardware::automotive::can::V1_0::implementation + +int main() { + ::android::hardware::automotive::can::V1_0::implementation::canControllerService(); + return 1; // canBusService (joinRpcThreadpool) shouldn't exit +} diff --git a/automotive/can/1.0/hidl-utils/Android.bp b/automotive/can/1.0/hidl-utils/Android.bp new file mode 100644 index 0000000000..63b07c9391 --- /dev/null +++ b/automotive/can/1.0/hidl-utils/Android.bp @@ -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. +// + +cc_library_headers { + name: "android.hardware.automotive.can@hidl-utils-lib", + export_include_dirs: ["include"], + vendor_available: true, +} diff --git a/automotive/can/1.0/hidl-utils/include/hidl-utils/hidl-utils.h b/automotive/can/1.0/hidl-utils/include/hidl-utils/hidl-utils.h new file mode 100644 index 0000000000..f63d43cf33 --- /dev/null +++ b/automotive/can/1.0/hidl-utils/include/hidl-utils/hidl-utils.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. + */ + +#pragma once + +namespace android::hardware::automotive::hidl_utils { + +/** + * Helper functor to fetch results from multi-return HIDL calls. + * It's meant to be used in place of _hidl_cb callbacks. + * + * Please note extracting these return variables outside of the callback scope requires making + * a copy of each return variable. This may be costly for frequently called HIDL methods with + * non-negligible return object size. Please be cautious about performance when using this. + * + * Example usage: + * Result result; + * sp iface; + * hidlObject->someMethod(arg1, arg2, hidl_utils::fill(&result, &iface)).assertOk(); + * // use result and iface + */ +template +struct fill : public std::function { + /** + * Create _hidl_cb functor that copies the call arguments to specified pointers. + * + * \param args... Targets to copy the call arguments to + */ + fill(T*... args) : mTargets(args...) {} + + void operator()(const T&... args) { copy<0, T...>(args...); } + + private: + std::tuple mTargets; + + template + inline void copy(const First& first) { + *std::get(mTargets) = first; + } + + template + inline void copy(const First& first, const Rest&... rest) { + *std::get(mTargets) = first; + copy(rest...); + } +}; + +} // namespace android::hardware::automotive::hidl_utils diff --git a/automotive/can/1.0/tools/Android.bp b/automotive/can/1.0/tools/Android.bp new file mode 100644 index 0000000000..a6c40d933a --- /dev/null +++ b/automotive/can/1.0/tools/Android.bp @@ -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. +// + +cc_binary { + name: "canhalctrl", + defaults: ["android.hardware.automotive.can@defaults"], + srcs: [ + "canhalctrl.cpp", + ], + shared_libs: [ + "android.hardware.automotive.can@1.0", + "libhidlbase", + ], + header_libs: [ + "android.hardware.automotive.can@hidl-utils-lib", + ], + static_libs: [ + "android.hardware.automotive.can@libcanhaltools", + ], +} + +cc_binary { + name: "canhaldump", + defaults: ["android.hardware.automotive.can@defaults"], + srcs: [ + "canhaldump.cpp", + ], + shared_libs: [ + "android.hardware.automotive.can@1.0", + "libhidlbase", + ], + header_libs: [ + "android.hardware.automotive.can@hidl-utils-lib", + ], + static_libs: [ + "android.hardware.automotive.can@libcanhaltools", + ], +} + +cc_binary { + name: "canhalsend", + defaults: ["android.hardware.automotive.can@defaults"], + srcs: [ + "canhalsend.cpp", + ], + shared_libs: [ + "android.hardware.automotive.can@1.0", + "libhidlbase", + ], + static_libs: [ + "android.hardware.automotive.can@libcanhaltools", + ], +} diff --git a/automotive/can/1.0/tools/canhalctrl.cpp b/automotive/can/1.0/tools/canhalctrl.cpp new file mode 100644 index 0000000000..bf1c3b10a6 --- /dev/null +++ b/automotive/can/1.0/tools/canhalctrl.cpp @@ -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. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace android::hardware::automotive::can { + +using ICanController = V1_0::ICanController; + +static void usage() { + std::cerr << "CAN bus HAL Control tool" << std::endl; + std::cerr << std::endl << "usage:" << std::endl << std::endl; + std::cerr << "canhalctrl up [bitrate]" << std::endl; + std::cerr << "where:" << std::endl; + std::cerr << " bus name - name under which ICanBus will be published" << std::endl; + std::cerr << " type - one of: virtual, socketcan, slcan, indexed" << std::endl; + std::cerr << " interface - hardware identifier (like can0, vcan0, /dev/ttyUSB0)" << std::endl; + std::cerr << " bitrate - such as 100000, 125000, 250000, 500000" << std::endl; + std::cerr << std::endl; + std::cerr << "canhalctrl down " << std::endl; + std::cerr << "where:" << std::endl; + std::cerr << " bus name - name under which ICanBus will be published" << std::endl; +} + +static int up(const std::string& busName, ICanController::InterfaceType type, + const std::string& interface, uint32_t bitrate) { + bool anySupported = false; + for (auto&& service : libcanhaltools::getControlServices()) { + auto ctrl = ICanController::getService(service); + if (ctrl == nullptr) { + std::cerr << "Couldn't open ICanController/" << service; + continue; + } + + if (!libcanhaltools::isSupported(ctrl, type)) continue; + anySupported = true; + + ICanController::BusConfig config = {}; + config.name = busName; + config.bitrate = bitrate; + + // TODO(b/146214370): move interfaceId constructors to a library + using IfCfg = ICanController::BusConfig::InterfaceId; + if (type == ICanController::InterfaceType::VIRTUAL) { + config.interfaceId.virtualif({interface}); + } else if (type == ICanController::InterfaceType::SOCKETCAN) { + IfCfg::Socketcan socketcan = {}; + socketcan.ifname(interface); + config.interfaceId.socketcan(socketcan); + } else if (type == ICanController::InterfaceType::SLCAN) { + IfCfg::Slcan slcan = {}; + slcan.ttyname(interface); + config.interfaceId.slcan(slcan); + } else if (type == ICanController::InterfaceType::INDEXED) { + unsigned idx; + if (!android::base::ParseUint(interface, &idx, unsigned(UINT8_MAX))) { + std::cerr << "Interface index out of range: " << idx; + return -1; + } + config.interfaceId.indexed({uint8_t(idx)}); + } else { + CHECK(false) << "Unexpected interface type: " << toString(type); + } + + const auto upresult = ctrl->upInterface(config); + if (upresult == ICanController::Result::OK) return 0; + std::cerr << "Failed to bring interface up: " << toString(upresult) << std::endl; + // Let's continue the loop to try other controllers. + } + + if (!anySupported) { + std::cerr << "No controller supports " << toString(type) << std::endl; + } + return -1; +} + +static int down(const std::string& busName) { + for (auto&& service : libcanhaltools::getControlServices()) { + auto ctrl = ICanController::getService(service); + if (ctrl == nullptr) continue; + + if (ctrl->downInterface(busName)) return 0; + } + + std::cerr << "Failed to bring interface " << busName << " down (maybe it's down already?)" + << std::endl; + return -1; +} + +static std::optional parseInterfaceType(const std::string& str) { + if (str == "virtual") return ICanController::InterfaceType::VIRTUAL; + if (str == "socketcan") return ICanController::InterfaceType::SOCKETCAN; + if (str == "slcan") return ICanController::InterfaceType::SLCAN; + if (str == "indexed") return ICanController::InterfaceType::INDEXED; + return std::nullopt; +} + +static int main(int argc, char* argv[]) { + base::SetDefaultTag("CanHalControl"); + base::SetMinimumLogSeverity(android::base::VERBOSE); + + if (argc == 0) { + usage(); + return 0; + } + + std::string cmd(argv[0]); + argv++; + argc--; + + if (cmd == "up") { + if (argc < 3 || argc > 4) { + std::cerr << "Invalid number of arguments to up command: " << argc << std::endl; + usage(); + return -1; + } + + const std::string busName(argv[0]); + const std::string typeStr(argv[1]); + const std::string interface(argv[2]); + + const auto type = parseInterfaceType(typeStr); + if (!type) { + std::cerr << "Invalid interface type: " << typeStr << std::endl; + usage(); + return -1; + } + + uint32_t bitrate = 0; + if (argc == 4 && !android::base::ParseUint(argv[3], &bitrate)) { + std::cerr << "Invalid bitrate!" << std::endl; + usage(); + return -1; + } + + return up(busName, *type, interface, bitrate); + } else if (cmd == "down") { + if (argc != 1) { + std::cerr << "Invalid number of arguments to down command: " << argc << std::endl; + usage(); + return -1; + } + + return down(argv[0]); + } else { + std::cerr << "Invalid command: " << cmd << std::endl; + usage(); + return -1; + } +} + +} // namespace android::hardware::automotive::can + +int main(int argc, char* argv[]) { + if (argc < 1) return -1; + return ::android::hardware::automotive::can::main(--argc, ++argv); +} diff --git a/automotive/can/1.0/tools/canhaldump.cpp b/automotive/can/1.0/tools/canhaldump.cpp new file mode 100644 index 0000000000..2f5ca61321 --- /dev/null +++ b/automotive/can/1.0/tools/canhaldump.cpp @@ -0,0 +1,133 @@ +/* + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace android::hardware::automotive::can { + +using namespace std::chrono_literals; + +using ICanBus = V1_0::ICanBus; +using Result = V1_0::Result; + +struct CanMessageListener : public V1_0::ICanMessageListener { + const std::string name; + + CanMessageListener(std::string name) : name(name) {} + + virtual Return onReceive(const V1_0::CanMessage& message) { + int msgIdWidth = 3; + if (message.isExtendedId) msgIdWidth = 8; + std::cout << " " << name << " " << std::hex << std::uppercase << std::setw(msgIdWidth) + << std::setfill('0') << message.id << std::setw(0); + std::cout << " [" << message.payload.size() << "] "; + if (message.remoteTransmissionRequest) { + std::cout << "remote request"; + } else { + for (const auto byte : message.payload) { + std::cout << " " << std::setfill('0') << std::setw(2) << unsigned(byte); + } + } + std::cout << std::nouppercase << std::dec << std::endl; + return {}; + } + + virtual Return onError(V1_0::ErrorEvent error) { + std::cout << " " << name << " " << toString(error) << std::endl; + return {}; + } +}; + +static void usage() { + std::cerr << "canhaldump - dump CAN bus traffic" << std::endl; + std::cerr << std::endl << "usage:" << std::endl << std::endl; + std::cerr << "canhaldump " << std::endl; + std::cerr << "where:" << std::endl; + std::cerr << " bus name - name under which ICanBus is be published" << std::endl; +} + +// TODO(b/135918744): extract to a new library +static sp tryOpen(const std::string& busname) { + auto bus = ICanBus::tryGetService(busname); + if (bus != nullptr) return bus; + + /* Fallback for interfaces not registered in manifest. For testing purposes only, + * one should not depend on this in production deployment. */ + auto manager = hidl::manager::V1_2::IServiceManager::getService(); + auto ret = manager->get(ICanBus::descriptor, busname).withDefault(nullptr); + if (ret == nullptr) return nullptr; + + std::cerr << "WARNING: bus " << busname << " is not registered in device manifest, " + << "trying to fetch it directly..." << std::endl; + + return ICanBus::castFrom(ret); +} + +static int candump(const std::string& busname) { + auto bus = tryOpen(busname); + if (bus == nullptr) { + std::cerr << "Bus " << busname << " is not available" << std::endl; + return -1; + } + + Result result; + sp chnd; + // TODO(b/135918744): extract to library + bus->listen({}, new CanMessageListener(busname), hidl_utils::fill(&result, &chnd)).assertOk(); + + if (result != Result::OK) { + std::cerr << "Listen call failed: " << toString(result) << std::endl; + return -1; + } + + while (true) std::this_thread::sleep_for(1h); +} + +static int main(int argc, char* argv[]) { + base::SetDefaultTag("CanHalDump"); + base::SetMinimumLogSeverity(android::base::VERBOSE); + + if (argc == 0) { + usage(); + return 0; + } + + if (argc != 1) { + std::cerr << "Invalid number of arguments" << std::endl; + usage(); + return -1; + } + + return candump(argv[0]); +} + +} // namespace android::hardware::automotive::can + +int main(int argc, char* argv[]) { + if (argc < 1) return -1; + return ::android::hardware::automotive::can::main(--argc, ++argv); +} diff --git a/automotive/can/1.0/tools/canhalsend.cpp b/automotive/can/1.0/tools/canhalsend.cpp new file mode 100644 index 0000000000..f0f9d10875 --- /dev/null +++ b/automotive/can/1.0/tools/canhalsend.cpp @@ -0,0 +1,153 @@ +/* + * 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 +#include +#include +#include +#include + +#include +#include + +namespace android::hardware::automotive::can { + +using ICanBus = V1_0::ICanBus; +using Result = V1_0::Result; + +static void usage() { + std::cerr << "canhalsend - simple command line tool to send raw CAN frames" << std::endl; + std::cerr << std::endl << "usage:" << std::endl << std::endl; + std::cerr << "canhalsend #" << std::endl; + std::cerr << "where:" << std::endl; + std::cerr << " bus name - name under which ICanBus is published" << std::endl; + std::cerr << " can id - such as 1a5 or 1fab5982" << std::endl; + std::cerr << " data - such as deadbeef, 010203, or R for a remote frame" << std::endl; +} + +// TODO(b/135918744): extract to a new library +static sp tryOpen(const std::string& busname) { + auto bus = ICanBus::tryGetService(busname); + if (bus != nullptr) return bus; + + /* Fallback for interfaces not registered in manifest. For testing purposes only, + * one should not depend on this in production deployment. */ + auto manager = hidl::manager::V1_2::IServiceManager::getService(); + auto ret = manager->get(ICanBus::descriptor, busname).withDefault(nullptr); + if (ret == nullptr) return nullptr; + + std::cerr << "WARNING: bus " << busname << " is not registered in device manifest, " + << "trying to fetch it directly..." << std::endl; + + return ICanBus::castFrom(ret); +} + +static int cansend(const std::string& busname, const V1_0::CanMessage& msg) { + auto bus = tryOpen(busname); + if (bus == nullptr) { + std::cerr << "Bus " << busname << " is not available" << std::endl; + return -1; + } + + const auto result = bus->send(msg); + if (result != Result::OK) { + std::cerr << "Send call failed: " << toString(result) << std::endl; + return -1; + } + return 0; +} + +static std::optional parseCanMessage(const std::string& msg) { + const auto hashpos = msg.find("#"); + if (hashpos == std::string::npos) return std::nullopt; + + const std::string msgidStr = msg.substr(0, hashpos); + const std::string payloadStr = msg.substr(hashpos + 1); + + V1_0::CanMessageId msgid; + // "0x" must be prepended to msgidStr, since ParseUint doesn't accept a base argument. + if (!android::base::ParseUint("0x" + msgidStr, &msgid)) return std::nullopt; + + V1_0::CanMessage canmsg = {}; + canmsg.id = msgid; + if (msgid > 0x7FF) { + canmsg.isExtendedId = true; + } + + if (android::base::StartsWith(payloadStr, "R")) { + canmsg.remoteTransmissionRequest = true; + + /* The CAN bus HAL doesn't define a data length code (DLC) field, since it is inferrred + * from the payload size. RTR messages indicate to the receiver how many bytes they are + * expecting to receive back via the DLC sent with the RTR frame. */ + if (payloadStr.size() <= 1) return canmsg; + + unsigned int dlc = 0; + + /* The maximum DLC for CAN-FD is 64 bytes and CAN 2.0 is 8 bytes. Limit the size of the DLC + * to something memory safe and let the HAL determine if the DLC is valid. */ + if (!android::base::ParseUint(payloadStr.substr(1), &dlc, 10000u)) { + std::cerr << "Invalid DLC for RTR frame!" << std::endl; + return std::nullopt; + } + canmsg.payload.resize(dlc); + return canmsg; + } + + std::vector payload; + if (payloadStr.size() % 2 != 0) return std::nullopt; + for (size_t i = 0; i < payloadStr.size(); i += 2) { + std::string byteStr(payloadStr, i, 2); + uint8_t byteBuf; + if (!android::base::ParseUint("0x" + byteStr, &byteBuf)) return std::nullopt; + payload.emplace_back(byteBuf); + } + canmsg.payload = payload; + + return canmsg; +} + +static int main(int argc, char* argv[]) { + base::SetDefaultTag("CanHalSend"); + base::SetMinimumLogSeverity(android::base::VERBOSE); + + if (argc == 0) { + usage(); + return 0; + } + + if (argc != 2) { + std::cerr << "Invalid number of arguments" << std::endl; + usage(); + return -1; + } + + std::string busname(argv[0]); + const auto canmsg = parseCanMessage(argv[1]); + if (!canmsg) { + std::cerr << "Failed to parse CAN message argument" << std::endl; + return -1; + } + + return cansend(busname, *canmsg); +} + +} // namespace android::hardware::automotive::can + +int main(int argc, char* argv[]) { + if (argc < 1) return -1; + return ::android::hardware::automotive::can::main(--argc, ++argv); +} diff --git a/automotive/can/1.0/tools/configurator/Android.bp b/automotive/can/1.0/tools/configurator/Android.bp new file mode 100644 index 0000000000..2c4bc1d6fc --- /dev/null +++ b/automotive/can/1.0/tools/configurator/Android.bp @@ -0,0 +1,34 @@ +// +// Copyright (C) 2020 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_binary { + name: "canhalconfigurator", + init_rc: ["canhalconfigurator.rc"], + defaults: ["android.hardware.automotive.can@defaults"], + srcs: [ + "canhalconfigurator.cpp", + "canprototools.cpp", + ], + shared_libs: [ + "android.hardware.automotive.can@1.0", + "libhidlbase", + "libprotobuf-cpp-full", + ], + static_libs: [ + "android.hardware.automotive.can@1.x-config-format", + "android.hardware.automotive.can@libcanhaltools", + ], +} diff --git a/automotive/can/1.0/tools/configurator/canhalconfigurator.cpp b/automotive/can/1.0/tools/configurator/canhalconfigurator.cpp new file mode 100644 index 0000000000..a100f06f1b --- /dev/null +++ b/automotive/can/1.0/tools/configurator/canhalconfigurator.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2020 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 "canbus_config.pb.h" +#include "canprototools.h" + +#include +#include +#include + +#include +#include + +namespace android::hardware::automotive::can { + +using ICanController = V1_0::ICanController; + +/** + * Takes output from parsed protobuf config and uses it to configure the CAN HAL. + * + * \param pb_cfg is an instance of the autogenerated protobuf object for our configuration. + * \return boolean status, true on success, false on failure. + */ +static bool processPbCfg(const config::CanBusConfig& pb_cfg) { + for (auto const& bus : pb_cfg.buses()) { + if (bus.name().empty()) { + LOG(ERROR) << "Invalid config: Bus config must have a valid name field"; + return false; + } + + LOG(INFO) << "Configure " << bus.name(); + auto bus_cfg = config::fromPbBus(bus); + if (!bus_cfg.has_value()) { + return false; + } + + // TODO(149405589): remove this sleep and associated includes. + std::this_thread::sleep_for(std::chrono::seconds(1)); + if (libcanhaltools::configureIface(*bus_cfg) != ICanController::Result::OK) { + LOG(ERROR) << "No controller supports " << bus.name() << std::endl; + // TODO(149405589): add retry logic in case a bus fails to come up. + continue; + } + LOG(INFO) << bus.name() << " has been successfully configured!"; + } + return true; +} + +/** + * This kicks off the CAN HAL configuration process. This starts the following: + * 1. Reading the config file + * 2. Setting up CAN buses + * 3. Handling services + * \param filepath is a string specifying the absolute path of the config file + * \return boolean status, true on success, false on failure + */ +static bool configuratorStart(const std::string& filepath) { + base::SetDefaultTag("CanConfigurator"); + + auto pb_cfg = config::parseConfigFile(filepath); + if (!pb_cfg.has_value()) { + return false; + } + + // process the rest of the config file data and configure the CAN buses. + if (!processPbCfg(*pb_cfg)) { + return false; + } + LOG(INFO) << "CAN HAL has been configured!"; + return true; +} + +} // namespace android::hardware::automotive::can + +int main(int argc, char* argv[]) { + std::string config_filepath = "/etc/canbus_config.pb"; + + // allow for CLI specification of a config file. + if (argc == 2) { + config_filepath = argv[1]; + } else if (argc > 2) { + std::cerr << "usage: " << argv[0] << " [optional config filepath]"; + return 1; + } + + if (!::android::hardware::automotive::can::configuratorStart(config_filepath)) { + return 1; + } + return 0; +} diff --git a/automotive/can/1.0/tools/configurator/canhalconfigurator.rc b/automotive/can/1.0/tools/configurator/canhalconfigurator.rc new file mode 100644 index 0000000000..12c24652dd --- /dev/null +++ b/automotive/can/1.0/tools/configurator/canhalconfigurator.rc @@ -0,0 +1,3 @@ +service canhalconfigurator /system/bin/canhalconfigurator + class core + oneshot diff --git a/automotive/can/1.0/tools/configurator/canprototools.cpp b/automotive/can/1.0/tools/configurator/canprototools.cpp new file mode 100644 index 0000000000..0a12bd6f21 --- /dev/null +++ b/automotive/can/1.0/tools/configurator/canprototools.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2020 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 "canprototools.h" + +#include +#include +#include +#include +#include + +#include + +namespace android::hardware::automotive::can::config { + +using ICanController = V1_0::ICanController; + +/** + * Helper function for parseConfigFile. readString is used to get the fist n characters (n) from an + * istream object (s) and return it as a string object. + * + * \param s istream of the file you intend to read. + * \param n streamsize object of the number of characters you'd like. + * \return optional string containing up to n characters from the stream(s) you provided. + */ +static std::optional readString(std::istream& s, std::streamsize n) { + char buff[n]; + auto got = s.read(buff, n).gcount(); + if (!s.good() && !s.eof()) return std::nullopt; + return std::string(buff, 0, std::min(n, got)); +} + +std::optional parseConfigFile(const std::string& filepath) { + std::ifstream cfg_stream(filepath); + + // text headers that would be present in a plaintext proto config file. + static const std::array text_headers = {"buses", "#", "controller"}; + auto cfg_file_snippet = readString(cfg_stream, 10); + + if (!cfg_file_snippet.has_value()) { + LOG(ERROR) << "Can't open " << filepath << " for reading"; + return std::nullopt; + } + cfg_stream.seekg(0); + + // check if any of the textHeaders are at the start of the config file. + bool text_format = false; + for (auto const& header : text_headers) { + if (cfg_file_snippet->compare(0, header.length(), header) == 0) { + text_format = true; + break; + } + } + + CanBusConfig config; + if (text_format) { + google::protobuf::io::IstreamInputStream pb_stream(&cfg_stream); + if (!google::protobuf::TextFormat::Parse(&pb_stream, &config)) { + LOG(ERROR) << "Failed to parse (text format) " << filepath; + return std::nullopt; + } + } else if (!config.ParseFromIstream(&cfg_stream)) { + LOG(ERROR) << "Failed to parse (binary format) " << filepath; + return std::nullopt; + } + return config; +} + +std::optional fromPbBus(const Bus& pb_bus) { + ICanController::BusConfig bus_cfg = {}; + bus_cfg.name = pb_bus.name(); + + switch (pb_bus.iface_type_case()) { + case Bus::kNative: { + const auto ifname = pb_bus.native().ifname(); + const auto serialno = pb_bus.native().serialno(); + if (ifname.empty() == serialno.empty()) { + LOG(ERROR) << "Invalid config: native type bus must have an iface name xor a " + << "serial number"; + return std::nullopt; + } + bus_cfg.bitrate = pb_bus.bitrate(); + ICanController::BusConfig::InterfaceId::Socketcan socketcan = {}; + if (!ifname.empty()) socketcan.ifname(ifname); + if (!serialno.empty()) socketcan.serialno({serialno.begin(), serialno.end()}); + bus_cfg.interfaceId.socketcan(socketcan); + break; + } + case Bus::kSlcan: { + const auto ttyname = pb_bus.slcan().ttyname(); + const auto serialno = pb_bus.slcan().serialno(); + if (ttyname.empty() == serialno.empty()) { + LOG(ERROR) << "Invalid config: slcan type bus must have a tty name"; + return std::nullopt; + } + bus_cfg.bitrate = pb_bus.bitrate(); + ICanController::BusConfig::InterfaceId::Slcan slcan = {}; + if (!ttyname.empty()) slcan.ttyname(ttyname); + if (!serialno.empty()) slcan.serialno({serialno.begin(), serialno.end()}); + bus_cfg.interfaceId.slcan(slcan); + break; + } + case Bus::kVirtual: { + // Theoretically, we could just create the next available vcan iface. + const auto ifname = pb_bus.virtual_().ifname(); + if (ifname.empty()) { + LOG(ERROR) << "Invalid config: native type bus must have an iface name"; + return std::nullopt; + } + bus_cfg.interfaceId.virtualif({ifname}); + break; + } + case Bus::kIndexed: { + const auto index = pb_bus.indexed().index(); + if (index > UINT8_MAX) { + LOG(ERROR) << "Interface index out of range: " << index; + return std::nullopt; + } + bus_cfg.interfaceId.indexed({uint8_t(index)}); + break; + } + default: + LOG(ERROR) << "Invalid config: bad interface type for " << bus_cfg.name; + return std::nullopt; + } + return bus_cfg; +} + +std::optional getHalIftype(const Bus& pb_bus) { + switch (pb_bus.iface_type_case()) { + case Bus::kNative: + return ICanController::InterfaceType::SOCKETCAN; + case Bus::kSlcan: + return ICanController::InterfaceType::SLCAN; + case Bus::kVirtual: + return ICanController::InterfaceType::VIRTUAL; + case Bus::kIndexed: + return ICanController::InterfaceType::INDEXED; + default: + return std::nullopt; + } +} + +} // namespace android::hardware::automotive::can::config diff --git a/automotive/can/1.0/tools/configurator/canprototools.h b/automotive/can/1.0/tools/configurator/canprototools.h new file mode 100644 index 0000000000..b7f2b6f0d2 --- /dev/null +++ b/automotive/can/1.0/tools/configurator/canprototools.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 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 "canbus_config.pb.h" + +#include + +namespace android::hardware::automotive::can::config { + +/** + * This reads the protobuf config file into a protobuf object. Both text based protobuf files as + * well as binary format protobuf files are supported. + * + * \param filepath string containing the name of the config file to read. + * \return a CanBusConfig protobuf object constructed from the config file. + */ +std::optional parseConfigFile(const std::string& filepath); + +/** + * Converts protobuf format single-bus config object to a HAL bus config object. + * + * \param pb_bus is the protobuf object representing a the configuration of one CAN bus. + * \return a converted HAL bus config object. + */ +std::optional fromPbBus(const Bus& pb_bus); + +/** + * Get the CAN HAL interface type specified by a given protobuf config object. + * + * \param pb_bus is the protobuf object representing a the configuration of one CAN bus. + * \return the CAN HAL interface type. + */ +std::optional getHalIftype(const Bus& pb_bus); + +} // namespace android::hardware::automotive::can::config diff --git a/automotive/can/1.0/tools/configurator/proto/Android.bp b/automotive/can/1.0/tools/configurator/proto/Android.bp new file mode 100644 index 0000000000..05e120524a --- /dev/null +++ b/automotive/can/1.0/tools/configurator/proto/Android.bp @@ -0,0 +1,28 @@ +// +// Copyright (C) 2020 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: "android.hardware.automotive.can@1.x-config-format", + defaults: ["android.hardware.automotive.can@defaults"], + proto: { + export_proto_headers: true, + type: "full", + }, + strip: { + keep_symbols: true, + }, + srcs: ["canbus_config.proto"], +} diff --git a/automotive/can/1.0/tools/configurator/proto/canbus_config.proto b/automotive/can/1.0/tools/configurator/proto/canbus_config.proto new file mode 100644 index 0000000000..9aa33aceb5 --- /dev/null +++ b/automotive/can/1.0/tools/configurator/proto/canbus_config.proto @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 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. + */ + +syntax = "proto3"; + +package android.hardware.automotive.can.config; + +message IfaceNative { + string ifname = 1; + repeated string serialno = 2; +}; + +message IfaceSlcan { + string ttyname = 1; + repeated string serialno = 2; +}; + +message IfaceVirtual { + string ifname = 1; +}; + +message IfaceIndexed { + uint32 index = 1; +}; + +message Bus { + string name = 1; // this is the name presented in the HAL + oneof iface_type { + IfaceNative native = 2; + IfaceSlcan slcan = 3; + IfaceVirtual virtual = 4; + IfaceIndexed indexed = 5; + } + uint32 bitrate = 6; +}; + +message CanBusConfig { + repeated Bus buses = 1; +}; diff --git a/automotive/can/1.0/tools/libcanhaltools/Android.bp b/automotive/can/1.0/tools/libcanhaltools/Android.bp new file mode 100644 index 0000000000..cee9eef20d --- /dev/null +++ b/automotive/can/1.0/tools/libcanhaltools/Android.bp @@ -0,0 +1,32 @@ +// +// Copyright (C) 2020 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: "android.hardware.automotive.can@libcanhaltools", + defaults: ["android.hardware.automotive.can@defaults"], + vendor_available: true, + srcs: [ + "libcanhaltools.cpp", + ], + export_include_dirs: ["include"], + shared_libs: [ + "android.hardware.automotive.can@1.0", + "libhidlbase", + ], + header_libs: [ + "android.hardware.automotive.can@hidl-utils-lib", + ], +} diff --git a/automotive/can/1.0/tools/libcanhaltools/include/libcanhaltools/libcanhaltools.h b/automotive/can/1.0/tools/libcanhaltools/include/libcanhaltools/libcanhaltools.h new file mode 100644 index 0000000000..bbd1fe5873 --- /dev/null +++ b/automotive/can/1.0/tools/libcanhaltools/include/libcanhaltools/libcanhaltools.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 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 +#include + +namespace android::hardware::automotive::can::libcanhaltools { + +/** + * Fetch the list of registered can controller services. + * + * \return list of service names identifying the registered can controllers. + */ +hidl_vec getControlServices(); + +/** + * Determine if an can controller supports a specific interface type. + * + * \param ctrl a pointer to a can controller instance to check for interface support. + * \param iftype the interface type we wish to check if ctrl supports. + * \return true if iftype is supported by ctrl, false if not supported. + */ +bool isSupported(sp ctrl, V1_0::ICanController::InterfaceType iftype); + +/** + * Configures a CAN interface through the CAN HAL and brings it up. + * + * \param can_config this holds the parameters for configuring a CAN bus. + * \return status passed back from the CAN HAL, should be OK on success. + */ +V1_0::ICanController::Result configureIface(V1_0::ICanController::BusConfig can_config); + +} // namespace android::hardware::automotive::can::libcanhaltools diff --git a/automotive/can/1.0/tools/libcanhaltools/libcanhaltools.cpp b/automotive/can/1.0/tools/libcanhaltools/libcanhaltools.cpp new file mode 100644 index 0000000000..9192e2f52d --- /dev/null +++ b/automotive/can/1.0/tools/libcanhaltools/libcanhaltools.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2020 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 "libcanhaltools/libcanhaltools.h" + +#include +#include +#include +#include + +#include +#include + +namespace android::hardware::automotive::can::libcanhaltools { + +using ICanBus = V1_0::ICanBus; +using ICanController = V1_0::ICanController; +using IfIdDisc = ICanController::BusConfig::InterfaceId::hidl_discriminator; + +hidl_vec getControlServices() { + auto manager = hidl::manager::V1_2::IServiceManager::getService(); + hidl_vec services; + manager->listManifestByInterface(ICanController::descriptor, hidl_utils::fill(&services)); + CHECK(services.size() > 0) << "No ICanController services registered (missing privileges?)" + << std::endl; + return services; +} + +bool isSupported(sp ctrl, ICanController::InterfaceType iftype) { + hidl_vec supported; + if (!ctrl->getSupportedInterfaceTypes(hidl_utils::fill(&supported)).isOk()) return false; + return supported.contains(iftype); +} + +ICanController::InterfaceType getIftype(ICanController::BusConfig can_config) { + switch (can_config.interfaceId.getDiscriminator()) { + case IfIdDisc::socketcan: + return ICanController::InterfaceType::SOCKETCAN; + case IfIdDisc::slcan: + return ICanController::InterfaceType::SLCAN; + case IfIdDisc::virtualif: + return ICanController::InterfaceType::VIRTUAL; + case IfIdDisc::indexed: + return ICanController::InterfaceType::INDEXED; + default: + CHECK(false) << "HAL returned unexpected interface type!"; + } +} + +ICanController::Result configureIface(ICanController::BusConfig can_config) { + auto iftype = getIftype(can_config); + auto can_controller_list = getControlServices(); + for (auto const& service : can_controller_list) { + auto ctrl = ICanController::getService(service); + if (ctrl == nullptr) { + LOG(ERROR) << "Couldn't open ICanController/" << service; + continue; + } + + if (!libcanhaltools::isSupported(ctrl, iftype)) continue; + + const auto up_result = ctrl->upInterface(can_config); + if (up_result != ICanController::Result::OK) { + LOG(ERROR) << "Failed to bring " << can_config.name << " up: " << toString(up_result) + << std::endl; + } + return up_result; + } + return ICanController::Result::NOT_SUPPORTED; +} + +} // namespace android::hardware::automotive::can::libcanhaltools diff --git a/automotive/can/1.0/types.hal b/automotive/can/1.0/types.hal new file mode 100644 index 0000000000..5eeed53349 --- /dev/null +++ b/automotive/can/1.0/types.hal @@ -0,0 +1,142 @@ +/* + * 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.can@1.0; + +/** + * CAN message ID. + * + * Does not include any flags like RTR nor ERR, only a plain 11-bit + * or 29-bit identifier, as defined in CAN 1.x/2.0x. + * + * Unused bits must be set to zero. + */ +typedef uint32_t CanMessageId; + +/** + * CAN message being sent or received. + */ +struct CanMessage { + CanMessageId id; + + /** + * CAN message payload, as defined in CAN 1.x and CAN 2.x standards. + * + * The length of the payload vector directly translates to the length + * of the data frame (i.e. includes any padding bytes), so it may be in + * a range of: + * - 0-8 bytes for standard CAN; + * - up to 64 bytes for CAN FD. + * ISO TP is not supported directly for now. + */ + vec payload; + + /** + * Time in nanoseconds since boot. + * + * Ignored for outgoing messages. + */ + uint64_t timestamp; + + /** + * A request to proactively pull certain data from other ECU in CAN network. + * + * For details please refer to CAN standard. + * + * If this flag is set, payload must be empty. + */ + bool remoteTransmissionRequest; + + /** + * Flag indicating if the message has an extended ID. + * + * Extended ID's are 29 bits long, as opposed to the standard 11 bit ID. + * It can not simply be inferred from the length of the ID itself, as the + * message ID 0x00000123 != message ID 0x123. + */ + bool isExtendedId; +}; + +/** + * Single filter rule for CAN messages. + * + * A filter is satisfied if: + * ((receivedId & mask) == (id & mask)) == !exclude + * + * In order for set of filters to match, at least one non-exclude filters must match (if there is + * one) and all exclude filters must match. In other words: + * - a single matching non-exclude filter makes the whole set matching; + * - a single non-matching excluded filter makes the whole set non-matching. + */ +struct CanMessageFilter { + CanMessageId id; + uint32_t mask; + /** Remote Transmission Request; another ECU requests bytes of data on this message ID */ + FilterFlag rtr; + /** 29 bit message ID is used instead of 11 bits */ + FilterFlag extendedFormat; + /** 'exclude' *DOES* apply to rtr and extendedFormat! */ + bool exclude; +}; + + +/** + * Types of filter that can be applied to a CanMessageFilter + */ +enum FilterFlag : uint8_t { + /** Default, FilterFlag doesn't effect what messages filtered */ + DONT_CARE = 0, + /** This FilterFlag MUST be present in received messages to pass though the filter */ + SET, + /** This FilterFlag must NOT be present in received messages to pass though the filter */ + NOT_SET, +}; + +enum Result : uint8_t { + OK, + UNKNOWN_ERROR, + PAYLOAD_TOO_LONG, + INTERFACE_DOWN, + TRANSMISSION_FAILURE, + INVALID_ARGUMENTS, +}; + +/** + * @see ICanMessageListener#onError + */ +enum ErrorEvent : uint8_t { + UNKNOWN_ERROR, + + /** A problem with CAN interface HW. */ + HARDWARE_ERROR, + + /** CAN interface is down. */ + INTERFACE_DOWN, + + /** TX buffer overflow: client is sending too many packets. */ + TX_OVERFLOW, + + /** RX buffer overflow: client is not reading packets fast enough. */ + RX_OVERFLOW, + + /** Received malformed input. */ + MALFORMED_INPUT, + + /** Bus overload: there is too much traffic on the bus. */ + BUS_OVERLOAD, + + /** Bus error: shorted Hi/Lo line, bus off etc. */ + BUS_ERROR, +}; diff --git a/automotive/can/1.0/vts/functional/Android.bp b/automotive/can/1.0/vts/functional/Android.bp new file mode 100644 index 0000000000..d020750f3a --- /dev/null +++ b/automotive/can/1.0/vts/functional/Android.bp @@ -0,0 +1,53 @@ +// +// 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_defaults { + name: "android.hardware.automotive.can@vts-defaults", + defaults: [ + "VtsHalTargetTestDefaults", + "android.hardware.automotive.can@defaults", + ], + header_libs: [ + "android.hardware.automotive.can@hidl-utils-lib", + ], + static_libs: [ + "android.hardware.automotive.can@1.0", + "android.hardware.automotive.can@vts-utils-lib", + "libgmock", + ], + test_suites: [ + "general-tests", + "vts", + ], +} + +cc_test { + name: "VtsHalCanControllerV1_0TargetTest", + defaults: ["android.hardware.automotive.can@vts-defaults"], + srcs: ["VtsHalCanControllerV1_0TargetTest.cpp"], +} + +cc_test { + name: "VtsHalCanBusV1_0TargetTest", + defaults: ["android.hardware.automotive.can@vts-defaults"], + srcs: ["VtsHalCanBusV1_0TargetTest.cpp"], +} + +cc_test { + name: "VtsHalCanBusVirtualV1_0TargetTest", + defaults: ["android.hardware.automotive.can@vts-defaults"], + srcs: ["VtsHalCanBusVirtualV1_0TargetTest.cpp"], +} diff --git a/automotive/can/1.0/vts/functional/VtsHalCanBusV1_0TargetTest.cpp b/automotive/can/1.0/vts/functional/VtsHalCanBusV1_0TargetTest.cpp new file mode 100644 index 0000000000..a8e7c0b633 --- /dev/null +++ b/automotive/can/1.0/vts/functional/VtsHalCanBusV1_0TargetTest.cpp @@ -0,0 +1,183 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android::hardware::automotive::can::V1_0::vts { + +using hardware::hidl_vec; + +struct CanMessageListener : public can::V1_0::ICanMessageListener { + virtual Return onReceive(const can::V1_0::CanMessage&) override { return {}; } +}; + +struct CanErrorListener : public can::V1_0::ICanErrorListener { + virtual Return onError(ErrorEvent, bool) override { return {}; } +}; + +class CanBusHalTest : public ::testing::TestWithParam { + protected: + virtual void SetUp() override; + virtual void TearDown() override; + + std::tuple> listen(const hidl_vec& filter, + const sp& listener); + sp listenForErrors(const sp& listener); + + sp mCanBus; +}; + +void CanBusHalTest::SetUp() { + mCanBus = ICanBus::getService(GetParam()); + ASSERT_TRUE(mCanBus) << "Couldn't open CAN Bus: " << GetParam(); +} + +void CanBusHalTest::TearDown() { + mCanBus.clear(); +} + +std::tuple> CanBusHalTest::listen( + const hidl_vec& filter, const sp& listener) { + Result halResult; + sp closeHandle; + mCanBus->listen(filter, listener, hidl_utils::fill(&halResult, &closeHandle)).assertOk(); + + return {halResult, closeHandle}; +} + +sp CanBusHalTest::listenForErrors(const sp& listener) { + const auto res = mCanBus->listenForErrors(listener); + res.assertOk(); + return res; +} + +TEST_P(CanBusHalTest, SendNoPayload) { + CanMessage msg = {}; + msg.id = 0x123; + ASSERT_NE(mCanBus, nullptr); + const auto result = mCanBus->send(msg); + ASSERT_EQ(Result::OK, result); +} + +TEST_P(CanBusHalTest, Send8B) { + CanMessage msg = {}; + msg.id = 0x234; + msg.payload = {1, 2, 3, 4, 5, 6, 7, 8}; + + const auto result = mCanBus->send(msg); + ASSERT_EQ(Result::OK, result); +} + +TEST_P(CanBusHalTest, SendZeroId) { + CanMessage msg = {}; + msg.payload = {1, 2, 3}; + + const auto result = mCanBus->send(msg); + ASSERT_EQ(Result::OK, result); +} + +TEST_P(CanBusHalTest, SendTooLong) { + CanMessage msg = {}; + msg.id = 0x123; + msg.payload = hidl_vec(102400); // 100kiB + + const auto result = mCanBus->send(msg); + ASSERT_EQ(Result::PAYLOAD_TOO_LONG, result); +} + +TEST_P(CanBusHalTest, ListenNoFilter) { + const auto [result, closeHandle] = listen({}, new CanMessageListener()); + ASSERT_EQ(Result::OK, result); + + closeHandle->close().assertOk(); +} + +TEST_P(CanBusHalTest, ListenSomeFilter) { + hidl_vec filters = { + {0x123, 0x1FF, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, false}, + {0x001, 0x00F, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, true}, + {0x200, 0x100, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, false}, + }; + + const auto [result, closeHandle] = listen(filters, new CanMessageListener()); + ASSERT_EQ(Result::OK, result); + + closeHandle->close().assertOk(); +} + +TEST_P(CanBusHalTest, ListenNull) { + const auto [result, closeHandle] = listen({}, nullptr); + ASSERT_EQ(Result::INVALID_ARGUMENTS, result); +} + +TEST_P(CanBusHalTest, DoubleCloseListener) { + const auto [result, closeHandle] = listen({}, new CanMessageListener()); + ASSERT_EQ(Result::OK, result); + + closeHandle->close().assertOk(); + closeHandle->close().assertOk(); +} + +TEST_P(CanBusHalTest, DontCloseListener) { + const auto [result, closeHandle] = listen({}, new CanMessageListener()); + ASSERT_EQ(Result::OK, result); +} + +TEST_P(CanBusHalTest, DoubleCloseErrorListener) { + auto closeHandle = listenForErrors(new CanErrorListener()); + ASSERT_NE(nullptr, closeHandle.get()); + + closeHandle->close().assertOk(); + closeHandle->close().assertOk(); +} + +TEST_P(CanBusHalTest, DoubleCloseNullErrorListener) { + auto closeHandle = listenForErrors(nullptr); + ASSERT_NE(nullptr, closeHandle.get()); + + closeHandle->close().assertOk(); + closeHandle->close().assertOk(); +} + +TEST_P(CanBusHalTest, DontCloseErrorListener) { + auto closeHandle = listenForErrors(new CanErrorListener()); + ASSERT_NE(nullptr, closeHandle.get()); +} + +/** + * This test requires that you bring up a valid bus first. + * + * Before running: + * mma -j && adb root && adb remount && adb sync + * + * Example manual invocation: + * adb shell canhalctrl up socketcan can0 125000 + * adb shell /data/nativetest64/VtsHalCanBusV1_0TargetTest/VtsHalCanBusV1_0TargetTest\ + * --gtest_filter=*_ + */ +INSTANTIATE_TEST_SUITE_P( // + PerInstance, CanBusHalTest, testing::ValuesIn(getAllHalInstanceNames(ICanBus::descriptor)), + PrintInstanceNameToString); + +} // namespace android::hardware::automotive::can::V1_0::vts diff --git a/automotive/can/1.0/vts/functional/VtsHalCanBusVirtualV1_0TargetTest.cpp b/automotive/can/1.0/vts/functional/VtsHalCanBusVirtualV1_0TargetTest.cpp new file mode 100644 index 0000000000..9039435458 --- /dev/null +++ b/automotive/can/1.0/vts/functional/VtsHalCanBusVirtualV1_0TargetTest.cpp @@ -0,0 +1,876 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace android::hardware::automotive::can::V1_0::vts { + +using namespace std::chrono_literals; + +using hardware::hidl_vec; +using InterfaceType = ICanController::InterfaceType; + +struct CanMessageListener : public can::V1_0::ICanMessageListener { + DISALLOW_COPY_AND_ASSIGN(CanMessageListener); + + CanMessageListener() {} + + virtual Return onReceive(const can::V1_0::CanMessage& msg) override { + std::unique_lock lk(mMessagesGuard); + mMessages.push_back(msg); + mMessagesUpdated.notify_one(); + return {}; + } + + virtual ~CanMessageListener() { mCloseHandle->close(); } + + void assignCloseHandle(sp closeHandle) { + EXPECT_TRUE(closeHandle); + EXPECT_FALSE(mCloseHandle); + mCloseHandle = closeHandle; + } + + std::vector fetchMessages(std::chrono::milliseconds timeout, + unsigned atLeast = 1) { + std::unique_lock lk(mMessagesGuard); + mMessagesUpdated.wait_for(lk, timeout, [&] { return mMessages.size() >= atLeast; }); + const auto messages = mMessages; + mMessages.clear(); + return messages; + } + + private: + sp mCloseHandle; + + std::mutex mMessagesGuard; + std::condition_variable mMessagesUpdated GUARDED_BY(mMessagesGuard); + std::vector mMessages GUARDED_BY(mMessagesGuard); +}; + +struct Bus { + DISALLOW_COPY_AND_ASSIGN(Bus); + + Bus(sp controller, const ICanController::BusConfig& config) + : mIfname(config.name), mController(controller) { + const auto result = controller->upInterface(config); + EXPECT_EQ(ICanController::Result::OK, result); + + /* Not using ICanBus::getService here, since it ignores interfaces not in the manifest + * file -- this is a test, so we don't want to add dummy services to a device manifest. */ + auto manager = hidl::manager::V1_2::IServiceManager::getService(); + auto service = manager->get(ICanBus::descriptor, config.name); + mBus = ICanBus::castFrom(service); + EXPECT_TRUE(mBus) << "ICanBus/" << config.name << " failed to register"; + } + + virtual ~Bus() { reset(); } + + void reset() { + mBus.clear(); + if (mController) { + const auto res = mController->downInterface(mIfname); + EXPECT_TRUE(res); + mController.clear(); + } + } + + ICanBus* operator->() const { return mBus.get(); } + sp get() { return mBus; } + + sp listen(const hidl_vec& filter) { + sp listener = new CanMessageListener(); + + Result result; + sp closeHandle; + mBus->listen(filter, listener, hidl_utils::fill(&result, &closeHandle)).assertOk(); + EXPECT_EQ(Result::OK, result); + listener->assignCloseHandle(closeHandle); + + return listener; + } + + void send(const CanMessage& msg) { + EXPECT_NE(mBus, nullptr); + if (!mBus) return; + const auto result = mBus->send(msg); + EXPECT_EQ(Result::OK, result); + } + + private: + const std::string mIfname; + sp mController; + sp mBus; +}; + +class CanBusVirtualHalTest : public ::testing::TestWithParam { + protected: + virtual void SetUp() override; + virtual void TearDown() override; + static void SetUpTestCase(); + + Bus makeBus(); + + protected: + static hidl_vec mBusNames; + + private: + unsigned mLastIface = 0; + sp mCanController = nullptr; + static bool mTestCaseInitialized; +}; + +hidl_vec CanBusVirtualHalTest::mBusNames; +bool CanBusVirtualHalTest::mTestCaseInitialized = false; + +static CanMessage makeMessage(CanMessageId id, bool rtr, bool extended) { + CanMessage msg = {}; + msg.id = id; + msg.remoteTransmissionRequest = rtr; + msg.isExtendedId = extended; + return msg; +} + +static void clearTimestamps(std::vector& messages) { + std::for_each(messages.begin(), messages.end(), [](auto& msg) { msg.timestamp = 0; }); +} + +void CanBusVirtualHalTest::SetUp() { + ASSERT_TRUE(mTestCaseInitialized); + + mCanController = ICanController::getService(GetParam()); + ASSERT_TRUE(mCanController) << "Couldn't open CAN Controller: " << GetParam(); + + hidl_vec supported; + mCanController->getSupportedInterfaceTypes(hidl_utils::fill(&supported)).assertOk(); + if (!supported.contains(InterfaceType::VIRTUAL)) GTEST_SKIP(); +} + +void CanBusVirtualHalTest::TearDown() { + mCanController.clear(); +} + +void CanBusVirtualHalTest::SetUpTestCase() { + mBusNames = utils::getBusNames(); + ASSERT_NE(0u, mBusNames.size()) << "No ICanBus HALs defined in device manifest"; + + mTestCaseInitialized = true; +} + +Bus CanBusVirtualHalTest::makeBus() { + const auto idx = mLastIface++; + EXPECT_LT(idx, mBusNames.size()); + + ICanController::BusConfig config = {}; + config.name = mBusNames[idx]; + config.interfaceId.virtualif({"vcan50"}); + + return Bus(mCanController, config); +} + +TEST_P(CanBusVirtualHalTest, Send) { + auto bus = makeBus(); + + CanMessage msg = {}; + msg.id = 0x123; + msg.payload = {1, 2, 3}; + + bus.send(msg); +} + +TEST_P(CanBusVirtualHalTest, SendAfterClose) { + auto bus = makeBus(); + auto zombie = bus.get(); + bus.reset(); + + const auto result = zombie->send({}); + ASSERT_EQ(Result::INTERFACE_DOWN, result); +} + +TEST_P(CanBusVirtualHalTest, SendAndRecv) { + if (mBusNames.size() < 2u) GTEST_SKIP() << "Not testable with less than two CAN buses."; + auto bus1 = makeBus(); + auto bus2 = makeBus(); + + auto listener = bus2.listen({}); + + CanMessage msg = {}; + msg.id = 0x123; + msg.payload = {1, 2, 3}; + bus1.send(msg); + + auto messages = listener->fetchMessages(100ms); + ASSERT_EQ(1u, messages.size()); + ASSERT_NEAR(uint64_t(elapsedRealtimeNano()), messages[0].timestamp, + std::chrono::nanoseconds(100ms).count()); + clearTimestamps(messages); + ASSERT_EQ(msg, messages[0]); +} + +TEST_P(CanBusVirtualHalTest, DownOneOfTwo) { + if (mBusNames.size() < 2u) GTEST_SKIP() << "Not testable with less than two CAN buses."; + + auto bus1 = makeBus(); + auto bus2 = makeBus(); + + bus2.reset(); + + bus1.send({}); +} + +TEST_P(CanBusVirtualHalTest, FilterPositive) { + if (mBusNames.size() < 2u) GTEST_SKIP() << "Not testable with less than two CAN buses."; + auto bus1 = makeBus(); + auto bus2 = makeBus(); + + /* clang-format off */ + /* id, mask, rtr, eff, exclude */ + hidl_vec filterPositive = { + {0x334, 0x73F, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, false}, + {0x49D, 0x700, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, false}, + {0x325, 0x7FC, FilterFlag::DONT_CARE, FilterFlag::NOT_SET, false}, + {0x246, 0x7FF, FilterFlag::SET, FilterFlag::DONT_CARE, false}, + {0x1A2, 0x7FB, FilterFlag::SET, FilterFlag::NOT_SET, false}, + {0x607, 0x7C9, FilterFlag::NOT_SET, FilterFlag::DONT_CARE, false}, + {0x7F4, 0x777, FilterFlag::NOT_SET, FilterFlag::NOT_SET, false}, + {0x1BF19EAF, 0x10F0F0F0, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, false}, + {0x12E99200, 0x1FFFFFFF, FilterFlag::DONT_CARE, FilterFlag::SET, false}, + {0x06B70270, 0x1FFFFFFF, FilterFlag::SET, FilterFlag::DONT_CARE, false}, + {0x096CFD2B, 0x1FFFFFFF, FilterFlag::SET, FilterFlag::SET, false}, + {0x1BDCB008, 0x0F0F0F0F, FilterFlag::NOT_SET, FilterFlag::DONT_CARE, false}, + {0x08318B46, 0x10F0F0F0, FilterFlag::NOT_SET, FilterFlag::SET, false}, + {0x06B, 0x70F, FilterFlag::DONT_CARE, FilterFlag::SET, false}, + {0x750, 0x70F, FilterFlag::SET, FilterFlag::SET, false}, + {0x5CF, 0x70F, FilterFlag::NOT_SET, FilterFlag::SET, false}, + }; + /* clang-format on */ + auto listenerPositive = bus2.listen(filterPositive); + + // 334:73F, DNC, DNC + bus1.send(makeMessage(0x3F4, false, false)); + bus1.send(makeMessage(0x334, false, true)); + bus1.send(makeMessage(0x374, true, false)); + bus1.send(makeMessage(0x3F4, true, true)); + + // 49D:700, DNC, DNC + bus1.send(makeMessage(0x404, false, false)); + bus1.send(makeMessage(0x4A5, false, true)); + bus1.send(makeMessage(0x4FF, true, false)); + bus1.send(makeMessage(0x46B, true, true)); + + // 325:7FC, DNC, NS + bus1.send(makeMessage(0x324, false, false)); + bus1.send(makeMessage(0x325, false, true)); // filtered out + bus1.send(makeMessage(0x326, true, false)); + bus1.send(makeMessage(0x327, true, true)); // filtered out + + // 246:7FF, SET, DNC + bus1.send(makeMessage(0x246, false, false)); // filtered out + bus1.send(makeMessage(0x246, false, true)); // filtered out + bus1.send(makeMessage(0x246, true, false)); + bus1.send(makeMessage(0x246, true, true)); + + // 1A2:7FB, SET, NS + bus1.send(makeMessage(0x1A2, false, false)); // filtered out + bus1.send(makeMessage(0x1A6, false, true)); // filtered out + bus1.send(makeMessage(0x1A2, true, false)); + bus1.send(makeMessage(0x1A6, true, true)); // filtered out + + // 607:7C9, NS, DNC + bus1.send(makeMessage(0x607, false, false)); + bus1.send(makeMessage(0x613, false, true)); + bus1.send(makeMessage(0x625, true, false)); // filtered out + bus1.send(makeMessage(0x631, true, true)); // filtered out + + // 7F4:777, NS, NS + bus1.send(makeMessage(0x774, false, false)); + bus1.send(makeMessage(0x7F4, false, true)); // filtered out + bus1.send(makeMessage(0x77C, true, false)); // filtered out + bus1.send(makeMessage(0x7FC, true, false)); // filtered out + + // 1BF19EAF:10F0F0F0, DNC, DNC + bus1.send(makeMessage(0x11F293A4, false, false)); + bus1.send(makeMessage(0x15F697A8, false, true)); + bus1.send(makeMessage(0x19FA9BAC, true, false)); + bus1.send(makeMessage(0x1DFE9FA0, true, true)); + + // 12E99200:1FFFFFFF, DNC, SET + bus1.send(makeMessage(0x12E99200, false, false)); // filtered out + bus1.send(makeMessage(0x12E99200, false, true)); + bus1.send(makeMessage(0x12E99200, true, false)); // filtered out + bus1.send(makeMessage(0x12E99200, true, true)); + + // 06B70270:1FFFFFFF, SET, DNC + bus1.send(makeMessage(0x06B70270, false, false)); // filtered out + bus1.send(makeMessage(0x06B70270, false, true)); // filtered out + bus1.send(makeMessage(0x06B70270, true, false)); + bus1.send(makeMessage(0x06B70270, true, true)); + + // 096CFD2B:1FFFFFFF, SET, SET + bus1.send(makeMessage(0x096CFD2B, false, false)); // filtered out + bus1.send(makeMessage(0x096CFD2B, false, true)); // filtered out + bus1.send(makeMessage(0x096CFD2B, true, false)); // filtered out + bus1.send(makeMessage(0x096CFD2B, true, true)); + + // 1BDCB008:0F0F0F0F, NS, DNC + bus1.send(makeMessage(0x1B2C3048, false, false)); + bus1.send(makeMessage(0x0B5C6078, false, true)); + bus1.send(makeMessage(0x1B8C90A8, true, false)); // filtered out + bus1.send(makeMessage(0x0BBCC0D8, true, true)); // filtered out + + // 08318B46:10F0F0F0, NS, SET + bus1.send(makeMessage(0x0F3E8D4C, false, false)); // filtered out + bus1.send(makeMessage(0x0B3A8948, false, true)); + bus1.send(makeMessage(0x07368544, true, false)); // filtered out + bus1.send(makeMessage(0x03328140, true, true)); // filtered out + + // 06B:70F, DNC, SET + bus1.send(makeMessage(0x00B, false, false)); // filtered out + bus1.send(makeMessage(0x04B, false, true)); + bus1.send(makeMessage(0x08B, true, false)); // filtered out + bus1.send(makeMessage(0x0FB, true, true)); + + // 750:70F, SET, SET + bus1.send(makeMessage(0x7F0, false, false)); // filtered out + bus1.send(makeMessage(0x780, false, true)); // filtered out + bus1.send(makeMessage(0x740, true, false)); // filtered out + bus1.send(makeMessage(0x700, true, true)); + + // 5CF:70F, NS, SET + bus1.send(makeMessage(0x51F, false, false)); // filtered out + bus1.send(makeMessage(0x53F, false, true)); + bus1.send(makeMessage(0x57F, true, false)); // filtered out + bus1.send(makeMessage(0x5FF, true, true)); // filtered out + + std::vector expectedPositive{ + makeMessage(0x3F4, false, false), // 334:73F, DNC, DNC + makeMessage(0x334, false, true), // 334:73F, DNC, DNC + makeMessage(0x374, true, false), // 334:73F, DNC, DNC + makeMessage(0x3F4, true, true), // 334:73F, DNC, DNC + makeMessage(0x404, false, false), // 49D:700, DNC, DNC + makeMessage(0x4A5, false, true), // 49D:700, DNC, DNC + makeMessage(0x4FF, true, false), // 49D:700, DNC, DNC + makeMessage(0x46B, true, true), // 49D:700, DNC, DNC + makeMessage(0x324, false, false), // 325:7FC, DNC, NS + makeMessage(0x326, true, false), // 325:7FC, DNC, NS + makeMessage(0x246, true, false), // 246:7FF, SET, DNC + makeMessage(0x246, true, true), // 246:7FF, SET, DNC + makeMessage(0x1A2, true, false), // 1A2:7FB, SET, NS + makeMessage(0x607, false, false), // 607:7C9, NS, DNC + makeMessage(0x613, false, true), // 607:7C9, NS, DNC + makeMessage(0x774, false, false), // 7F4:777, NS, NS + makeMessage(0x11F293A4, false, false), // 1BF19EAF:10F0F0F0, DNC, DNC + makeMessage(0x15F697A8, false, true), // 1BF19EAF:10F0F0F0, DNC, DNC + makeMessage(0x19FA9BAC, true, false), // 1BF19EAF:10F0F0F0, DNC, DNC + makeMessage(0x1DFE9FA0, true, true), // 1BF19EAF:10F0F0F0, DNC, DNC + makeMessage(0x12E99200, false, true), // 12E99200:1FFFFFFF, DNC, SET + makeMessage(0x12E99200, true, true), // 12E99200:1FFFFFFF, DNC, SET + makeMessage(0x06B70270, true, false), // 06B70270:1FFFFFFF, SET, DNC + makeMessage(0x06B70270, true, true), // 06B70270:1FFFFFFF, SET, DNC + makeMessage(0x096CFD2B, true, true), // 096CFD2B:1FFFFFFF, SET, SET + makeMessage(0x1B2C3048, false, false), // 1BDCB008:0F0F0F0F, NS, DNC + makeMessage(0x0B5C6078, false, true), // 1BDCB008:0F0F0F0F, NS, DNC + makeMessage(0x0B3A8948, false, true), // 08318B46:10F0F0F0, NS, SET + makeMessage(0x04B, false, true), // 06B:70F, DNC, SET + makeMessage(0x0FB, true, true), // 06B:70F, DNC, SET + makeMessage(0x700, true, true), // 750:70F, SET, SET + makeMessage(0x53F, false, true), // 5CF:70F, NS, SET + }; + + auto messagesPositive = listenerPositive->fetchMessages(100ms, expectedPositive.size()); + clearTimestamps(messagesPositive); + ASSERT_EQ(expectedPositive, messagesPositive); +} + +TEST_P(CanBusVirtualHalTest, FilterNegative) { + if (mBusNames.size() < 2u) GTEST_SKIP() << "Not testable with less than two CAN buses."; + auto bus1 = makeBus(); + auto bus2 = makeBus(); + + /* clang-format off */ + /* id, mask, rtr, eff exclude */ + hidl_vec filterNegative = { + {0x063, 0x7F3, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, true}, + {0x0A1, 0x78F, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, true}, + {0x18B, 0x7E3, FilterFlag::DONT_CARE, FilterFlag::NOT_SET, true}, + {0x1EE, 0x7EC, FilterFlag::SET, FilterFlag::DONT_CARE, true}, + {0x23F, 0x7A5, FilterFlag::SET, FilterFlag::NOT_SET, true}, + {0x31F, 0x77F, FilterFlag::NOT_SET, FilterFlag::DONT_CARE, true}, + {0x341, 0x77F, FilterFlag::NOT_SET, FilterFlag::NOT_SET, true}, + {0x196573DB, 0x1FFFFF7F, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, true}, + {0x1CFCB417, 0x1FFFFFEC, FilterFlag::DONT_CARE, FilterFlag::SET, true}, + {0x17CCC433, 0x1FFFFFEC, FilterFlag::SET, FilterFlag::DONT_CARE, true}, + {0x0BC2F508, 0x1FFFFFC3, FilterFlag::SET, FilterFlag::SET, true}, + {0x1179B5D2, 0x1FFFFFC3, FilterFlag::NOT_SET, FilterFlag::DONT_CARE, true}, + {0x082AF63D, 0x1FFFFFFF, FilterFlag::NOT_SET, FilterFlag::SET, true}, + {0x66D, 0x76F, FilterFlag::DONT_CARE, FilterFlag::SET, true}, + {0x748, 0x7CC, FilterFlag::SET, FilterFlag::SET, true}, + {0x784, 0x7CC, FilterFlag::NOT_SET, FilterFlag::SET, true}, + }; + /* clang-format on */ + + auto listenerNegative = bus2.listen(filterNegative); + + // 063:7F3, DNC, DNC: ~06[3,7,B,F] + bus1.send(makeMessage(0x063, false, false)); // filtered out + bus1.send(makeMessage(0x060, false, true)); + bus1.send(makeMessage(0x05B, true, false)); + bus1.send(makeMessage(0x06F, true, true)); // filtered out + + // 0A1:78F, DNC, DNC: ~0[8-F]1 + bus1.send(makeMessage(0x081, false, false)); // filtered out + bus1.send(makeMessage(0x031, false, true)); + bus1.send(makeMessage(0x061, true, false)); + bus1.send(makeMessage(0x071, true, true)); + + // 18B:7E3, DNC, NS: ~1[8-9][7,B,F] + bus1.send(makeMessage(0x18B, false, false)); // filtered out + bus1.send(makeMessage(0x188, false, true)); + bus1.send(makeMessage(0x123, true, false)); + bus1.send(makeMessage(0x1D5, true, true)); + + // 1EE:7EC, SET, DNC: ~1[E-F][C-F] + bus1.send(makeMessage(0x17E, false, false)); + bus1.send(makeMessage(0x138, false, true)); + bus1.send(makeMessage(0x123, true, false)); + bus1.send(makeMessage(0x1EC, true, true)); // filtered out + + // 23F:7A5, SET, NS: ~2[2,3,6,7][5,7,D,F] + bus1.send(makeMessage(0x222, false, false)); + bus1.send(makeMessage(0x275, false, true)); + bus1.send(makeMessage(0x23f, true, false)); // filtered out + bus1.send(makeMessage(0x241, true, false)); + bus1.send(makeMessage(0x2FF, true, true)); + + // 31F:77F, NS, DNC: ~3[1,9]F + bus1.send(makeMessage(0x32F, false, false)); + bus1.send(makeMessage(0x31F, false, true)); // filtered out + bus1.send(makeMessage(0x36F, false, true)); + bus1.send(makeMessage(0x31F, true, false)); + bus1.send(makeMessage(0x3F3, true, true)); + + // 341:77F, NS, NS: ~3[4,C]1 + bus1.send(makeMessage(0x341, false, false)); // filtered out + bus1.send(makeMessage(0x352, false, false)); + bus1.send(makeMessage(0x3AA, false, true)); + bus1.send(makeMessage(0x3BC, true, false)); + bus1.send(makeMessage(0x3FF, true, true)); + + // 196573DB:1FFFFF7F, DNC, DNC: ~196573[5,D]B + bus1.send(makeMessage(0x1965733B, false, false)); + bus1.send(makeMessage(0x1965734B, false, true)); + bus1.send(makeMessage(0x1965735B, true, false)); // filtered out + bus1.send(makeMessage(0x1965736B, true, true)); + + // 1CFCB417:1FFFFFEC, DNC, SET: ~1CFCB4[0-1][4-7] + bus1.send(makeMessage(0x1CFCB407, false, false)); + bus1.send(makeMessage(0x1CFCB4FF, false, true)); + bus1.send(makeMessage(0x1CFCB414, true, false)); + bus1.send(makeMessage(0x1CFCB407, true, true)); // filtered out + + // 17CCC433:1FFFFFEC, SET, DNC: ~17CCC4[2-3][0-3] + bus1.send(makeMessage(0x17CCC430, false, false)); + bus1.send(makeMessage(0x17CCC423, false, true)); + bus1.send(makeMessage(0x17CCC420, true, false)); // filtered out + bus1.send(makeMessage(0x17CCC444, true, true)); + + // 0BC2F508:1FFFFFC3, SET, SET: ~5[0-3][0,4,8,C] + bus1.send(makeMessage(0x0BC2F504, false, false)); + bus1.send(makeMessage(0x0BC2F518, false, true)); + bus1.send(makeMessage(0x0BC2F52C, true, false)); + bus1.send(makeMessage(0x0BC2F500, true, true)); // filtered out + bus1.send(makeMessage(0x0BC2F543, true, true)); + + // 1179B5D2:1FFFFFC3, NS, DNC: ~5[C-F][2,6,A,E] + bus1.send(makeMessage(0x1179B5BB, false, false)); + bus1.send(makeMessage(0x1179B5EA, false, true)); // filtered out + bus1.send(makeMessage(0x1179B5C2, true, false)); + bus1.send(makeMessage(0x1179B5DA, true, true)); + + // 082AF63D:1FFFFF6F, NS, SET: ~6[2,3,A,B]D + bus1.send(makeMessage(0x082AF62D, false, false)); + bus1.send(makeMessage(0x082AF63D, false, true)); // filtered out + bus1.send(makeMessage(0x082AF60D, false, true)); + bus1.send(makeMessage(0x082AF6AD, true, false)); + bus1.send(makeMessage(0x082AF6BD, true, true)); + + // 66D:76F, DNC, SET: ~6[6,7,E,F]D + bus1.send(makeMessage(0x66D, false, false)); + bus1.send(makeMessage(0x68D, false, true)); + bus1.send(makeMessage(0x67D, true, false)); + bus1.send(makeMessage(0x6ED, true, true)); // filtered out + + // 748:7CC, SET, SET: ~0x7[4-7][8-F] + bus1.send(makeMessage(0x749, false, false)); + bus1.send(makeMessage(0x75A, false, true)); + bus1.send(makeMessage(0x76B, true, false)); + bus1.send(makeMessage(0x748, true, true)); // filtered out + bus1.send(makeMessage(0x788, true, true)); + + // 784:7CC, NS, SET: ~0x7[8-F][4-7] + bus1.send(makeMessage(0x795, false, false)); + bus1.send(makeMessage(0x784, false, true)); // filtered out + bus1.send(makeMessage(0x71B, false, true)); + bus1.send(makeMessage(0x769, true, false)); + bus1.send(makeMessage(0x784, true, true)); + + std::vector expectedNegative{ + makeMessage(0x060, false, true), // 063:7F3, DNC, DNC + makeMessage(0x05B, true, false), // 063:7F3, DNC, DNC + makeMessage(0x031, false, true), // 0A1:78F, DNC, DNC + makeMessage(0x061, true, false), // 0A1:78F, DNC, DNC + makeMessage(0x071, true, true), // 0A1:78F, DNC, DNC + makeMessage(0x188, false, true), // 18B:7E3, DNC, NS + makeMessage(0x123, true, false), // 18B:7E3, DNC, NS + makeMessage(0x1D5, true, true), // 18B:7E3, DNC, NS + makeMessage(0x17E, false, false), // 1EE:7EC, SET, DNC + makeMessage(0x138, false, true), // 1EE:7EC, SET, DNC + makeMessage(0x123, true, false), // 1EE:7EC, SET, DNC + makeMessage(0x222, false, false), // 23F:7A5, SET, NS + makeMessage(0x275, false, true), // 23F:7A5, SET, NS + makeMessage(0x241, true, false), // 23F:7A5, SET, NS + makeMessage(0x2FF, true, true), // 23F:7A5, SET, NS + makeMessage(0x32F, false, false), // 31F:77F, NS, DNC + makeMessage(0x36F, false, true), // 31F:77F, NS, DNC + makeMessage(0x31F, true, false), // 31F:77F, NS, DNC + makeMessage(0x3F3, true, true), // 31F:77F, NS, DNC + makeMessage(0x352, false, false), // 341:77F, NS, NS + makeMessage(0x3AA, false, true), // 341:77F, NS, NS + makeMessage(0x3BC, true, false), // 341:77F, NS, NS + makeMessage(0x3FF, true, true), // 341:77F, NS, NS + makeMessage(0x1965733B, false, false), // 196573DB:1FFFFF7F, DNC, DNC + makeMessage(0x1965734B, false, true), // 196573DB:1FFFFF7F, DNC, DNC + makeMessage(0x1965736B, true, true), // 196573DB:1FFFFF7F, DNC, DNC + makeMessage(0x1CFCB407, false, false), // 1CFCB417:1FFFFFEC, DNC, SET + makeMessage(0x1CFCB4FF, false, true), // 1CFCB417:1FFFFFEC, DNC, SET + makeMessage(0x1CFCB414, true, false), // 1CFCB417:1FFFFFEC, DNC, SET + makeMessage(0x17CCC430, false, false), // 17CCC433:1FFFFFEC, SET, DNC + makeMessage(0x17CCC423, false, true), // 17CCC433:1FFFFFEC, SET, DNC + makeMessage(0x17CCC444, true, true), // 17CCC433:1FFFFFEC, SET, DNC + makeMessage(0x0BC2F504, false, false), // 0BC2F508:1FFFFFC3, SET, SET + makeMessage(0x0BC2F518, false, true), // 0BC2F508:1FFFFFC3, SET, SET + makeMessage(0x0BC2F52C, true, false), // 0BC2F508:1FFFFFC3, SET, SET + makeMessage(0x0BC2F543, true, true), // 0BC2F508:1FFFFFC3, SET, SET + makeMessage(0x1179B5BB, false, false), // 1179B5D2:1FFFFFC3, NS, DNC + makeMessage(0x1179B5C2, true, false), // 1179B5D2:1FFFFFC3, NS, DNC + makeMessage(0x1179B5DA, true, true), // 1179B5D2:1FFFFFC3, NS, DNC + makeMessage(0x082AF62D, false, false), // 082AF63D:1FFFFF6F, NS, SET + makeMessage(0x082AF60D, false, true), // 082AF63D:1FFFFF6F, NS, SET + makeMessage(0x082AF6AD, true, false), // 082AF63D:1FFFFF6F, NS, SET + makeMessage(0x082AF6BD, true, true), // 082AF63D:1FFFFF6F, NS, SET + makeMessage(0x66D, false, false), // 66D:76F, DNC, SET + makeMessage(0x68D, false, true), // 66D:76F, DNC, SET + makeMessage(0x67D, true, false), // 66D:76F, DNC, SET + makeMessage(0x749, false, false), // 748:7CC, SET, SET + makeMessage(0x75A, false, true), // 748:7CC, SET, SET + makeMessage(0x76B, true, false), // 748:7CC, SET, SET + makeMessage(0x788, true, true), // 748:7CC, SET, SET + makeMessage(0x795, false, false), // 784:7CC, NS, SET + makeMessage(0x71B, false, true), // 784:7CC, NS, SET + makeMessage(0x769, true, false), // 784:7CC, NS, SET + makeMessage(0x784, true, true), // 784:7CC, NS, SET + }; + + auto messagesNegative = listenerNegative->fetchMessages(100ms, expectedNegative.size()); + clearTimestamps(messagesNegative); + ASSERT_EQ(expectedNegative, messagesNegative); +} + +TEST_P(CanBusVirtualHalTest, FilterMixed) { + if (mBusNames.size() < 2u) GTEST_SKIP() << "Not testable with less than two CAN buses."; + auto bus1 = makeBus(); + auto bus2 = makeBus(); + + /* clang-format off */ + /* id, mask, rtr, eff exclude */ + hidl_vec filterMixed = { + {0x000, 0x700, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, false}, + {0x0D5, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, true}, + {0x046, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::NOT_SET, true}, + {0x11D89097, 0x1FFFFFFF, FilterFlag::DONT_CARE, FilterFlag::SET, true}, + {0x0AB, 0x7FF, FilterFlag::NOT_SET, FilterFlag::DONT_CARE, true}, + {0x00D, 0x7FF, FilterFlag::NOT_SET, FilterFlag::NOT_SET, true}, + {0x0F82400E, 0x1FFFFFFF, FilterFlag::NOT_SET, FilterFlag::SET, true}, + {0x08F, 0x7FF, FilterFlag::SET, FilterFlag::DONT_CARE, true}, + {0x0BE, 0x7FF, FilterFlag::SET, FilterFlag::NOT_SET, true}, + {0x0A271011, 0x1FFFFFFF, FilterFlag::SET, FilterFlag::SET, true}, + {0x0BE, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, false}, + + {0x100, 0x700, FilterFlag::DONT_CARE, FilterFlag::NOT_SET, false}, + {0x138, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, true}, + {0x1BF, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::NOT_SET, true}, + {0x13AB6165, 0x1FFFFFFF, FilterFlag::DONT_CARE, FilterFlag::SET, true}, + {0x17A, 0x7FF, FilterFlag::NOT_SET, FilterFlag::DONT_CARE, true}, + {0x13C, 0x7FF, FilterFlag::NOT_SET, FilterFlag::NOT_SET, true}, + {0x102C5197, 0x1FFFFFFF, FilterFlag::NOT_SET, FilterFlag::SET, true}, + {0x19B, 0x7FF, FilterFlag::SET, FilterFlag::DONT_CARE, true}, + {0x1B8, 0x7FF, FilterFlag::SET, FilterFlag::NOT_SET, true}, + {0x0D6D5185, 0x1FFFFFFF, FilterFlag::SET, FilterFlag::SET, true}, + {0x1B8, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, false}, + + {0x096A2200, 0x1FFFFF00, FilterFlag::DONT_CARE, FilterFlag::SET, false}, + {0x201, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, true}, + {0x22A, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::NOT_SET, true}, + {0x1D1C3238, 0x1FFFFFFF, FilterFlag::DONT_CARE, FilterFlag::SET, true}, + {0x2C0, 0x7FF, FilterFlag::NOT_SET, FilterFlag::DONT_CARE, true}, + {0x23C, 0x7FF, FilterFlag::NOT_SET, FilterFlag::NOT_SET, true}, + {0x016182C6, 0x1FFFFFFF, FilterFlag::NOT_SET, FilterFlag::SET, true}, + {0x27B, 0x7FF, FilterFlag::SET, FilterFlag::DONT_CARE, true}, + {0x2A5, 0x7FF, FilterFlag::SET, FilterFlag::NOT_SET, true}, + {0x160EB24B, 0x1FFFFFFF, FilterFlag::SET, FilterFlag::SET, true}, + {0x2A5, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, false}, + + {0x300, 0x700, FilterFlag::NOT_SET, FilterFlag::DONT_CARE, false}, + {0x339, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, true}, + {0x3D4, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::NOT_SET, true}, + {0x182263BE, 0x1FFFFFFF, FilterFlag::DONT_CARE, FilterFlag::SET, true}, + {0x327, 0x7FF, FilterFlag::NOT_SET, FilterFlag::DONT_CARE, true}, + {0x36B, 0x7FF, FilterFlag::NOT_SET, FilterFlag::NOT_SET, true}, + {0x1A1D8374, 0x1FFFFFFF, FilterFlag::NOT_SET, FilterFlag::SET, true}, + {0x319, 0x7FF, FilterFlag::SET, FilterFlag::DONT_CARE, true}, + {0x39E, 0x7FF, FilterFlag::SET, FilterFlag::NOT_SET, true}, + {0x1B657332, 0x1FFFFFFF, FilterFlag::SET, FilterFlag::SET, true}, + {0x39E, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, false}, + + {0x06C5D400, 0x1FFFFF00, FilterFlag::NOT_SET, FilterFlag::SET, false}, + {0x492, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, true}, + {0x4EE, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::NOT_SET, true}, + {0x07725454, 0x1FFFFFFF, FilterFlag::DONT_CARE, FilterFlag::SET, true}, + {0x4D5, 0x7FF, FilterFlag::NOT_SET, FilterFlag::DONT_CARE, true}, + {0x402, 0x7FF, FilterFlag::NOT_SET, FilterFlag::NOT_SET, true}, + {0x139714A7, 0x1FFFFFFF, FilterFlag::NOT_SET, FilterFlag::SET, true}, + {0x464, 0x7FF, FilterFlag::SET, FilterFlag::DONT_CARE, true}, + {0x454, 0x7FF, FilterFlag::SET, FilterFlag::NOT_SET, true}, + {0x0EF4B46F, 0x1FFFFFFF, FilterFlag::SET, FilterFlag::SET, true}, + {0x454, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, false}, + + {0x500, 0x700, FilterFlag::SET, FilterFlag::DONT_CARE, false}, + {0x503, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, true}, + {0x566, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::NOT_SET, true}, + {0x137605E7, 0x1FFFFFFF, FilterFlag::DONT_CARE, FilterFlag::SET, true}, + {0x564, 0x7FF, FilterFlag::NOT_SET, FilterFlag::DONT_CARE, true}, + {0x58E, 0x7FF, FilterFlag::NOT_SET, FilterFlag::NOT_SET, true}, + {0x05F9052D, 0x1FFFFFFF, FilterFlag::NOT_SET, FilterFlag::SET, true}, + {0x595, 0x7FF, FilterFlag::SET, FilterFlag::DONT_CARE, true}, + {0x563, 0x7FF, FilterFlag::SET, FilterFlag::NOT_SET, true}, + {0x13358537, 0x1FFFFFFF, FilterFlag::SET, FilterFlag::SET, true}, + {0x563, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, false}, + + {0x600, 0x700, FilterFlag::SET, FilterFlag::NOT_SET, false}, + {0x64D, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, true}, + {0x620, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::NOT_SET, true}, + {0x1069A676, 0x1FFFFFFF, FilterFlag::DONT_CARE, FilterFlag::SET, true}, + {0x62D, 0x7FF, FilterFlag::NOT_SET, FilterFlag::DONT_CARE, true}, + {0x6C4, 0x7FF, FilterFlag::NOT_SET, FilterFlag::NOT_SET, true}, + {0x14C76629, 0x1FFFFFFF, FilterFlag::NOT_SET, FilterFlag::SET, true}, + {0x689, 0x7FF, FilterFlag::SET, FilterFlag::DONT_CARE, true}, + {0x6A4, 0x7FF, FilterFlag::SET, FilterFlag::NOT_SET, true}, + {0x0BCCA6C2, 0x1FFFFFFF, FilterFlag::SET, FilterFlag::SET, true}, + {0x6A4, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, false}, + + {0x04BB1700, 0x1FFFFF00, FilterFlag::SET, FilterFlag::SET, false}, + {0x784, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, true}, + {0x7F9, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::NOT_SET, true}, + {0x0200F77D, 0x1FFFFFFF, FilterFlag::DONT_CARE, FilterFlag::SET, true}, + {0x783, 0x7FF, FilterFlag::NOT_SET, FilterFlag::DONT_CARE, true}, + {0x770, 0x7FF, FilterFlag::NOT_SET, FilterFlag::NOT_SET, true}, + {0x06602719, 0x1FFFFFFF, FilterFlag::NOT_SET, FilterFlag::SET, true}, + {0x76B, 0x7FF, FilterFlag::SET, FilterFlag::DONT_CARE, true}, + {0x7DF, 0x7FF, FilterFlag::SET, FilterFlag::NOT_SET, true}, + {0x1939E736, 0x1FFFFFFF, FilterFlag::SET, FilterFlag::SET, true}, + {0x7DF, 0x7FF, FilterFlag::DONT_CARE, FilterFlag::DONT_CARE, false}, + }; + /* clang-format on */ + + auto listenerMixed = bus2.listen(filterMixed); + + bus1.send(makeMessage(0x000, true, true)); // positive filter + bus1.send(makeMessage(0x0D5, false, false)); + bus1.send(makeMessage(0x046, true, false)); + bus1.send(makeMessage(0x046, false, false)); + bus1.send(makeMessage(0x11D89097, true, true)); + bus1.send(makeMessage(0x11D89097, false, true)); + bus1.send(makeMessage(0x0AB, false, false)); + bus1.send(makeMessage(0x0AB, false, true)); + bus1.send(makeMessage(0x00D, false, false)); + bus1.send(makeMessage(0x0F82400E, false, true)); + bus1.send(makeMessage(0x08F, true, false)); + bus1.send(makeMessage(0x08F, true, true)); + bus1.send(makeMessage(0x0BE, true, false)); + bus1.send(makeMessage(0x0A271011, true, true)); + bus1.send(makeMessage(0x0BE, false, true)); // not filtered + bus1.send(makeMessage(0x100, false, false)); // positive filter + bus1.send(makeMessage(0x138, false, true)); + bus1.send(makeMessage(0x138, true, false)); + bus1.send(makeMessage(0x1BF, false, false)); + bus1.send(makeMessage(0x1BF, true, false)); + bus1.send(makeMessage(0x13AB6165, false, true)); + bus1.send(makeMessage(0x13AB6165, true, true)); + bus1.send(makeMessage(0x17A, false, false)); + bus1.send(makeMessage(0x17A, false, true)); + bus1.send(makeMessage(0x13C, false, false)); + bus1.send(makeMessage(0x102C5197, false, true)); + bus1.send(makeMessage(0x19B, true, false)); + bus1.send(makeMessage(0x19B, true, true)); + bus1.send(makeMessage(0x1B8, true, false)); + bus1.send(makeMessage(0x0D6D5185, true, true)); + bus1.send(makeMessage(0x1B8, false, true)); // not filtered + bus1.send(makeMessage(0x096A2200, false, true)); // positive filter + bus1.send(makeMessage(0x201, false, true)); + bus1.send(makeMessage(0x201, true, false)); + bus1.send(makeMessage(0x22A, false, false)); + bus1.send(makeMessage(0x22A, true, false)); + bus1.send(makeMessage(0x1D1C3238, false, true)); + bus1.send(makeMessage(0x1D1C3238, true, true)); + bus1.send(makeMessage(0x2C0, false, false)); + bus1.send(makeMessage(0x2C0, false, true)); + bus1.send(makeMessage(0x23C, false, false)); + bus1.send(makeMessage(0x016182C6, false, true)); + bus1.send(makeMessage(0x27B, true, false)); + bus1.send(makeMessage(0x27B, true, true)); + bus1.send(makeMessage(0x2A5, true, false)); + bus1.send(makeMessage(0x160EB24B, true, true)); + bus1.send(makeMessage(0x2A5, false, true)); // not filtereed + bus1.send(makeMessage(0x300, false, false)); // positive filter + bus1.send(makeMessage(0x339, false, true)); + bus1.send(makeMessage(0x339, false, false)); + bus1.send(makeMessage(0x3D4, true, false)); + bus1.send(makeMessage(0x182263BE, false, true)); + bus1.send(makeMessage(0x182263BE, true, true)); + bus1.send(makeMessage(0x327, false, false)); + bus1.send(makeMessage(0x327, false, true)); + bus1.send(makeMessage(0x36B, false, false)); + bus1.send(makeMessage(0x1A1D8374, false, true)); + bus1.send(makeMessage(0x319, true, false)); + bus1.send(makeMessage(0x319, true, true)); + bus1.send(makeMessage(0x39E, true, false)); + bus1.send(makeMessage(0x1B657332, true, true)); + bus1.send(makeMessage(0x39E, false, true)); // not filtered + bus1.send(makeMessage(0x06C5D400, false, true)); // positive filter + bus1.send(makeMessage(0x492, false, true)); + bus1.send(makeMessage(0x492, true, false)); + bus1.send(makeMessage(0x4EE, false, false)); + bus1.send(makeMessage(0x4EE, true, false)); + bus1.send(makeMessage(0x07725454, false, true)); + bus1.send(makeMessage(0x07725454, true, true)); + bus1.send(makeMessage(0x4D5, false, false)); + bus1.send(makeMessage(0x4D5, false, true)); + bus1.send(makeMessage(0x402, false, false)); + bus1.send(makeMessage(0x139714A7, false, true)); + bus1.send(makeMessage(0x464, true, false)); + bus1.send(makeMessage(0x464, true, true)); + bus1.send(makeMessage(0x454, true, false)); + bus1.send(makeMessage(0x0EF4B46F, true, true)); + bus1.send(makeMessage(0x454, false, true)); // not filtered + bus1.send(makeMessage(0x500, true, false)); // positive filter + bus1.send(makeMessage(0x503, false, true)); + bus1.send(makeMessage(0x503, true, false)); + bus1.send(makeMessage(0x566, false, false)); + bus1.send(makeMessage(0x566, true, false)); + bus1.send(makeMessage(0x137605E7, false, true)); + bus1.send(makeMessage(0x137605E7, true, true)); + bus1.send(makeMessage(0x564, false, false)); + bus1.send(makeMessage(0x564, false, true)); + bus1.send(makeMessage(0x58E, false, false)); + bus1.send(makeMessage(0x05F9052D, false, true)); + bus1.send(makeMessage(0x595, true, false)); + bus1.send(makeMessage(0x595, true, true)); + bus1.send(makeMessage(0x563, true, false)); + bus1.send(makeMessage(0x13358537, true, true)); + bus1.send(makeMessage(0x563, false, true)); // not filtered + bus1.send(makeMessage(0x600, true, false)); // positive filter + bus1.send(makeMessage(0x64D, false, true)); + bus1.send(makeMessage(0x64D, true, false)); + bus1.send(makeMessage(0x620, false, false)); + bus1.send(makeMessage(0x620, true, false)); + bus1.send(makeMessage(0x1069A676, false, true)); + bus1.send(makeMessage(0x1069A676, true, true)); + bus1.send(makeMessage(0x62D, false, false)); + bus1.send(makeMessage(0x62D, false, true)); + bus1.send(makeMessage(0x6C4, false, false)); + bus1.send(makeMessage(0x14C76629, false, true)); + bus1.send(makeMessage(0x689, true, false)); + bus1.send(makeMessage(0x689, true, true)); + bus1.send(makeMessage(0x6A4, true, false)); + bus1.send(makeMessage(0x0BCCA6C2, true, true)); + bus1.send(makeMessage(0x6A4, false, true)); // not filtered + bus1.send(makeMessage(0x04BB1700, true, true)); // positive filter + bus1.send(makeMessage(0x784, false, true)); + bus1.send(makeMessage(0x784, true, false)); + bus1.send(makeMessage(0x7F9, false, false)); + bus1.send(makeMessage(0x7F9, true, false)); + bus1.send(makeMessage(0x0200F77D, false, true)); + bus1.send(makeMessage(0x0200F77D, true, true)); + bus1.send(makeMessage(0x783, false, false)); + bus1.send(makeMessage(0x783, false, true)); + bus1.send(makeMessage(0x770, false, false)); + bus1.send(makeMessage(0x06602719, false, true)); + bus1.send(makeMessage(0x76B, true, false)); + bus1.send(makeMessage(0x76B, true, true)); + bus1.send(makeMessage(0x7DF, true, false)); + bus1.send(makeMessage(0x1939E736, true, true)); + bus1.send(makeMessage(0x7DF, false, true)); // not filtered + + std::vector expectedMixed{ + makeMessage(0x000, true, true), // 0x000:0x700, DONT_CARE, DONT_CARE + makeMessage(0x0BE, false, true), + makeMessage(0x100, false, false), // 0x100:0x700, DONT_CARE, NOT_SET + makeMessage(0x1B8, false, true), + makeMessage(0x096A2200, false, true), // 0x096A2200:0x1FFFFF00, DONT_CARE, SET + makeMessage(0x2A5, false, true), + makeMessage(0x300, false, false), // 0x300:0x700, NOT_SET, DONT_CARE + makeMessage(0x39E, false, true), + makeMessage(0x06C5D400, false, true), // 0x06C5D400:0x1FFFFF00, NOT_SET, SET + makeMessage(0x454, false, true), + makeMessage(0x500, true, false), // 0x500:0x700, SET, DONT_CARE + makeMessage(0x563, false, true), + makeMessage(0x600, true, false), // 0x600:0x700, SET, NOT_SET + makeMessage(0x6A4, false, true), + makeMessage(0x04BB1700, true, true), // 0x04BB1700:0x1FFFFF00, SET, SET + makeMessage(0x7DF, false, true), + }; + + auto messagesMixed = listenerMixed->fetchMessages(100ms, expectedMixed.size()); + clearTimestamps(messagesMixed); + ASSERT_EQ(expectedMixed, messagesMixed); +} + +/** + * Example manual invocation: + * adb shell /data/nativetest64/VtsHalCanBusVirtualV1_0TargetTest/VtsHalCanBusVirtualV1_0TargetTest + */ +INSTANTIATE_TEST_SUITE_P( // + PerInstance, CanBusVirtualHalTest, + testing::ValuesIn(getAllHalInstanceNames(ICanController::descriptor)), + PrintInstanceNameToString); + +} // namespace android::hardware::automotive::can::V1_0::vts diff --git a/automotive/can/1.0/vts/functional/VtsHalCanControllerV1_0TargetTest.cpp b/automotive/can/1.0/vts/functional/VtsHalCanControllerV1_0TargetTest.cpp new file mode 100644 index 0000000000..8ef57589cd --- /dev/null +++ b/automotive/can/1.0/vts/functional/VtsHalCanControllerV1_0TargetTest.cpp @@ -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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android::hardware::automotive::can::V1_0::vts { + +using hardware::hidl_vec; +using InterfaceType = ICanController::InterfaceType; +using IfId = ICanController::BusConfig::InterfaceId; + +class CanControllerHalTest : public ::testing::TestWithParam { + protected: + virtual void SetUp() override; + virtual void TearDown() override; + static void SetUpTestCase(); + + hidl_vec getSupportedInterfaceTypes(); + bool isSupported(InterfaceType iftype); + + bool up(InterfaceType iftype, const std::string srvname, std::string ifname, + ICanController::Result expected); + void assertRegistered(const std::string srvname, bool expectRegistered); + + sp mCanController; + static hidl_vec mBusNames; + + private: + static bool mTestCaseInitialized; +}; + +hidl_vec CanControllerHalTest::mBusNames; +bool CanControllerHalTest::mTestCaseInitialized = false; + +void CanControllerHalTest::SetUp() { + ASSERT_TRUE(mTestCaseInitialized); + + mCanController = ICanController::getService(GetParam()); + ASSERT_TRUE(mCanController) << "Couldn't open CAN Controller: " << GetParam(); +} + +void CanControllerHalTest::TearDown() { + mCanController.clear(); +} + +void CanControllerHalTest::SetUpTestCase() { + mBusNames = utils::getBusNames(); + ASSERT_NE(0u, mBusNames.size()) << "No ICanBus HALs defined in device manifest"; + + mTestCaseInitialized = true; +} + +hidl_vec CanControllerHalTest::getSupportedInterfaceTypes() { + hidl_vec iftypesResult; + mCanController->getSupportedInterfaceTypes(hidl_utils::fill(&iftypesResult)).assertOk(); + return iftypesResult; +} + +bool CanControllerHalTest::isSupported(InterfaceType iftype) { + const auto supported = getSupportedInterfaceTypes(); + return std::find(supported.begin(), supported.end(), iftype) != supported.end(); +} + +bool CanControllerHalTest::up(InterfaceType iftype, std::string srvname, std::string ifname, + ICanController::Result expected) { + ICanController::BusConfig config = {}; + config.name = srvname; + + // TODO(b/146214370): move interfaceId constructors to a library + if (iftype == InterfaceType::SOCKETCAN) { + IfId::Socketcan socketcan = {}; + socketcan.ifname(ifname); + config.interfaceId.socketcan(socketcan); + } else if (iftype == InterfaceType::SLCAN) { + IfId::Slcan slcan = {}; + slcan.ttyname(ifname); + config.interfaceId.slcan(slcan); + } else if (iftype == InterfaceType::VIRTUAL) { + config.interfaceId.virtualif({ifname}); + } else { + EXPECT_TRUE(false) << "Unexpected iftype: " << toString(iftype); + } + + const auto upresult = mCanController->upInterface(config); + + if (!isSupported(iftype)) { + LOG(INFO) << iftype << " interfaces not supported"; + EXPECT_EQ(ICanController::Result::NOT_SUPPORTED, upresult); + return false; + } + + EXPECT_EQ(expected, upresult); + return true; +} + +void CanControllerHalTest::assertRegistered(std::string srvname, bool expectRegistered) { + /* Not using ICanBus::tryGetService here, since it ignores interfaces not in the manifest + * file -- this is a test, so we don't want to add dummy services to a device manifest. */ + auto manager = hidl::manager::V1_2::IServiceManager::getService(); + auto busService = manager->get(ICanBus::descriptor, srvname); + ASSERT_EQ(expectRegistered, busService.withDefault(nullptr) != nullptr) + << "ICanBus/" << srvname << (expectRegistered ? " is not " : " is ") << "registered" + << " (should be otherwise)"; +} + +TEST_P(CanControllerHalTest, SupportsSomething) { + const auto supported = getSupportedInterfaceTypes(); + ASSERT_GT(supported.size(), 0u); +} + +TEST_P(CanControllerHalTest, BringUpDown) { + const std::string name = mBusNames[0]; + + assertRegistered(name, false); + if (!up(InterfaceType::VIRTUAL, name, "vcan57", ICanController::Result::OK)) GTEST_SKIP(); + assertRegistered(name, true); + + const auto dnresult = mCanController->downInterface(name); + ASSERT_TRUE(dnresult); + + assertRegistered(name, false); +} + +TEST_P(CanControllerHalTest, DownDummy) { + const auto result = mCanController->downInterface("imnotup"); + ASSERT_FALSE(result); +} + +TEST_P(CanControllerHalTest, UpTwice) { + const std::string name = mBusNames[0]; + + assertRegistered(name, false); + if (!up(InterfaceType::VIRTUAL, name, "vcan72", ICanController::Result::OK)) GTEST_SKIP(); + assertRegistered(name, true); + if (!up(InterfaceType::VIRTUAL, name, "vcan73", ICanController::Result::INVALID_STATE)) { + GTEST_SKIP(); + } + assertRegistered(name, true); + + const auto result = mCanController->downInterface(name); + ASSERT_TRUE(result); + assertRegistered(name, false); +} + +TEST_P(CanControllerHalTest, ConfigCompatibility) { + // using random-ish addresses, which may not be valid - we can't test the success case + // TODO(b/146214370): move interfaceId constructors to a library + IfId virtualCfg = {}; + virtualCfg.virtualif({"vcan70"}); + + IfId::Socketcan socketcanIfname = {}; + socketcanIfname.ifname("can0"); + IfId socketcanIfnameCfg = {}; + socketcanIfnameCfg.socketcan(socketcanIfname); + + IfId::Socketcan socketcanSerial = {}; + socketcanSerial.serialno({"1234", "2345"}); + IfId socketcanSerialCfg = {}; + socketcanSerialCfg.socketcan(socketcanSerial); + + IfId::Slcan slcanTtyname = {}; + slcanTtyname.ttyname("/dev/ttyUSB0"); + IfId slcanTtynameCfg = {}; + slcanTtynameCfg.slcan(slcanTtyname); + + IfId::Slcan slcanSerial = {}; + slcanSerial.serialno({"dead", "beef"}); + IfId slcanSerialCfg = {}; + slcanSerialCfg.slcan(slcanSerial); + + IfId indexedCfg = {}; + indexedCfg.indexed({0}); + + static const std::vector> compatMatrix = { + {InterfaceType::VIRTUAL, virtualCfg}, + {InterfaceType::SOCKETCAN, socketcanIfnameCfg}, + {InterfaceType::SOCKETCAN, socketcanSerialCfg}, + {InterfaceType::SLCAN, slcanTtynameCfg}, + {InterfaceType::SLCAN, slcanSerialCfg}, + {InterfaceType::INDEXED, indexedCfg}, + }; + + for (const auto [iftype, cfg] : compatMatrix) { + LOG(INFO) << "Compatibility testing: " << iftype << " / " << cfg; + + ICanController::BusConfig config = {}; + config.name = "compattestsrv"; + config.bitrate = 125000; + config.interfaceId = cfg; + + const auto upresult = mCanController->upInterface(config); + + if (!isSupported(iftype)) { + ASSERT_EQ(ICanController::Result::NOT_SUPPORTED, upresult); + continue; + } + ASSERT_NE(ICanController::Result::NOT_SUPPORTED, upresult); + + if (upresult == ICanController::Result::OK) { + const auto dnresult = mCanController->downInterface(config.name); + ASSERT_TRUE(dnresult); + continue; + } + } +} + +TEST_P(CanControllerHalTest, FailEmptyName) { + const std::string name = ""; + + assertRegistered(name, false); + if (!up(InterfaceType::VIRTUAL, name, "vcan57", ICanController::Result::BAD_SERVICE_NAME)) { + GTEST_SKIP(); + } + assertRegistered(name, false); +} + +TEST_P(CanControllerHalTest, FailBadName) { + // 33 characters (name can be at most 32 characters long) + const std::string name = "ab012345678901234567890123456789c"; + + assertRegistered(name, false); + if (!up(InterfaceType::VIRTUAL, name, "vcan57", ICanController::Result::BAD_SERVICE_NAME)) { + GTEST_SKIP(); + } + assertRegistered(name, false); +} + +TEST_P(CanControllerHalTest, FailBadVirtualAddress) { + const std::string name = mBusNames[0]; + + assertRegistered(name, false); + if (!up(InterfaceType::VIRTUAL, name, "", ICanController::Result::BAD_INTERFACE_ID)) { + GTEST_SKIP(); + } + assertRegistered(name, false); +} + +TEST_P(CanControllerHalTest, FailBadSocketcanAddress) { + const std::string name = mBusNames[0]; + + assertRegistered(name, false); + if (!up(InterfaceType::SOCKETCAN, name, "can87", ICanController::Result::BAD_INTERFACE_ID)) { + GTEST_SKIP(); + } + assertRegistered(name, false); + + auto supported = + up(InterfaceType::SOCKETCAN, name, "", ICanController::Result::BAD_INTERFACE_ID); + ASSERT_TRUE(supported); + assertRegistered(name, false); +} + +TEST_P(CanControllerHalTest, FailBadSlcanAddress) { + const std::string name = mBusNames[0]; + + assertRegistered(name, false); + if (!up(InterfaceType::SLCAN, name, "/dev/shouldnotexist123", + ICanController::Result::BAD_INTERFACE_ID)) { + GTEST_SKIP(); + } + assertRegistered(name, false); + + auto supported = up(InterfaceType::SLCAN, name, "", ICanController::Result::BAD_INTERFACE_ID); + ASSERT_TRUE(supported); + assertRegistered(name, false); +} + +/** + * Example manual invocation: + * adb shell /data/nativetest64/VtsHalCanControllerV1_0TargetTest/VtsHalCanControllerV1_0TargetTest + */ +INSTANTIATE_TEST_SUITE_P( // + PerInstance, CanControllerHalTest, + testing::ValuesIn(getAllHalInstanceNames(ICanController::descriptor)), + PrintInstanceNameToString); + +} // namespace android::hardware::automotive::can::V1_0::vts diff --git a/automotive/can/1.0/vts/utils/Android.bp b/automotive/can/1.0/vts/utils/Android.bp new file mode 100644 index 0000000000..d03ead30eb --- /dev/null +++ b/automotive/can/1.0/vts/utils/Android.bp @@ -0,0 +1,30 @@ +// +// 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: "android.hardware.automotive.can@vts-utils-lib", + defaults: ["android.hardware.automotive.can@defaults"], + srcs: [ + "bus-enumerator.cpp", + ], + export_include_dirs: ["include"], + header_libs: [ + "android.hardware.automotive.can@hidl-utils-lib", + ], + static_libs: [ + "android.hardware.automotive.can@1.0", + ], +} diff --git a/automotive/can/1.0/vts/utils/bus-enumerator.cpp b/automotive/can/1.0/vts/utils/bus-enumerator.cpp new file mode 100644 index 0000000000..c012dd21cf --- /dev/null +++ b/automotive/can/1.0/vts/utils/bus-enumerator.cpp @@ -0,0 +1,30 @@ +/* + * 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 +#include +#include + +namespace android::hardware::automotive::can::V1_0::vts::utils { + +hidl_vec getBusNames() { + auto manager = hidl::manager::V1_2::IServiceManager::getService(); + hidl_vec services; + manager->listManifestByInterface(ICanBus::descriptor, hidl_utils::fill(&services)); + return services; +} + +} // namespace android::hardware::automotive::can::V1_0::vts::utils diff --git a/wifi/hostapd/1.1/vts/functional/VtsHalWifiHostapdV1_1TargetTest.cpp b/automotive/can/1.0/vts/utils/include/can-vts-utils/bus-enumerator.h similarity index 71% rename from wifi/hostapd/1.1/vts/functional/VtsHalWifiHostapdV1_1TargetTest.cpp rename to automotive/can/1.0/vts/utils/include/can-vts-utils/bus-enumerator.h index 7e0f3cdc47..ef385eb395 100644 --- a/wifi/hostapd/1.1/vts/functional/VtsHalWifiHostapdV1_1TargetTest.cpp +++ b/automotive/can/1.0/vts/utils/include/can-vts-utils/bus-enumerator.h @@ -14,8 +14,12 @@ * limitations under the License. */ -#include +#pragma once -// TODO(b/143892896): Remove this file after wifi_hidl_test_utils.cpp is -// updated. -::testing::VtsHalHidlTargetTestEnvBase* gEnv = nullptr; \ No newline at end of file +#include + +namespace android::hardware::automotive::can::V1_0::vts::utils { + +hidl_vec getBusNames(); + +} // namespace android::hardware::automotive::can::V1_0::vts::utils diff --git a/automotive/can/1.0/vts/utils/include/can-vts-utils/can-hal-printers.h b/automotive/can/1.0/vts/utils/include/can-vts-utils/can-hal-printers.h new file mode 100644 index 0000000000..383b54cae5 --- /dev/null +++ b/automotive/can/1.0/vts/utils/include/can-vts-utils/can-hal-printers.h @@ -0,0 +1,46 @@ +/* + * 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 + +namespace android::hardware::automotive::can::V1_0 { + +/** + * Define gTest printer for a given HIDL type, but skip definition for Return. + */ +#define DEFINE_CAN_HAL_PRINTER_SIMPLE(T, converter) \ + std::ostream& operator<<(std::ostream& os, const T& v) { return os << converter(v); } + +/** + * Define gTest printer for a given HIDL type. + */ +#define DEFINE_CAN_HAL_PRINTER(T, converter) \ + DEFINE_CAN_HAL_PRINTER_SIMPLE(T, converter) \ + std::ostream& operator<<(std::ostream& os, const Return& v) { return os << converter(v); } + +DEFINE_CAN_HAL_PRINTER(CanMessage, toString) +DEFINE_CAN_HAL_PRINTER(ErrorEvent, toString) +DEFINE_CAN_HAL_PRINTER(ICanController::BusConfig::InterfaceId, toString); +DEFINE_CAN_HAL_PRINTER(ICanController::InterfaceType, toString) +DEFINE_CAN_HAL_PRINTER(ICanController::Result, toString) +DEFINE_CAN_HAL_PRINTER(Result, toString) + +#undef DEFINE_CAN_HAL_PRINTER +#undef DEFINE_CAN_HAL_PRINTER_SIMPLE + +} // namespace android::hardware::automotive::can::V1_0 diff --git a/automotive/evs/1.0/IEvsCamera.hal b/automotive/evs/1.0/IEvsCamera.hal index dbcaf9288c..464dafba75 100644 --- a/automotive/evs/1.0/IEvsCamera.hal +++ b/automotive/evs/1.0/IEvsCamera.hal @@ -16,7 +16,6 @@ package android.hardware.automotive.evs@1.0; -import types; import IEvsCameraStream; @@ -28,8 +27,8 @@ interface IEvsCamera { /** * Returns the ID of this camera. * - * Returns the description of this camera. This must be the same value as reported - * by EvsEnumerator::getCamerList(). + * @return info The description of this camera. This must be the same value as + * reported by EvsEnumerator::getCameraList(). */ getCameraInfo() generates (CameraDesc info); @@ -43,16 +42,20 @@ interface IEvsCamera { * in which case buffers should be added or removed from the chain as appropriate. * If no call is made to this entry point, the IEvsCamera must support at least one * frame by default. More is acceptable. - * BUFFER_NOT_AVAILABLE is returned if the implementation cannot support the - * requested number of concurrent frames. + * + * @param bufferCount Number of buffers the client of IEvsCamera may hold concurrently. + * @return result EvsResult::OK is returned if this call is successful. */ setMaxFramesInFlight(uint32_t bufferCount) generates (EvsResult result); /** - * Request delivery of EVS camera frames from this camera. + * Request to start EVS camera stream from this camera. * - * The IEvsCameraStream must begin receiving periodic calls with new image - * frames until stopVideoStream() is called. + * The IEvsCameraStream must begin receiving calls with various events + * including new image frame ready until stopVideoStream() is called. + * + * @param receiver IEvsCameraStream implementation. + * @return result EvsResult::OK is returned if this call is successful. */ startVideoStream(IEvsCameraStream receiver) generates (EvsResult result); @@ -64,6 +67,8 @@ interface IEvsCamera { * A small, finite number of buffers are available (possibly as small * as one), and if the supply is exhausted, no further frames may be * delivered until a buffer is returned. + * + * @param buffer A buffer to be returned. */ oneway doneWithFrame(BufferDesc buffer); @@ -83,6 +88,11 @@ interface IEvsCamera { * The values allowed for opaqueIdentifier are driver specific, * but no value passed in may crash the driver. The driver should * return 0 for any unrecognized opaqueIdentifier. + * + * @param opaqueIdentifier An unique identifier of the information to + * request. + * @return value Requested information. Zero is returned if the + * driver does not recognize a given identifier. */ getExtendedInfo(uint32_t opaqueIdentifier) generates (int32_t value); @@ -94,6 +104,11 @@ interface IEvsCamera { * in order to function in a default state. * INVALID_ARG is returned if the opaqueValue is not meaningful to * the driver implementation. + * + * @param opaqueIdentifier An unique identifier of the information to + * program. + * opaqueValue A value to program. + * @return result EvsResult::OK is returned if this call is successful. */ setExtendedInfo(uint32_t opaqueIdentifier, int32_t opaqueValue) generates (EvsResult result); }; diff --git a/automotive/evs/1.0/IEvsCameraStream.hal b/automotive/evs/1.0/IEvsCameraStream.hal index 4e743b2683..ec18f6a822 100644 --- a/automotive/evs/1.0/IEvsCameraStream.hal +++ b/automotive/evs/1.0/IEvsCameraStream.hal @@ -31,6 +31,8 @@ interface IEvsCameraStream { * When the last frame in the stream has been delivered, a NULL bufferHandle * must be delivered, signifying the end of the stream. No further frame * deliveries may happen thereafter. + * + * @param buffer a buffer descriptor of a delivered image frame. */ oneway deliverFrame(BufferDesc buffer); }; diff --git a/automotive/evs/1.0/IEvsDisplay.hal b/automotive/evs/1.0/IEvsDisplay.hal index 12541f3513..72f767e9c0 100644 --- a/automotive/evs/1.0/IEvsDisplay.hal +++ b/automotive/evs/1.0/IEvsDisplay.hal @@ -16,8 +16,6 @@ package android.hardware.automotive.evs@1.0; -import types; - /** * Represents a single camera and is the primary interface for capturing images. @@ -28,6 +26,9 @@ interface IEvsDisplay { * Returns basic information about the EVS display provided by the system. * * See the description of the DisplayDesc structure for details. + * + * @return info The description of this display. Please see the description + * of the DisplayDesc structure for details. */ getDisplayInfo() generates (DisplayDesc info); @@ -42,6 +43,9 @@ interface IEvsDisplay { * video. When the display is no longer required, the client is expected to request * the NOT_VISIBLE state after passing the last video frame. * Returns INVALID_ARG if the requested state is not a recognized value. + * + * @param state Desired new DisplayState. + * @return result EvsResult::OK is returned if this call is successful. */ setDisplayState(DisplayState state) generates (EvsResult result); @@ -54,6 +58,8 @@ interface IEvsDisplay { * the logic responsible for changing display states should generally live above * the device layer, making it undesirable for the HAL implementation to spontaneously * change display states. + * + * @return state Current DisplayState of this Display. */ getDisplayState() generates (DisplayState state); @@ -61,9 +67,11 @@ interface IEvsDisplay { /** * This call returns a handle to a frame buffer associated with the display. * - * The returned buffer may be locked and written to by software and/or GL. This buffer - * must be returned via a call to returnTargetBufferForDisplay() even if the - * display is no longer visible. + * @return buffer A handle to a frame buffer. The returned buffer may be + * locked and written to by software and/or GL. This buffer + * must be returned via a call to + * returnTargetBufferForDisplay() even if the display is no + * longer visible. */ getTargetBuffer() generates (BufferDesc buffer); @@ -75,6 +83,9 @@ interface IEvsDisplay { * There is no maximum time the caller may hold onto the buffer before making this * call. The buffer may be returned at any time and in any DisplayState, but all * buffers are expected to be returned before the IEvsDisplay interface is destroyed. + * + * @param buffer A buffer handle to the frame that is ready for display. + * @return result EvsResult::OK is returned if this call is successful. */ returnTargetBufferForDisplay(BufferDesc buffer) generates (EvsResult result); }; diff --git a/automotive/evs/1.0/IEvsEnumerator.hal b/automotive/evs/1.0/IEvsEnumerator.hal index ee51e7e994..e5633df973 100644 --- a/automotive/evs/1.0/IEvsEnumerator.hal +++ b/automotive/evs/1.0/IEvsEnumerator.hal @@ -16,7 +16,6 @@ package android.hardware.automotive.evs@1.0; -import types; import IEvsCamera; import IEvsDisplay; @@ -28,6 +27,8 @@ interface IEvsEnumerator { /** * Returns a list of all EVS cameras available to the system + * + * @return cameras A list of cameras availale for EVS service. */ getCameraList() generates (vec cameras); @@ -37,9 +38,9 @@ interface IEvsEnumerator { * 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(). - * Note: Reliance on the sp<> going out of scope is not recommended - * because the resources may not be released right away due to asynchronos - * behavior in the hardware binder (ref b/36122635). + * + * @param cameraId A unique identifier of the camera. + * @return carCamera EvsCamera object associated with a given cameraId. */ openCamera(string cameraId) generates (IEvsCamera carCamera); @@ -48,6 +49,8 @@ interface IEvsEnumerator { * * When the IEvsCamera object is no longer required, it must be released. * NOTE: Video streaming must be cleanly stopped before making this call. + * + * @param carCamera EvsCamera object to be closed. */ closeCamera(IEvsCamera carCamera); @@ -60,8 +63,8 @@ interface IEvsEnumerator { * the old instance shall be closed and give the new caller exclusive * access. * When done using the display, the caller may release it by calling closeDisplay(). - * TODO(b/36122635) Reliance on the sp<> going out of scope is not recommended because the - * resources may not be released right away due to asynchronos behavior in the hardware binder. + * + * @return display EvsDisplay object to be used. */ openDisplay() generates (IEvsDisplay display); @@ -70,6 +73,8 @@ interface IEvsEnumerator { * * When the IEvsDisplay object is no longer required, it must be released. * NOTE: All buffers must have been returned to the display before making this call. + * + * @param display EvsDisplay object to be closed. */ closeDisplay(IEvsDisplay display); @@ -80,6 +85,8 @@ interface IEvsEnumerator { * the actual state of the active display. This call is replicated on the IEvsEnumerator * interface in order to allow secondary clients to monitor the state of the EVS display * without acquiring exclusive ownership of the display. + * + * @return state Current DisplayState of this Display. */ getDisplayState() generates (DisplayState state); }; diff --git a/automotive/evs/1.0/default/Android.bp b/automotive/evs/1.0/default/Android.bp index 69bb721dd7..6e28ab1a54 100644 --- a/automotive/evs/1.0/default/Android.bp +++ b/automotive/evs/1.0/default/Android.bp @@ -27,4 +27,8 @@ cc_binary { "-O0", "-g", ], + + vintf_fragments: [ + "manifest_android.hardware.automotive.evs@1.0-service.xml", + ], } diff --git a/automotive/evs/1.0/default/ServiceNames.h b/automotive/evs/1.0/default/ServiceNames.h index 1178da5a9c..84b1697d0f 100644 --- a/automotive/evs/1.0/default/ServiceNames.h +++ b/automotive/evs/1.0/default/ServiceNames.h @@ -14,4 +14,4 @@ * limitations under the License. */ -const static char kEnumeratorServiceName[] = "EvsEnumeratorHw"; +const static char kEnumeratorServiceName[] = "hw/0"; diff --git a/automotive/evs/1.0/default/android.hardware.automotive.evs@1.0-service.rc b/automotive/evs/1.0/default/android.hardware.automotive.evs@1.0-service.rc index 117c249a51..8dcd9694b0 100644 --- a/automotive/evs/1.0/default/android.hardware.automotive.evs@1.0-service.rc +++ b/automotive/evs/1.0/default/android.hardware.automotive.evs@1.0-service.rc @@ -2,3 +2,4 @@ service vendor.evs-hal-mock /vendor/bin/hw/android.hardware.automotive.evs@1.0-s class hal user automotive_evs group automotive_evs + disabled # do not start automatically diff --git a/automotive/evs/1.0/default/manifest_android.hardware.automotive.evs@1.0-service.xml b/automotive/evs/1.0/default/manifest_android.hardware.automotive.evs@1.0-service.xml new file mode 100644 index 0000000000..39aec43974 --- /dev/null +++ b/automotive/evs/1.0/default/manifest_android.hardware.automotive.evs@1.0-service.xml @@ -0,0 +1,26 @@ + + + + + android.hardware.automotive.evs + hwbinder + 1.0 + + IEvsEnumerator + hw/0 + + + diff --git a/automotive/evs/1.0/types.hal b/automotive/evs/1.0/types.hal index 7cebf6d179..1efd5ebf3d 100644 --- a/automotive/evs/1.0/types.hal +++ b/automotive/evs/1.0/types.hal @@ -24,8 +24,15 @@ package android.hardware.automotive.evs@1.0; * EVS camera in the system. */ struct CameraDesc { + /* Unique identifier for camera devices. This may be a path to detected + * camera device; for example, "/dev/video0". + */ string cameraId; - uint32_t vendorFlags; // Opaque value from driver + + /* Opaque value from driver. Vendor may use this field to store additional + * information; for example, sensor and bridge chip id. + */ + uint32_t vendorFlags; }; @@ -38,8 +45,11 @@ struct CameraDesc { * presentation device. */ struct DisplayDesc { + /* Unique identifier for the display */ string displayId; - uint32_t vendorFlags; // Opaque value from driver + + /* Opaque value from driver */ + uint32_t vendorFlags; }; @@ -56,14 +66,31 @@ struct DisplayDesc { * Specifically consider if format and/or usage should become enumerated types. */ struct BufferDesc { - uint32_t width; // Units of pixels - uint32_t height; // Units of pixels - uint32_t stride; // Units of pixels to match gralloc - uint32_t pixelSize; // Units of bytes - uint32_t format; // May contain values from android_pixel_format_t - uint32_t usage; // May contain values from from Gralloc.h - uint32_t bufferId; // Opaque value from driver - handle memHandle; // gralloc memory buffer handle + /* A frame width in the units of pixels */ + uint32_t width; + + /* A frame height in the units of pixels */ + uint32_t height; + + /* A frame stride in the units of pixels, to match gralloc */ + uint32_t stride; + + /* The size of a pixel in the units of bytes */ + uint32_t pixelSize; + + /* The image format of the frame; may contain values from + * android_pixel_format_t + */ + uint32_t format; + + /* May contain values from Gralloc.h */ + uint32_t usage; + + /* Opaque value from driver */ + uint32_t bufferId; + + /* Gralloc memory buffer handle */ + handle memHandle; }; @@ -77,12 +104,23 @@ struct BufferDesc { * presentation device. */ enum DisplayState : uint32_t { - NOT_OPEN = 0, // Display has not been requested by any application - NOT_VISIBLE, // Display is inhibited - VISIBLE_ON_NEXT_FRAME, // Will become visible with next frame - VISIBLE, // Display is currently active - DEAD, // Driver is in an undefined state. Interface should be closed. - NUM_STATES // Must be last + /* Display has not been requested by any application yet */ + NOT_OPEN = 0, + + /* Display is inhibited */ + NOT_VISIBLE, + + /* Will become visible with next frame */ + VISIBLE_ON_NEXT_FRAME, + + /* Display is currently active */ + VISIBLE, + + /* Driver is in an undefined state. Interface should be closed. */ + DEAD, + + /* Must be the last */ + NUM_STATES }; diff --git a/automotive/evs/1.0/vts/functional/Android.bp b/automotive/evs/1.0/vts/functional/Android.bp index c4cbb2da6a..74d512293b 100644 --- a/automotive/evs/1.0/vts/functional/Android.bp +++ b/automotive/evs/1.0/vts/functional/Android.bp @@ -19,7 +19,6 @@ cc_test { srcs: [ "VtsHalEvsV1_0TargetTest.cpp", "FrameHandler.cpp", - "FormatConvert.cpp" ], defaults: ["VtsHalTargetTestDefaults"], shared_libs: [ @@ -27,6 +26,7 @@ cc_test { ], static_libs: [ "android.hardware.automotive.evs@1.0", + "android.hardware.automotive.evs@common-default-lib", ], test_suites: ["vts"], cflags: [ diff --git a/automotive/evs/1.0/vts/functional/FormatConvert.h b/automotive/evs/1.0/vts/functional/FormatConvert.h deleted file mode 100644 index 4a94f996d0..0000000000 --- a/automotive/evs/1.0/vts/functional/FormatConvert.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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. - */ - -#ifndef EVS_VTS_FORMATCONVERT_H -#define EVS_VTS_FORMATCONVERT_H - -#include -#include - - -// Given an image buffer in NV21 format (HAL_PIXEL_FORMAT_YCRCB_420_SP), output 32bit RGBx/BGRx -// values. The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved -// U/V array. It assumes an even width and height for the overall image, and a horizontal -// stride that is an even multiple of 16 bytes for both the Y and UV arrays. -void copyNV21toRGB32(unsigned width, unsigned height, - uint8_t* src, - uint32_t* dst, unsigned dstStridePixels, - bool bgrxFormat = false); - -void copyNV21toBGR32(unsigned width, unsigned height, - uint8_t* src, - uint32_t* dst, unsigned dstStridePixels); - - -// Given an image buffer in YV12 format (HAL_PIXEL_FORMAT_YV12), output 32bit RGBx/BGRx values. -// The YV12 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 U array, followed -// by another 1/2 x 1/2 V array. It assumes an even width and height for the overall image, -// and a horizontal stride that is an even multiple of 16 bytes for each of the Y, U, -// and V arrays. -void copyYV12toRGB32(unsigned width, unsigned height, - uint8_t* src, - uint32_t* dst, unsigned dstStridePixels, - bool bgrxFormat = false); - -void copyYV12toBGR32(unsigned width, unsigned height, - uint8_t* src, - uint32_t* dst, unsigned dstStridePixels); - -// Given an image buffer in YUYV format (HAL_PIXEL_FORMAT_YCBCR_422_I), output 32bit RGBx/BGRx -// values. The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved -// U/V array. It assumes an even width and height for the overall image, and a horizontal -// stride that is an even multiple of 16 bytes for both the Y and UV arrays. -void copyYUYVtoRGB32(unsigned width, unsigned height, - uint8_t* src, unsigned srcStrideBytes, - uint32_t* dst, unsigned dstStrideBytes, - bool bgrxFormat = false); - -void copyYUYVtoBGR32(unsigned width, unsigned height, - uint8_t* src, unsigned srcStrideBytes, - uint32_t* dst, unsigned dstStrideBytes); - - -// Given an simple rectangular image buffer with an integer number of bytes per pixel, -// copy the pixel values into a new rectangular buffer (potentially with a different stride). -// This is typically used to copy RGBx data into an RGBx output buffer. -void copyMatchedInterleavedFormats(unsigned width, unsigned height, - void* src, unsigned srcStridePixels, - void* dst, unsigned dstStridePixels, - unsigned pixelSize); - -#endif // EVS_VTS_FORMATCONVERT_H diff --git a/automotive/evs/1.0/vts/functional/FrameHandler.cpp b/automotive/evs/1.0/vts/functional/FrameHandler.cpp index bc3790f385..6a01a44dfe 100644 --- a/automotive/evs/1.0/vts/functional/FrameHandler.cpp +++ b/automotive/evs/1.0/vts/functional/FrameHandler.cpp @@ -240,46 +240,47 @@ bool FrameHandler::copyBufferContents(const BufferDesc& tgtBuffer, tgt->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)&tgtPixels); if (srcPixels && tgtPixels) { + using namespace ::android::hardware::automotive::evs::common; if (tgtBuffer.format == HAL_PIXEL_FORMAT_RGBA_8888) { if (srcBuffer.format == HAL_PIXEL_FORMAT_YCRCB_420_SP) { // 420SP == NV21 - copyNV21toRGB32(width, height, - srcPixels, - tgtPixels, tgtBuffer.stride); + Utils::copyNV21toRGB32(width, height, + srcPixels, + tgtPixels, tgtBuffer.stride); } else if (srcBuffer.format == HAL_PIXEL_FORMAT_YV12) { // YUV_420P == YV12 - copyYV12toRGB32(width, height, - srcPixels, - tgtPixels, tgtBuffer.stride); + Utils::copyYV12toRGB32(width, height, + srcPixels, + tgtPixels, tgtBuffer.stride); } else if (srcBuffer.format == HAL_PIXEL_FORMAT_YCBCR_422_I) { // YUYV - copyYUYVtoRGB32(width, height, - srcPixels, srcBuffer.stride, - tgtPixels, tgtBuffer.stride); + Utils::copyYUYVtoRGB32(width, height, + srcPixels, srcBuffer.stride, + tgtPixels, tgtBuffer.stride); } else if (srcBuffer.format == tgtBuffer.format) { // 32bit RGBA - copyMatchedInterleavedFormats(width, height, - srcPixels, srcBuffer.stride, - tgtPixels, tgtBuffer.stride, - tgtBuffer.pixelSize); + Utils::copyMatchedInterleavedFormats(width, height, + srcPixels, srcBuffer.stride, + tgtPixels, tgtBuffer.stride, + tgtBuffer.pixelSize); } else { ALOGE("Camera buffer format is not supported"); success = false; } } else if (tgtBuffer.format == HAL_PIXEL_FORMAT_BGRA_8888) { if (srcBuffer.format == HAL_PIXEL_FORMAT_YCRCB_420_SP) { // 420SP == NV21 - copyNV21toBGR32(width, height, - srcPixels, - tgtPixels, tgtBuffer.stride); + Utils::copyNV21toBGR32(width, height, + srcPixels, + tgtPixels, tgtBuffer.stride); } else if (srcBuffer.format == HAL_PIXEL_FORMAT_YV12) { // YUV_420P == YV12 - copyYV12toBGR32(width, height, - srcPixels, - tgtPixels, tgtBuffer.stride); + Utils::copyYV12toBGR32(width, height, + srcPixels, + tgtPixels, tgtBuffer.stride); } else if (srcBuffer.format == HAL_PIXEL_FORMAT_YCBCR_422_I) { // YUYV - copyYUYVtoBGR32(width, height, - srcPixels, srcBuffer.stride, - tgtPixels, tgtBuffer.stride); + Utils::copyYUYVtoBGR32(width, height, + srcPixels, srcBuffer.stride, + tgtPixels, tgtBuffer.stride); } else if (srcBuffer.format == tgtBuffer.format) { // 32bit RGBA - copyMatchedInterleavedFormats(width, height, - srcPixels, srcBuffer.stride, - tgtPixels, tgtBuffer.stride, - tgtBuffer.pixelSize); + Utils::copyMatchedInterleavedFormats(width, height, + srcPixels, srcBuffer.stride, + tgtPixels, tgtBuffer.stride, + tgtBuffer.pixelSize); } else { ALOGE("Camera buffer format is not supported"); success = false; diff --git a/automotive/evs/1.1/Android.bp b/automotive/evs/1.1/Android.bp new file mode 100644 index 0000000000..443422e5aa --- /dev/null +++ b/automotive/evs/1.1/Android.bp @@ -0,0 +1,25 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.automotive.evs@1.1", + root: "android.hardware", + srcs: [ + "types.hal", + "IEvsCamera.hal", + "IEvsCameraStream.hal", + "IEvsDisplay.hal", + "IEvsEnumerator.hal", + "IEvsUltrasonicsArray.hal", + "IEvsUltrasonicsArrayStream.hal", + ], + interfaces: [ + "android.frameworks.automotive.display@1.0", + "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: false, +} diff --git a/automotive/evs/1.1/IEvsCamera.hal b/automotive/evs/1.1/IEvsCamera.hal new file mode 100644 index 0000000000..3e7ac2b299 --- /dev/null +++ b/automotive/evs/1.1/IEvsCamera.hal @@ -0,0 +1,236 @@ +/* + * 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 @1.0::IEvsCamera; +import @1.0::IEvsDisplay; +import @1.0::EvsResult; +import IEvsCameraStream; + +/** + * Represents a single camera and is the primary interface for capturing images. + */ +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); + + /** + * Returns the description of the physical camera device that backs this + * logical camera. + * + * If a requested device does not either exist or back this logical device, + * this method returns a null camera descriptor. And, if this is called on + * a physical camera device, this method is the same as getCameraInfo_1_1() + * method if a given device ID is matched. Otherwise, this will return a + * null camera descriptor. + * + * @param deviceId Physical camera device identifier string. + * @return info The description of a member physical camera device. + * This must be the same value as reported by + * EvsEnumerator::getCameraList_1_1(). + */ + getPhysicalCameraInfo(string deviceId) generates (CameraDesc info); + + /** + * Requests to pause EVS camera stream events. + * + * Like stopVideoStream(), events may continue to arrive for some time + * after this call returns. Delivered frame buffers must be returned. + * + * @return result EvsResult::OK is returned if this call is successful. + */ + pauseVideoStream() generates (EvsResult result); + + /** + * Requests to resume EVS camera stream. + * + * @return result EvsResult::OK is returned if this call is successful. + */ + resumeVideoStream() generates (EvsResult result); + + /** + * Returns frame that were delivered by to the IEvsCameraStream. + * + * When done consuming a frame delivered to the IEvsCameraStream + * interface, it must be returned to the IEvsCamera for reuse. + * A small, finite number of buffers are available (possibly as small + * as one), and if the supply is exhausted, no further frames may be + * delivered until a buffer is returned. + * + * @param buffer Buffers to be returned. + * @return result Return EvsResult::OK if this call is successful. + */ + doneWithFrame_1_1(vec buffer) generates (EvsResult result); + + /** + * Requests to be a master client. + * + * When multiple clients subscribe to a single camera hardware and one of + * them adjusts a camera parameter such as the contrast, it may disturb + * other clients' operations. Therefore, the client must call this method + * to be a master client. Once it becomes a master, it will be able to + * change camera parameters until either it dies or explicitly gives up the + * role. + * + * @return result EvsResult::OK if a master role is granted. + * EvsResult::OWNERSHIP_LOST if there is already a + * master client. + */ + setMaster() generates (EvsResult result); + + /** + * Sets to be a master client forcibly. + * + * The client, which owns the display, has a high priority and can take over + * a master role from other clients without the display. + * + * @param display IEvsDisplay handle. If a given display is in either + * NOT_VISIBLE, VISIBLE_ON_NEXT_FRAME, or VISIBLE state, the + * calling client is considered as the high priority client + * and therefore allowed to take over a master role from + * existing master client. + * + * @return result EvsResult::OK if a master role is granted. + * EvsResult::INVALID_ARG if a given display handle is null + * or in valid states. + */ + forceMaster(IEvsDisplay display) generates (EvsResult result); + + /** + * Retires from a master client role. + * + * @return result EvsResult::OK if this call is successful. + * EvsResult::INVALID_ARG if the caller client is not a + * master client. + */ + unsetMaster() generates (EvsResult result); + + /** + * Retrieves a list of parameters this camera supports. + * + * @return params A list of CameraParam that this camera supports. + */ + getParameterList() generates (vec 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. + * When this method is called on a logical camera device, it will be forwarded + * to each physical device and, if it fails to program any physical device, + * it will return an error code with the same number of effective values as + * the number of backing camera devices. + * + * @param id The identifier of camera parameter, CameraParam enum. + * value A desired parameter value. + * @return result EvsResult::OK if it succeeds to set a parameter. + * EvsResult::INVALID_ARG if either the request is + * not made by a master client, or a requested + * parameter is not supported. + * EvsResult::UNDERLYING_SERVICE_ERROR if it fails to + * program a value by any other reason. + * effectiveValue Programmed parameter values. This may differ + * from what the client gives if, for example, the + * driver does not support a target parameter. + */ + setIntParameter(CameraParam id, int32_t value) + generates (EvsResult result, vec effectiveValue); + + /** + * Retrieves values of given camera parameter. + * + * @param id The identifier of camera parameter, CameraParam enum. + * @return result EvsResult::OK if it succeeds to read a parameter. + * EvsResult::INVALID_ARG if either a requested parameter is + * not supported. + * value Values of requested camera parameter, the same number of + * values as backing camera devices. + */ + getIntParameter(CameraParam id) generates(EvsResult result, vec value); + + /** + * Request driver specific information from the HAL implementation. + * + * The values allowed for opaqueIdentifier are driver specific, + * but no value passed in may crash the driver. The driver should + * return EvsResult::INVALID_ARG for any unrecognized opaqueIdentifier. + * + * @param opaqueIdentifier An unique identifier of the information to + * request. + * @return result EvsResult::OK if the driver recognizes a given + * identifier. + * EvsResult::INVALID_ARG, otherwise. + * @return value Requested information. Zero-size vector is + * returned if the driver does not recognize a + * given identifier. + */ + getExtendedInfo_1_1(uint32_t opaqueIdentifier) + generates (EvsResult result, vec value); + + /** + * Send a driver specific value to the HAL implementation. + * + * This extension is provided to facilitate car specific + * extensions, but no HAL implementation may require this call + * in order to function in a default state. + * INVALID_ARG is returned if the opaqueValue is not meaningful to + * the driver implementation. + * + * @param opaqueIdentifier An unique identifier of the information to + * program. + * opaqueValue A value to program. + * @return result EvsResult::OK is returned if this call is successful. + * EvsResult::INVALID_ARG, otherwise. + */ + setExtendedInfo_1_1(uint32_t opaqueIdentifier, vec opaqueValue) + generates (EvsResult result); + + /** + * Import external buffers to capture frames + * + * This API must be called with a physical camera device identifier. + * + * @param buffers A list of buffers allocated by the caller. EvsCamera + * will use these buffers to capture frames, in addition to + * other buffers already in its buffer pool. + * @return result EvsResult::OK if it succeeds to import buffers. + * EvsResult::UNDERLYING_SERVICE_ERROR if this is called + * for logical camera devices or EVS fails to import + * buffers. + * delta The amount of buffer pool size changes after importing + * given buffers. + */ + importExternalBuffers(vec buffers) + generates (EvsResult result, int32_t delta); +}; diff --git a/automotive/evs/1.1/IEvsCameraStream.hal b/automotive/evs/1.1/IEvsCameraStream.hal new file mode 100644 index 0000000000..aa35c62f45 --- /dev/null +++ b/automotive/evs/1.1/IEvsCameraStream.hal @@ -0,0 +1,53 @@ +/* + * 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 @1.0::IEvsCameraStream; +import @1.1::BufferDesc; +import @1.1::EvsEventDesc; + +/** + * Implemented on client side to receive asynchronous streaming event deliveries. + */ +interface IEvsCameraStream extends @1.0::IEvsCameraStream { + + /** + * Receives calls from the HAL each time video frames 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. + * + * A camera device will deliver the same number of frames as number of + * backing physical camera devices; it means, a physical camera device + * sends always a single frame and a logical camera device sends multiple + * frames as many as number of backing physical camera devices. + * + * @param buffer Buffer descriptors of delivered image frames. + */ + oneway deliverFrame_1_1(vec buffer); + + /** + * Receives calls from the HAL each time an event happens. + * + * @param event EVS event with possible event information. + */ + oneway notify(EvsEventDesc event); +}; diff --git a/automotive/evs/1.1/IEvsDisplay.hal b/automotive/evs/1.1/IEvsDisplay.hal new file mode 100644 index 0000000000..38da536189 --- /dev/null +++ b/automotive/evs/1.1/IEvsDisplay.hal @@ -0,0 +1,35 @@ +/* + * Copyright 2020 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 @1.0::IEvsDisplay; +import @1.0::EvsResult; +import android.frameworks.automotive.display@1.0::HwDisplayConfig; +import android.frameworks.automotive.display@1.0::HwDisplayState; + +/** + * Represents a single display. + */ +interface IEvsDisplay extends @1.0::IEvsDisplay { + /** + * Returns the description of this display. + * + * @return cfg Current configuration of this display. + * @return state Current state of this display. + */ + getDisplayInfo_1_1() generates (HwDisplayConfig cfg, HwDisplayState state); +}; diff --git a/automotive/evs/1.1/IEvsEnumerator.hal b/automotive/evs/1.1/IEvsEnumerator.hal new file mode 100644 index 0000000000..d604e4f1f6 --- /dev/null +++ b/automotive/evs/1.1/IEvsEnumerator.hal @@ -0,0 +1,109 @@ +/* + * 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 IEvsDisplay; +import IEvsUltrasonicsArray; +import @1.0::IEvsEnumerator; +import @1.0::EvsResult; +import android.hardware.camera.device@3.2::Stream; + +/** + * Provides the mechanism for EVS camera and ultrasonics array 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 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); + + /** + * Tells whether this is EVS manager or HAL implementation. + * + * @return result False for EVS manager implementations and true for all others. + */ + isHardware() generates (bool result); + + /** + * Returns a list of all EVS displays available to the system + * + * @return displayIds Identifiers of available displays. + */ + getDisplayIdList() generates (vec displayIds); + + /** + * Get exclusive access to IEvsDisplay for the system + * + * There can be more than one EVS display objects for the system and this function + * requests access to the display identified by a given ID. If the target EVS display + * is not available or is already in use the old instance shall be closed and give + * the new caller exclusive access. + * When done using the display, the caller may release it by calling closeDisplay(). + * + * @param id Target display identifier. + * @return display EvsDisplay object to be used. + */ + openDisplay_1_1(uint8_t id) generates (IEvsDisplay display); + + /** + * Returns a list of all ultrasonics array available to the system. + * Will return an empty vector if ultrasonics is not supported. + * + * @return ultrasonicsArrays A list of ultrasonics available for EVS service. + */ + getUltrasonicsArrayList() generates (vec ultrasonicsArrays); + + /** + * Gets the IEvsUltrasonicsArray associated with a ultrasonicsArrayId from a + * UltrasonicsDataDesc + * + * @param ultrasonicsArrayId A unique identifier of the ultrasonic array. + * @return evsUltrasonicsArray IEvsUltrasonicsArray object associated with a + * given ultrasonicsArrayId. + */ + openUltrasonicsArray(string ultrasonicsArrayId) generates ( + IEvsUltrasonicsArray evsUltrasonicsArray); + + /** + * Return the specified IEvsUltrasonicsArray interface as no longer in use + * + * When the IEvsUltrasonicsArray object is no longer required, it must be released. + * NOTE: Data streaming must be cleanly stopped before making this call. + * + * @param evsUltrasonicsArray EvsUltrasonics array object to be closed. + */ + closeUltrasonicsArray(IEvsUltrasonicsArray evsUltrasonicsArray); +}; diff --git a/automotive/evs/1.1/IEvsUltrasonicsArray.hal b/automotive/evs/1.1/IEvsUltrasonicsArray.hal new file mode 100644 index 0000000000..ae4f94144c --- /dev/null +++ b/automotive/evs/1.1/IEvsUltrasonicsArray.hal @@ -0,0 +1,81 @@ +/* + * Copyright 2020 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 @1.0::EvsResult; +import UltrasonicsArrayDesc; +import UltrasonicsDataFrameDesc; +import IEvsUltrasonicsArrayStream; + +/** + * HAL interface for ultrasonics sensor array. + */ +interface IEvsUltrasonicsArray { + /** + * Returns the ultrasonic sensor array information. + * + * @return info The description of this ultrasonic array. This must be the + * same value as reported by IEvsEnumerator::getUltrasonicsArrayList(). + */ + getUltrasonicArrayInfo() generates (UltrasonicsArrayDesc info); + + /** + * Specifies the depth of the buffer chain the ultrasonic sensors is + * asked to support. + * + * Up to this many data frames may be held concurrently by the client of IEvsUltrasonicsArray. + * If this many frames have been delivered to the receiver without being returned + * by doneWithFrame, the stream must skip frames until a buffer is returned for reuse. + * It is legal for this call to come at any time, even while streams are already running, + * in which case buffers should be added or removed from the chain as appropriate. + * If no call is made to this entry point, the IEvsUltrasonicsArray must support at least one + * data frame by default. More is acceptable. + * + * @param bufferCount Number of buffers the client of + * IEvsUltrasonicsArray may hold concurrently. + * @return result EvsResult::OK is returned if this call is successful. + * Will return EvsResult::INVALID_ARG on invalid bufferCount. + */ + setMaxFramesInFlight(uint32_t bufferCount) generates (EvsResult result); + + /** + * Requests to start the stream. + * + * @param stream Implementation of IEvsUltrasonicsArrayStream. + * @return result EvsResult::OK is returned if this call is successful. Returns + * EvsResult::STREAM_ALREADY_RUNNING if stream is already running. + */ + startStream(IEvsUltrasonicsArrayStream stream) generates (EvsResult result); + + /** + * Requests to stop the delivery of the ultrasonic array data frames. + * + * Because delivery is asynchronous, frames may continue to arrive for + * some time after this call returns. Each must be returned until the + * closure of the stream is signaled to the IEvsCameraStream. + * This function cannot fail and is ignored if the stream isn't running. + */ + stopStream(); + + /** + * Notifies the UltrasonicsDataDesc is consumed that was received from + * IEvsUltrasonicsArrayStream. + * + * @param dataFrameDesc Ultrasonics data descriptor. + */ + doneWithDataFrame(UltrasonicsDataFrameDesc dataFrameDesc); +}; diff --git a/automotive/evs/1.1/IEvsUltrasonicsArrayStream.hal b/automotive/evs/1.1/IEvsUltrasonicsArrayStream.hal new file mode 100644 index 0000000000..f95209fe68 --- /dev/null +++ b/automotive/evs/1.1/IEvsUltrasonicsArrayStream.hal @@ -0,0 +1,40 @@ +/* + * Copyright 2020 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 UltrasonicsDataFrameDesc; +import @1.1::EvsEventDesc; + +/** + * Implemented on client side to receive asynchronous ultrasonic data + * deliveries. + */ +interface IEvsUltrasonicsArrayStream { + /** + * Receives calls from the HAL each time a data frame is ready. + * + * @param dataFrameDesc Ultrasonic array data frame descriptor. + */ + oneway deliverDataFrame(UltrasonicsDataFrameDesc dataFrameDesc); + + /** + * Receives calls from the HAL each time an event happens. + * + * @param event Event EVS event with possible event information. + */ + oneway notify(EvsEventDesc event); +}; diff --git a/automotive/evs/1.1/default/Android.bp b/automotive/evs/1.1/default/Android.bp new file mode 100644 index 0000000000..6e5695d7c4 --- /dev/null +++ b/automotive/evs/1.1/default/Android.bp @@ -0,0 +1,59 @@ +cc_binary { + name: "android.hardware.automotive.evs@1.1-service", + defaults: ["hidl_defaults"], + proprietary: true, + relative_install_path: "hw", + srcs: [ + "service.cpp", + "EvsCamera.cpp", + "EvsEnumerator.cpp", + "EvsDisplay.cpp", + "ConfigManager.cpp", + "ConfigManagerUtil.cpp", + "EvsUltrasonicsArray.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.3", + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libbase", + "libbinder", + "liblog", + "libhardware", + "libhidlbase", + "libhidlmemory", + "liblog", + "libui", + "libutils", + "libcamera_metadata", + "libtinyxml2", + "android.hidl.token@1.0-utils", + "android.frameworks.automotive.display@1.0", + "android.hardware.graphics.bufferqueue@1.0", + "android.hardware.graphics.bufferqueue@2.0", + ], + + cflags: [ + "-O0", + "-g", + ], + + required: [ + "evs_default_configuration.xml", + ], + + vintf_fragments: [ + "manifest_android.hardware.automotive.evs@1.1-service.xml", + ], +} + +prebuilt_etc { + name: "evs_default_configuration.xml", + soc_specific: true, + 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..986793e8a8 --- /dev/null +++ b/automotive/evs/1.1/default/ConfigManager.cpp @@ -0,0 +1,512 @@ +/* + * 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 +#include +#include + +#include +#include +#include + +#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 *id = curElem->FindAttribute("id")->Value(); + + /* create a camera group to be filled */ + CameraGroupInfo *aCamera = new CameraGroupInfo(); + + /* read camera device information */ + if (!readCameraDeviceInfo(aCamera, curElem)) { + ALOGW("Failed to read a camera information of %s", id); + delete aCamera; + continue; + } + + /* camera group synchronization */ + const char *sync = curElem->FindAttribute("synchronized")->Value(); + if (!strcmp(sync, "CALIBRATED")) { + aCamera->synchronized = + ANDROID_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_CALIBRATED; + } else if (!strcmp(sync, "APPROXIMATE")) { + aCamera->synchronized = + ANDROID_LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE; + } else { + aCamera->synchronized = 0; // Not synchronized + } + + /* add a group to hash map */ + mCameraGroupInfos.insert_or_assign(id, unique_ptr(aCamera)); + } 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(); + + /* create a camera device to be filled */ + CameraInfo *aCamera = new CameraInfo(); + + /* read camera device information */ + if (!readCameraDeviceInfo(aCamera, curElem)) { + ALOGW("Failed to read a camera information of %s", id); + delete aCamera; + continue; + } + + /* store read camera module information */ + mCameraInfo.insert_or_assign(id, unique_ptr(aCamera)); + + /* 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(); + } +} + + +bool +ConfigManager::readCameraDeviceInfo(CameraInfo *aCamera, + const XMLElement *aDeviceElem) { + if (aCamera == nullptr || aDeviceElem == nullptr) { + return false; + } + + /* 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 true; +} + + +size_t +ConfigManager::readCameraCapabilities(const XMLElement * const aCapElem, + CameraInfo *aCamera, + size_t &dataSize) { + if (aCapElem == nullptr || aCamera == 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.insert_or_assign(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, + CameraInfo *aCamera, + size_t &dataSize) { + if (aParamElem == nullptr || aCamera == 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.insert_or_assign( + tag, make_pair(make_unique(data), count) + ); + + ++numEntries; + dataSize += calculate_camera_metadata_entry_data_size( + get_camera_metadata_tag_type(tag), count + ); + + break; + } + + case ANDROID_REQUEST_AVAILABLE_CAPABILITIES: { + camera_metadata_enum_android_request_available_capabilities_t *data = + new camera_metadata_enum_android_request_available_capabilities_t[1]; + if (ConfigManagerUtil::convertToCameraCapability( + curElem->FindAttribute("value")->Value(), *data)) { + curElem->FindAttribute("value")->Value(), + aCamera->cameraMetadata.insert_or_assign( + tag, make_pair(make_unique(data), 1) + ); + + ++numEntries; + dataSize += calculate_camera_metadata_entry_data_size( + get_camera_metadata_tag_type(tag), 1 + ); + } + break; + } + + case ANDROID_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS: { + /* a comma-separated list of physical camera devices */ + size_t len = strlen(curElem->FindAttribute("value")->Value()); + char *data = new char[len + 1]; + memcpy(data, + curElem->FindAttribute("value")->Value(), + len * sizeof(char)); + + /* replace commas with null char */ + char *p = data; + while (*p != '\0') { + if (*p == ',') { + *p = '\0'; + } + ++p; + } + + aCamera->cameraMetadata.insert_or_assign( + tag, make_pair(make_unique(data), len) + ); + + ++numEntries; + dataSize += calculate_camera_metadata_entry_data_size( + get_camera_metadata_tag_type(tag), len + ); + break; + } + + default: + ALOGW("Parameter %s is not supported", + curElem->FindAttribute("name")->Value()); + break; + } + } + + curElem = curElem->NextSiblingElement("parameter"); + } + + return numEntries; +} + + +bool +ConfigManager::constructCameraMetadata(CameraInfo *aCamera, + const size_t totalEntries, + const size_t totalDataSize) { + if (aCamera == nullptr || !aCamera->allocate(totalEntries, totalDataSize)) { + ALOGE("Failed to allocate memory for camera metadata"); + return false; + } + + const size_t numStreamConfigs = aCamera->streamConfigurations.size(); + unique_ptr 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 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.insert_or_assign(id, cfg); + } + + curStream = curStream->NextSiblingElement("stream"); + } + } + + mDisplayInfo.insert_or_assign(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; +} + + +std::unique_ptr ConfigManager::Create(const char *path) { + unique_ptr 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..870af1caa9 --- /dev/null +++ b/automotive/evs/1.1/default/ConfigManager.h @@ -0,0 +1,318 @@ +/* + * 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 +#include +#include +#include + +#include + +#include +#include +#include + +#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 RawStreamConfiguration; + +class ConfigManager { +public: + static std::unique_ptr 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> controls; + + /* + * List of supported output stream configurations; each array stores + * format, width, height, and direction values in the order. + */ + unordered_map streamConfigurations; + + /* + * Internal storage for camera metadata. Each entry holds a pointer to + * data and number of elements + */ + unordered_map, size_t>> cameraMetadata; + + /* Camera module characteristics */ + camera_metadata_t *characteristics; + }; + + class CameraGroupInfo : public CameraInfo { + public: + CameraGroupInfo() {} + + /* ID of member camera devices */ + unordered_set devices; + + /* The capture operation of member camera devices are synchronized */ + bool synchronized = false; + }; + + 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 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 + * A vector that contains unique camera device identifiers. + */ + vector getCameraList() { + vector aList; + for (auto &v : mCameraInfo) { + aList.emplace_back(v.first); + } + + return aList; + } + + + /* + * Return a list of cameras + * + * @return CameraGroupInfo + * A pointer to a camera group identified by a given id. + */ + unique_ptr& getCameraGroupInfo(const string& gid) { + return mCameraGroupInfos[gid]; + } + + + /* + * Return a camera metadata + * + * @param cameraId + * Unique camera node identifier in string + * + * @return unique_ptr + * 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& 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> mCameraInfo; + + /* Internal data structure for camera device information */ + unordered_map> mDisplayInfo; + + /* Camera groups are stored in hash map */ + unordered_map> mCameraGroupInfos; + + /* + * Camera positions are stored in hash map. + * The position must be one of front, rear, left, and right. + */ + unordered_map> 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 aCamera + * A pointer to CameraInfo that will be completed by this + * method. + * aDeviceElem + * A pointer to "device" XML element that contains camera module + * capability info and its characteristics. + * + * @return bool + * Return false upon any failure in reading and processing camera + * device information. + */ + bool readCameraDeviceInfo(CameraInfo *aCamera, + 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, + 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, + 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(CameraInfo *aCamera, + const size_t totalEntries, + const size_t totalDataSize); +}; +#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..d10f236bd4 --- /dev/null +++ b/automotive/evs/1.1/default/ConfigManagerUtil.cpp @@ -0,0 +1,155 @@ +/* + * 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 +#include +#include + +#include +#include + + +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 if (!strcmp(name, "REQUEST_AVAILABLE_CAPABILITIES")) { + aTag = ANDROID_REQUEST_AVAILABLE_CAPABILITIES; + } else if (!strcmp(name, "LOGICAL_MULTI_CAMERA_PHYSICAL_IDS")) { + aTag = ANDROID_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS; + } else { + return false; + } + + return true; +} + + +bool ConfigManagerUtil::convertToCameraCapability( + const char *name, + camera_metadata_enum_android_request_available_capabilities_t &cap) { + + if (!strcmp(name, "DEPTH_OUTPUT")) { + cap = ANDROID_REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT; + } else if (!strcmp(name, "LOGICAL_MULTI_CAMERA")) { + cap = ANDROID_REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA; + } else if (!strcmp(name, "MONOCHROME")) { + cap = ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME; + } else if (!strcmp(name, "SECURE_IMAGE_DATA")) { + cap = ANDROID_REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA; + } 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..1710cac4f2 --- /dev/null +++ b/automotive/evs/1.1/default/ConfigManagerUtil.h @@ -0,0 +1,69 @@ +/* + * 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 +#include +#include +#include + +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"); + + /** + * Convert a given string to corresponding camera capabilities + */ + static bool convertToCameraCapability( + const char *name, + camera_metadata_enum_android_request_available_capabilities_t &cap); + +}; + +#endif // CONFIG_MANAGER_UTIL_H + diff --git a/automotive/evs/1.1/default/EvsCamera.cpp b/automotive/evs/1.1/default/EvsCamera.cpp new file mode 100644 index 0000000000..0e69ed408b --- /dev/null +++ b/automotive/evs/1.1/default/EvsCamera.cpp @@ -0,0 +1,724 @@ +/* + * 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.automotive.evs@1.1-service" + +#include "EvsCamera.h" +#include "EvsEnumerator.h" + +#include +#include +#include + +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace V1_1 { +namespace implementation { + + +// Special camera names for which we'll initialize alternate test data +const char EvsCamera::kCameraName_Backup[] = "backup"; + + +// Arbitrary limit on number of graphics buffers allowed to be allocated +// Safeguards against unreasonable resource consumption and provides a testable limit +const unsigned MAX_BUFFERS_IN_FLIGHT = 100; + + +EvsCamera::EvsCamera(const char *id, + unique_ptr &camInfo) : + mFramesAllowed(0), + mFramesInUse(0), + mStreamState(STOPPED), + mCameraInfo(camInfo) { + + ALOGD("EvsCamera instantiated"); + + /* set a camera id */ + mDescription.v1.cameraId = id; + + /* set camera metadata */ + mDescription.metadata.setToExternal((uint8_t *)camInfo->characteristics, + get_camera_metadata_size(camInfo->characteristics)); +} + + +EvsCamera::~EvsCamera() { + ALOGD("EvsCamera being destroyed"); + forceShutdown(); +} + + +// +// This gets called if another caller "steals" ownership of the camera +// +void EvsCamera::forceShutdown() +{ + ALOGD("EvsCamera forceShutdown"); + + // Make sure our output stream is cleaned up + // (It really should be already) + stopVideoStream(); + + // Claim the lock while we work on internal state + std::lock_guard lock(mAccessLock); + + // Drop all the graphics buffers we've been using + if (mBuffers.size() > 0) { + GraphicBufferAllocator& alloc(GraphicBufferAllocator::get()); + for (auto&& rec : mBuffers) { + if (rec.inUse) { + ALOGE("Error - releasing buffer despite remote ownership"); + } + alloc.free(rec.handle); + rec.handle = nullptr; + } + mBuffers.clear(); + } + + // Put this object into an unrecoverable error state since somebody else + // is going to own the underlying camera now + mStreamState = DEAD; +} + + +// Methods from ::android::hardware::automotive::evs::V1_0::IEvsCamera follow. +Return EvsCamera::getCameraInfo(getCameraInfo_cb _hidl_cb) { + ALOGD("getCameraInfo"); + + // Send back our self description + _hidl_cb(mDescription.v1); + return Void(); +} + + +Return EvsCamera::setMaxFramesInFlight(uint32_t bufferCount) { + ALOGD("setMaxFramesInFlight"); + std::lock_guard lock(mAccessLock); + + // If we've been displaced by another owner of the camera, then we can't do anything else + if (mStreamState == DEAD) { + ALOGE("ignoring setMaxFramesInFlight call when camera has been lost."); + return EvsResult::OWNERSHIP_LOST; + } + + // We cannot function without at least one video buffer to send data + if (bufferCount < 1) { + ALOGE("Ignoring setMaxFramesInFlight with less than one buffer requested"); + return EvsResult::INVALID_ARG; + } + + // Update our internal state + if (setAvailableFrames_Locked(bufferCount)) { + return EvsResult::OK; + } else { + return EvsResult::BUFFER_NOT_AVAILABLE; + } +} + + +Return EvsCamera::startVideoStream(const ::android::sp& stream) { + ALOGD("startVideoStream"); + std::lock_guard lock(mAccessLock); + + // If we've been displaced by another owner of the camera, then we can't do anything else + if (mStreamState == DEAD) { + ALOGE("ignoring startVideoStream call when camera has been lost."); + return EvsResult::OWNERSHIP_LOST; + } + if (mStreamState != STOPPED) { + ALOGE("ignoring startVideoStream call when a stream is already running."); + return EvsResult::STREAM_ALREADY_RUNNING; + } + + // If the client never indicated otherwise, configure ourselves for a single streaming buffer + if (mFramesAllowed < 1) { + if (!setAvailableFrames_Locked(1)) { + ALOGE("Failed to start stream because we couldn't get a graphics buffer"); + return EvsResult::BUFFER_NOT_AVAILABLE; + } + } + + // Record the user's callback for use when we have a frame ready + mStream = IEvsCameraStream_1_1::castFrom(stream).withDefault(nullptr); + if (mStream == nullptr) { + ALOGE("Default implementation does not support v1.0 IEvsCameraStream"); + return EvsResult::INVALID_ARG; + } + + // Start the frame generation thread + mStreamState = RUNNING; + mCaptureThread = std::thread([this](){ generateFrames(); }); + + return EvsResult::OK; +} + + +Return EvsCamera::doneWithFrame(const BufferDesc_1_0& buffer) { + std::lock_guard lock(mAccessLock); + returnBuffer(buffer.bufferId, buffer.memHandle); + + return Void(); +} + + +Return EvsCamera::stopVideoStream() { + ALOGD("stopVideoStream"); + std::unique_lock lock(mAccessLock); + + if (mStreamState == RUNNING) { + // Tell the GenerateFrames loop we want it to stop + mStreamState = STOPPING; + + // 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..."); + lock.unlock(); + mCaptureThread.join(); + lock.lock(); + + mStreamState = STOPPED; + mStream = nullptr; + ALOGD("Stream marked STOPPED."); + } + + return Void(); +} + + +Return EvsCamera::getExtendedInfo(uint32_t opaqueIdentifier) { + ALOGD("getExtendedInfo"); + std::lock_guard lock(mAccessLock); + + // For any single digit value, return the index itself as a test value + if (opaqueIdentifier <= 9) { + return opaqueIdentifier; + } + + // Return zero by default as required by the spec + return 0; +} + + +Return EvsCamera::setExtendedInfo(uint32_t /*opaqueIdentifier*/, int32_t /*opaqueValue*/) { + ALOGD("setExtendedInfo"); + std::lock_guard lock(mAccessLock); + + // If we've been displaced by another owner of the camera, then we can't do anything else + if (mStreamState == DEAD) { + ALOGE("ignoring setExtendedInfo call when camera has been lost."); + return EvsResult::OWNERSHIP_LOST; + } + + // We don't store any device specific information in this implementation + return EvsResult::INVALID_ARG; +} + + +// Methods from ::android::hardware::automotive::evs::V1_1::IEvsCamera follow. +Return 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 EvsCamera::getPhysicalCameraInfo(const hidl_string& id, + getCameraInfo_1_1_cb _hidl_cb) { + ALOGD("%s", __FUNCTION__); + + // This works exactly same as getCameraInfo_1_1() in default implementation. + (void)id; + _hidl_cb(mDescription); + return Void(); +} + + +Return EvsCamera::doneWithFrame_1_1(const hidl_vec& buffers) { + std::lock_guard lock(mAccessLock); + + for (auto&& buffer : buffers) { + returnBuffer(buffer.bufferId, buffer.buffer.nativeHandle); + } + + return EvsResult::OK; +} + + +Return EvsCamera::pauseVideoStream() { + // Default implementation does not support this. + return EvsResult::UNDERLYING_SERVICE_ERROR; +} + + +Return EvsCamera::resumeVideoStream() { + // Default implementation does not support this. + return EvsResult::UNDERLYING_SERVICE_ERROR; +} + + +Return EvsCamera::setMaster() { + // Default implementation does not expect multiple subscribers and therefore + // return a success code always. + return EvsResult::OK; +} + +Return EvsCamera::forceMaster(const sp& ) { + // Default implementation does not expect multiple subscribers and therefore + // return a success code always. + return EvsResult::OK; +} + + +Return EvsCamera::unsetMaster() { + // Default implementation does not expect multiple subscribers and therefore + // return a success code always. + return EvsResult::OK; +} + + +Return EvsCamera::getParameterList(getParameterList_cb _hidl_cb) { + hidl_vec hidlCtrls; + hidlCtrls.resize(mCameraInfo->controls.size()); + unsigned idx = 0; + for (auto& [cid, cfg] : mCameraInfo->controls) { + hidlCtrls[idx++] = cid; + } + + _hidl_cb(hidlCtrls); + return Void(); +} + + +Return 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 EvsCamera::setIntParameter(CameraParam id, int32_t value, + setIntParameter_cb _hidl_cb) { + // Default implementation does not support this. + (void)id; + (void)value; + _hidl_cb(EvsResult::INVALID_ARG, 0); + return Void(); +} + + +Return EvsCamera::getIntParameter(CameraParam id, + getIntParameter_cb _hidl_cb) { + // Default implementation does not support this. + (void)id; + _hidl_cb(EvsResult::INVALID_ARG, 0); + return Void(); +} + + +Return EvsCamera::setExtendedInfo_1_1(uint32_t opaqueIdentifier, + const hidl_vec& opaqueValue) { + // Default implementation does not use an extended info. + (void)opaqueIdentifier; + (void)opaqueValue; + return EvsResult::INVALID_ARG; +} + + +Return EvsCamera::getExtendedInfo_1_1(uint32_t opaqueIdentifier, + getExtendedInfo_1_1_cb _hidl_cb) { + // Default implementation does not use an extended info. + (void)opaqueIdentifier; + + hidl_vec value; + _hidl_cb(EvsResult::INVALID_ARG, value); + return Void(); +} + + +Return +EvsCamera::importExternalBuffers(const hidl_vec& /* buffers */, + importExternalBuffers_cb _hidl_cb) { + ALOGW("%s is not implemented yet.", __FUNCTION__); + _hidl_cb(EvsResult::UNDERLYING_SERVICE_ERROR, 0); + return {}; +} + + +bool EvsCamera::setAvailableFrames_Locked(unsigned bufferCount) { + if (bufferCount < 1) { + ALOGE("Ignoring request to set buffer count to zero"); + return false; + } + if (bufferCount > MAX_BUFFERS_IN_FLIGHT) { + ALOGE("Rejecting buffer request in excess of internal limit"); + return false; + } + + // Is an increase required? + if (mFramesAllowed < bufferCount) { + // An increase is required + unsigned needed = bufferCount - mFramesAllowed; + ALOGI("Allocating %d buffers for camera frames", needed); + + unsigned added = increaseAvailableFrames_Locked(needed); + if (added != needed) { + // If we didn't add all the frames we needed, then roll back to the previous state + ALOGE("Rolling back to previous frame queue size"); + decreaseAvailableFrames_Locked(added); + return false; + } + } else if (mFramesAllowed > bufferCount) { + // A decrease is required + unsigned framesToRelease = mFramesAllowed - bufferCount; + ALOGI("Returning %d camera frame buffers", framesToRelease); + + unsigned released = decreaseAvailableFrames_Locked(framesToRelease); + if (released != framesToRelease) { + // This shouldn't happen with a properly behaving client because the client + // should only make this call after returning sufficient outstanding buffers + // to allow a clean resize. + ALOGE("Buffer queue shrink failed -- too many buffers currently in use?"); + } + } + + return true; +} + + +unsigned EvsCamera::increaseAvailableFrames_Locked(unsigned numToAdd) { + // Acquire the graphics buffer allocator + GraphicBufferAllocator &alloc(GraphicBufferAllocator::get()); + + unsigned added = 0; + + while (added < numToAdd) { + buffer_handle_t memHandle = nullptr; + status_t result = alloc.allocate(mWidth, mHeight, mFormat, 1, mUsage, + &memHandle, &mStride, 0, "EvsCamera"); + if (result != NO_ERROR) { + ALOGE("Error %d allocating %d x %d graphics buffer", result, mWidth, mHeight); + break; + } + if (!memHandle) { + ALOGE("We didn't get a buffer handle back from the allocator"); + break; + } + + // Find a place to store the new buffer + bool stored = false; + for (auto&& rec : mBuffers) { + if (rec.handle == nullptr) { + // Use this existing entry + rec.handle = memHandle; + rec.inUse = false; + stored = true; + break; + } + } + if (!stored) { + // Add a BufferRecord wrapping this handle to our set of available buffers + mBuffers.emplace_back(memHandle); + } + + mFramesAllowed++; + added++; + } + + return added; +} + + +unsigned EvsCamera::decreaseAvailableFrames_Locked(unsigned numToRemove) { + // Acquire the graphics buffer allocator + GraphicBufferAllocator &alloc(GraphicBufferAllocator::get()); + + unsigned removed = 0; + + for (auto&& rec : mBuffers) { + // Is this record not in use, but holding a buffer that we can free? + if ((rec.inUse == false) && (rec.handle != nullptr)) { + // Release buffer and update the record so we can recognize it as "empty" + alloc.free(rec.handle); + rec.handle = nullptr; + + mFramesAllowed--; + removed++; + + if (removed == numToRemove) { + break; + } + } + } + + return removed; +} + + +// This is the asynchronous frame generation thread that runs in parallel with the +// main serving thread. There is one for each active camera instance. +void EvsCamera::generateFrames() { + ALOGD("Frame generation loop started"); + + unsigned idx; + + while (true) { + bool timeForFrame = false; + nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC); + + // Lock scope for updating shared state + { + std::lock_guard lock(mAccessLock); + + if (mStreamState != RUNNING) { + // Break out of our main thread loop + break; + } + + // Are we allowed to issue another buffer? + if (mFramesInUse >= mFramesAllowed) { + // Can't do anything right now -- skip this frame + ALOGW("Skipped a frame because too many are in flight\n"); + } else { + // Identify an available buffer to fill + for (idx = 0; idx < mBuffers.size(); idx++) { + if (!mBuffers[idx].inUse) { + if (mBuffers[idx].handle != nullptr) { + // Found an available record, so stop looking + break; + } + } + } + if (idx >= mBuffers.size()) { + // This shouldn't happen since we already checked mFramesInUse vs mFramesAllowed + ALOGE("Failed to find an available buffer slot\n"); + } else { + // We're going to make the frame busy + mBuffers[idx].inUse = true; + mFramesInUse++; + timeForFrame = true; + } + } + } + + if (timeForFrame) { + // Assemble the buffer description we'll transmit below + BufferDesc_1_1 newBuffer = {}; + AHardwareBuffer_Desc* pDesc = + reinterpret_cast(&newBuffer.buffer.description); + pDesc->width = mWidth; + pDesc->height = mHeight; + pDesc->layers = 1; + pDesc->format = mFormat; + pDesc->usage = mUsage; + pDesc->stride = mStride; + newBuffer.buffer.nativeHandle = mBuffers[idx].handle; + newBuffer.pixelSize = sizeof(uint32_t); + newBuffer.bufferId = idx; + newBuffer.deviceId = mDescription.v1.cameraId; + newBuffer.timestamp = elapsedRealtimeNano(); + + // Write test data into the image buffer + fillTestFrame(newBuffer); + + // Issue the (asynchronous) callback to the client -- can't be holding the lock + hidl_vec frames; + frames.resize(1); + frames[0] = newBuffer; + auto result = mStream->deliverFrame_1_1(frames); + if (result.isOk()) { + ALOGD("Delivered %p as id %d", + newBuffer.buffer.nativeHandle.getNativeHandle(), newBuffer.bufferId); + } else { + // This can happen if the client dies and is likely unrecoverable. + // To avoid consuming resources generating failing calls, we stop sending + // frames. Note, however, that the stream remains in the "STREAMING" state + // until cleaned up on the main thread. + ALOGE("Frame delivery call failed in the transport layer."); + + // Since we didn't actually deliver it, mark the frame as available + std::lock_guard lock(mAccessLock); + mBuffers[idx].inUse = false; + mFramesInUse--; + + break; + } + } + + // We arbitrarily choose to generate frames at 12 fps to ensure we pass the 10fps test requirement + static const int kTargetFrameRate = 12; + static const nsecs_t kTargetFrameTimeUs = 1000*1000 / kTargetFrameRate; + const nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + const nsecs_t workTimeUs = (now - startTime) / 1000; + const nsecs_t sleepDurationUs = kTargetFrameTimeUs - workTimeUs; + if (sleepDurationUs > 0) { + usleep(sleepDurationUs); + } + } + + // If we've been asked to stop, send an event to signal the actual end of stream + EvsEventDesc event; + event.aType = EvsEventType::STREAM_STOPPED; + auto result = mStream->notify(event); + if (!result.isOk()) { + ALOGE("Error delivering end of stream marker"); + } + + return; +} + + +void EvsCamera::fillTestFrame(const BufferDesc_1_1& buff) { + // Lock our output buffer for writing + uint32_t *pixels = nullptr; + const AHardwareBuffer_Desc* pDesc = + reinterpret_cast(&buff.buffer.description); + GraphicBufferMapper &mapper = GraphicBufferMapper::get(); + mapper.lock(buff.buffer.nativeHandle, + GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_NEVER, + android::Rect(pDesc->width, pDesc->height), + (void **) &pixels); + + // If we failed to lock the pixel buffer, we're about to crash, but log it first + if (!pixels) { + ALOGE("Camera failed to gain access to image buffer for writing"); + } + + // Fill in the test pixels + for (unsigned row = 0; row < pDesc->height; row++) { + for (unsigned col = 0; col < pDesc->width; col++) { + // Index into the row to check the pixel at this column. + // We expect 0xFF in the LSB channel, a vertical gradient in the + // second channel, a horitzontal gradient in the third channel, and + // 0xFF in the MSB. + // The exception is the very first 32 bits which is used for the + // time varying frame signature to avoid getting fooled by a static image. + uint32_t expectedPixel = 0xFF0000FF | // MSB and LSB + ((row & 0xFF) << 8) | // vertical gradient + ((col & 0xFF) << 16); // horizontal gradient + if ((row | col) == 0) { + static uint32_t sFrameTicker = 0; + expectedPixel = (sFrameTicker) & 0xFF; + sFrameTicker++; + } + pixels[col] = expectedPixel; + } + // Point to the next row + // NOTE: stride retrieved from gralloc is in units of pixels + pixels = pixels + pDesc->stride; + } + + // Release our output buffer + mapper.unlock(buff.buffer.nativeHandle); +} + + +void EvsCamera::fillTestFrame(const BufferDesc_1_0& buff) { + BufferDesc_1_1 newBufDesc = {}; + AHardwareBuffer_Desc desc = { + buff.width, // width + buff.height, // height + 1, // layers, always 1 for EVS + buff.format, // One of AHardwareBuffer_Format + buff.usage, // Combination of AHardwareBuffer_UsageFlags + buff.stride, // Row stride in pixels + 0, // Reserved + 0 // Reserved + }; + memcpy(&desc, &newBufDesc.buffer.description, sizeof(desc)); + newBufDesc.buffer.nativeHandle = buff.memHandle; + newBufDesc.pixelSize = buff.pixelSize; + newBufDesc.bufferId = buff.bufferId; + + return fillTestFrame(newBufDesc); +} + + +void EvsCamera::returnBuffer(const uint32_t bufferId, const buffer_handle_t memHandle) { + std::lock_guard lock(mAccessLock); + + if (memHandle == nullptr) { + ALOGE("ignoring doneWithFrame called with null handle"); + } else if (bufferId >= mBuffers.size()) { + ALOGE("ignoring doneWithFrame called with invalid bufferId %d (max is %zu)", + bufferId, mBuffers.size()-1); + } else if (!mBuffers[bufferId].inUse) { + ALOGE("ignoring doneWithFrame called on frame %d which is already free", + bufferId); + } else { + // Mark the frame as available + mBuffers[bufferId].inUse = false; + mFramesInUse--; + + // If this frame's index is high in the array, try to move it down + // to improve locality after mFramesAllowed has been reduced. + if (bufferId >= mFramesAllowed) { + // Find an empty slot lower in the array (which should always exist in this case) + for (auto&& rec : mBuffers) { + if (rec.handle == nullptr) { + rec.handle = mBuffers[bufferId].handle; + mBuffers[bufferId].handle = nullptr; + break; + } + } + } + } +} + + +sp EvsCamera::Create(const char *deviceName) { + unique_ptr nullCamInfo = nullptr; + + return Create(deviceName, nullCamInfo); +} + + +sp EvsCamera::Create(const char *deviceName, + unique_ptr &camInfo, + const Stream *streamCfg) { + sp 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 +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/evs/1.1/default/EvsCamera.h b/automotive/evs/1.1/default/EvsCamera.h new file mode 100644 index 0000000000..6163a34e08 --- /dev/null +++ b/automotive/evs/1.1/default/EvsCamera.h @@ -0,0 +1,163 @@ +/* + * 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_AUTOMOTIVE_EVS_V1_1_EVSCAMERA_H +#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSCAMERA_H + +#include +#include +#include +#include +#include + +#include + +#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; +using IEvsCameraStream_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsCameraStream; +using ::android::hardware::automotive::evs::V1_0::EvsResult; +using ::android::hardware::automotive::evs::V1_0::CameraDesc; +using IEvsDisplay_1_0 = ::android::hardware::automotive::evs::V1_0::IEvsDisplay; +using IEvsDisplay_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsDisplay; + + +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace V1_1 { +namespace implementation { + + +// From EvsEnumerator.h +class EvsEnumerator; + + +class EvsCamera : public IEvsCamera { +public: + // Methods from ::android::hardware::automotive::evs::V1_0::IEvsCamera follow. + Return getCameraInfo(getCameraInfo_cb _hidl_cb) override; + Return setMaxFramesInFlight(uint32_t bufferCount) override; + Return startVideoStream(const ::android::sp& stream) override; + Return stopVideoStream() override; + Return doneWithFrame(const BufferDesc_1_0& buffer) override; + + Return getExtendedInfo(uint32_t opaqueIdentifier) override; + Return setExtendedInfo(uint32_t opaqueIdentifier, int32_t opaqueValue) override; + + // Methods from ::android::hardware::automotive::evs::V1_1::IEvsCamera follow. + Return getCameraInfo_1_1(getCameraInfo_1_1_cb _hidl_cb) override; + Return getPhysicalCameraInfo(const hidl_string& id, + getPhysicalCameraInfo_cb _hidl_cb) override; + Return pauseVideoStream() override; + Return resumeVideoStream() override; + Return doneWithFrame_1_1(const hidl_vec& buffer) override; + Return setMaster() override; + Return forceMaster(const sp& display) override; + Return unsetMaster() override; + Return getParameterList(getParameterList_cb _hidl_cb) override; + Return getIntParameterRange(CameraParam id, + getIntParameterRange_cb _hidl_cb) override; + Return setIntParameter(CameraParam id, int32_t value, + setIntParameter_cb _hidl_cb) override; + Return getIntParameter(CameraParam id, + getIntParameter_cb _hidl_cb) override; + Return setExtendedInfo_1_1(uint32_t opaqueIdentifier, + const hidl_vec& opaqueValue) override; + Return getExtendedInfo_1_1(uint32_t opaqueIdentifier, + getExtendedInfo_1_1_cb _hidl_cb) override; + Return importExternalBuffers(const hidl_vec& buffers, + importExternalBuffers_cb _hidl_cb) override; + + static sp Create(const char *deviceName); + static sp Create(const char *deviceName, + unique_ptr &camInfo, + const Stream *streamCfg = nullptr); + EvsCamera(const EvsCamera&) = delete; + EvsCamera& operator=(const EvsCamera&) = delete; + + virtual ~EvsCamera() override; + void forceShutdown(); // This gets called if another caller "steals" ownership of the camera + + const CameraDesc& getDesc() { return mDescription; }; + + static const char kCameraName_Backup[]; + +private: + EvsCamera(const char *id, + unique_ptr &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); + + void generateFrames(); + void fillTestFrame(const BufferDesc_1_0& buff); + void fillTestFrame(const BufferDesc_1_1& buff); + void returnBuffer(const uint32_t bufferId, const buffer_handle_t memHandle); + + sp mEnumerator; // The enumerator object that created this camera + + CameraDesc mDescription = {}; // The properties of this camera + + std::thread mCaptureThread; // The thread we'll use to synthesize frames + + uint32_t mWidth = 0; // Horizontal pixel count in the buffers + uint32_t mHeight = 0; // Vertical pixel count in the buffers + uint32_t mFormat = 0; // Values from android_pixel_format_t + uint64_t mUsage = 0; // Values from from Gralloc.h + uint32_t mStride = 0; // Bytes per line in the buffers + + sp mStream = nullptr; // The callback used to deliver each frame + + struct BufferRecord { + buffer_handle_t handle; + bool inUse; + + explicit BufferRecord(buffer_handle_t h) : handle(h), inUse(false) {}; + }; + + std::vector mBuffers; // Graphics buffers to transfer images + unsigned mFramesAllowed; // How many buffers are we currently using + unsigned mFramesInUse; // How many buffers are currently outstanding + + enum StreamStateValues { + STOPPED, + RUNNING, + STOPPING, + DEAD, + }; + StreamStateValues mStreamState; + + // Synchronization necessary to deconflict mCaptureThread from the main service thread + std::mutex mAccessLock; + + // Static camera module information + unique_ptr &mCameraInfo; +}; + +} // namespace implementation +} // namespace V1_1 +} // namespace evs +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSCAMERA_H diff --git a/automotive/evs/1.1/default/EvsDisplay.cpp b/automotive/evs/1.1/default/EvsDisplay.cpp new file mode 100644 index 0000000000..2b5a4a977c --- /dev/null +++ b/automotive/evs/1.1/default/EvsDisplay.cpp @@ -0,0 +1,356 @@ +/* + * 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.automotive.evs@1.1-service" + +#include "EvsDisplay.h" + +#include +#include + +using ::android::frameworks::automotive::display::V1_0::HwDisplayConfig; +using ::android::frameworks::automotive::display::V1_0::HwDisplayState; + +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace V1_1 { +namespace implementation { + + +EvsDisplay::EvsDisplay() { + EvsDisplay(nullptr, 0); +} + + +EvsDisplay::EvsDisplay(sp pDisplayProxy, uint64_t displayId) + : mDisplayProxy(pDisplayProxy), + mDisplayId(displayId) { + ALOGD("EvsDisplay instantiated"); + + // Set up our self description + // NOTE: These are arbitrary values chosen for testing + mInfo.displayId = "Mock Display"; + mInfo.vendorFlags = 3870; + + // Assemble the buffer description we'll use for our render target + mBuffer.width = 320; + mBuffer.height = 240; + mBuffer.format = HAL_PIXEL_FORMAT_RGBA_8888; + mBuffer.usage = GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_COMPOSER; + mBuffer.bufferId = 0x3870; // Arbitrary magic number for self recognition + mBuffer.pixelSize = 4; +} + + +EvsDisplay::~EvsDisplay() { + ALOGD("EvsDisplay being destroyed"); + forceShutdown(); +} + + +/** + * This gets called if another caller "steals" ownership of the display + */ +void EvsDisplay::forceShutdown() +{ + ALOGD("EvsDisplay forceShutdown"); + std::lock_guard lock(mAccessLock); + + // If the buffer isn't being held by a remote client, release it now as an + // optimization to release the resources more quickly than the destructor might + // get called. + if (mBuffer.memHandle) { + // Report if we're going away while a buffer is outstanding + if (mFrameBusy) { + ALOGE("EvsDisplay going down while client is holding a buffer"); + } + + // Drop the graphics buffer we've been using + GraphicBufferAllocator& alloc(GraphicBufferAllocator::get()); + alloc.free(mBuffer.memHandle); + mBuffer.memHandle = nullptr; + } + + // Put this object into an unrecoverable error state since somebody else + // is going to own the display now. + mRequestedState = DisplayState::DEAD; +} + + +/** + * Returns basic information about the EVS display provided by the system. + * See the description of the DisplayDesc structure for details. + */ +Return EvsDisplay::getDisplayInfo(getDisplayInfo_cb _hidl_cb) { + ALOGD("getDisplayInfo"); + + // Send back our self description + _hidl_cb(mInfo); + return Void(); +} + + +/** + * Clients may set the display state to express their desired state. + * The HAL implementation must gracefully accept a request for any state + * while in any other state, although the response may be to ignore the request. + * The display is defined to start in the NOT_VISIBLE state upon initialization. + * The client is then expected to request the VISIBLE_ON_NEXT_FRAME state, and + * then begin providing video. When the display is no longer required, the client + * is expected to request the NOT_VISIBLE state after passing the last video frame. + */ +Return EvsDisplay::setDisplayState(DisplayState state) { + ALOGD("setDisplayState"); + std::lock_guard lock(mAccessLock); + + if (mRequestedState == DisplayState::DEAD) { + // This object no longer owns the display -- it's been superceeded! + return EvsResult::OWNERSHIP_LOST; + } + + // Ensure we recognize the requested state so we don't go off the rails + if (state < DisplayState::NUM_STATES) { + // Record the requested state + mRequestedState = state; + return EvsResult::OK; + } + else { + // Turn off the display if asked for an unrecognized state + mRequestedState = DisplayState::NOT_VISIBLE; + return EvsResult::INVALID_ARG; + } +} + + +/** + * The HAL implementation should report the actual current state, which might + * transiently differ from the most recently requested state. Note, however, that + * the logic responsible for changing display states should generally live above + * the device layer, making it undesirable for the HAL implementation to + * spontaneously change display states. + */ +Return EvsDisplay::getDisplayState() { + ALOGD("getDisplayState"); + std::lock_guard lock(mAccessLock); + + return mRequestedState; +} + + +/** + * This call returns a handle to a frame buffer associated with the display. + * This buffer may be locked and written to by software and/or GL. This buffer + * must be returned via a call to returnTargetBufferForDisplay() even if the + * display is no longer visible. + */ +// TODO: We need to know if/when our client dies so we can get the buffer back! (blocked b/31632518) +Return EvsDisplay::getTargetBuffer(getTargetBuffer_cb _hidl_cb) { + ALOGD("getTargetBuffer"); + std::lock_guard lock(mAccessLock); + + if (mRequestedState == DisplayState::DEAD) { + ALOGE("Rejecting buffer request from object that lost ownership of the display."); + BufferDesc_1_0 nullBuff = {}; + _hidl_cb(nullBuff); + return Void(); + } + + // If we don't already have a buffer, allocate one now + if (!mBuffer.memHandle) { + // Allocate the buffer that will hold our displayable image + buffer_handle_t handle = nullptr; + GraphicBufferAllocator& alloc(GraphicBufferAllocator::get()); + status_t result = alloc.allocate( + mBuffer.width, mBuffer.height, mBuffer.format, 1, mBuffer.usage, + &handle, &mBuffer.stride, 0, "EvsDisplay"); + if (result != NO_ERROR) { + ALOGE("Error %d allocating %d x %d graphics buffer", + result, mBuffer.width, mBuffer.height); + BufferDesc_1_0 nullBuff = {}; + _hidl_cb(nullBuff); + return Void(); + } + if (!handle) { + ALOGE("We didn't get a buffer handle back from the allocator"); + BufferDesc_1_0 nullBuff = {}; + _hidl_cb(nullBuff); + return Void(); + } + + mBuffer.memHandle = handle; + mFrameBusy = false; + ALOGD("Allocated new buffer %p with stride %u", + mBuffer.memHandle.getNativeHandle(), mBuffer.stride); + } + + // Do we have a frame available? + if (mFrameBusy) { + // This means either we have a 2nd client trying to compete for buffers + // (an unsupported mode of operation) or else the client hasn't returned + // a previously issued buffer yet (they're behaving badly). + // NOTE: We have to make the callback even if we have nothing to provide + ALOGE("getTargetBuffer called while no buffers available."); + BufferDesc_1_0 nullBuff = {}; + _hidl_cb(nullBuff); + return Void(); + } else { + // Mark our buffer as busy + mFrameBusy = true; + + // Send the buffer to the client + ALOGD("Providing display buffer handle %p as id %d", + mBuffer.memHandle.getNativeHandle(), mBuffer.bufferId); + _hidl_cb(mBuffer); + return Void(); + } +} + + +/** + * This call tells the display that the buffer is ready for display. + * The buffer is no longer valid for use by the client after this call. + */ +Return EvsDisplay::returnTargetBufferForDisplayImpl(const uint32_t bufferId, const buffer_handle_t memHandle) { + ALOGD("returnTargetBufferForDisplay %p", memHandle); + std::lock_guard lock(mAccessLock); + + // Nobody should call us with a null handle + if (!memHandle) { + ALOGE ("returnTargetBufferForDisplay called without a valid buffer handle.\n"); + return EvsResult::INVALID_ARG; + } + if (bufferId != mBuffer.bufferId) { + ALOGE ("Got an unrecognized frame returned.\n"); + return EvsResult::INVALID_ARG; + } + if (!mFrameBusy) { + ALOGE ("A frame was returned with no outstanding frames.\n"); + return EvsResult::BUFFER_NOT_AVAILABLE; + } + + mFrameBusy = false; + + // If we've been displaced by another owner of the display, then we can't do anything else + if (mRequestedState == DisplayState::DEAD) { + return EvsResult::OWNERSHIP_LOST; + } + + // If we were waiting for a new frame, this is it! + if (mRequestedState == DisplayState::VISIBLE_ON_NEXT_FRAME) { + mRequestedState = DisplayState::VISIBLE; + } + + // Validate we're in an expected state + if (mRequestedState != DisplayState::VISIBLE) { + // We shouldn't get frames back when we're not visible. + ALOGE ("Got an unexpected frame returned while not visible - ignoring.\n"); + } else { + // This is where the buffer would be made visible. + // For now we simply validate it has the data we expect in it by reading it back + + // Lock our display buffer for reading + uint32_t* pixels = nullptr; + GraphicBufferMapper &mapper = GraphicBufferMapper::get(); + mapper.lock(mBuffer.memHandle, + GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_NEVER, + android::Rect(mBuffer.width, mBuffer.height), + (void **)&pixels); + + // If we failed to lock the pixel buffer, we're about to crash, but log it first + if (!pixels) { + ALOGE("Display failed to gain access to image buffer for reading"); + } + + // Check the test pixels + bool frameLooksGood = true; + for (unsigned row = 0; row < mBuffer.height; row++) { + for (unsigned col = 0; col < mBuffer.width; col++) { + // Index into the row to check the pixel at this column. + // We expect 0xFF in the LSB channel, a vertical gradient in the + // second channel, a horitzontal gradient in the third channel, and + // 0xFF in the MSB. + // The exception is the very first 32 bits which is used for the + // time varying frame signature to avoid getting fooled by a static image. + uint32_t expectedPixel = 0xFF0000FF | // MSB and LSB + ((row & 0xFF) << 8) | // vertical gradient + ((col & 0xFF) << 16); // horizontal gradient + if ((row | col) == 0) { + // we'll check the "uniqueness" of the frame signature below + continue; + } + // Walk across this row (we'll step rows below) + uint32_t receivedPixel = pixels[col]; + if (receivedPixel != expectedPixel) { + ALOGE("Pixel check mismatch in frame buffer"); + frameLooksGood = false; + break; + } + } + + if (!frameLooksGood) { + break; + } + + // Point to the next row (NOTE: gralloc reports stride in units of pixels) + pixels = pixels + mBuffer.stride; + } + + // Ensure we don't see the same buffer twice without it being rewritten + static uint32_t prevSignature = ~0; + uint32_t signature = pixels[0] & 0xFF; + if (prevSignature == signature) { + frameLooksGood = false; + ALOGE("Duplicate, likely stale frame buffer detected"); + } + + + // Release our output buffer + mapper.unlock(mBuffer.memHandle); + + if (!frameLooksGood) { + return EvsResult::UNDERLYING_SERVICE_ERROR; + } + } + + return EvsResult::OK; +} + + +Return EvsDisplay::returnTargetBufferForDisplay(const BufferDesc_1_0& buffer) { + return returnTargetBufferForDisplayImpl(buffer.bufferId, buffer.memHandle); +} + + +Return EvsDisplay::getDisplayInfo_1_1(getDisplayInfo_1_1_cb _info_cb) { + if (mDisplayProxy != nullptr) { + return mDisplayProxy->getDisplayInfo(mDisplayId, _info_cb); + } else { + HwDisplayConfig nullConfig; + HwDisplayState nullState; + _info_cb(nullConfig, nullState); + return Void(); + } +} + + +} // namespace implementation +} // namespace V1_1 +} // namespace evs +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/evs/1.1/default/EvsDisplay.h b/automotive/evs/1.1/default/EvsDisplay.h new file mode 100644 index 0000000000..9b2ed90128 --- /dev/null +++ b/automotive/evs/1.1/default/EvsDisplay.h @@ -0,0 +1,80 @@ +/* + * 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. + */ + +#ifndef ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSDISPLAY_H +#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSDISPLAY_H + +#include +#include +#include + +using ::android::hardware::automotive::evs::V1_1::IEvsDisplay; +using ::android::hardware::automotive::evs::V1_0::DisplayDesc; +using ::android::hardware::automotive::evs::V1_0::DisplayState; +using ::android::hardware::automotive::evs::V1_0::EvsResult; +using BufferDesc_1_0 = ::android::hardware::automotive::evs::V1_0::BufferDesc; +using android::frameworks::automotive::display::V1_0::IAutomotiveDisplayProxyService; + +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace V1_1 { +namespace implementation { + + +class EvsDisplay : public IEvsDisplay { +public: + // Methods from ::android::hardware::automotive::evs::V1_0::IEvsDisplay follow. + Return getDisplayInfo(getDisplayInfo_cb _hidl_cb) override; + Return setDisplayState(DisplayState state) override; + Return getDisplayState() override; + Return getTargetBuffer(getTargetBuffer_cb _hidl_cb) override; + Return returnTargetBufferForDisplay(const BufferDesc_1_0& buffer) override; + + // Methods from ::android::hardware::automotive::evs::V1_1::IEvsDisplay follow. + Return getDisplayInfo_1_1(getDisplayInfo_1_1_cb _info_cb) override; + + // Implementation details + EvsDisplay(); + EvsDisplay(sp pDisplayProxy, uint64_t displayId); + virtual ~EvsDisplay() override; + + void forceShutdown(); // This gets called if another caller "steals" ownership of the display + Return returnTargetBufferForDisplayImpl(const uint32_t bufferId, + const buffer_handle_t memHandle); + +private: + DisplayDesc mInfo = {}; + BufferDesc_1_0 mBuffer = {}; // A graphics buffer into which we'll store images + + bool mFrameBusy = false; // A flag telling us our buffer is in use + DisplayState mRequestedState = DisplayState::NOT_VISIBLE; + + std::mutex mAccessLock; + + sp mDisplayProxy; + uint64_t mDisplayId; +}; + +} // namespace implementation +} // namespace V1_1 +} // namespace evs +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSDISPLAY_H diff --git a/automotive/evs/1.1/default/EvsEnumerator.cpp b/automotive/evs/1.1/default/EvsEnumerator.cpp new file mode 100644 index 0000000000..117ee7aec4 --- /dev/null +++ b/automotive/evs/1.1/default/EvsEnumerator.cpp @@ -0,0 +1,464 @@ +/* + * 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.automotive.evs@1.1-service" + +#include "EvsEnumerator.h" +#include "EvsCamera.h" +#include "EvsDisplay.h" +#include "EvsUltrasonicsArray.h" + +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace V1_1 { +namespace implementation { + + +// NOTE: All members values are static so that all clients operate on the same state +// That is to say, this is effectively a singleton despite the fact that HIDL +// constructs a new instance for each client. +std::list EvsEnumerator::sCameraList; +wp EvsEnumerator::sActiveDisplay; +unique_ptr EvsEnumerator::sConfigManager; +sp EvsEnumerator::sDisplayProxyService; +std::unordered_map EvsEnumerator::sDisplayPortList; +std::list EvsEnumerator::sUltrasonicsArrayRecordList; + +EvsEnumerator::EvsEnumerator(sp windowService) { + ALOGD("EvsEnumerator created"); + + // Add sample camera data to our list of cameras + // In a real driver, this would be expected to can the available hardware + sConfigManager = + ConfigManager::Create("/vendor/etc/automotive/evs/evs_default_configuration.xml"); + + // Add available cameras + for (auto v : sConfigManager->getCameraList()) { + sCameraList.emplace_back(v.c_str()); + } + + if (sDisplayProxyService == nullptr) { + /* sets a car-window service handle */ + sDisplayProxyService = windowService; + } + + // Add available displays + if (sDisplayProxyService != nullptr) { + // Get a display ID list. + sDisplayProxyService->getDisplayIdList([](const auto& displayIds) { + for (const auto& id : displayIds) { + const auto port = id & 0xF; + sDisplayPortList.insert_or_assign(port, id); + } + }); + } + + // Add ultrasonics array desc. + sUltrasonicsArrayRecordList.emplace_back( + EvsUltrasonicsArray::GetDummyArrayDesc("front_array")); +} + + +// Methods from ::android::hardware::automotive::evs::V1_0::IEvsEnumerator follow. +Return EvsEnumerator::getCameraList(getCameraList_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 descriptions; + descriptions.reserve(numCameras); + for (const auto& cam : sCameraList) { + descriptions.push_back( cam.desc.v1 ); + } + + // Encapsulate our camera descriptions in the HIDL vec type + hidl_vec 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> EvsEnumerator::openCamera(const hidl_string& cameraId) { + ALOGD("openCamera"); + + // 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 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)); + } + pRecord->activeInstance = pActiveCamera; + if (pActiveCamera == nullptr) { + ALOGE("Failed to allocate new EvsCamera object for %s\n", cameraId.c_str()); + } + + return pActiveCamera; +} + + +Return EvsEnumerator::closeCamera(const ::android::sp& pCamera) { + ALOGD("closeCamera"); + + auto pCamera_1_1 = IEvsCamera_1_1::castFrom(pCamera).withDefault(nullptr); + if (pCamera_1_1 == nullptr) { + ALOGE("Ignoring call to closeCamera with null camera ptr"); + return Void(); + } + + // Get the camera id so we can find it in our list + std::string 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.v1.cameraId == cameraId) { + // Found a match! + pRecord = &cam; + break; + } + } + + // Is the display being destroyed actually the one we think is active? + if (!pRecord) { + ALOGE("Asked to close a camera who's name isn't recognized"); + } else { + sp pActiveCamera = pRecord->activeInstance.promote(); + + if (pActiveCamera == nullptr) { + ALOGE("Somehow a camera is being destroyed when the enumerator didn't know one existed"); + } else if (pActiveCamera != pCamera_1_1) { + // This can happen if the camera was aggressively reopened, orphaning this previous instance + ALOGW("Ignoring close of previously orphaned camera - why did a client steal?"); + } else { + // Drop the active camera + pActiveCamera->forceShutdown(); + pRecord->activeInstance = nullptr; + } + } + + return Void(); +} + + +Return> EvsEnumerator::openDisplay() { + ALOGD("openDisplay"); + + // If we already have a display active, then we need to shut it down so we can + // give exclusive access to the new caller. + sp pActiveDisplay = sActiveDisplay.promote(); + if (pActiveDisplay != nullptr) { + ALOGW("Killing previous display because of new caller"); + closeDisplay(pActiveDisplay); + } + + // Create a new display interface and return it + pActiveDisplay = new EvsDisplay(); + sActiveDisplay = pActiveDisplay; + + ALOGD("Returning new EvsDisplay object %p", pActiveDisplay.get()); + return pActiveDisplay; +} + + +Return EvsEnumerator::getDisplayIdList(getDisplayIdList_cb _list_cb) { + hidl_vec ids; + + ids.resize(sDisplayPortList.size()); + unsigned i = 0; + for (const auto& [port, id] : sDisplayPortList) { + ids[i++] = port; + } + + _list_cb(ids); + return Void(); +} + + +Return> EvsEnumerator::openDisplay_1_1(uint8_t port) { + ALOGD("%s", __FUNCTION__); + + // If we already have a display active, then we need to shut it down so we can + // give exclusive access to the new caller. + sp pActiveDisplay = sActiveDisplay.promote(); + if (pActiveDisplay != nullptr) { + ALOGW("Killing previous display because of new caller"); + closeDisplay(pActiveDisplay); + } + + // Create a new display interface and return it + pActiveDisplay = new EvsDisplay(sDisplayProxyService, sDisplayPortList[port]); + sActiveDisplay = pActiveDisplay; + + ALOGD("Returning new EvsDisplay object %p", pActiveDisplay.get()); + return pActiveDisplay; +} + + + +Return EvsEnumerator::closeDisplay(const ::android::sp& pDisplay) { + ALOGD("closeDisplay"); + + // Do we still have a display object we think should be active? + sp pActiveDisplay = sActiveDisplay.promote(); + if (pActiveDisplay == nullptr) { + ALOGE("Somehow a display is being destroyed when the enumerator didn't know one existed"); + } else if (sActiveDisplay != pDisplay) { + ALOGW("Ignoring close of previously orphaned display - why did a client steal?"); + } else { + // Drop the active display + pActiveDisplay->forceShutdown(); + sActiveDisplay = nullptr; + } + + return Void(); +} + + +Return EvsEnumerator::getDisplayState() { + ALOGD("getDisplayState"); + + // Do we still have a display object we think should be active? + sp pActiveDisplay = sActiveDisplay.promote(); + if (pActiveDisplay != nullptr) { + return pActiveDisplay->getDisplayState(); + } else { + return DisplayState::NOT_OPEN; + } +} + + +// Methods from ::android::hardware::automotive::evs::V1_1::IEvsEnumerator follow. +Return 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 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 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> +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 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; +} + +EvsEnumerator::UltrasonicsArrayRecord* EvsEnumerator::findUltrasonicsArrayById( + const std::string& ultrasonicsArrayId) { + auto recordIt = std::find_if( + sUltrasonicsArrayRecordList.begin(), sUltrasonicsArrayRecordList.end(), + [&ultrasonicsArrayId](const UltrasonicsArrayRecord& record) { + return ultrasonicsArrayId == record.desc.ultrasonicsArrayId;}); + + return (recordIt != sUltrasonicsArrayRecordList.end()) ? &*recordIt : nullptr; +} + +Return EvsEnumerator::getUltrasonicsArrayList(getUltrasonicsArrayList_cb _hidl_cb) { + hidl_vec desc; + desc.resize(sUltrasonicsArrayRecordList.size()); + + // Copy over desc from sUltrasonicsArrayRecordList. + for (auto p = std::make_pair(sUltrasonicsArrayRecordList.begin(), desc.begin()); + p.first != sUltrasonicsArrayRecordList.end(); p.first++, p.second++) { + *p.second = p.first->desc; + } + + // Send back the results + ALOGD("reporting %zu ultrasonics arrays available", desc.size()); + _hidl_cb(desc); + + // HIDL convention says we return Void if we sent our result back via callback + return Void(); +} + +Return> EvsEnumerator::openUltrasonicsArray( + const hidl_string& ultrasonicsArrayId) { + // Find the named ultrasonic array. + UltrasonicsArrayRecord* pRecord = findUltrasonicsArrayById(ultrasonicsArrayId); + + // Is this a recognized ultrasonic array id? + if (!pRecord) { + ALOGE("Requested ultrasonics array %s not found", ultrasonicsArrayId.c_str()); + return nullptr; + } + + // Has this ultrasonic array already been instantiated by another caller? + sp pActiveUltrasonicsArray = pRecord->activeInstance.promote(); + if (pActiveUltrasonicsArray != nullptr) { + ALOGW("Killing previous ultrasonics array because of new caller"); + closeUltrasonicsArray(pActiveUltrasonicsArray); + } + + // Construct a ultrasonic array instance for the caller + pActiveUltrasonicsArray = EvsUltrasonicsArray::Create(ultrasonicsArrayId.c_str()); + pRecord->activeInstance = pActiveUltrasonicsArray; + if (pActiveUltrasonicsArray == nullptr) { + ALOGE("Failed to allocate new EvsUltrasonicsArray object for %s\n", + ultrasonicsArrayId.c_str()); + } + + return pActiveUltrasonicsArray; +} + +Return EvsEnumerator::closeUltrasonicsArray( + const sp& pEvsUltrasonicsArray) { + + if (pEvsUltrasonicsArray.get() == nullptr) { + ALOGE("Ignoring call to closeUltrasonicsArray with null ultrasonics array"); + return Void(); + } + + // Get the ultrasonics array id so we can find it in our list. + std::string ultrasonicsArrayId; + pEvsUltrasonicsArray->getUltrasonicArrayInfo([&ultrasonicsArrayId](UltrasonicsArrayDesc desc) { + ultrasonicsArrayId.assign(desc.ultrasonicsArrayId); + }); + + // Find the named ultrasonics array + UltrasonicsArrayRecord* pRecord = findUltrasonicsArrayById(ultrasonicsArrayId); + if (!pRecord) { + ALOGE("Asked to close a ultrasonics array whose name isnt not found"); + return Void(); + } + + sp pActiveUltrasonicsArray = pRecord->activeInstance.promote(); + + if (pActiveUltrasonicsArray.get() == nullptr) { + ALOGE("Somehow a ultrasonics array is being destroyed when the enumerator didn't know " + "one existed"); + } else if (pActiveUltrasonicsArray != pEvsUltrasonicsArray) { + // This can happen if the ultrasonics array was aggressively reopened, + // orphaning this previous instance + ALOGW("Ignoring close of previously orphaned ultrasonics array - why did a client steal?"); + } else { + // Drop the active ultrasonics array + pActiveUltrasonicsArray->forceShutdown(); + pRecord->activeInstance = nullptr; + } + + return Void(); +} + +} // namespace implementation +} // namespace V1_1 +} // namespace evs +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/evs/1.1/default/EvsEnumerator.h b/automotive/evs/1.1/default/EvsEnumerator.h new file mode 100644 index 0000000000..d80124b94e --- /dev/null +++ b/automotive/evs/1.1/default/EvsEnumerator.h @@ -0,0 +1,122 @@ +/* + * 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_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H +#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H + +#include +#include +#include +#include +#include + +#include + +#include "ConfigManager.h" + +using ::android::hardware::automotive::evs::V1_0::EvsResult; +using ::android::hardware::automotive::evs::V1_0::DisplayState; +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; +using IEvsDisplay_1_0 = ::android::hardware::automotive::evs::V1_0::IEvsDisplay; +using IEvsDisplay_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsDisplay; +using android::frameworks::automotive::display::V1_0::IAutomotiveDisplayProxyService; + +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace V1_1 { +namespace implementation { + + +class EvsCamera; // from EvsCamera.h +class EvsDisplay; // from EvsDisplay.h +class EvsUltrasonicsArray; // from EvsUltrasonicsArray.h + + +class EvsEnumerator : public IEvsEnumerator { +public: + // Methods from ::android::hardware::automotive::evs::V1_0::IEvsEnumerator follow. + Return getCameraList(getCameraList_cb _hidl_cb) override; + Return> openCamera(const hidl_string& cameraId) override; + Return closeCamera(const ::android::sp& carCamera) override; + Return> openDisplay() override; + Return closeDisplay(const ::android::sp& display) override; + Return getDisplayState() override; + + // Methods from ::android::hardware::automotive::evs::V1_1::IEvsEnumerator follow. + Return getCameraList_1_1(getCameraList_1_1_cb _hidl_cb) override; + Return> openCamera_1_1(const hidl_string& cameraId, + const Stream& streamCfg) override; + Return isHardware() override { return true; } + Return getDisplayIdList(getDisplayIdList_cb _list_cb) override; + Return> openDisplay_1_1(uint8_t port) override; + Return getUltrasonicsArrayList(getUltrasonicsArrayList_cb _hidl_cb) override; + Return> openUltrasonicsArray( + const hidl_string& ultrasonicsArrayId) override; + Return closeUltrasonicsArray( + const ::android::sp& evsUltrasonicsArray) override; + + // Implementation details + EvsEnumerator(sp windowService = nullptr); + +private: + // NOTE: All members values are static so that all clients operate on the same state + // 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_1 desc; + wp activeInstance; + + CameraRecord(const char *cameraId) : desc() { desc.v1.cameraId = cameraId; } + }; + + struct UltrasonicsArrayRecord { + UltrasonicsArrayDesc desc; + wp activeInstance; + + UltrasonicsArrayRecord(const UltrasonicsArrayDesc& arrayDesc) : desc(arrayDesc) {}; + }; + + static CameraRecord* findCameraById(const std::string& cameraId); + + static std::list sCameraList; + + static UltrasonicsArrayRecord* findUltrasonicsArrayById(const std::string& ultrasonicsArrayId); + + static std::list sUltrasonicsArrayRecordList; + + // Weak pointer. Object destructs if client dies. + static wp sActiveDisplay; + + static unique_ptr sConfigManager; + + static sp sDisplayProxyService; + static std::unordered_map sDisplayPortList; +}; + +} // namespace implementation +} // namespace V1_1 +} // namespace evs +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSCAMERAENUMERATOR_H diff --git a/automotive/evs/1.1/default/EvsUltrasonicsArray.cpp b/automotive/evs/1.1/default/EvsUltrasonicsArray.cpp new file mode 100644 index 0000000000..bc69aa4156 --- /dev/null +++ b/automotive/evs/1.1/default/EvsUltrasonicsArray.cpp @@ -0,0 +1,551 @@ +/* + * Copyright 2020 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 "EvsUltrasonicsArray.h" + +#include +#include +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace V1_1 { +namespace implementation { + +// Arbitrary limit on number of data frames allowed to be allocated +// Safeguards against unreasonable resource consumption and provides a testable limit +const unsigned int kMaximumDataFramesInFlight = 100; + +const uint32_t kMaxReadingsPerSensor = 5; +const uint32_t kMaxReceiversCount = 3; + +const unsigned int kSharedMemoryMaxSize = + kMaxReadingsPerSensor * kMaxReceiversCount * 2 * sizeof(float); + +// Target frame rate in frames per second. +const int kTargetFrameRate = 10; + +namespace { + +void fillDummyArrayDesc(UltrasonicsArrayDesc& arrayDesc) { + arrayDesc.maxReadingsPerSensorCount = kMaxReadingsPerSensor; + arrayDesc.maxReceiversCount = kMaxReceiversCount; + + const int kSensorCount = 3; + const float kMaxRange = 4000; // 4 metres. + const float kAngleOfMeasurement = 0.261799; // 15 degrees. + + std::vector sensors(kSensorCount); + + // Sensor pointing forward on left side of front bumper. + sensors[0].maxRange = kMaxRange; + sensors[0].angleOfMeasurement = kAngleOfMeasurement; + sensors[0].pose = {{1, 0, 0, 0}, {-1000, 2000, 200}}; + + // Sensor pointing forward on center of front bumper. + sensors[1].maxRange = kMaxRange; + sensors[1].angleOfMeasurement = kAngleOfMeasurement; + sensors[1].pose = {{1, 0, 0, 0}, {0, 2000, 200}}; + + // Sensor pointing forward on right side of front bumper. + sensors[2].maxRange = kMaxRange; + sensors[2].angleOfMeasurement = kAngleOfMeasurement; + sensors[2].pose = {{1, 0, 0, 0}, {1000, 2000, 200}}; + + arrayDesc.sensors = sensors; +} + +// Struct used by SerializeWaveformData(). +struct WaveformData { + uint8_t receiverId; + std::vector> readings; +}; + +// Serializes data provided in waveformDataList to a shared memory data pointer. +// TODO(b/149950362): Add a common library for serialiazing and deserializing waveform data. +void SerializeWaveformData(const std::vector& waveformDataList, uint8_t* pData) { + for (auto& waveformData : waveformDataList) { + // Set Id + memcpy(pData, &waveformData.receiverId, sizeof(uint8_t)); + pData += sizeof(uint8_t); + + for (auto& reading : waveformData.readings) { + // Set the time of flight. + memcpy(pData, &reading.first, sizeof(float)); + pData += sizeof(float); + + // Set the resonance. + memcpy(pData, &reading.second, sizeof(float)); + pData += sizeof(float); + } + } +} + +// Fills dataFrameDesc with dummy data. +bool fillDummyDataFrame(UltrasonicsDataFrameDesc& dataFrameDesc, sp pIMemory) { + dataFrameDesc.timestampNs = elapsedRealtimeNano(); + + const std::vector transmittersIdList = {0}; + dataFrameDesc.transmittersIdList = transmittersIdList; + + const std::vector recvIdList = {0, 1, 2}; + dataFrameDesc.receiversIdList = recvIdList; + + const std::vector receiversReadingsCountList = {2, 2, 4}; + dataFrameDesc.receiversReadingsCountList = receiversReadingsCountList; + + const std::vector waveformDataList = { + {recvIdList[0], { {1000, 0.1f}, {2000, 0.8f} }}, + {recvIdList[1], { {1000, 0.1f}, {2000, 1.0f} }}, + {recvIdList[2], { {1000, 0.1f}, {2000, 0.2f}, {4000, 0.2f}, {5000, 0.1f} }} + }; + + if (pIMemory.get() == nullptr) { + return false; + } + + uint8_t* pData = (uint8_t*)((void*)pIMemory->getPointer()); + + pIMemory->update(); + SerializeWaveformData(waveformDataList, pData); + pIMemory->commit(); + + return true; +} + +} // namespace + +EvsUltrasonicsArray::EvsUltrasonicsArray(const char* deviceName) + : mFramesAllowed(0), mFramesInUse(0), mStreamState(STOPPED) { + LOG(DEBUG) << "EvsUltrasonicsArray instantiated"; + + // Set up dummy data for description. + mArrayDesc.ultrasonicsArrayId = deviceName; + fillDummyArrayDesc(mArrayDesc); + + // Assign allocator. + mShmemAllocator = IAllocator::getService("ashmem"); + if (mShmemAllocator.get() == nullptr) { + LOG(ERROR) << "SurroundViewHidlTest getService ashmem failed"; + } +} + +sp EvsUltrasonicsArray::Create(const char* deviceName) { + return sp(new EvsUltrasonicsArray(deviceName)); +} + +EvsUltrasonicsArray::~EvsUltrasonicsArray() { + LOG(DEBUG) << "EvsUltrasonicsArray being destroyed"; + forceShutdown(); +} + +// This gets called if another caller "steals" ownership of the ultrasonic array. +void EvsUltrasonicsArray::forceShutdown() { + LOG(DEBUG) << "EvsUltrasonicsArray forceShutdown"; + + // Make sure our output stream is cleaned up + // (It really should be already) + stopStream(); + + // Claim the lock while we work on internal state + std::lock_guard lock(mAccessLock); + + // Drop all the data frames we've been using + for (auto&& dataFrame : mDataFrames) { + if (dataFrame.inUse) { + LOG(ERROR) << "Error - releasing data frame despite remote ownership"; + } + dataFrame.sharedMemory.clear(); + } + mDataFrames.clear(); + + // Put this object into an unrecoverable error state since somebody else + // is going to own the underlying ultrasonic array now + mStreamState = DEAD; +} + +UltrasonicsArrayDesc EvsUltrasonicsArray::GetDummyArrayDesc(const char* deviceName) { + UltrasonicsArrayDesc ultrasonicsArrayDesc; + ultrasonicsArrayDesc.ultrasonicsArrayId = deviceName; + fillDummyArrayDesc(ultrasonicsArrayDesc); + return ultrasonicsArrayDesc; +} + +Return EvsUltrasonicsArray::getUltrasonicArrayInfo(getUltrasonicArrayInfo_cb _get_info_cb) { + LOG(DEBUG) << "EvsUltrasonicsArray getUltrasonicsArrayInfo"; + + // Return the description for the get info callback. + _get_info_cb(mArrayDesc); + + return Void(); +} + +Return EvsUltrasonicsArray::setMaxFramesInFlight(uint32_t bufferCount) { + LOG(DEBUG) << "EvsUltrasonicsArray setMaxFramesInFlight"; + + // Lock mutex for performing changes to available frames. + std::lock_guard lock(mAccessLock); + + // We cannot function without at least one buffer to send data. + if (bufferCount < 1) { + LOG(ERROR) << "Ignoring setMaxFramesInFlight with less than one buffer requested"; + return EvsResult::INVALID_ARG; + } + + // Update our internal state of buffer count. + if (setAvailableFrames_Locked(bufferCount)) { + return EvsResult::OK; + } else { + return EvsResult::BUFFER_NOT_AVAILABLE; + } + + return EvsResult::OK; +} + +Return EvsUltrasonicsArray::doneWithDataFrame(const UltrasonicsDataFrameDesc& dataFrameDesc) { + LOG(DEBUG) << "EvsUltrasonicsArray doneWithFrame"; + + std::lock_guard lock(mAccessLock); + + if (dataFrameDesc.dataFrameId >= mDataFrames.size()) { + LOG(ERROR) << "ignoring doneWithFrame called with invalid dataFrameId " + << dataFrameDesc.dataFrameId << "(max is " << mDataFrames.size() - 1 << ")"; + return Void(); + } + + if (!mDataFrames[dataFrameDesc.dataFrameId].inUse) { + LOG(ERROR) << "ignoring doneWithFrame called on frame " << dataFrameDesc.dataFrameId + << "which is already free"; + return Void(); + } + + // Mark the frame as available + mDataFrames[dataFrameDesc.dataFrameId].inUse = false; + mFramesInUse--; + + // If this frame's index is high in the array, try to move it down + // to improve locality after mFramesAllowed has been reduced. + if (dataFrameDesc.dataFrameId >= mFramesAllowed) { + // Find an empty slot lower in the array (which should always exist in this case) + for (auto&& dataFrame : mDataFrames) { + if (!dataFrame.sharedMemory.IsValid()) { + dataFrame.sharedMemory = mDataFrames[dataFrameDesc.dataFrameId].sharedMemory; + mDataFrames[dataFrameDesc.dataFrameId].sharedMemory.clear(); + return Void(); + } + } + } + + return Void(); +} + +Return EvsUltrasonicsArray::startStream( + const ::android::sp& stream) { + LOG(DEBUG) << "EvsUltrasonicsArray startStream"; + + std::lock_guard lock(mAccessLock); + + if (mStreamState != STOPPED) { + LOG(ERROR) << "ignoring startStream call when a stream is already running."; + return EvsResult::STREAM_ALREADY_RUNNING; + } + + // If the client never indicated otherwise, configure ourselves for a single streaming buffer + if (mFramesAllowed < 1) { + if (!setAvailableFrames_Locked(1)) { + LOG(ERROR) + << "Failed to start stream because we couldn't get shared memory data buffer"; + return EvsResult::BUFFER_NOT_AVAILABLE; + } + } + + // Record the user's callback for use when we have a frame ready + mStream = stream; + + // Start the frame generation thread + mStreamState = RUNNING; + mCaptureThread = std::thread([this]() { generateDataFrames(); }); + + return EvsResult::OK; +} + +Return EvsUltrasonicsArray::stopStream() { + LOG(DEBUG) << "EvsUltrasonicsArray stopStream"; + + bool streamStateStopping = false; + { + std::lock_guard lock(mAccessLock); + if (mStreamState == RUNNING) { + // Tell the GenerateFrames loop we want it to stop + mStreamState = STOPPING; + streamStateStopping = true; + } + } + + if (streamStateStopping) { + // 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 + LOG(DEBUG) << "Waiting for stream thread to end..."; + mCaptureThread.join(); + } + + { + std::lock_guard lock(mAccessLock); + mStreamState = STOPPED; + mStream = nullptr; + LOG(DEBUG) << "Stream marked STOPPED."; + } + + return Void(); +} + +bool EvsUltrasonicsArray::setAvailableFrames_Locked(unsigned bufferCount) { + if (bufferCount < 1) { + LOG(ERROR) << "Ignoring request to set buffer count to zero"; + return false; + } + if (bufferCount > kMaximumDataFramesInFlight) { + LOG(ERROR) << "Rejecting buffer request in excess of internal limit"; + return false; + } + + // Is an increase required? + if (mFramesAllowed < bufferCount) { + // An increase is required + unsigned needed = bufferCount - mFramesAllowed; + LOG(INFO) << "Number of data frame buffers to add: " << needed; + + unsigned added = increaseAvailableFrames_Locked(needed); + if (added != needed) { + // If we didn't add all the frames we needed, then roll back to the previous state + LOG(ERROR) << "Rolling back to previous frame queue size"; + decreaseAvailableFrames_Locked(added); + return false; + } + } else if (mFramesAllowed > bufferCount) { + // A decrease is required + unsigned framesToRelease = mFramesAllowed - bufferCount; + LOG(INFO) << "Number of data frame buffers to reduce: " << framesToRelease; + + unsigned released = decreaseAvailableFrames_Locked(framesToRelease); + if (released != framesToRelease) { + // This shouldn't happen with a properly behaving client because the client + // should only make this call after returning sufficient outstanding buffers + // to allow a clean resize. + LOG(ERROR) << "Buffer queue shrink failed -- too many buffers currently in use?"; + } + } + + return true; +} + +EvsUltrasonicsArray::SharedMemory EvsUltrasonicsArray::allocateAndMapSharedMemory() { + SharedMemory sharedMemory; + + // Check shared memory allocator is valid. + if (mShmemAllocator.get() == nullptr) { + LOG(ERROR) << "Shared memory allocator not initialized."; + return SharedMemory(); + } + + // Allocate memory. + bool allocateSuccess = false; + Return result = mShmemAllocator->allocate(kSharedMemoryMaxSize, + [&](bool success, const hidl_memory& hidlMem) { + if (!success) { + return; + } + allocateSuccess = success; + sharedMemory.hidlMemory = hidlMem; + }); + + // Check result of allocated memory. + if (!result.isOk() || !allocateSuccess) { + LOG(ERROR) << "Shared memory allocation failed."; + return SharedMemory(); + } + + // Map shared memory. + sharedMemory.pIMemory = mapMemory(sharedMemory.hidlMemory); + if (sharedMemory.pIMemory.get() == nullptr) { + LOG(ERROR) << "Shared memory mapping failed."; + return SharedMemory(); + } + + // Return success. + return sharedMemory; +} + +unsigned EvsUltrasonicsArray::increaseAvailableFrames_Locked(unsigned numToAdd) { + unsigned added = 0; + + while (added < numToAdd) { + SharedMemory sharedMemory = allocateAndMapSharedMemory(); + + // If allocate and map fails, break. + if (!sharedMemory.IsValid()) { + break; + } + + // Find a place to store the new buffer + bool stored = false; + for (auto&& dataFrame : mDataFrames) { + if (!dataFrame.sharedMemory.IsValid()) { + // Use this existing entry + dataFrame.sharedMemory = sharedMemory; + dataFrame.inUse = false; + stored = true; + break; + } + } + + if (!stored) { + // Add a BufferRecord wrapping this handle to our set of available buffers + mDataFrames.emplace_back(sharedMemory); + } + + mFramesAllowed++; + added++; + } + + return added; +} + +unsigned EvsUltrasonicsArray::decreaseAvailableFrames_Locked(unsigned numToRemove) { + unsigned removed = 0; + + for (auto&& dataFrame : mDataFrames) { + // Is this record not in use, but holding a buffer that we can free? + if (!dataFrame.inUse && dataFrame.sharedMemory.IsValid()) { + // Release buffer and update the record so we can recognize it as "empty" + dataFrame.sharedMemory.clear(); + + mFramesAllowed--; + removed++; + + if (removed == numToRemove) { + break; + } + } + } + + return removed; +} + +// This is the asynchronous data frame generation thread that runs in parallel with the +// main serving thread. There is one for each active ultrasonic array instance. +void EvsUltrasonicsArray::generateDataFrames() { + LOG(DEBUG) << "Data frame generation loop started"; + + unsigned idx = 0; + + while (true) { + bool timeForFrame = false; + + nsecs_t startTime = elapsedRealtimeNano(); + + // Lock scope for updating shared state + { + std::lock_guard lock(mAccessLock); + + if (mStreamState != RUNNING) { + // Break out of our main thread loop + break; + } + + // Are we allowed to issue another buffer? + if (mFramesInUse >= mFramesAllowed) { + // Can't do anything right now -- skip this frame + LOG(WARNING) << "Skipped a frame because too many are in flight"; + } else { + // Identify an available buffer to fill + for (idx = 0; idx < mDataFrames.size(); idx++) { + if (!mDataFrames[idx].inUse && mDataFrames[idx].sharedMemory.IsValid()) { + // Found an available record, so stop looking + break; + } + } + if (idx >= mDataFrames.size()) { + // This shouldn't happen since we already checked mFramesInUse vs mFramesAllowed + LOG(ERROR) << "Failed to find an available buffer slot"; + } else { + // We're going to make the frame busy + mDataFrames[idx].inUse = true; + mFramesInUse++; + timeForFrame = true; + } + } + } + + if (timeForFrame) { + // Assemble the buffer description we'll transmit below + UltrasonicsDataFrameDesc dummyDataFrameDesc; + dummyDataFrameDesc.dataFrameId = idx; + dummyDataFrameDesc.waveformsData = mDataFrames[idx].sharedMemory.hidlMemory; + + // Fill dummy waveform data. + fillDummyDataFrame(dummyDataFrameDesc, mDataFrames[idx].sharedMemory.pIMemory); + + // Issue the (asynchronous) callback to the client -- can't be holding the lock + auto result = mStream->deliverDataFrame(dummyDataFrameDesc); + if (result.isOk()) { + LOG(DEBUG) << "Delivered data frame id: " << dummyDataFrameDesc.dataFrameId; + } else { + // This can happen if the client dies and is likely unrecoverable. + // To avoid consuming resources generating failing calls, we stop sending + // frames. Note, however, that the stream remains in the "STREAMING" state + // until cleaned up on the main thread. + LOG(ERROR) << "Frame delivery call failed in the transport layer."; + + // Since we didn't actually deliver it, mark the frame as available + std::lock_guard lock(mAccessLock); + mDataFrames[idx].inUse = false; + mFramesInUse--; + + break; + } + } + + // Sleep to generate frames at kTargetFrameRate. + static const nsecs_t kTargetFrameTimeUs = 1000 * 1000 / kTargetFrameRate; + const nsecs_t now = elapsedRealtimeNano(); + const nsecs_t workTimeUs = (now - startTime) / 1000; + const nsecs_t sleepDurationUs = kTargetFrameTimeUs - workTimeUs; + if (sleepDurationUs > 0) { + usleep(sleepDurationUs); + } + } + + // If we've been asked to stop, send an event to signal the actual end of stream + EvsEventDesc event; + event.aType = EvsEventType::STREAM_STOPPED; + auto result = mStream->notify(event); + if (!result.isOk()) { + LOG(ERROR) << "Error delivering end of stream marker"; + } +} + +} // namespace implementation +} // namespace V1_1 +} // namespace evs +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/evs/1.1/default/EvsUltrasonicsArray.h b/automotive/evs/1.1/default/EvsUltrasonicsArray.h new file mode 100644 index 0000000000..7a4101290e --- /dev/null +++ b/automotive/evs/1.1/default/EvsUltrasonicsArray.h @@ -0,0 +1,134 @@ +/* + * Copyright 2020 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_AUTOMOTIVE_EVS_V1_1_EVSULTRASONICSARRAY_H +#define ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSULTRASONICSARRAY_H + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +using ::android::hardware::hidl_memory; +using ::android::hardware::automotive::evs::V1_0::EvsResult; +using ::android::hardware::automotive::evs::V1_1::IEvsUltrasonicsArray; +using ::android::hardware::automotive::evs::V1_1::IEvsUltrasonicsArrayStream; +using ::android::hardware::automotive::evs::V1_1::UltrasonicsArrayDesc; +using ::android::hardware::automotive::evs::V1_1::UltrasonicsDataFrameDesc; +using ::android::hidl::allocator::V1_0::IAllocator; +using ::android::hidl::memory::V1_0::IMemory; + +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace V1_1 { +namespace implementation { + +class EvsUltrasonicsArray : public IEvsUltrasonicsArray { + public: + // Methods from ::android::hardware::automotive::evs::V1_1::IEvsUltrasonicsArray follow. + Return getUltrasonicArrayInfo(getUltrasonicArrayInfo_cb _get_info_cb) override; + Return setMaxFramesInFlight(uint32_t bufferCount) override; + Return doneWithDataFrame(const UltrasonicsDataFrameDesc& dataFrameDesc) override; + Return startStream(const ::android::sp& stream) override; + Return stopStream() override; + + // Factory function to create a array. + static sp Create(const char* deviceName); + + // Returns a ultrasonics array descriptor filled with sample data. + static UltrasonicsArrayDesc GetDummyArrayDesc(const char* id); + + DISALLOW_COPY_AND_ASSIGN(EvsUltrasonicsArray); + virtual ~EvsUltrasonicsArray() override; + void forceShutdown(); // This gets called if another caller "steals" ownership + + private: + // Structure holding the hidl memory struct and the interface to a shared memory. + struct SharedMemory { + hidl_memory hidlMemory; + sp pIMemory; + + SharedMemory() : hidlMemory(hidl_memory()), pIMemory(nullptr){}; + + SharedMemory(hidl_memory hidlMem, sp pIMem) + : hidlMemory(hidlMem), pIMemory(pIMem) {} + + bool IsValid() { return (pIMemory.get() != nullptr && hidlMemory.valid()); } + + void clear() { + hidlMemory = hidl_memory(); + pIMemory.clear(); + } + }; + + // Struct for a data frame record. + struct DataFrameRecord { + SharedMemory sharedMemory; + bool inUse; + explicit DataFrameRecord(SharedMemory shMem) : sharedMemory(shMem), inUse(false){}; + }; + + enum StreamStateValues { + STOPPED, + RUNNING, + STOPPING, + DEAD, + }; + + EvsUltrasonicsArray(const char* deviceName); + + // 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); + + void generateDataFrames(); + + SharedMemory allocateAndMapSharedMemory(); + + UltrasonicsArrayDesc mArrayDesc = {}; // The properties of this ultrasonic array. + + std::thread mCaptureThread; // The thread we'll use to synthesize frames + + sp mStream = nullptr; // The callback used to deliver each frame + + sp mShmemAllocator = nullptr; // Shared memory allocator. + + std::mutex mAccessLock; + std::vector mDataFrames GUARDED_BY(mAccessLock); // Shared memory buffers. + unsigned mFramesAllowed GUARDED_BY(mAccessLock); // How many buffers are we currently using. + unsigned mFramesInUse GUARDED_BY(mAccessLock); // How many buffers are currently outstanding. + + StreamStateValues mStreamState GUARDED_BY(mAccessLock); +}; + +} // namespace implementation +} // namespace V1_1 +} // namespace evs +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_AUTOMOTIVE_EVS_V1_1_EVSULTRASONICSARRAY_H diff --git a/wifi/supplicant/1.0/vts/functional/VtsHalWifiSupplicantV1_0TargetTest.cpp b/automotive/evs/1.1/default/ServiceNames.h similarity index 78% rename from wifi/supplicant/1.0/vts/functional/VtsHalWifiSupplicantV1_0TargetTest.cpp rename to automotive/evs/1.1/default/ServiceNames.h index f582cc1ff6..84b1697d0f 100644 --- a/wifi/supplicant/1.0/vts/functional/VtsHalWifiSupplicantV1_0TargetTest.cpp +++ b/automotive/evs/1.1/default/ServiceNames.h @@ -14,8 +14,4 @@ * limitations under the License. */ -#include "supplicant_hidl_test_utils.h" - -// TODO(b/143892896): Remove this file after wifi_hidl_test_utils.cpp is -// updated. -WifiSupplicantHidlEnvironment* gEnv = nullptr; +const static char kEnumeratorServiceName[] = "hw/0"; diff --git a/automotive/evs/1.1/default/android.hardware.automotive.evs@1.1-service.rc b/automotive/evs/1.1/default/android.hardware.automotive.evs@1.1-service.rc new file mode 100644 index 0000000000..284b3fda4e --- /dev/null +++ b/automotive/evs/1.1/default/android.hardware.automotive.evs@1.1-service.rc @@ -0,0 +1,5 @@ +service vendor.evs-hal-mock /vendor/bin/hw/android.hardware.automotive.evs@1.1-service + class hal + user automotive_evs + group automotive_evs + disabled diff --git a/automotive/evs/1.1/default/manifest_android.hardware.automotive.evs@1.1-service.xml b/automotive/evs/1.1/default/manifest_android.hardware.automotive.evs@1.1-service.xml new file mode 100644 index 0000000000..d4d9b17c05 --- /dev/null +++ b/automotive/evs/1.1/default/manifest_android.hardware.automotive.evs@1.1-service.xml @@ -0,0 +1,26 @@ + + + + + android.hardware.automotive.evs + hwbinder + 1.1 + + IEvsEnumerator + hw/0 + + + 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..a79e7c2b2d --- /dev/null +++ b/automotive/evs/1.1/default/resources/evs_default_configuration.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/automotive/evs/1.1/default/service.cpp b/automotive/evs/1.1/default/service.cpp new file mode 100644 index 0000000000..374b646812 --- /dev/null +++ b/automotive/evs/1.1/default/service.cpp @@ -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. + */ + +#define LOG_TAG "android.hardware.automotive.evs@1.1-service" + +#include + +#include +#include +#include +#include + +#include "ServiceNames.h" +#include "EvsEnumerator.h" +#include "EvsDisplay.h" + + +// libhidl: +using android::hardware::configureRpcThreadpool; +using android::hardware::joinRpcThreadpool; + +// Generated HIDL files +using android::hardware::automotive::evs::V1_1::IEvsEnumerator; + +// The namespace in which all our implementation code lives +using namespace android::hardware::automotive::evs::V1_1::implementation; +using namespace android; + + +int main() { + ALOGI("EVS Hardware Enumerator service is starting"); + android::sp service = new EvsEnumerator(); + + configureRpcThreadpool(1, true /* callerWillJoin */); + + // Register our service -- if somebody is already registered by our name, + // they will be killed (their thread pool will throw an exception). + status_t status = service->registerAsService(kEnumeratorServiceName); + if (status == OK) { + ALOGD("%s is ready.", kEnumeratorServiceName); + joinRpcThreadpool(); + } else { + ALOGE("Could not register service %s (%d).", kEnumeratorServiceName, status); + } + + // In normal operation, we don't expect the thread pool to exit + ALOGE("EVS Hardware Enumerator is shutting down"); + return 1; +} diff --git a/automotive/evs/1.1/types.hal b/automotive/evs/1.1/types.hal new file mode 100644 index 0000000000..e699fd0d1b --- /dev/null +++ b/automotive/evs/1.1/types.hal @@ -0,0 +1,377 @@ +/* + * 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 @1.0::CameraDesc; +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 + * + * In addition to the handle to the graphics memory, we need to retain + * the properties of the buffer for easy reference and reconstruction of + * an ANativeWindowBuffer object on the remote side of API calls. + * (Not least because OpenGL expect an ANativeWindowBuffer* for us as a + * texture via eglCreateImageKHR(). + */ +struct BufferDesc { + /** + * HIDL counterpart of `AHardwareBuffer_Desc`. Please see + * hardware/interfaces/graphics/common/1.2/types.hal for more details. + */ + HardwareBuffer buffer; + /** + * The size of a pixel in the units of bytes + */ + uint32_t pixelSize; + /** + * Opaque value from driver + */ + uint32_t bufferId; + /** + * Unique identifier of the physical camera device that produces this buffer. + */ + string deviceId; + /** + * Time that this buffer is being filled. + */ + int64_t timestamp; + + /** + * Frame metadata. This is opaque to EVS manager. + */ + vec metadata; +}; + +/** + * Types of informative streaming events + */ +enum EvsEventType : uint32_t { + /** + * Video stream is started + */ + STREAM_STARTED = 0, + /** + * Video stream is stopped + */ + STREAM_STOPPED, + /** + * Video frame is dropped + */ + FRAME_DROPPED, + /** + * Timeout happens + */ + TIMEOUT, + /** + * Camera parameter is changed; payload contains a changed parameter ID and + * its value + */ + PARAMETER_CHANGED, + /** + * Master role has become available + */ + MASTER_RELEASED, + /** + * Any other erroneous streaming events + */ + STREAM_ERROR, +}; + +/** + * Structure that describes informative events occurred during EVS is streaming + */ +struct EvsEventDesc { + /** + * Type of an informative event + */ + EvsEventType aType; + /** + * Device identifier + */ + string deviceId; + /** + * Possible additional information + */ + uint32_t[4] payload; +}; + +/** + * EVS Camera Parameter + */ +enum CameraParam : uint32_t { + /** + * The brightness of image frames + */ + BRIGHTNESS, + /** + * The contrast of image frames + */ + CONTRAST, + /** + * Automatic gain/exposure control + */ + AUTOGAIN, + /** + * Gain control + */ + GAIN, + /** + * Automatic Whitebalance + */ + AUTO_WHITE_BALANCE, + /** + * Manual white balance setting as a color temperature in Kelvin. + */ + WHITE_BALANCE_TEMPERATURE, + /** + * Image sharpness adjustment + */ + SHARPNESS, + /** + * Auto Exposure Control modes; auto, manual, shutter priority, or + * aperture priority. + */ + AUTO_EXPOSURE, + /** + * Manual exposure time of the camera + */ + ABSOLUTE_EXPOSURE, + /** + * Set the focal point of the camera to the specified position. This + * parameter may not be effective when auto focus is enabled. + */ + ABSOLUTE_FOCUS, + /** + * Enables continuous automatic focus adjustments. + */ + AUTO_FOCUS, + /** + * Specify the objective lens focal length as an absolute value. + */ + ABSOLUTE_ZOOM, +}; + +/** + * Structure identifies and describes an ultrasonics array in the car. + * + * A ultrasonics array represents a group of ultrasonic sensors within the + * car. These may be sensors that are physically connected to the same hardware + * control unit or represent a logical group of sensors like front and back. + * The HAL is responsible for filling out this structure for each Ultrasonics + * Array. + */ +struct UltrasonicsArrayDesc { + /** + * Unique identifier for the ultrasonic array. This may be a path or name of the + * physical control device or a string identifying a logical group of sensors forming an array + * such as "front_array" and "back_array". + */ + string ultrasonicsArrayId; + + /** + * Maximum number of readings (points on waveform) provided per sensor in + * each data frame. Used by client to pre-allocate required memory buffer for + * incoming data. + */ + uint32_t maxReadingsPerSensorCount; + + /** + * Maximum number of receiver sensors in a data frame. Must be between 1 + * and sensorCount. Used by client to pre-allocate required memory buffer for + * incoming data. + */ + uint32_t maxReceiversCount; + + /** + * The order of sensors specified should preferably be in clockwise order + * around the car, starting from front left-most sensor. + */ + vec sensors; +}; + +/** + * Structure for rotation expressed as quaternions. + * Convention used: Unit quaternion with hamilton convention. + */ +struct RotationQuat { + float x; + float y; + float z; + float w; +}; + +/** Structure for translation with x, y and z units. */ +struct Translation { + float x; + float y; + float z; +}; + +/** + * Provides the orientation and location of a car sensor relative to the android automotive + * coordinate system: + * https://source.android.com/devices/sensors/sensor-types#auto_axes + * The sensor pose defines the transformation to be applied to the android automotive axes to + * obtain the sensor local axes. + * The pose consists of rotation, (specified as a quaternions) and translation + * (vector with x, y, z). + * This rotation and translation applied to the sensor data in the sensor's local coordinate + * system transform the data to the automotive coordinate system. + * i.e Pcar = ( Rot * Psensor ) + Trans + * Here Pcar is a point in automotive coordinate system and Psensor is a point in the sensor's + * coordinate system. + * Example: + * For a sensor on the front bumper and on the left corner of the car with its X axis pointing to + * the front, the sensor is located at (-2, 4, 0) meters w.r.t android automotive axes and the + * sensor local axes has a rotation of 90 degrees counter-clockwise w.r.t android automotive axes + * when viewing the car from top on the +Z axis side: + * + * ↑X sensor + * Y←∘______ + * | | front + * | car | + * | ↑Y | + * | ∘→X | rear + * |______| + * + * For this example the rotation and translation will be: + * Rotation = + 90 degrees around Z axis = (0.7071, 0, 0, 0.7071) as a unit quaternion. + * Translation = (-2, 4, 0) in meters = (-2000, 4000, 0) in milli-meters. + * Note: Every sensor type must specify its own pose. + */ +struct SensorPose { + /** + * Rotation part of the sensor pose, expressed as a unit quaternion. + */ + RotationQuat rotation; + + /** + * Translation part of the sensor pose, in (x, y, z) format with milli-meter units. + */ + Translation translation; +}; + +/** + * Structure that contains all information of an ultrasonic sensor. + */ +struct UltrasonicSensor { + /** + * Pose provides the orientation and location of the ultrasonic sensor within the car. + * The +Y axis points along the center of the beam spread the X axis to the right and the Z + * axis in the up direction. + */ + SensorPose pose; + + /** + * Maximum range of the sensor in milli-metres. + */ + float maxRange; + + /** + * Half-angle of the angle of measurement of the sensor, relative to the + * sensor’s x axis, in radians. + */ + float angleOfMeasurement; +}; + +/** + * Structure that describes the data frame received from an ultrasonics array. + * + * Each data frame returned consists of received waveform signals from a subset + * of sensors in an array as indicated by the receiversIdList. The signal is + * transmitted at a particular time instant indicated by timestampNs from a + * subset of sensors in the array as provided in the transmittersIdList. + */ +struct UltrasonicsDataFrameDesc { + /** + * Timestamp of the start of the transmit signal for this data frame. + * Timestamp unit is nanoseconds and is obtained from android elapsed realtime clock which is + * the time since system was booted and includes deep sleep. + * timeOfFlight readings are future-deltas to this timestamp. + */ + uint64_t timestampNs; + + /** + * Identifier of data frame. Used by implementation for managing multiple frames in flight. + */ + uint32_t dataFrameId; + + /** + * List of indexes of sensors in range [0, sensorCount - 1] that + * transmitted the signal for this data frame. + */ + vec transmittersIdList; + + /** + * List of indexes of sensors in range [0, sensorCount - 1] that received + * the signal. The order of ids must match the order of the waveforms in the + * waveformsData. + * Size of list is upper bound by maxReceiversCount. + */ + vec receiversIdList; + + /** + * List of the number of readings corresponding to each ultrasonics sensor in + * the receiversIdList. Order of the readings count must match the order in + * receiversIdList. + * Size of list is upper bound by maxReadingsPerSensorCount. + */ + vec receiversReadingsCountList; + + /** + * Shared memory object containing the waveforms data. Contains one waveform + * for each sensor specified in receiversIdList, in order. + * Each waveform is represented by a number of readings, which are sample + * points on the waveform. The number of readings for each waveform is as + * specified in the receiversReadingsCountList. + * Each reading is a pair of time Of flight and resonance. + * Time of flight (float): Time between transmit and receive signal in nanoseconds. + * Resonance (float): Resonance at time on waveform in range [0.0, 1.0]. + * + * The structure of shared memory (example with 2 waveforms, each with 2 readings): + * + * Byte: | 0 | 1-4 | 5-8 | 9-12 | 13-16 || 17 | 18-21 | 22-25 | 26-29 | 30-33 | + * Data: | RecId1 | TOF1 | RES1 | TOF2 | RES2 || RecId2 | TOF1 | RES1 | TOF2 | RES2 | + * | Waveform1 || Waveform2 | + * Here: + * RecId : Receiver's Id. Order matches the receiversIdList, type uint8_t + * TOF : Time of flight, type float (4 bytes) + * RES : Resonance, type float (4 bytes) + * Note: All readings and waveforms are contigious with no padding. + */ + memory waveformsData; +}; diff --git a/automotive/evs/1.1/vts/functional/Android.bp b/automotive/evs/1.1/vts/functional/Android.bp new file mode 100644 index 0000000000..086a199ca5 --- /dev/null +++ b/automotive/evs/1.1/vts/functional/Android.bp @@ -0,0 +1,46 @@ +// +// 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: "VtsHalEvsV1_1TargetTest", + srcs: [ + "FrameHandler.cpp", + "FrameHandlerUltrasonics.cpp", + "VtsHalEvsV1_1TargetTest.cpp", + ], + defaults: ["VtsHalTargetTestDefaults"], + shared_libs: [ + "libui", + "libcamera_metadata", + "libhidlmemory", + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + ], + static_libs: [ + "android.hardware.automotive.evs@1.0", + "android.hardware.automotive.evs@1.1", + "android.hardware.automotive.evs@common-default-lib", + "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: ["vts-core"], + cflags: [ + "-O0", + "-g", + ], +} diff --git a/automotive/evs/1.1/vts/functional/FrameHandler.cpp b/automotive/evs/1.1/vts/functional/FrameHandler.cpp new file mode 100644 index 0000000000..ebf488acb3 --- /dev/null +++ b/automotive/evs/1.1/vts/functional/FrameHandler.cpp @@ -0,0 +1,402 @@ +/* + * 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 "VtsHalEvsTest" + +#include "FrameHandler.h" +#include "FormatConvert.h" + +#include +#include +#include + +#include +#include +#include + +using namespace std::chrono_literals; + +FrameHandler::FrameHandler(android::sp pCamera, CameraDesc cameraInfo, + android::sp pDisplay, + BufferControlFlag mode) : + mCamera(pCamera), + mCameraInfo(cameraInfo), + mDisplay(pDisplay), + mReturnMode(mode) { + // Nothing but member initialization here... +} + + +void FrameHandler::shutdown() +{ + // Make sure we're not still streaming + blockingStopStream(); + + // At this point, the receiver thread is no longer running, so we can safely drop + // our remote object references so they can be freed + mCamera = nullptr; + mDisplay = nullptr; +} + + +bool FrameHandler::startStream() { + // Tell the camera to start streaming + Return result = mCamera->startVideoStream(this); + if (result != EvsResult::OK) { + return false; + } + + // Mark ourselves as running + mLock.lock(); + mRunning = true; + mLock.unlock(); + + return true; +} + + +void FrameHandler::asyncStopStream() { + // Tell the camera to stop streaming. + // This will result in a null frame being delivered when the stream actually stops. + mCamera->stopVideoStream(); +} + + +void FrameHandler::blockingStopStream() { + // Tell the stream to stop + asyncStopStream(); + + // Wait until the stream has actually stopped + std::unique_lock lock(mEventLock); + if (mRunning) { + mEventSignal.wait(lock, [this]() { return !mRunning; }); + } +} + + +bool FrameHandler::returnHeldBuffer() { + std::lock_guard lock(mLock); + + // Return the oldest buffer we're holding + if (mHeldBuffers.empty()) { + // No buffers are currently held + return false; + } + + hidl_vec buffers = mHeldBuffers.front(); + mHeldBuffers.pop(); + mCamera->doneWithFrame_1_1(buffers); + + return true; +} + + +bool FrameHandler::isRunning() { + std::lock_guard lock(mLock); + return mRunning; +} + + +void FrameHandler::waitForFrameCount(unsigned frameCount) { + // Wait until we've seen at least the requested number of frames (could be more) + std::unique_lock lock(mLock); + mFrameSignal.wait(lock, [this, frameCount](){ + return mFramesReceived >= frameCount; + }); +} + + +void FrameHandler::getFramesCounters(unsigned* received, unsigned* displayed) { + std::lock_guard lock(mLock); + + if (received) { + *received = mFramesReceived; + } + if (displayed) { + *displayed = mFramesDisplayed; + } +} + + +Return FrameHandler::deliverFrame(const BufferDesc_1_0& bufferArg) { + ALOGW("A frame delivered via v1.0 method is rejected."); + mCamera->doneWithFrame(bufferArg); + return Void(); +} + + +Return FrameHandler::deliverFrame_1_1(const hidl_vec& buffers) { + mLock.lock(); + // For VTS tests, FrameHandler uses a single frame among delivered frames. + auto bufferIdx = mFramesDisplayed % buffers.size(); + auto buffer = buffers[bufferIdx]; + mLock.unlock(); + + const AHardwareBuffer_Desc* pDesc = + reinterpret_cast(&buffer.buffer.description); + ALOGD("Received a frame from the camera (%p)", + buffer.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. + bool displayed = false; + 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 { + // Copy the contents of the of buffer.memHandle into tgtBuffer + copyBufferContents(tgtBuffer, buffer); + + // Send the target buffer back for display + Return 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 + displayed = true; + } + } + } + + mLock.lock(); + // increases counters + ++mFramesReceived; + mFramesDisplayed += (int)displayed; + mLock.unlock(); + mFrameSignal.notify_all(); + + switch (mReturnMode) { + case eAutoReturn: + // Send the camera buffer back now that the client has seen it + ALOGD("Calling doneWithFrame"); + mCamera->doneWithFrame_1_1(buffers); + break; + case eNoAutoReturn: + // Hang onto the buffer handles for now -- the client will return it explicitly later + mHeldBuffers.push(buffers); + break; + } + + ALOGD("Frame handling complete"); + + return Void(); +} + + +Return FrameHandler::notify(const EvsEventDesc& event) { + // Local flag we use to keep track of when the stream is stopping + std::unique_lock lock(mEventLock); + mLatestEventDesc.aType = event.aType; + mLatestEventDesc.payload[0] = event.payload[0]; + mLatestEventDesc.payload[1] = event.payload[1]; + 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)); + } + lock.unlock(); + mEventSignal.notify_one(); + + return Void(); +} + + +bool FrameHandler::copyBufferContents(const BufferDesc_1_0& tgtBuffer, + const BufferDesc_1_1& srcBuffer) { + bool success = true; + const AHardwareBuffer_Desc* pSrcDesc = + reinterpret_cast(&srcBuffer.buffer.description); + + // Make sure we don't run off the end of either buffer + const unsigned width = std::min(tgtBuffer.width, + pSrcDesc->width); + const unsigned height = std::min(tgtBuffer.height, + pSrcDesc->height); + + sp tgt = new android::GraphicBuffer(tgtBuffer.memHandle, + android::GraphicBuffer::CLONE_HANDLE, + tgtBuffer.width, + tgtBuffer.height, + tgtBuffer.format, + 1, + tgtBuffer.usage, + tgtBuffer.stride); + sp src = new android::GraphicBuffer(srcBuffer.buffer.nativeHandle, + android::GraphicBuffer::CLONE_HANDLE, + pSrcDesc->width, + pSrcDesc->height, + pSrcDesc->format, + pSrcDesc->layers, + pSrcDesc->usage, + pSrcDesc->stride); + + // Lock our source buffer for reading (current expectation are for this to be NV21 format) + uint8_t* srcPixels = nullptr; + src->lock(GRALLOC_USAGE_SW_READ_OFTEN, (void**)&srcPixels); + + // Lock our target buffer for writing (should be either RGBA8888 or BGRA8888 format) + uint32_t* tgtPixels = nullptr; + tgt->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)&tgtPixels); + + if (srcPixels && tgtPixels) { + using namespace ::android::hardware::automotive::evs::common; + if (tgtBuffer.format == HAL_PIXEL_FORMAT_RGBA_8888) { + if (pSrcDesc->format == HAL_PIXEL_FORMAT_YCRCB_420_SP) { // 420SP == NV21 + Utils::copyNV21toRGB32(width, height, + srcPixels, + tgtPixels, tgtBuffer.stride); + } else if (pSrcDesc->format == HAL_PIXEL_FORMAT_YV12) { // YUV_420P == YV12 + Utils::copyYV12toRGB32(width, height, + srcPixels, + tgtPixels, tgtBuffer.stride); + } else if (pSrcDesc->format == HAL_PIXEL_FORMAT_YCBCR_422_I) { // YUYV + Utils::copyYUYVtoRGB32(width, height, + srcPixels, pSrcDesc->stride, + tgtPixels, tgtBuffer.stride); + } else if (pSrcDesc->format == tgtBuffer.format) { // 32bit RGBA + Utils::copyMatchedInterleavedFormats(width, height, + srcPixels, pSrcDesc->stride, + tgtPixels, tgtBuffer.stride, + tgtBuffer.pixelSize); + } else { + ALOGE("Camera buffer format is not supported"); + success = false; + } + } else if (tgtBuffer.format == HAL_PIXEL_FORMAT_BGRA_8888) { + if (pSrcDesc->format == HAL_PIXEL_FORMAT_YCRCB_420_SP) { // 420SP == NV21 + Utils::copyNV21toBGR32(width, height, + srcPixels, + tgtPixels, tgtBuffer.stride); + } else if (pSrcDesc->format == HAL_PIXEL_FORMAT_YV12) { // YUV_420P == YV12 + Utils::copyYV12toBGR32(width, height, + srcPixels, + tgtPixels, tgtBuffer.stride); + } else if (pSrcDesc->format == HAL_PIXEL_FORMAT_YCBCR_422_I) { // YUYV + Utils::copyYUYVtoBGR32(width, height, + srcPixels, pSrcDesc->stride, + tgtPixels, tgtBuffer.stride); + } else if (pSrcDesc->format == tgtBuffer.format) { // 32bit RGBA + Utils::copyMatchedInterleavedFormats(width, height, + srcPixels, pSrcDesc->stride, + tgtPixels, tgtBuffer.stride, + tgtBuffer.pixelSize); + } else { + ALOGE("Camera buffer format is not supported"); + success = false; + } + } else { + // We always expect 32 bit RGB for the display output for now. Is there a need for 565? + ALOGE("Diplay buffer is always expected to be 32bit RGBA"); + success = false; + } + } else { + ALOGE("Failed to lock buffer contents for contents transfer"); + success = false; + } + + if (srcPixels) { + src->unlock(); + } + if (tgtPixels) { + tgt->unlock(); + } + + return success; +} + +void FrameHandler::getFrameDimension(unsigned* width, unsigned* height) { + if (width) { + *width = mFrameWidth; + } + + if (height) { + *height = mFrameHeight; + } +} + +bool FrameHandler::waitForEvent(const EvsEventDesc& aTargetEvent, + EvsEventDesc& aReceivedEvent, + bool ignorePayload) { + // Wait until we get an expected parameter change event. + std::unique_lock lock(mEventLock); + auto now = std::chrono::system_clock::now(); + bool found = false; + while (!found) { + bool result = mEventSignal.wait_until(lock, now + 5s, + [this, aTargetEvent, ignorePayload, &aReceivedEvent, &found](){ + found = (mLatestEventDesc.aType == aTargetEvent.aType) && + (ignorePayload || (mLatestEventDesc.payload[0] == aTargetEvent.payload[0] && + mLatestEventDesc.payload[1] == aTargetEvent.payload[1])); + + aReceivedEvent.aType = mLatestEventDesc.aType; + aReceivedEvent.payload[0] = mLatestEventDesc.payload[0]; + aReceivedEvent.payload[1] = mLatestEventDesc.payload[1]; + return found; + } + ); + + if (!result) { + ALOGW("A timer is expired before a target event has happened."); + break; + } + } + + return found; +} + +const char *FrameHandler::eventToString(const EvsEventType aType) { + switch (aType) { + case EvsEventType::STREAM_STARTED: + return "STREAM_STARTED"; + case EvsEventType::STREAM_STOPPED: + return "STREAM_STOPPED"; + case EvsEventType::FRAME_DROPPED: + return "FRAME_DROPPED"; + case EvsEventType::TIMEOUT: + return "TIMEOUT"; + case EvsEventType::PARAMETER_CHANGED: + return "PARAMETER_CHANGED"; + 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 new file mode 100644 index 0000000000..21e85fe0c5 --- /dev/null +++ b/automotive/evs/1.1/vts/functional/FrameHandler.h @@ -0,0 +1,118 @@ +/* + * 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 EVS_VTS_FRAMEHANDLER_H +#define EVS_VTS_FRAMEHANDLER_H + +#include + +#include + +#include +#include +#include + +using namespace ::android::hardware::automotive::evs::V1_1; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::hidl_vec; +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 BufferDesc_1_0 = ::android::hardware::automotive::evs::V1_0::BufferDesc; +using BufferDesc_1_1 = ::android::hardware::automotive::evs::V1_1::BufferDesc; + + +/* + * FrameHandler: + * This class can be used to receive camera imagery from an IEvsCamera implementation. Given an + * IEvsDisplay instance at startup, it will forward the received imagery to the display, + * providing a trivial implementation of a rear vew camera type application. + * Note that the video frames are delivered on a background thread, while the control interface + * is actuated from the applications foreground thread. + */ +class FrameHandler : public IEvsCameraStream { +public: + enum BufferControlFlag { + eAutoReturn, + eNoAutoReturn, + }; + + FrameHandler(android::sp pCamera, CameraDesc cameraInfo, + android::sp pDisplay = nullptr, + BufferControlFlag mode = eAutoReturn); + virtual ~FrameHandler() { + if (mCamera != nullptr) { + /* shutdown a camera explicitly */ + shutdown(); + } + } + + void shutdown(); + + bool startStream(); + void asyncStopStream(); + void blockingStopStream(); + + bool returnHeldBuffer(); + + bool isRunning(); + + void waitForFrameCount(unsigned frameCount); + bool waitForEvent(const EvsEventDesc& aTargetEvent, + EvsEventDesc& aReceivedEvent, + bool ignorePayload = false); + void getFramesCounters(unsigned* received, unsigned* displayed); + void getFrameDimension(unsigned* width, unsigned* height); + +private: + // Implementation for ::android::hardware::automotive::evs::V1_0::IEvsCameraStream + Return deliverFrame(const BufferDesc_1_0& buffer) override; + + // Implementation for ::android::hardware::automotive::evs::V1_1::IEvsCameraStream + Return deliverFrame_1_1(const hidl_vec& buffer) override; + Return notify(const EvsEventDesc& event) override; + + // Local implementation details + bool copyBufferContents(const BufferDesc_1_0& tgtBuffer, const BufferDesc_1_1& srcBuffer); + const char *eventToString(const EvsEventType aType); + + // Values initialized as startup + android::sp mCamera; + CameraDesc mCameraInfo; + android::sp mDisplay; + BufferControlFlag mReturnMode; + + // Since we get frames delivered to us asynchronously via the IEvsCameraStream interface, + // we need to protect all member variables that may be modified while we're streaming + // (ie: those below) + std::mutex mLock; + std::mutex mEventLock; + std::condition_variable mEventSignal; + std::condition_variable mFrameSignal; + std::queue> mHeldBuffers; + + bool mRunning = false; + unsigned mFramesReceived = 0; // Simple counter -- rolls over eventually! + unsigned mFramesDisplayed = 0; // Simple counter -- rolls over eventually! + unsigned mFrameWidth = 0; + unsigned mFrameHeight = 0; + EvsEventDesc mLatestEventDesc; +}; + + +#endif //EVS_VTS_FRAMEHANDLER_H diff --git a/automotive/evs/1.1/vts/functional/FrameHandlerUltrasonics.cpp b/automotive/evs/1.1/vts/functional/FrameHandlerUltrasonics.cpp new file mode 100644 index 0000000000..22522ced67 --- /dev/null +++ b/automotive/evs/1.1/vts/functional/FrameHandlerUltrasonics.cpp @@ -0,0 +1,169 @@ +/* + * Copyright 2020 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 "FrameHandlerUltrasonics.h" + +#include +#include +#include + +using ::android::hidl::memory::V1_0::IMemory; +using ::android::hardware::Return; +using ::android::sp; + +using ::android::hardware::automotive::evs::V1_1::IEvsUltrasonicsArrayStream; +using ::android::hardware::automotive::evs::V1_1::IEvsUltrasonicsArray; +using ::android::hardware::automotive::evs::V1_1::UltrasonicsDataFrameDesc; +using ::android::hardware::automotive::evs::V1_1::EvsEventDesc; +using ::android::hardware::automotive::evs::V1_1::EvsEventType; + +FrameHandlerUltrasonics::FrameHandlerUltrasonics(sp pEvsUltrasonicsArray) : + mEvsUltrasonicsArray(pEvsUltrasonicsArray), mReceiveFramesCount(0) { + // Nothing but member initialization +} + +Return FrameHandlerUltrasonics::notify(const EvsEventDesc& evsEvent) { + switch (evsEvent.aType) { + case EvsEventType::STREAM_STARTED: + case EvsEventType::STREAM_STOPPED: + case EvsEventType::FRAME_DROPPED: + case EvsEventType::TIMEOUT: + mReceivedEvents.emplace_back(evsEvent); + break; + default: + LOG(ERROR) << "Received unexpected event"; + } + + return android::hardware::Void(); +} + +// Struct used by SerializeWaveformData(). +struct WaveformData { + uint8_t receiverId; + std::vector> readings; +}; + +// De-serializes shared memory to vector of WaveformData. +// TODO(b/149950362): Add a common library for serialiazing and deserializing waveform data. +std::vector DeSerializeWaveformData(std::vector recvReadingsCountList, + uint8_t* pData) { + std::vector waveformDataList(recvReadingsCountList.size()); + + for (int i = 0; i < waveformDataList.size(); i++) { + // Set Id + memcpy(&waveformDataList[i].receiverId, pData, sizeof(uint8_t)); + pData += sizeof(uint8_t); + + waveformDataList[i].readings.resize(recvReadingsCountList[i]); + + for (auto& reading : waveformDataList[i].readings) { + // Set the time of flight. + memcpy(&reading.first, pData, sizeof(float)); + pData += sizeof(float); + + // Set the resonance. + memcpy(&reading.second, pData, sizeof(float)); + pData += sizeof(float); + } + } + return waveformDataList; +} + +bool DataFrameValidator(const UltrasonicsDataFrameDesc& dataFrameDesc) { + + if (dataFrameDesc.receiversIdList.size() != dataFrameDesc.receiversReadingsCountList.size()) { + LOG(ERROR) << "Size mismatch of receiversIdList and receiversReadingsCountList"; + return false; + } + + if(!dataFrameDesc.waveformsData.valid()) { + LOG(ERROR) << "Data frame does not valid hidl memory"; + return false; + } + + // Check total bytes from dataFrameDesc are within the shared memory size. + int totalWaveformDataBytesSize = 0; + for (int i = 0; i < dataFrameDesc.receiversIdList.size(); i++) { + totalWaveformDataBytesSize = 1 + (4 * 2 * dataFrameDesc.receiversReadingsCountList[i]); + } + if (totalWaveformDataBytesSize > dataFrameDesc.waveformsData.size()) { + LOG(ERROR) << "Total waveform data bytes in desc exceed shared memory size"; + return false; + } + + sp pIMemory = mapMemory(dataFrameDesc.waveformsData); + if(pIMemory.get() == nullptr) { + LOG(ERROR) << "Failed to map hidl memory"; + return false; + } + + uint8_t* pData = (uint8_t*)((void*)pIMemory->getPointer()); + if(pData == nullptr) { + LOG(ERROR) << "Failed getPointer from mapped shared memory"; + return false; + } + + const std::vector waveformDataList = DeSerializeWaveformData( + dataFrameDesc.receiversReadingsCountList, pData); + + // Verify the waveforms data. + for(int i = 0; i < waveformDataList.size(); i++) { + if (waveformDataList[i].receiverId != dataFrameDesc.receiversIdList[i]) { + LOG(ERROR) << "Receiver Id mismatch"; + return false; + } + for(auto& reading : waveformDataList[i].readings) { + if (reading.second < 0.0f || reading.second > 1.0f) { + LOG(ERROR) << "Resonance reading is not in range [0, 1]"; + return false; + } + } + } + return true; +} + +Return FrameHandlerUltrasonics::deliverDataFrame( + const UltrasonicsDataFrameDesc& dataFrameDesc) { + LOG(DEBUG) << "FrameHandlerUltrasonics::receiveFrames"; + + mReceiveFramesCount++; + mLastReceivedFrames = dataFrameDesc; + + if(!DataFrameValidator(dataFrameDesc)) { + mAllFramesValid = false; + } + + // Send done with data frame. + mEvsUltrasonicsArray->doneWithDataFrame(dataFrameDesc); + + return android::hardware::Void(); +} + +bool FrameHandlerUltrasonics::checkEventReceived(EvsEventDesc evsEvent) { + LOG(DEBUG) << "FrameHandlerUltrasonics::checkEventReceived"; + int size = mReceivedEvents.size(); // work around + LOG(DEBUG) << "Received event number: " << size; + auto iter = find(mReceivedEvents.begin(), mReceivedEvents.end(), evsEvent); + return iter != mReceivedEvents.end(); +} + +int FrameHandlerUltrasonics::getReceiveFramesCount() { + return mReceiveFramesCount; +} + +bool FrameHandlerUltrasonics::areAllFramesValid() { + return mAllFramesValid; +} diff --git a/automotive/evs/1.1/vts/functional/FrameHandlerUltrasonics.h b/automotive/evs/1.1/vts/functional/FrameHandlerUltrasonics.h new file mode 100644 index 0000000000..1fc2143605 --- /dev/null +++ b/automotive/evs/1.1/vts/functional/FrameHandlerUltrasonics.h @@ -0,0 +1,53 @@ +/* + * Copyright 2020 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 FRAME_HANDLER_ULTRASONICS_H +#define FRAME_HANDLER_ULTRASONICS_H + +#include +#include +#include + +#include + +class FrameHandlerUltrasonics : public + android::hardware::automotive::evs::V1_1::IEvsUltrasonicsArrayStream { +public: + FrameHandlerUltrasonics( + android::sp + pEvsUltrasonicsArray); + + // Implementation for ::android::hardware::automotive::evs::V1_1::IEvsUltrasonicsArrayStream + android::hardware::Return notify( + const android::hardware::automotive::evs::V1_1::EvsEventDesc& evsEvent) override; + android::hardware::Return deliverDataFrame( + const android::hardware::automotive::evs::V1_1::UltrasonicsDataFrameDesc& + dataFrameDesc) override; + + bool checkEventReceived(android::hardware::automotive::evs::V1_1::EvsEventDesc evsEvent); + int getReceiveFramesCount(); + bool areAllFramesValid(); + +private: + android::sp + mEvsUltrasonicsArray; + android::hardware::automotive::evs::V1_1::UltrasonicsDataFrameDesc mLastReceivedFrames; + std::vector mReceivedEvents; + int mReceiveFramesCount; + bool mAllFramesValid = true; +}; + +#endif //FRAME_HANDLER_ULTRASONICS_H diff --git a/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp b/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp new file mode 100644 index 0000000000..8b68fd6f0a --- /dev/null +++ b/automotive/evs/1.1/vts/functional/VtsHalEvsV1_1TargetTest.cpp @@ -0,0 +1,2480 @@ +/* + * 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 "VtsHalEvsTest" + + +// These values are called out in the EVS design doc (as of Mar 8, 2017) +static const int kMaxStreamStartMilliseconds = 500; +static const int kMinimumFramesPerSecond = 10; + +static const int kSecondsToMilliseconds = 1000; +static const int kMillisecondsToMicroseconds = 1000; +static const float kNanoToMilliseconds = 0.000001f; +static const float kNanoToSeconds = 0.000000001f; + + +#include "FrameHandler.h" +#include "FrameHandlerUltrasonics.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace ::android::hardware::automotive::evs::V1_1; +using namespace std::chrono_literals; + +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::hidl_vec; +using ::android::hardware::hidl_handle; +using ::android::hardware::hidl_string; +using ::android::sp; +using ::android::wp; +using ::android::hardware::camera::device::V3_2::Stream; +using ::android::hardware::automotive::evs::V1_1::BufferDesc; +using ::android::hardware::automotive::evs::V1_0::DisplayDesc; +using ::android::hardware::automotive::evs::V1_0::DisplayState; +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; +using IEvsDisplay_1_0 = ::android::hardware::automotive::evs::V1_0::IEvsDisplay; +using IEvsDisplay_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsDisplay; + +/* + * 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; + + +// The main test class for EVS +class EvsHidlTest : public ::testing::TestWithParam { +public: + virtual void SetUp() override { + // Make sure we can connect to the enumerator + std::string service_name = GetParam(); + pEnumerator = IEvsEnumerator::getService(service_name); + ASSERT_NE(pEnumerator.get(), nullptr); + LOG(INFO) << "Test target service: " << service_name; + + mIsHwModule = pEnumerator->isHardware(); + } + + virtual void TearDown() override { + // Attempt to close any active camera + for (auto &&cam : activeCameras) { + if (cam != nullptr) { + pEnumerator->closeCamera(cam); + } + } + activeCameras.clear(); + } + +protected: + void loadCameraList() { + // SetUp() must run first! + assert(pEnumerator != nullptr); + + // Get the camera list + pEnumerator->getCameraList_1_1( + [this](hidl_vec cameraList) { + LOG(INFO) << "Camera list callback received " + << cameraList.size() + << " cameras"; + cameraInfo.reserve(cameraList.size()); + for (auto&& cam: cameraList) { + LOG(INFO) << "Found camera " << cam.v1.cameraId; + cameraInfo.push_back(cam); + } + } + ); + } + + void loadUltrasonicsArrayList() { + // SetUp() must run first! + assert(pEnumerator != nullptr); + + // Get the ultrasonics array list + pEnumerator->getUltrasonicsArrayList([this](hidl_vec ultraList) { + LOG(INFO) << "Ultrasonics array list callback received " + << ultraList.size() + << " arrays"; + ultrasonicsArraysInfo.reserve(ultraList.size()); + for (auto&& ultraArray : ultraList) { + LOG(INFO) << "Found ultrasonics array " << ultraArray.ultrasonicsArrayId; + ultrasonicsArraysInfo.push_back(ultraArray); + } + }); + } + + bool isLogicalCamera(const camera_metadata_t *metadata) { + if (metadata == nullptr) { + // A logical camera device must have a valid camera metadata. + return false; + } + + // Looking for LOGICAL_MULTI_CAMERA capability from metadata. + camera_metadata_ro_entry_t entry; + int rc = find_camera_metadata_ro_entry(metadata, + ANDROID_REQUEST_AVAILABLE_CAPABILITIES, + &entry); + if (0 != rc) { + // No capabilities are found. + return false; + } + + for (size_t i = 0; i < entry.count; ++i) { + uint8_t cap = entry.data.u8[i]; + if (cap == ANDROID_REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA) { + return true; + } + } + + return false; + } + + std::unordered_set getPhysicalCameraIds(const std::string& id, + bool& flag) { + std::unordered_set physicalCameras; + + auto it = cameraInfo.begin(); + while (it != cameraInfo.end()) { + if (it->v1.cameraId == id) { + break; + } + ++it; + } + + if (it == cameraInfo.end()) { + // Unknown camera is requested. Return an empty list. + return physicalCameras; + } + + const camera_metadata_t *metadata = + reinterpret_cast(&it->metadata[0]); + flag = isLogicalCamera(metadata); + if (!flag) { + // EVS assumes that the device w/o a valid metadata is a physical + // device. + LOG(INFO) << id << " is not a logical camera device."; + physicalCameras.emplace(id); + return physicalCameras; + } + + // Look for physical camera identifiers + camera_metadata_ro_entry entry; + int rc = find_camera_metadata_ro_entry(metadata, + ANDROID_LOGICAL_MULTI_CAMERA_PHYSICAL_IDS, + &entry); + if (rc != 0) { + LOG(ERROR) << "No physical camera ID is found for a logical camera device"; + } + + const uint8_t *ids = entry.data.u8; + size_t start = 0; + for (size_t i = 0; i < entry.count; ++i) { + if (ids[i] == '\0') { + if (start != i) { + std::string id(reinterpret_cast(ids + start)); + physicalCameras.emplace(id); + } + start = i + 1; + } + } + + LOG(INFO) << id + << " consists of " + << physicalCameras.size() + << " physical camera devices"; + return physicalCameras; + } + + + sp pEnumerator; // Every test needs access to the service + std::vector cameraInfo; // Empty unless/until loadCameraList() is called + bool mIsHwModule; // boolean to tell current module under testing + // is HW module implementation. + std::deque> activeCameras; // A list of active camera handles that are + // needed to be cleaned up. + std::vector + ultrasonicsArraysInfo; // Empty unless/until + // loadUltrasonicsArrayList() is called + std::deque> activeUltrasonicsArrays; // A list of active ultrasonic array + // handles that are to be cleaned up. +}; + + +// Test cases, their implementations, and corresponding requirements are +// documented at go/aae-evs-public-api-test. + +/* + * CameraOpenClean: + * Opens each camera reported by the enumerator and then explicitly closes it via a + * call to closeCamera. Then repeats the test to ensure all cameras can be reopened. + */ +TEST_P(EvsHidlTest, CameraOpenClean) { + LOG(INFO) << "Starting CameraOpenClean test"; + + // 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) { + bool isLogicalCam = false; + auto devices = getPhysicalCameraIds(cam.v1.cameraId, isLogicalCam); + if (mIsHwModule && isLogicalCam) { + LOG(INFO) << "Skip a logical device, " << cam.v1.cameraId << " for HW target."; + continue; + } + + for (int pass = 0; pass < 2; pass++) { + sp pCam = pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg); + ASSERT_NE(pCam, nullptr); + + for (auto&& devName : devices) { + bool matched = false; + pCam->getPhysicalCameraInfo(devName, + [&devName, &matched](const CameraDesc& info) { + matched = devName == info.v1.cameraId; + }); + ASSERT_TRUE(matched); + } + + // Store a camera handle for a clean-up + activeCameras.push_back(pCam); + + // Verify that this camera self-identifies correctly + pCam->getCameraInfo_1_1([&cam](CameraDesc desc) { + LOG(DEBUG) << "Found camera " << desc.v1.cameraId; + EXPECT_EQ(cam.v1.cameraId, desc.v1.cameraId); + } + ); + + // Verify methods for extended info + const auto id = 0xFFFFFFFF; // meaningless id + hidl_vec values; + auto err = pCam->setExtendedInfo_1_1(id, values); + ASSERT_NE(EvsResult::INVALID_ARG, err); + + pCam->getExtendedInfo_1_1(id, [](const auto& result, const auto& data) { + ASSERT_NE(EvsResult::INVALID_ARG, result); + ASSERT_EQ(0, data.size()); + }); + + // Explicitly close the camera so resources are released right away + pEnumerator->closeCamera(pCam); + activeCameras.clear(); + } + } +} + + +/* + * CameraOpenAggressive: + * Opens each camera reported by the enumerator twice in a row without an intervening closeCamera + * call. This ensures that the intended "aggressive open" behavior works. This is necessary for + * the system to be tolerant of shutdown/restart race conditions. + */ +TEST_P(EvsHidlTest, CameraOpenAggressive) { + LOG(INFO) << "Starting CameraOpenAggressive test"; + + // 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) { + bool isLogicalCam = false; + getPhysicalCameraIds(cam.v1.cameraId, isLogicalCam); + if (mIsHwModule && isLogicalCam) { + LOG(INFO) << "Skip a logical device, " << cam.v1.cameraId << " for HW target."; + continue; + } + + activeCameras.clear(); + sp pCam = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) + .withDefault(nullptr); + ASSERT_NE(pCam, nullptr); + + // Store a camera handle for a clean-up + activeCameras.push_back(pCam); + + // Verify that this camera self-identifies correctly + pCam->getCameraInfo_1_1([&cam](CameraDesc desc) { + LOG(DEBUG) << "Found camera " << desc.v1.cameraId; + EXPECT_EQ(cam.v1.cameraId, desc.v1.cameraId); + } + ); + + sp pCam2 = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) + .withDefault(nullptr); + ASSERT_NE(pCam2, nullptr); + + // Store a camera handle for a clean-up + activeCameras.push_back(pCam2); + + ASSERT_NE(pCam, pCam2); + + Return result = pCam->setMaxFramesInFlight(2); + if (mIsHwModule) { + // Verify that the old camera rejects calls via HW module. + EXPECT_EQ(EvsResult::OWNERSHIP_LOST, EvsResult(result)); + } else { + // default implementation supports multiple clients. + EXPECT_EQ(EvsResult::OK, EvsResult(result)); + } + + // Close the superceded camera + pEnumerator->closeCamera(pCam); + activeCameras.pop_front(); + + // Verify that the second camera instance self-identifies correctly + pCam2->getCameraInfo_1_1([&cam](CameraDesc desc) { + LOG(DEBUG) << "Found camera " << desc.v1.cameraId; + EXPECT_EQ(cam.v1.cameraId, desc.v1.cameraId); + } + ); + + // Close the second camera instance + pEnumerator->closeCamera(pCam2); + activeCameras.pop_front(); + } + + // Sleep here to ensure the destructor cleanup has time to run so we don't break follow on tests + sleep(1); // I hate that this is an arbitrary time to wait. :( b/36122635 +} + + +/* + * CameraStreamPerformance: + * Measure and qualify the stream start up time and streaming frame rate of each reported camera + */ +TEST_P(EvsHidlTest, CameraStreamPerformance) { + LOG(INFO) << "Starting CameraStreamPerformance test"; + + // 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) { + bool isLogicalCam = false; + auto devices = getPhysicalCameraIds(cam.v1.cameraId, isLogicalCam); + if (mIsHwModule && isLogicalCam) { + LOG(INFO) << "Skip a logical device " << cam.v1.cameraId; + continue; + } + + sp pCam = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) + .withDefault(nullptr); + ASSERT_NE(pCam, nullptr); + + // Store a camera handle for a clean-up + activeCameras.push_back(pCam); + + // Set up a frame receiver object which will fire up its own thread + sp frameHandler = new FrameHandler(pCam, cam, + nullptr, + FrameHandler::eAutoReturn); + + // Start the camera's video stream + nsecs_t start = systemTime(SYSTEM_TIME_MONOTONIC); + + bool startResult = frameHandler->startStream(); + ASSERT_TRUE(startResult); + + // Ensure the first frame arrived within the expected time + frameHandler->waitForFrameCount(1); + nsecs_t firstFrame = systemTime(SYSTEM_TIME_MONOTONIC); + nsecs_t timeToFirstFrame = systemTime(SYSTEM_TIME_MONOTONIC) - start; + + // Extra delays are expected when we attempt to start a video stream on + // the logical camera device. The amount of delay is expected the + // number of physical camera devices multiplied by + // kMaxStreamStartMilliseconds at most. + EXPECT_LE(nanoseconds_to_milliseconds(timeToFirstFrame), + kMaxStreamStartMilliseconds * devices.size()); + printf("%s: Measured time to first frame %0.2f ms\n", + cam.v1.cameraId.c_str(), timeToFirstFrame * kNanoToMilliseconds); + LOG(INFO) << cam.v1.cameraId + << ": Measured time to first frame " + << std::scientific << timeToFirstFrame * kNanoToMilliseconds + << " ms."; + + // Check aspect ratio + unsigned width = 0, height = 0; + frameHandler->getFrameDimension(&width, &height); + EXPECT_GE(width, height); + + // Wait a bit, then ensure we get at least the required minimum number of frames + sleep(5); + nsecs_t end = systemTime(SYSTEM_TIME_MONOTONIC); + + // Even when the camera pointer goes out of scope, the FrameHandler object will + // keep the stream alive unless we tell it to shutdown. + // Also note that the FrameHandle and the Camera have a mutual circular reference, so + // we have to break that cycle in order for either of them to get cleaned up. + frameHandler->shutdown(); + + unsigned framesReceived = 0; + frameHandler->getFramesCounters(&framesReceived, nullptr); + framesReceived = framesReceived - 1; // Back out the first frame we already waited for + nsecs_t runTime = end - firstFrame; + float framesPerSecond = framesReceived / (runTime * kNanoToSeconds); + printf("Measured camera rate %3.2f fps\n", framesPerSecond); + LOG(INFO) << "Measured camera rate " + << std::scientific << framesPerSecond + << " fps."; + EXPECT_GE(framesPerSecond, kMinimumFramesPerSecond); + + // Explicitly release the camera + pEnumerator->closeCamera(pCam); + activeCameras.clear(); + } +} + + +/* + * CameraStreamBuffering: + * Ensure the camera implementation behaves properly when the client holds onto buffers for more + * than one frame time. The camera must cleanly skip frames until the client is ready again. + */ +TEST_P(EvsHidlTest, CameraStreamBuffering) { + LOG(INFO) << "Starting CameraStreamBuffering test"; + + // Arbitrary constant (should be > 1 and less than crazy) + static const unsigned int kBuffersToHold = 6; + + // 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) { + bool isLogicalCam = false; + getPhysicalCameraIds(cam.v1.cameraId, isLogicalCam); + if (mIsHwModule && isLogicalCam) { + LOG(INFO) << "Skip a logical device " << cam.v1.cameraId << " for HW target."; + continue; + } + + sp pCam = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) + .withDefault(nullptr); + ASSERT_NE(pCam, nullptr); + + // Store a camera handle for a clean-up + activeCameras.push_back(pCam); + + // Ask for a crazy number of buffers in flight to ensure it errors correctly + Return badResult = pCam->setMaxFramesInFlight(0xFFFFFFFF); + EXPECT_EQ(EvsResult::BUFFER_NOT_AVAILABLE, badResult); + + // Now ask for exactly two buffers in flight as we'll test behavior in that case + Return goodResult = pCam->setMaxFramesInFlight(kBuffersToHold); + EXPECT_EQ(EvsResult::OK, goodResult); + + + // Set up a frame receiver object which will fire up its own thread. + sp frameHandler = new FrameHandler(pCam, cam, + nullptr, + FrameHandler::eNoAutoReturn); + + // Start the camera's video stream + bool startResult = frameHandler->startStream(); + ASSERT_TRUE(startResult); + + // Check that the video stream stalls once we've gotten exactly the number of buffers + // we requested since we told the frameHandler not to return them. + sleep(1); // 1 second should be enough for at least 5 frames to be delivered worst case + unsigned framesReceived = 0; + frameHandler->getFramesCounters(&framesReceived, nullptr); + ASSERT_EQ(kBuffersToHold, framesReceived) << "Stream didn't stall at expected buffer limit"; + + + // Give back one buffer + bool didReturnBuffer = frameHandler->returnHeldBuffer(); + EXPECT_TRUE(didReturnBuffer); + + // Once we return a buffer, it shouldn't take more than 1/10 second to get a new one + // filled since we require 10fps minimum -- but give a 10% allowance just in case. + usleep(110 * kMillisecondsToMicroseconds); + frameHandler->getFramesCounters(&framesReceived, nullptr); + EXPECT_EQ(kBuffersToHold+1, framesReceived) << "Stream should've resumed"; + + // Even when the camera pointer goes out of scope, the FrameHandler object will + // keep the stream alive unless we tell it to shutdown. + // Also note that the FrameHandle and the Camera have a mutual circular reference, so + // we have to break that cycle in order for either of them to get cleaned up. + frameHandler->shutdown(); + + // Explicitly release the camera + pEnumerator->closeCamera(pCam); + activeCameras.clear(); + } +} + + +/* + * CameraToDisplayRoundTrip: + * End to end test of data flowing from the camera to the display. Each delivered frame of camera + * imagery is simply copied to the display buffer and presented on screen. This is the one test + * which a human could observe to see the operation of the system on the physical display. + */ +TEST_P(EvsHidlTest, CameraToDisplayRoundTrip) { + LOG(INFO) << "Starting CameraToDisplayRoundTrip test"; + + // Get the camera list + loadCameraList(); + + // Using null stream configuration makes EVS uses the default resolution and + // output format. + Stream nullCfg = {}; + + // Request available display IDs + uint8_t targetDisplayId = 0; + pEnumerator->getDisplayIdList([&targetDisplayId](auto ids) { + ASSERT_GT(ids.size(), 0); + targetDisplayId = ids[0]; + }); + + // Request exclusive access to the first EVS display + sp pDisplay = pEnumerator->openDisplay_1_1(targetDisplayId); + ASSERT_NE(pDisplay, nullptr); + LOG(INFO) << "Display " << targetDisplayId << " is alreay in use."; + + // Get the display descriptor + pDisplay->getDisplayInfo_1_1([](const auto& config, const auto& state) { + android::DisplayConfig* pConfig = (android::DisplayConfig*)config.data(); + const auto width = pConfig->resolution.getWidth(); + const auto height = pConfig->resolution.getHeight(); + LOG(INFO) << " Resolution: " << width << "x" << height; + ASSERT_GT(width, 0); + ASSERT_GT(height, 0); + + android::ui::DisplayState* pState = (android::ui::DisplayState*)state.data(); + ASSERT_NE(pState->layerStack, -1); + }); + + // Test each reported camera + for (auto&& cam: cameraInfo) { + bool isLogicalCam = false; + getPhysicalCameraIds(cam.v1.cameraId, isLogicalCam); + if (mIsHwModule && isLogicalCam) { + LOG(INFO) << "Skip a logical device " << cam.v1.cameraId << " for HW target."; + continue; + } + + sp pCam = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) + .withDefault(nullptr); + ASSERT_NE(pCam, nullptr); + + // Store a camera handle for a clean-up + activeCameras.push_back(pCam); + + // Set up a frame receiver object which will fire up its own thread. + sp 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); + activeCameras.clear(); + } + + // Explicitly release the display + pEnumerator->closeDisplay(pDisplay); +} + + +/* + * MultiCameraStream: + * Verify that each client can start and stop video streams on the same + * underlying camera. + */ +TEST_P(EvsHidlTest, MultiCameraStream) { + LOG(INFO) << "Starting MultiCameraStream 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 = {}; + + // Test each reported camera + for (auto&& cam: cameraInfo) { + // Create two camera clients. + sp pCam0 = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) + .withDefault(nullptr); + ASSERT_NE(pCam0, nullptr); + + // Store a camera handle for a clean-up + activeCameras.push_back(pCam0); + + sp pCam1 = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) + .withDefault(nullptr); + ASSERT_NE(pCam1, nullptr); + + // Store a camera handle for a clean-up + activeCameras.push_back(pCam1); + + // Set up per-client frame receiver objects which will fire up its own thread + sp frameHandler0 = new FrameHandler(pCam0, cam, + nullptr, + FrameHandler::eAutoReturn); + ASSERT_NE(frameHandler0, nullptr); + + sp 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); + LOG(INFO) << "Measured camera rate " + << std::scientific << framesPerSecond0 << " fps and " + << framesPerSecond1 << " fps"; + 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); + activeCameras.clear(); + + // TODO(b/145459970, b/145457727): below sleep() is added to ensure the + // destruction of active camera objects; this may be related with two + // issues. + sleep(1); + } +} + + +/* + * CameraParameter: + * Verify that a client can adjust a camera parameter. + */ +TEST_P(EvsHidlTest, CameraParameter) { + LOG(INFO) << "Starting CameraParameter test"; + + // 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 result = EvsResult::OK; + for (auto&& cam: cameraInfo) { + bool isLogicalCam = false; + getPhysicalCameraIds(cam.v1.cameraId, isLogicalCam); + if (isLogicalCam) { + // TODO(b/145465724): Support camera parameter programming on + // logical devices. + LOG(INFO) << "Skip a logical device " << cam.v1.cameraId; + continue; + } + + // Create a camera client + sp pCam = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) + .withDefault(nullptr); + ASSERT_NE(pCam, nullptr); + + // Store a camera + activeCameras.push_back(pCam); + + // Get the parameter list + std::vector cmds; + pCam->getParameterList([&cmds](hidl_vec 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 = new FrameHandler(pCam, cam, + nullptr, + FrameHandler::eAutoReturn); + ASSERT_NE(frameHandler, nullptr); + + // Start the camera's video stream + bool startResult = frameHandler->startStream(); + ASSERT_TRUE(startResult); + + // Ensure the stream starts + frameHandler->waitForFrameCount(1); + + result = pCam->setMaster(); + 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; + } + ); + + EvsResult result = EvsResult::OK; + if (cmd == CameraParam::ABSOLUTE_FOCUS) { + // Try to turn off auto-focus + std::vector values; + pCam->setIntParameter(CameraParam::AUTO_FOCUS, 0, + [&result, &values](auto status, auto effectiveValues) { + result = status; + if (status == EvsResult::OK) { + for (auto &&v : effectiveValues) { + values.push_back(v); + } + } + }); + ASSERT_EQ(EvsResult::OK, result); + for (auto &&v : values) { + ASSERT_EQ(v, 0); + } + } + + // Try to program a parameter with a random value [minVal, maxVal] + int32_t val0 = minVal + (std::rand() % (maxVal - minVal)); + std::vector values; + + // Rounding down + val0 = val0 - (val0 % step); + pCam->setIntParameter(cmd, val0, + [&result, &values](auto status, auto effectiveValues) { + result = status; + if (status == EvsResult::OK) { + for (auto &&v : effectiveValues) { + values.push_back(v); + } + } + }); + + ASSERT_EQ(EvsResult::OK, result); + + values.clear(); + pCam->getIntParameter(cmd, + [&result, &values](auto status, auto readValues) { + result = status; + if (status == EvsResult::OK) { + for (auto &&v : readValues) { + values.push_back(v); + } + } + }); + ASSERT_EQ(EvsResult::OK, result); + for (auto &&v : values) { + ASSERT_EQ(val0, v) << "Values are not matched."; + } + } + + result = pCam->unsetMaster(); + ASSERT_EQ(EvsResult::OK, result); + + // Shutdown + frameHandler->shutdown(); + + // Explicitly release the camera + pEnumerator->closeCamera(pCam); + activeCameras.clear(); + } +} + + +/* + * CameraMasterRelease + * Verify that non-master client gets notified when the master client either + * terminates or releases a role. + */ +TEST_P(EvsHidlTest, CameraMasterRelease) { + LOG(INFO) << "Starting CameraMasterRelease 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 = {}; + + // Test each reported camera + for (auto&& cam: cameraInfo) { + bool isLogicalCam = false; + getPhysicalCameraIds(cam.v1.cameraId, isLogicalCam); + if (isLogicalCam) { + // TODO(b/145465724): Support camera parameter programming on + // logical devices. + LOG(INFO) << "Skip a logical device " << cam.v1.cameraId; + continue; + } + + // Create two camera clients. + sp pCamMaster = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) + .withDefault(nullptr); + ASSERT_NE(pCamMaster, nullptr); + + // Store a camera handle for a clean-up + activeCameras.push_back(pCamMaster); + + sp pCamNonMaster = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) + .withDefault(nullptr); + ASSERT_NE(pCamNonMaster, nullptr); + + // Store a camera handle for a clean-up + activeCameras.push_back(pCamNonMaster); + + // Set up per-client frame receiver objects which will fire up its own thread + sp frameHandlerMaster = + new FrameHandler(pCamMaster, cam, + nullptr, + FrameHandler::eAutoReturn); + ASSERT_NE(frameHandlerMaster, nullptr); + sp frameHandlerNonMaster = + new FrameHandler(pCamNonMaster, cam, + nullptr, + FrameHandler::eAutoReturn); + ASSERT_NE(frameHandlerNonMaster, nullptr); + + // Set one client as the master + EvsResult result = pCamMaster->setMaster(); + ASSERT_TRUE(result == EvsResult::OK); + + // Try to set another client as the master. + result = pCamNonMaster->setMaster(); + ASSERT_TRUE(result == EvsResult::OWNERSHIP_LOST); + + // Start the camera's video stream via a master client. + bool startResult = frameHandlerMaster->startStream(); + ASSERT_TRUE(startResult); + + // Ensure the stream starts + frameHandlerMaster->waitForFrameCount(1); + + // Start the camera's video stream via another client + startResult = frameHandlerNonMaster->startStream(); + ASSERT_TRUE(startResult); + + // Ensure the stream starts + frameHandlerNonMaster->waitForFrameCount(1); + + // Non-master client expects to receive a master role relesed + // notification. + EvsEventDesc aTargetEvent = {}; + EvsEventDesc aNotification = {}; + + bool listening = false; + std::mutex eventLock; + std::condition_variable eventCond; + std::thread listener = std::thread( + [&aNotification, &frameHandlerNonMaster, &listening, &eventCond]() { + // Notify that a listening thread is running. + listening = true; + eventCond.notify_all(); + + EvsEventDesc aTargetEvent; + aTargetEvent.aType = EvsEventType::MASTER_RELEASED; + if (!frameHandlerNonMaster->waitForEvent(aTargetEvent, aNotification, true)) { + LOG(WARNING) << "A timer is expired before a target event is fired."; + } + + } + ); + + // Wait until a listening thread starts. + std::unique_lock lock(eventLock); + auto timer = std::chrono::system_clock::now(); + while (!listening) { + timer += 1s; + eventCond.wait_until(lock, timer); + } + lock.unlock(); + + // Release a master role. + pCamMaster->unsetMaster(); + + // Join a listening thread. + if (listener.joinable()) { + listener.join(); + } + + // Verify change notifications. + ASSERT_EQ(EvsEventType::MASTER_RELEASED, + static_cast(aNotification.aType)); + + // Non-master becomes a master. + result = pCamNonMaster->setMaster(); + ASSERT_TRUE(result == EvsResult::OK); + + // Previous master client fails to become a master. + result = pCamMaster->setMaster(); + ASSERT_TRUE(result == EvsResult::OWNERSHIP_LOST); + + listening = false; + listener = std::thread( + [&aNotification, &frameHandlerMaster, &listening, &eventCond]() { + // Notify that a listening thread is running. + listening = true; + eventCond.notify_all(); + + EvsEventDesc aTargetEvent; + aTargetEvent.aType = EvsEventType::MASTER_RELEASED; + if (!frameHandlerMaster->waitForEvent(aTargetEvent, aNotification, true)) { + LOG(WARNING) << "A timer is expired before a target event is fired."; + } + + } + ); + + // Wait until a listening thread starts. + timer = std::chrono::system_clock::now(); + lock.lock(); + while (!listening) { + eventCond.wait_until(lock, timer + 1s); + } + lock.unlock(); + + // Closing current master client. + frameHandlerNonMaster->shutdown(); + + // Join a listening thread. + if (listener.joinable()) { + listener.join(); + } + + // Verify change notifications. + ASSERT_EQ(EvsEventType::MASTER_RELEASED, + static_cast(aNotification.aType)); + + // Closing streams. + frameHandlerMaster->shutdown(); + + // Explicitly release the camera + pEnumerator->closeCamera(pCamMaster); + pEnumerator->closeCamera(pCamNonMaster); + activeCameras.clear(); + } +} + + +/* + * MultiCameraParameter: + * Verify that master and non-master clients behave as expected when they try to adjust + * camera parameters. + */ +TEST_P(EvsHidlTest, MultiCameraParameter) { + LOG(INFO) << "Starting MultiCameraParameter 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 = {}; + + // Test each reported camera + for (auto&& cam: cameraInfo) { + bool isLogicalCam = false; + getPhysicalCameraIds(cam.v1.cameraId, isLogicalCam); + if (isLogicalCam) { + // TODO(b/145465724): Support camera parameter programming on + // logical devices. + LOG(INFO) << "Skip a logical device " << cam.v1.cameraId; + continue; + } + + // Create two camera clients. + sp pCamMaster = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) + .withDefault(nullptr); + ASSERT_NE(pCamMaster, nullptr); + + // Store a camera handle for a clean-up + activeCameras.push_back(pCamMaster); + + sp pCamNonMaster = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) + .withDefault(nullptr); + ASSERT_NE(pCamNonMaster, nullptr); + + // Store a camera handle for a clean-up + activeCameras.push_back(pCamNonMaster); + + // Get the parameter list + std::vector camMasterCmds, camNonMasterCmds; + pCamMaster->getParameterList([&camMasterCmds](hidl_vec cmdList) { + camMasterCmds.reserve(cmdList.size()); + for (auto &&cmd : cmdList) { + camMasterCmds.push_back(cmd); + } + } + ); + + pCamNonMaster->getParameterList([&camNonMasterCmds](hidl_vec 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 frameHandlerMaster = + new FrameHandler(pCamMaster, cam, + nullptr, + FrameHandler::eAutoReturn); + ASSERT_NE(frameHandlerMaster, nullptr); + sp frameHandlerNonMaster = + new FrameHandler(pCamNonMaster, cam, + nullptr, + FrameHandler::eAutoReturn); + ASSERT_NE(frameHandlerNonMaster, nullptr); + + // Set one client as the master + EvsResult result = pCamMaster->setMaster(); + ASSERT_EQ(EvsResult::OK, result); + + // Try to set another client as the master. + result = pCamNonMaster->setMaster(); + ASSERT_EQ(EvsResult::OWNERSHIP_LOST, result); + + // Start the camera's video stream via a master client. + bool startResult = frameHandlerMaster->startStream(); + ASSERT_TRUE(startResult); + + // Ensure the stream starts + frameHandlerMaster->waitForFrameCount(1); + + // Start the camera's video stream via another client + startResult = frameHandlerNonMaster->startStream(); + ASSERT_TRUE(startResult); + + // Ensure the stream starts + frameHandlerNonMaster->waitForFrameCount(1); + + int32_t val0 = 0; + std::vector values; + EvsEventDesc aNotification0 = {}; + EvsEventDesc aNotification1 = {}; + 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; + } + ); + + EvsResult result = EvsResult::OK; + if (cmd == CameraParam::ABSOLUTE_FOCUS) { + // Try to turn off auto-focus + values.clear(); + pCamMaster->setIntParameter(CameraParam::AUTO_FOCUS, 0, + [&result, &values](auto status, auto effectiveValues) { + result = status; + if (status == EvsResult::OK) { + for (auto &&v : effectiveValues) { + values.push_back(v); + } + } + }); + ASSERT_EQ(EvsResult::OK, result); + for (auto &&v : values) { + ASSERT_EQ(v, 0); + } + } + + // Calculate a parameter value to program. + val0 = minVal + (std::rand() % (maxVal - minVal)); + val0 = val0 - (val0 % step); + + // Prepare and start event listeners. + bool listening0 = false; + bool listening1 = false; + std::condition_variable eventCond; + std::thread listener0 = std::thread( + [cmd, val0, + &aNotification0, &frameHandlerMaster, &listening0, &listening1, &eventCond]() { + listening0 = true; + if (listening1) { + eventCond.notify_all(); + } + + EvsEventDesc aTargetEvent; + aTargetEvent.aType = EvsEventType::PARAMETER_CHANGED; + aTargetEvent.payload[0] = static_cast(cmd); + aTargetEvent.payload[1] = val0; + if (!frameHandlerMaster->waitForEvent(aTargetEvent, aNotification0)) { + LOG(WARNING) << "A timer is expired before a target event is fired."; + } + } + ); + std::thread listener1 = std::thread( + [cmd, val0, + &aNotification1, &frameHandlerNonMaster, &listening0, &listening1, &eventCond]() { + listening1 = true; + if (listening0) { + eventCond.notify_all(); + } + + EvsEventDesc aTargetEvent; + aTargetEvent.aType = EvsEventType::PARAMETER_CHANGED; + aTargetEvent.payload[0] = static_cast(cmd); + aTargetEvent.payload[1] = val0; + if (!frameHandlerNonMaster->waitForEvent(aTargetEvent, aNotification1)) { + LOG(WARNING) << "A timer is expired before a target event is fired."; + } + } + ); + + // Wait until a listening thread starts. + std::mutex eventLock; + std::unique_lock lock(eventLock); + auto timer = std::chrono::system_clock::now(); + while (!listening0 || !listening1) { + eventCond.wait_until(lock, timer + 1s); + } + lock.unlock(); + + // Try to program a parameter + values.clear(); + pCamMaster->setIntParameter(cmd, val0, + [&result, &values](auto status, auto effectiveValues) { + result = status; + if (status == EvsResult::OK) { + for (auto &&v : effectiveValues) { + values.push_back(v); + } + } + }); + + ASSERT_EQ(EvsResult::OK, result); + for (auto &&v : values) { + ASSERT_EQ(val0, v) << "Values are not matched."; + } + + // Join a listening thread. + if (listener0.joinable()) { + listener0.join(); + } + if (listener1.joinable()) { + listener1.join(); + } + + // Verify a change notification + ASSERT_EQ(EvsEventType::PARAMETER_CHANGED, + static_cast(aNotification0.aType)); + ASSERT_EQ(EvsEventType::PARAMETER_CHANGED, + static_cast(aNotification1.aType)); + ASSERT_EQ(cmd, + static_cast(aNotification0.payload[0])); + ASSERT_EQ(cmd, + static_cast(aNotification1.payload[0])); + for (auto &&v : values) { + ASSERT_EQ(v, + static_cast(aNotification0.payload[1])); + ASSERT_EQ(v, + static_cast(aNotification1.payload[1])); + } + + // Clients expects to receive a parameter change notification + // whenever a master client adjusts it. + values.clear(); + pCamMaster->getIntParameter(cmd, + [&result, &values](auto status, auto readValues) { + result = status; + if (status == EvsResult::OK) { + for (auto &&v : readValues) { + values.push_back(v); + } + } + }); + ASSERT_EQ(EvsResult::OK, result); + for (auto &&v : values) { + ASSERT_EQ(val0, v) << "Values are not matched."; + } + } + + // Try to adjust a parameter via non-master client + values.clear(); + pCamNonMaster->setIntParameter(camNonMasterCmds[0], val0, + [&result, &values](auto status, auto effectiveValues) { + result = status; + if (status == EvsResult::OK) { + for (auto &&v : effectiveValues) { + values.push_back(v); + } + } + }); + ASSERT_EQ(EvsResult::INVALID_ARG, result); + + // Non-master client attemps to be a master + result = pCamNonMaster->setMaster(); + ASSERT_EQ(EvsResult::OWNERSHIP_LOST, result); + + // Master client retires from a master role + bool listening = false; + std::condition_variable eventCond; + std::thread listener = std::thread( + [&aNotification0, &frameHandlerNonMaster, &listening, &eventCond]() { + listening = true; + eventCond.notify_all(); + + EvsEventDesc aTargetEvent; + aTargetEvent.aType = EvsEventType::MASTER_RELEASED; + if (!frameHandlerNonMaster->waitForEvent(aTargetEvent, aNotification0, true)) { + LOG(WARNING) << "A timer is expired before a target event is fired."; + } + } + ); + + std::mutex eventLock; + auto timer = std::chrono::system_clock::now(); + std::unique_lock lock(eventLock); + while (!listening) { + eventCond.wait_until(lock, timer + 1s); + } + lock.unlock(); + + result = pCamMaster->unsetMaster(); + ASSERT_EQ(EvsResult::OK, result); + + if (listener.joinable()) { + listener.join(); + } + ASSERT_EQ(EvsEventType::MASTER_RELEASED, + static_cast(aNotification0.aType)); + + // Try to adjust a parameter after being retired + values.clear(); + pCamMaster->setIntParameter(camMasterCmds[0], val0, + [&result, &values](auto status, auto effectiveValues) { + result = status; + if (status == EvsResult::OK) { + for (auto &&v : effectiveValues) { + values.push_back(v); + } + } + }); + ASSERT_EQ(EvsResult::INVALID_ARG, result); + + // Non-master client becomes a master + result = pCamNonMaster->setMaster(); + ASSERT_EQ(EvsResult::OK, result); + + // Try to adjust a parameter via new master client + 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; + } + ); + + EvsResult result = EvsResult::OK; + values.clear(); + if (cmd == CameraParam::ABSOLUTE_FOCUS) { + // Try to turn off auto-focus + values.clear(); + pCamNonMaster->setIntParameter(CameraParam::AUTO_FOCUS, 0, + [&result, &values](auto status, auto effectiveValues) { + result = status; + if (status == EvsResult::OK) { + for (auto &&v : effectiveValues) { + values.push_back(v); + } + } + }); + ASSERT_EQ(EvsResult::OK, result); + for (auto &&v : values) { + ASSERT_EQ(v, 0); + } + } + + // Calculate a parameter value to program. This is being rounding down. + val0 = minVal + (std::rand() % (maxVal - minVal)); + val0 = val0 - (val0 % step); + + // Prepare and start event listeners. + bool listening0 = false; + bool listening1 = false; + std::condition_variable eventCond; + std::thread listener0 = std::thread( + [&cmd, &val0, &aNotification0, &frameHandlerMaster, &listening0, &listening1, &eventCond]() { + listening0 = true; + if (listening1) { + eventCond.notify_all(); + } + + EvsEventDesc aTargetEvent; + aTargetEvent.aType = EvsEventType::PARAMETER_CHANGED; + aTargetEvent.payload[0] = static_cast(cmd); + aTargetEvent.payload[1] = val0; + if (!frameHandlerMaster->waitForEvent(aTargetEvent, aNotification0)) { + LOG(WARNING) << "A timer is expired before a target event is fired."; + } + } + ); + std::thread listener1 = std::thread( + [&cmd, &val0, &aNotification1, &frameHandlerNonMaster, &listening0, &listening1, &eventCond]() { + listening1 = true; + if (listening0) { + eventCond.notify_all(); + } + + EvsEventDesc aTargetEvent; + aTargetEvent.aType = EvsEventType::PARAMETER_CHANGED; + aTargetEvent.payload[0] = static_cast(cmd); + aTargetEvent.payload[1] = val0; + if (!frameHandlerNonMaster->waitForEvent(aTargetEvent, aNotification1)) { + LOG(WARNING) << "A timer is expired before a target event is fired."; + } + } + ); + + // Wait until a listening thread starts. + std::mutex eventLock; + std::unique_lock lock(eventLock); + auto timer = std::chrono::system_clock::now(); + while (!listening0 || !listening1) { + eventCond.wait_until(lock, timer + 1s); + } + lock.unlock(); + + // Try to program a parameter + values.clear(); + pCamNonMaster->setIntParameter(cmd, val0, + [&result, &values](auto status, auto effectiveValues) { + result = status; + if (status == EvsResult::OK) { + for (auto &&v : effectiveValues) { + values.push_back(v); + } + } + }); + ASSERT_EQ(EvsResult::OK, result); + + // Clients expects to receive a parameter change notification + // whenever a master client adjusts it. + values.clear(); + pCamNonMaster->getIntParameter(cmd, + [&result, &values](auto status, auto readValues) { + result = status; + if (status == EvsResult::OK) { + for (auto &&v : readValues) { + values.push_back(v); + } + } + }); + ASSERT_EQ(EvsResult::OK, result); + for (auto &&v : values) { + ASSERT_EQ(val0, v) << "Values are not matched."; + } + + // Join a listening thread. + if (listener0.joinable()) { + listener0.join(); + } + if (listener1.joinable()) { + listener1.join(); + } + + // Verify a change notification + ASSERT_EQ(EvsEventType::PARAMETER_CHANGED, + static_cast(aNotification0.aType)); + ASSERT_EQ(EvsEventType::PARAMETER_CHANGED, + static_cast(aNotification1.aType)); + ASSERT_EQ(cmd, + static_cast(aNotification0.payload[0])); + ASSERT_EQ(cmd, + static_cast(aNotification1.payload[0])); + for (auto &&v : values) { + ASSERT_EQ(v, + static_cast(aNotification0.payload[1])); + ASSERT_EQ(v, + static_cast(aNotification1.payload[1])); + } + } + + // New master retires from a master role + result = pCamNonMaster->unsetMaster(); + ASSERT_EQ(EvsResult::OK, result); + + // Shutdown + frameHandlerMaster->shutdown(); + frameHandlerNonMaster->shutdown(); + + // Explicitly release the camera + pEnumerator->closeCamera(pCamMaster); + pEnumerator->closeCamera(pCamNonMaster); + activeCameras.clear(); + } +} + + +/* + * HighPriorityCameraClient: + * EVS client, which owns the display, is priortized and therefore can take over + * a master role from other EVS clients without the display. + */ +TEST_P(EvsHidlTest, HighPriorityCameraClient) { + LOG(INFO) << "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 pDisplay = pEnumerator->openDisplay(); + ASSERT_NE(pDisplay, nullptr); + + // Test each reported camera + for (auto&& cam: cameraInfo) { + // Create two clients + sp pCam0 = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) + .withDefault(nullptr); + ASSERT_NE(pCam0, nullptr); + + // Store a camera handle for a clean-up + activeCameras.push_back(pCam0); + + sp pCam1 = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) + .withDefault(nullptr); + ASSERT_NE(pCam1, nullptr); + + // Store a camera handle for a clean-up + activeCameras.push_back(pCam1); + + // Get the parameter list; this test will use the first command in both + // lists. + std::vector cam0Cmds, cam1Cmds; + pCam0->getParameterList([&cam0Cmds](hidl_vec cmdList) { + cam0Cmds.reserve(cmdList.size()); + for (auto &&cmd : cmdList) { + cam0Cmds.push_back(cmd); + } + } + ); + + pCam1->getParameterList([&cam1Cmds](hidl_vec 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 frameHandler0 = new FrameHandler(pCam0, cam, + pDisplay, + FrameHandler::eAutoReturn); + sp frameHandler1 = new FrameHandler(pCam1, cam, + nullptr, + FrameHandler::eAutoReturn); + + // Activate the display + pDisplay->setDisplayState(DisplayState::VISIBLE_ON_NEXT_FRAME); + + // Start the camera's video stream + ASSERT_TRUE(frameHandler0->startStream()); + ASSERT_TRUE(frameHandler1->startStream()); + + // Ensure the stream starts + frameHandler0->waitForFrameCount(1); + frameHandler1->waitForFrameCount(1); + + // Client 1 becomes a master and programs a parameter. + EvsResult result = EvsResult::OK; + // 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; + } + ); + + // Client1 becomes a master + result = pCam1->setMaster(); + ASSERT_EQ(EvsResult::OK, result); + + std::vector values; + EvsEventDesc aTargetEvent = {}; + EvsEventDesc aNotification = {}; + bool listening = false; + std::mutex eventLock; + std::condition_variable eventCond; + if (cam1Cmds[0] == CameraParam::ABSOLUTE_FOCUS) { + std::thread listener = std::thread( + [&frameHandler0, &aNotification, &listening, &eventCond] { + listening = true; + eventCond.notify_all(); + + EvsEventDesc aTargetEvent; + aTargetEvent.aType = EvsEventType::PARAMETER_CHANGED; + aTargetEvent.payload[0] = static_cast(CameraParam::AUTO_FOCUS); + aTargetEvent.payload[1] = 0; + if (!frameHandler0->waitForEvent(aTargetEvent, aNotification)) { + LOG(WARNING) << "A timer is expired before a target event is fired."; + } + } + ); + + // Wait until a lister starts. + std::unique_lock lock(eventLock); + auto timer = std::chrono::system_clock::now(); + while (!listening) { + eventCond.wait_until(lock, timer + 1s); + } + lock.unlock(); + + // Try to turn off auto-focus + pCam1->setIntParameter(CameraParam::AUTO_FOCUS, 0, + [&result, &values](auto status, auto effectiveValues) { + result = status; + if (status == EvsResult::OK) { + for (auto &&v : effectiveValues) { + values.push_back(v); + } + } + }); + ASSERT_EQ(EvsResult::OK, result); + for (auto &&v : values) { + ASSERT_EQ(v, 0); + } + + // Join a listener + if (listener.joinable()) { + listener.join(); + } + + // Make sure AUTO_FOCUS is off. + ASSERT_EQ(static_cast(aNotification.aType), + EvsEventType::PARAMETER_CHANGED); + } + + // Try to program a parameter with a random value [minVal, maxVal] after + // rounding it down. + int32_t val0 = minVal + (std::rand() % (maxVal - minVal)); + val0 = val0 - (val0 % step); + + std::thread listener = std::thread( + [&frameHandler1, &aNotification, &listening, &eventCond, &cam1Cmds, val0] { + listening = true; + eventCond.notify_all(); + + EvsEventDesc aTargetEvent; + aTargetEvent.aType = EvsEventType::PARAMETER_CHANGED; + aTargetEvent.payload[0] = static_cast(cam1Cmds[0]); + aTargetEvent.payload[1] = val0; + if (!frameHandler1->waitForEvent(aTargetEvent, aNotification)) { + LOG(WARNING) << "A timer is expired before a target event is fired."; + } + } + ); + + // Wait until a lister starts. + listening = false; + std::unique_lock lock(eventLock); + auto timer = std::chrono::system_clock::now(); + while (!listening) { + eventCond.wait_until(lock, timer + 1s); + } + lock.unlock(); + + values.clear(); + pCam1->setIntParameter(cam1Cmds[0], val0, + [&result, &values](auto status, auto effectiveValues) { + result = status; + if (status == EvsResult::OK) { + for (auto &&v : effectiveValues) { + values.push_back(v); + } + } + }); + ASSERT_EQ(EvsResult::OK, result); + for (auto &&v : values) { + ASSERT_EQ(val0, v); + } + + // Join a listener + if (listener.joinable()) { + listener.join(); + } + + // Verify a change notification + ASSERT_EQ(static_cast(aNotification.aType), + EvsEventType::PARAMETER_CHANGED); + ASSERT_EQ(static_cast(aNotification.payload[0]), + cam1Cmds[0]); + for (auto &&v : values) { + ASSERT_EQ(v, static_cast(aNotification.payload[1])); + } + + listener = std::thread( + [&frameHandler1, &aNotification, &listening, &eventCond] { + listening = true; + eventCond.notify_all(); + + EvsEventDesc aTargetEvent; + aTargetEvent.aType = EvsEventType::MASTER_RELEASED; + if (!frameHandler1->waitForEvent(aTargetEvent, aNotification, true)) { + LOG(WARNING) << "A timer is expired before a target event is fired."; + } + } + ); + + // Wait until a lister starts. + listening = false; + lock.lock(); + timer = std::chrono::system_clock::now(); + while (!listening) { + eventCond.wait_until(lock, timer + 1s); + } + lock.unlock(); + + // Client 0 steals a master role + ASSERT_EQ(EvsResult::OK, pCam0->forceMaster(pDisplay)); + + // Join a listener + if (listener.joinable()) { + listener.join(); + } + + ASSERT_EQ(static_cast(aNotification.aType), + EvsEventType::MASTER_RELEASED); + + // Client 0 programs a parameter + val0 = minVal + (std::rand() % (maxVal - minVal)); + + // Rounding down + val0 = val0 - (val0 % step); + + if (cam0Cmds[0] == CameraParam::ABSOLUTE_FOCUS) { + std::thread listener = std::thread( + [&frameHandler1, &aNotification, &listening, &eventCond] { + listening = true; + eventCond.notify_all(); + + EvsEventDesc aTargetEvent; + aTargetEvent.aType = EvsEventType::PARAMETER_CHANGED; + aTargetEvent.payload[0] = static_cast(CameraParam::AUTO_FOCUS); + aTargetEvent.payload[1] = 0; + if (!frameHandler1->waitForEvent(aTargetEvent, aNotification)) { + LOG(WARNING) << "A timer is expired before a target event is fired."; + } + } + ); + + // Wait until a lister starts. + std::unique_lock lock(eventLock); + auto timer = std::chrono::system_clock::now(); + while (!listening) { + eventCond.wait_until(lock, timer + 1s); + } + lock.unlock(); + + // Try to turn off auto-focus + values.clear(); + pCam0->setIntParameter(CameraParam::AUTO_FOCUS, 0, + [&result, &values](auto status, auto effectiveValues) { + result = status; + if (status == EvsResult::OK) { + for (auto &&v : effectiveValues) { + values.push_back(v); + } + } + }); + ASSERT_EQ(EvsResult::OK, result); + for (auto &&v : values) { + ASSERT_EQ(v, 0); + } + + // Join a listener + if (listener.joinable()) { + listener.join(); + } + + // Make sure AUTO_FOCUS is off. + ASSERT_EQ(static_cast(aNotification.aType), + EvsEventType::PARAMETER_CHANGED); + } + + listener = std::thread( + [&frameHandler0, &aNotification, &listening, &eventCond, &cam0Cmds, val0] { + listening = true; + eventCond.notify_all(); + + EvsEventDesc aTargetEvent; + aTargetEvent.aType = EvsEventType::PARAMETER_CHANGED; + aTargetEvent.payload[0] = static_cast(cam0Cmds[0]); + aTargetEvent.payload[1] = val0; + if (!frameHandler0->waitForEvent(aTargetEvent, aNotification)) { + LOG(WARNING) << "A timer is expired before a target event is fired."; + } + } + ); + + // Wait until a lister starts. + listening = false; + timer = std::chrono::system_clock::now(); + lock.lock(); + while (!listening) { + eventCond.wait_until(lock, timer + 1s); + } + lock.unlock(); + + values.clear(); + pCam0->setIntParameter(cam0Cmds[0], val0, + [&result, &values](auto status, auto effectiveValues) { + result = status; + if (status == EvsResult::OK) { + for (auto &&v : effectiveValues) { + values.push_back(v); + } + } + }); + ASSERT_EQ(EvsResult::OK, result); + + // Join a listener + if (listener.joinable()) { + listener.join(); + } + // Verify a change notification + ASSERT_EQ(static_cast(aNotification.aType), + EvsEventType::PARAMETER_CHANGED); + ASSERT_EQ(static_cast(aNotification.payload[0]), + cam0Cmds[0]); + for (auto &&v : values) { + ASSERT_EQ(v, static_cast(aNotification.payload[1])); + } + + // Turn off the display (yes, before the stream stops -- it should be handled) + pDisplay->setDisplayState(DisplayState::NOT_VISIBLE); + + // Shut down the streamer + frameHandler0->shutdown(); + frameHandler1->shutdown(); + + // Explicitly release the camera + pEnumerator->closeCamera(pCam0); + pEnumerator->closeCamera(pCam1); + activeCameras.clear(); + + } + + // Explicitly release the display + pEnumerator->closeDisplay(pDisplay); +} + + +/* + * 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_P(EvsHidlTest, CameraUseStreamConfigToDisplay) { + LOG(INFO) << "Starting CameraUseStreamConfigToDisplay test"; + + // Get the camera list + loadCameraList(); + + // Request exclusive access to the EVS display + sp 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(cam.metadata.data()), + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, + &streamCfgs)) { + // Stream configurations are found in metadata + RawStreamConfig *ptr = reinterpret_cast(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(HAL_PIXEL_FORMAT_RGBA_8888); + + if (!foundCfg) { + // Current EVS camera does not provide stream configurations in the + // metadata. + continue; + } + + sp pCam = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, targetCfg)) + .withDefault(nullptr); + ASSERT_NE(pCam, nullptr); + + // Store a camera handle for a clean-up + activeCameras.push_back(pCam); + + // Set up a frame receiver object which will fire up its own thread. + sp 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); + activeCameras.clear(); + } + + // 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_P(EvsHidlTest, MultiCameraStreamUseConfig) { + LOG(INFO) << "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(cam.metadata.data()), + ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS, + &streamCfgs)) { + // Stream configurations are found in metadata + RawStreamConfig *ptr = reinterpret_cast(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(HAL_PIXEL_FORMAT_RGBA_8888); + + if (!foundCfg) { + LOG(INFO) << "Device " << cam.v1.cameraId + << " does not provide a list of supported stream configurations, skipped"; + continue; + } + + // Create the first camera client with a selected stream configuration. + sp pCam0 = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, targetCfg)) + .withDefault(nullptr); + ASSERT_NE(pCam0, nullptr); + + // Store a camera handle for a clean-up + activeCameras.push_back(pCam0); + + // 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 pCam1 = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, targetCfg)) + .withDefault(nullptr); + ASSERT_EQ(pCam1, nullptr); + + // Store a camera handle for a clean-up + activeCameras.push_back(pCam0); + + // 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 frameHandler0 = new FrameHandler(pCam0, cam, + nullptr, + FrameHandler::eAutoReturn); + ASSERT_NE(frameHandler0, nullptr); + + sp 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); + LOG(INFO) << "Measured camera rate " + << std::scientific << framesPerSecond0 << " fps and " + << framesPerSecond1 << " fps"; + 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); + activeCameras.clear(); + } +} + + +/* + * LogicalCameraMetadata: + * Opens logical camera reported by the enumerator and validate its metadata by + * checking its capability and locating supporting physical camera device + * identifiers. + */ +TEST_P(EvsHidlTest, LogicalCameraMetadata) { + LOG(INFO) << "Starting LogicalCameraMetadata test"; + + // Get the camera list + loadCameraList(); + + // Open and close each camera twice + for (auto&& cam: cameraInfo) { + bool isLogicalCam = false; + auto devices = getPhysicalCameraIds(cam.v1.cameraId, isLogicalCam); + if (isLogicalCam) { + ASSERT_GE(devices.size(), 1) << + "Logical camera device must have at least one physical camera device ID in its metadata."; + } + } +} + + +/* + * CameraStreamExternalBuffering: + * This is same with CameraStreamBuffering except frame buffers are allocated by + * the test client and then imported by EVS framework. + */ +TEST_P(EvsHidlTest, CameraStreamExternalBuffering) { + LOG(INFO) << "Starting CameraStreamExternalBuffering test"; + + // Arbitrary constant (should be > 1 and less than crazy) + static const unsigned int kBuffersToHold = 6; + + // Get the camera list + loadCameraList(); + + // Using null stream configuration makes EVS uses the default resolution and + // output format. + Stream nullCfg = {}; + + // Acquire the graphics buffer allocator + android::GraphicBufferAllocator& alloc(android::GraphicBufferAllocator::get()); + const auto usage = GRALLOC_USAGE_HW_TEXTURE | + GRALLOC_USAGE_SW_READ_RARELY | + GRALLOC_USAGE_SW_WRITE_OFTEN; + const auto format = HAL_PIXEL_FORMAT_RGBA_8888; + const auto width = 640; + const auto height = 360; + + // Allocate buffers to use + hidl_vec buffers; + buffers.resize(kBuffersToHold); + for (auto i = 0; i < kBuffersToHold; ++i) { + unsigned pixelsPerLine; + buffer_handle_t memHandle = nullptr; + android::status_t result = alloc.allocate(width, + height, + format, + 1, + usage, + &memHandle, + &pixelsPerLine, + 0, + "EvsApp"); + if (result != android::NO_ERROR) { + LOG(ERROR) << __FUNCTION__ << " failed to allocate memory."; + } else { + BufferDesc buf; + AHardwareBuffer_Desc* pDesc = + reinterpret_cast(&buf.buffer.description); + pDesc->width = width; + pDesc->height = height; + pDesc->layers = 1; + pDesc->format = format; + pDesc->usage = usage; + pDesc->stride = pixelsPerLine; + buf.buffer.nativeHandle = memHandle; + buf.bufferId = i; // Unique number to identify this buffer + buffers[i] = buf; + } + } + + // Test each reported camera + for (auto&& cam: cameraInfo) { + bool isLogicalCam = false; + getPhysicalCameraIds(cam.v1.cameraId, isLogicalCam); + + sp pCam = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1(cam.v1.cameraId, nullCfg)) + .withDefault(nullptr); + ASSERT_NE(pCam, nullptr); + + // Store a camera handle for a clean-up + activeCameras.push_back(pCam); + + // Request to import buffers + EvsResult result = EvsResult::OK; + int delta = 0; + pCam->importExternalBuffers(buffers, + [&] (auto _result, auto _delta) { + result = _result; + delta = _delta; + }); + if (isLogicalCam) { + EXPECT_EQ(result, EvsResult::UNDERLYING_SERVICE_ERROR); + continue; + } + + EXPECT_EQ(result, EvsResult::OK); + EXPECT_GE(delta, 0); + + // Set up a frame receiver object which will fire up its own thread. + sp frameHandler = new FrameHandler(pCam, cam, + nullptr, + FrameHandler::eNoAutoReturn); + + // Start the camera's video stream + bool startResult = frameHandler->startStream(); + ASSERT_TRUE(startResult); + + // Check that the video stream stalls once we've gotten exactly the number of buffers + // we requested since we told the frameHandler not to return them. + sleep(1); // 1 second should be enough for at least 5 frames to be delivered worst case + unsigned framesReceived = 0; + frameHandler->getFramesCounters(&framesReceived, nullptr); + ASSERT_EQ(kBuffersToHold, framesReceived) << "Stream didn't stall at expected buffer limit"; + + + // Give back one buffer + bool didReturnBuffer = frameHandler->returnHeldBuffer(); + EXPECT_TRUE(didReturnBuffer); + + // Once we return a buffer, it shouldn't take more than 1/10 second to get a new one + // filled since we require 10fps minimum -- but give a 10% allowance just in case. + usleep(110 * kMillisecondsToMicroseconds); + frameHandler->getFramesCounters(&framesReceived, nullptr); + EXPECT_EQ(kBuffersToHold+1, framesReceived) << "Stream should've resumed"; + + // Even when the camera pointer goes out of scope, the FrameHandler object will + // keep the stream alive unless we tell it to shutdown. + // Also note that the FrameHandle and the Camera have a mutual circular reference, so + // we have to break that cycle in order for either of them to get cleaned up. + frameHandler->shutdown(); + + // Explicitly release the camera + pEnumerator->closeCamera(pCam); + activeCameras.clear(); + } + + // Release buffers + for (auto& b : buffers) { + alloc.free(b.buffer.nativeHandle); + } + buffers.resize(0); +} + + +/* + * UltrasonicsArrayOpenClean: + * Opens each ultrasonics arrays reported by the enumerator and then explicitly closes it via a + * call to closeUltrasonicsArray. Then repeats the test to ensure all ultrasonics arrays + * can be reopened. + */ +TEST_P(EvsHidlTest, UltrasonicsArrayOpenClean) { + LOG(INFO) << "Starting UltrasonicsArrayOpenClean test"; + + // Get the ultrasonics array list + loadUltrasonicsArrayList(); + + // Open and close each ultrasonics array twice + for (auto&& ultraInfo : ultrasonicsArraysInfo) { + for (int pass = 0; pass < 2; pass++) { + sp pUltrasonicsArray = + pEnumerator->openUltrasonicsArray(ultraInfo.ultrasonicsArrayId); + ASSERT_NE(pUltrasonicsArray, nullptr); + + // Verify that this ultrasonics array self-identifies correctly + pUltrasonicsArray->getUltrasonicArrayInfo([&ultraInfo](UltrasonicsArrayDesc desc) { + LOG(DEBUG) << "Found ultrasonics array " << ultraInfo.ultrasonicsArrayId; + EXPECT_EQ(ultraInfo.ultrasonicsArrayId, desc.ultrasonicsArrayId); + }); + + // Explicitly close the ultrasonics array so resources are released right away + pEnumerator->closeUltrasonicsArray(pUltrasonicsArray); + } + } +} + + +// Starts a stream and verifies all data received is valid. +TEST_P(EvsHidlTest, UltrasonicsVerifyStreamData) { + LOG(INFO) << "Starting UltrasonicsVerifyStreamData"; + + // Get the ultrasonics array list + loadUltrasonicsArrayList(); + + // For each ultrasonics array. + for (auto&& ultraInfo : ultrasonicsArraysInfo) { + LOG(DEBUG) << "Testing ultrasonics array: " << ultraInfo.ultrasonicsArrayId; + + sp pUltrasonicsArray = + pEnumerator->openUltrasonicsArray(ultraInfo.ultrasonicsArrayId); + ASSERT_NE(pUltrasonicsArray, nullptr); + + sp frameHandler = new FrameHandlerUltrasonics(pUltrasonicsArray); + + // Start stream. + EvsResult result = pUltrasonicsArray->startStream(frameHandler); + ASSERT_EQ(result, EvsResult::OK); + + // Wait 5 seconds to receive frames. + sleep(5); + + // Stop stream. + pUltrasonicsArray->stopStream(); + + EXPECT_GT(frameHandler->getReceiveFramesCount(), 0); + EXPECT_TRUE(frameHandler->areAllFramesValid()); + + // Explicitly close the ultrasonics array so resources are released right away + pEnumerator->closeUltrasonicsArray(pUltrasonicsArray); + } +} + + +// Sets frames in flight before and after start of stream and verfies success. +TEST_P(EvsHidlTest, UltrasonicsSetFramesInFlight) { + LOG(INFO) << "Starting UltrasonicsSetFramesInFlight"; + + // Get the ultrasonics array list + loadUltrasonicsArrayList(); + + // For each ultrasonics array. + for (auto&& ultraInfo : ultrasonicsArraysInfo) { + LOG(DEBUG) << "Testing ultrasonics array: " << ultraInfo.ultrasonicsArrayId; + + sp pUltrasonicsArray = + pEnumerator->openUltrasonicsArray(ultraInfo.ultrasonicsArrayId); + ASSERT_NE(pUltrasonicsArray, nullptr); + + EvsResult result = pUltrasonicsArray->setMaxFramesInFlight(10); + EXPECT_EQ(result, EvsResult::OK); + + sp frameHandler = new FrameHandlerUltrasonics(pUltrasonicsArray); + + // Start stream. + result = pUltrasonicsArray->startStream(frameHandler); + ASSERT_EQ(result, EvsResult::OK); + + result = pUltrasonicsArray->setMaxFramesInFlight(5); + EXPECT_EQ(result, EvsResult::OK); + + // Stop stream. + pUltrasonicsArray->stopStream(); + + // Explicitly close the ultrasonics array so resources are released right away + pEnumerator->closeUltrasonicsArray(pUltrasonicsArray); + } +} + + +INSTANTIATE_TEST_SUITE_P( + PerInstance, + EvsHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(IEvsEnumerator::descriptor)), + android::hardware::PrintInstanceNameToString); + diff --git a/automotive/evs/1.1/vts/fuzzing/Android.bp b/automotive/evs/1.1/vts/fuzzing/Android.bp new file mode 100644 index 0000000000..48427ee532 --- /dev/null +++ b/automotive/evs/1.1/vts/fuzzing/Android.bp @@ -0,0 +1,50 @@ +// +// Copyright 2020 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_defaults { + name: "android.hardware.automotive.evs@fuzz-defaults", + defaults: ["VtsHalTargetTestDefaults"], + shared_libs: [ + "libui", + "libcamera_metadata", + ], + static_libs: [ + "android.hardware.automotive.evs@1.0", + "android.hardware.automotive.evs@1.1", + "android.hardware.automotive.evs@common-default-lib", + "android.hardware.graphics.common@1.0", + "android.hardware.graphics.common@1.1", + "android.hardware.graphics.common@1.2", + "android.hardware.camera.device@3.2", + ], + cflags: [ + "-O0", + "-g", + ], +} + +cc_fuzz { + name: "VtsHalEvsV1_1CameraOpenFuzz", + defaults: ["android.hardware.automotive.evs@fuzz-defaults"], + srcs: [ + "VtsHalEvsV1_1CameraOpenFuzz.cpp", + ], + fuzz_config: { + // wait for Haiku device ready + fuzz_on_haiku_device: false, + fuzz_on_haiku_host: false, + }, +} diff --git a/automotive/evs/1.1/vts/fuzzing/VtsHalEvsV1_1CameraOpenFuzz.cpp b/automotive/evs/1.1/vts/fuzzing/VtsHalEvsV1_1CameraOpenFuzz.cpp new file mode 100644 index 0000000000..4f05d21e69 --- /dev/null +++ b/automotive/evs/1.1/vts/fuzzing/VtsHalEvsV1_1CameraOpenFuzz.cpp @@ -0,0 +1,84 @@ +/* + * Copyright 2020 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 "common.h" + +using ::android::hardware::automotive::evs::V1_0::DisplayDesc; +using ::android::hardware::camera::device::V3_2::Stream; +using IEvsCamera_1_1 = ::android::hardware::automotive::evs::V1_1::IEvsCamera; + +extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) { + UNUSED(argc); + UNUSED(argv); + pEnumerator = IEvsEnumerator::getService(kEnumeratorName); + sp dr = new EvsDeathRecipient(); + + pEnumerator->linkToDeath(dr, 0); + + loadCameraList(); + return 0; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedDataProvider fdp(data, size); + + std::vector> camList; + Stream nullCfg = {}; + + while (fdp.remaining_bytes() > 4) { + switch (fdp.ConsumeIntegralInRange(0, 3)) { + case 0: // open camera + { + uint32_t whichCam = fdp.ConsumeIntegralInRange(0, cameraInfo.size() - 1); + sp pCam = + IEvsCamera_1_1::castFrom(pEnumerator->openCamera_1_1( + cameraInfo[whichCam].v1.cameraId, nullCfg)) + .withDefault(nullptr); + camList.emplace_back(pCam); + break; + } + case 1: // close camera + { + if (!camList.empty()) { + uint32_t whichCam = fdp.ConsumeIntegralInRange(0, camList.size() - 1); + pEnumerator->closeCamera(camList[whichCam]); + } + break; + } + case 2: // get camera info + { + if (!camList.empty()) { + uint32_t whichCam = fdp.ConsumeIntegralInRange(0, camList.size() - 1); + camList[whichCam]->getCameraInfo_1_1([](CameraDesc desc) { UNUSED(desc); }); + } + break; + } + case 3: // setMaxFramesInFlight + { + if (!camList.empty()) { + uint32_t whichCam = fdp.ConsumeIntegralInRange(0, camList.size() - 1); + int32_t numFrames = fdp.ConsumeIntegral(); + camList[whichCam]->setMaxFramesInFlight(numFrames); + } + break; + } + default: + break; + } + } + + return 0; +} diff --git a/automotive/evs/1.1/vts/fuzzing/common.h b/automotive/evs/1.1/vts/fuzzing/common.h new file mode 100644 index 0000000000..af6fd545ea --- /dev/null +++ b/automotive/evs/1.1/vts/fuzzing/common.h @@ -0,0 +1,57 @@ +/* + * Copyright 2020 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 "VtsHalEvsTest" +#define UNUSED(x) (void)(x) + +const static char kEnumeratorName[] = "EvsEnumeratorHw"; + +#include +#include +#include +#include + +using namespace ::android::hardware::automotive::evs::V1_1; + +using ::android::sp; +using ::android::hardware::hidl_death_recipient; +using ::android::hardware::hidl_vec; + +static sp pEnumerator; +static std::vector cameraInfo; + +class EvsDeathRecipient : public hidl_death_recipient { + public: + void serviceDied(uint64_t /*cookie*/, + const android::wp<::android::hidl::base::V1_0::IBase>& /*who*/) override { + abort(); + } +}; + +void loadCameraList() { + // SetUp() must run first! + assert(pEnumerator != nullptr); + + // Get the camera list + pEnumerator->getCameraList_1_1([](hidl_vec 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); + } + }); +} diff --git a/automotive/evs/common/utils/default/Android.bp b/automotive/evs/common/utils/default/Android.bp new file mode 100644 index 0000000000..776ef81875 --- /dev/null +++ b/automotive/evs/common/utils/default/Android.bp @@ -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. +// + +cc_library_static { + host_supported: true, + name: "android.hardware.automotive.evs@common-default-lib", + vendor_available: true, + relative_install_path: "hw", + cflags: [ + "-O0", + "-g", + ], + srcs: [ + "FormatConvert.cpp" + ], + export_include_dirs: ["include"], + shared_libs: [ + ], +} diff --git a/automotive/evs/1.0/vts/functional/FormatConvert.cpp b/automotive/evs/common/utils/default/FormatConvert.cpp similarity index 74% rename from automotive/evs/1.0/vts/functional/FormatConvert.cpp rename to automotive/evs/common/utils/default/FormatConvert.cpp index 3d82d32cac..d4c7da0363 100644 --- a/automotive/evs/1.0/vts/functional/FormatConvert.cpp +++ b/automotive/evs/common/utils/default/FormatConvert.cpp @@ -18,10 +18,15 @@ #include "FormatConvert.h" +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace common { // Round up to the nearest multiple of the given alignment value template -int align(int value) { +int Utils::align(int value) { static_assert((alignment && !(alignment & (alignment - 1))), "alignment must be a power of 2"); @@ -31,15 +36,17 @@ int align(int value) { // Limit the given value to the provided range. :) -static inline float clamp(float v, float min, float max) { +inline float Utils::clamp(float v, float min, float max) { if (v < min) return min; if (v > max) return max; return v; } -static uint32_t yuvToRgbx(const unsigned char Y, const unsigned char Uin, const unsigned char Vin, - bool bgrxFormat = false) { +uint32_t Utils::yuvToRgbx(const unsigned char Y, + const unsigned char Uin, + const unsigned char Vin, + bool bgrxFormat) { // Don't use this if you want to see the best performance. :) // Better to do this in a pixel shader if we really have to, but on actual // embedded hardware we expect to be able to texture directly from the YUV data @@ -67,10 +74,10 @@ static uint32_t yuvToRgbx(const unsigned char Y, const unsigned char Uin, const } -void copyNV21toRGB32(unsigned width, unsigned height, - uint8_t* src, - uint32_t* dst, unsigned dstStridePixels, - bool bgrxFormat) +void Utils::copyNV21toRGB32(unsigned width, unsigned height, + uint8_t* src, + uint32_t* dst, unsigned dstStridePixels, + bool bgrxFormat) { // The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved // U/V array. It assumes an even width and height for the overall image, and a horizontal @@ -99,10 +106,10 @@ void copyNV21toRGB32(unsigned width, unsigned height, } -void copyYV12toRGB32(unsigned width, unsigned height, - uint8_t* src, - uint32_t* dst, unsigned dstStridePixels, - bool bgrxFormat) +void Utils::copyYV12toRGB32(unsigned width, unsigned height, + uint8_t* src, + uint32_t* dst, unsigned dstStridePixels, + bool bgrxFormat) { // The YV12 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 U array, followed // by another 1/2 x 1/2 V array. It assumes an even width and height for the overall image, @@ -134,10 +141,10 @@ void copyYV12toRGB32(unsigned width, unsigned height, } -void copyYUYVtoRGB32(unsigned width, unsigned height, - uint8_t* src, unsigned srcStridePixels, - uint32_t* dst, unsigned dstStridePixels, - bool bgrxFormat) +void Utils::copyYUYVtoRGB32(unsigned width, unsigned height, + uint8_t* src, unsigned srcStridePixels, + uint32_t* dst, unsigned dstStridePixels, + bool bgrxFormat) { uint32_t* srcWords = (uint32_t*)src; @@ -167,34 +174,34 @@ void copyYUYVtoRGB32(unsigned width, unsigned height, } -void copyNV21toBGR32(unsigned width, unsigned height, - uint8_t* src, - uint32_t* dst, unsigned dstStridePixels) +void Utils::copyNV21toBGR32(unsigned width, unsigned height, + uint8_t* src, + uint32_t* dst, unsigned dstStridePixels) { return copyNV21toRGB32(width, height, src, dst, dstStridePixels, true); } -void copyYV12toBGR32(unsigned width, unsigned height, - uint8_t* src, - uint32_t* dst, unsigned dstStridePixels) +void Utils::copyYV12toBGR32(unsigned width, unsigned height, + uint8_t* src, + uint32_t* dst, unsigned dstStridePixels) { return copyYV12toRGB32(width, height, src, dst, dstStridePixels, true); } -void copyYUYVtoBGR32(unsigned width, unsigned height, - uint8_t* src, unsigned srcStridePixels, - uint32_t* dst, unsigned dstStridePixels) +void Utils::copyYUYVtoBGR32(unsigned width, unsigned height, + uint8_t* src, unsigned srcStridePixels, + uint32_t* dst, unsigned dstStridePixels) { return copyYUYVtoRGB32(width, height, src, srcStridePixels, dst, dstStridePixels, true); } -void copyMatchedInterleavedFormats(unsigned width, unsigned height, - void* src, unsigned srcStridePixels, - void* dst, unsigned dstStridePixels, - unsigned pixelSize) { +void Utils::copyMatchedInterleavedFormats(unsigned width, unsigned height, + void* src, unsigned srcStridePixels, + void* dst, unsigned dstStridePixels, + unsigned pixelSize) { for (unsigned row = 0; row < height; row++) { // Copy the entire row of pixel data memcpy(dst, src, width * pixelSize); @@ -204,3 +211,10 @@ void copyMatchedInterleavedFormats(unsigned width, unsigned height, dst = (uint8_t*)dst + dstStridePixels * pixelSize; } } + +} // namespace common +} // namespace evs +} // namespace automotive +} // namespace hardware +} // namespace android + diff --git a/automotive/evs/common/utils/default/include/FormatConvert.h b/automotive/evs/common/utils/default/include/FormatConvert.h new file mode 100644 index 0000000000..2bb89554da --- /dev/null +++ b/automotive/evs/common/utils/default/include/FormatConvert.h @@ -0,0 +1,99 @@ +/* + * 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. + */ + +#ifndef EVS_VTS_FORMATCONVERT_H +#define EVS_VTS_FORMATCONVERT_H + +#include +#include + + +namespace android { +namespace hardware { +namespace automotive { +namespace evs { +namespace common { + +class Utils { +public: + // Given an image buffer in NV21 format (HAL_PIXEL_FORMAT_YCRCB_420_SP), output 32bit RGBx/BGRx + // values. The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved + // U/V array. It assumes an even width and height for the overall image, and a horizontal + // stride that is an even multiple of 16 bytes for both the Y and UV arrays. + static void copyNV21toRGB32(unsigned width, unsigned height, + uint8_t* src, + uint32_t* dst, unsigned dstStridePixels, + bool bgrxFormat = false); + + static void copyNV21toBGR32(unsigned width, unsigned height, + uint8_t* src, + uint32_t* dst, unsigned dstStridePixels); + + + // Given an image buffer in YV12 format (HAL_PIXEL_FORMAT_YV12), output 32bit RGBx/BGRx values. + // The YV12 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 U array, followed + // by another 1/2 x 1/2 V array. It assumes an even width and height for the overall image, + // and a horizontal stride that is an even multiple of 16 bytes for each of the Y, U, + // and V arrays. + static void copyYV12toRGB32(unsigned width, unsigned height, + uint8_t* src, + uint32_t* dst, unsigned dstStridePixels, + bool bgrxFormat = false); + + static void copyYV12toBGR32(unsigned width, unsigned height, + uint8_t* src, + uint32_t* dst, unsigned dstStridePixels); + + // Given an image buffer in YUYV format (HAL_PIXEL_FORMAT_YCBCR_422_I), output 32bit RGBx/BGRx + // values. The NV21 format provides a Y array of 8bit values, followed by a 1/2 x 1/2 interleaved + // U/V array. It assumes an even width and height for the overall image, and a horizontal + // stride that is an even multiple of 16 bytes for both the Y and UV arrays. + static void copyYUYVtoRGB32(unsigned width, unsigned height, + uint8_t* src, unsigned srcStrideBytes, + uint32_t* dst, unsigned dstStrideBytes, + bool bgrxFormat = false); + + static void copyYUYVtoBGR32(unsigned width, unsigned height, + uint8_t* src, unsigned srcStrideBytes, + uint32_t* dst, unsigned dstStrideBytes); + + + // Given an simple rectangular image buffer with an integer number of bytes per pixel, + // copy the pixel values into a new rectangular buffer (potentially with a different stride). + // This is typically used to copy RGBx data into an RGBx output buffer. + static void copyMatchedInterleavedFormats(unsigned width, unsigned height, + void* src, unsigned srcStridePixels, + void* dst, unsigned dstStridePixels, + unsigned pixelSize); + +private: + template + static int align(int value); + + static inline float clamp(float v, float min, float max); + static uint32_t yuvToRgbx(const unsigned char Y, + const unsigned char Uin, + const unsigned char Vin, + bool bgrxFormat = false); +}; + +} // namespace common +} // namespace evs +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // EVS_VTS_FORMATCONVERT_H diff --git a/automotive/evs/common/utils/default/test/fuzz/Android.bp b/automotive/evs/common/utils/default/test/fuzz/Android.bp new file mode 100644 index 0000000000..105ec68b04 --- /dev/null +++ b/automotive/evs/common/utils/default/test/fuzz/Android.bp @@ -0,0 +1,99 @@ +// +// 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_fuzz { + host_supported: true, + name : "FormatConvertFuzzer_copyNV21toRGB32", + srcs: [ + "FormatConvertFuzzer.cpp", + ], + static_libs: [ + "android.hardware.automotive.evs@common-default-lib" + ], + cflags: [ + "-DCOPY_NV21_TO_RGB32", + ], +} + +cc_fuzz { + host_supported: true, + name : "FormatConvertFuzzer_copyNV21toBGR32", + srcs: [ + "FormatConvertFuzzer.cpp", + ], + static_libs: [ + "android.hardware.automotive.evs@common-default-lib" + ], + cflags: [ + "-DCOPY_NV21_TO_BGR32", + ], +} + +cc_fuzz { + host_supported: true, + name : "FormatConvertFuzzer_copyYV12toRGB32", + srcs: [ + "FormatConvertFuzzer.cpp", + ], + static_libs: [ + "android.hardware.automotive.evs@common-default-lib" + ], + cflags: [ + "-DCOPY_YV12_TO_RGB32", + ], +} + +cc_fuzz { + host_supported: true, + name : "FormatConvertFuzzer_copyYV12toBGR32", + srcs: [ + "FormatConvertFuzzer.cpp", + ], + static_libs: [ + "android.hardware.automotive.evs@common-default-lib" + ], + cflags: [ + "-DCOPY_YV12_TO_BGR32", + ], +} + +cc_fuzz { + host_supported: true, + name : "FormatConvertFuzzer_copyYUYVtoRGB32", + srcs: [ + "FormatConvertFuzzer.cpp", + ], + static_libs: [ + "android.hardware.automotive.evs@common-default-lib" + ], + cflags: [ + "-DCOPY_YUYV_TO_RGB32", + ], +} + +cc_fuzz { + host_supported: true, + name : "FormatConvertFuzzer_copyYUYVtoBGR32", + srcs: [ + "FormatConvertFuzzer.cpp", + ], + static_libs: [ + "android.hardware.automotive.evs@common-default-lib" + ], + cflags: [ + "-DCOPY_YUYV_TO_BGR32", + ], +} diff --git a/automotive/evs/common/utils/default/test/fuzz/FormatConvertFuzzer.cpp b/automotive/evs/common/utils/default/test/fuzz/FormatConvertFuzzer.cpp new file mode 100644 index 0000000000..583a455160 --- /dev/null +++ b/automotive/evs/common/utils/default/test/fuzz/FormatConvertFuzzer.cpp @@ -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. + */ + +#include +#include +#include +#include +#include "FormatConvert.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, std::size_t size) { + if (size < 256) { + return 0; + } + + std::srand(std::time(nullptr)); // use current time as seed for random generator + int random_variable = std::rand() % 10; + int width = (int)sqrt(size); + int height = width * ((float)random_variable / 10.0); + + uint8_t* src = (uint8_t*)malloc(sizeof(uint8_t) * size); + memcpy(src, data, sizeof(uint8_t) * (size)); + uint32_t* tgt = (uint32_t*)malloc(sizeof(uint32_t) * size); + +#ifdef COPY_NV21_TO_RGB32 + android::hardware::automotive::evs::common::Utils::copyNV21toRGB32(width, height, src, tgt, 0); +#elif COPY_NV21_TO_BGR32 + android::hardware::automotive::evs::common::Utils::copyNV21toBGR32(width, height, src, tgt, 0); +#elif COPY_YV12_TO_RGB32 + android::hardware::automotive::evs::common::Utils::copyYV12toRGB32(width, height, src, tgt, 0); +#elif COPY_YV12_TO_BGR32 + android::hardware::automotive::evs::common::Utils::copyYV12toBGR32(width, height, src, tgt, 0); +#elif COPY_YUYV_TO_RGB32 + android::hardware::automotive::evs::common::Utils::copyYUYVtoRGB32(width, height, src, 0, tgt, + 0); +#elif COPY_YUYV_TO_BGR32 + android::hardware::automotive::evs::common::Utils::copyYUYVtoBGR32(width, height, src, 0, tgt, + 0); +#endif + + free(src); + free(tgt); + + return 0; +} diff --git a/automotive/occupant_awareness/aidl/Android.bp b/automotive/occupant_awareness/aidl/Android.bp new file mode 100644 index 0000000000..4127f333b3 --- /dev/null +++ b/automotive/occupant_awareness/aidl/Android.bp @@ -0,0 +1,19 @@ +aidl_interface { + name: "android.hardware.automotive.occupant_awareness", + vendor_available: true, + srcs: [ + "android/hardware/automotive/occupant_awareness/*.aidl", + ], + stability: "vintf", + backend: { + java: { + platform_apis: true, + }, + ndk: { + vndk: { + enabled: true, + }, + }, + }, + versions: ["1"], +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/.hash b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/.hash new file mode 100644 index 0000000000..6786231b86 --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/.hash @@ -0,0 +1 @@ +3614b1c47ed7be85c1e77554e7f04966cf35b465 diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/ConfidenceLevel.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/ConfidenceLevel.aidl new file mode 100644 index 0000000000..6e40d79cab --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/ConfidenceLevel.aidl @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@Backing(type="byte") @VintfStability +enum ConfidenceLevel { + NONE = 0, + LOW = 1, + HIGH = 2, + MAX = 3, +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/DriverMonitoringDetection.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/DriverMonitoringDetection.aidl new file mode 100644 index 0000000000..51d2149aa3 --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/DriverMonitoringDetection.aidl @@ -0,0 +1,24 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@VintfStability +parcelable DriverMonitoringDetection { + android.hardware.automotive.occupant_awareness.ConfidenceLevel confidenceScore; + boolean isLookingOnRoad; + long gazeDurationMillis; +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/GazeDetection.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/GazeDetection.aidl new file mode 100644 index 0000000000..5ce14df9b8 --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/GazeDetection.aidl @@ -0,0 +1,28 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@VintfStability +parcelable GazeDetection { + android.hardware.automotive.occupant_awareness.ConfidenceLevel gazeConfidence; + double[] headPosition; + double[] headAngleUnitVector; + double[] gazeAngleUnitVector; + android.hardware.automotive.occupant_awareness.VehicleRegion gazeTarget; + String customGazeTarget; + long timeOnTargetMillis; +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/IOccupantAwareness.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/IOccupantAwareness.aidl new file mode 100644 index 0000000000..7faff00841 --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/IOccupantAwareness.aidl @@ -0,0 +1,31 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@VintfStability +interface IOccupantAwareness { + android.hardware.automotive.occupant_awareness.OccupantAwarenessStatus startDetection(); + android.hardware.automotive.occupant_awareness.OccupantAwarenessStatus stopDetection(); + int getCapabilityForRole(in android.hardware.automotive.occupant_awareness.Role occupantRole); + android.hardware.automotive.occupant_awareness.OccupantAwarenessStatus getState(in android.hardware.automotive.occupant_awareness.Role occupantRole, in int detectionCapability); + void setCallback(in android.hardware.automotive.occupant_awareness.IOccupantAwarenessClientCallback callback); + void getLatestDetection(out android.hardware.automotive.occupant_awareness.OccupantDetections detections); + const int CAP_NONE = 0; + const int CAP_PRESENCE_DETECTION = 1; + const int CAP_GAZE_DETECTION = 2; + const int CAP_DRIVER_MONITORING_DETECTION = 4; +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/IOccupantAwarenessClientCallback.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/IOccupantAwarenessClientCallback.aidl new file mode 100644 index 0000000000..31b8220d5f --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/IOccupantAwarenessClientCallback.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@VintfStability +interface IOccupantAwarenessClientCallback { + oneway void onSystemStatusChanged(in int detectionFlags, in android.hardware.automotive.occupant_awareness.OccupantAwarenessStatus status); + oneway void onDetectionEvent(in android.hardware.automotive.occupant_awareness.OccupantDetections detections); +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/OccupantAwarenessStatus.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/OccupantAwarenessStatus.aidl new file mode 100644 index 0000000000..26371c9573 --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/OccupantAwarenessStatus.aidl @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@Backing(type="byte") @VintfStability +enum OccupantAwarenessStatus { + READY = 0, + NOT_SUPPORTED = 1, + NOT_INITIALIZED = 2, + FAILURE = 3, +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/OccupantDetection.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/OccupantDetection.aidl new file mode 100644 index 0000000000..42b109550e --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/OccupantDetection.aidl @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@VintfStability +parcelable OccupantDetection { + android.hardware.automotive.occupant_awareness.Role role; + android.hardware.automotive.occupant_awareness.PresenceDetection[] presenceData; + android.hardware.automotive.occupant_awareness.GazeDetection[] gazeData; + android.hardware.automotive.occupant_awareness.DriverMonitoringDetection[] attentionData; +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/OccupantDetections.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/OccupantDetections.aidl new file mode 100644 index 0000000000..9bcad6b4fc --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/OccupantDetections.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@VintfStability +parcelable OccupantDetections { + long timeStampMillis; + android.hardware.automotive.occupant_awareness.OccupantDetection[] detections; +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/PresenceDetection.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/PresenceDetection.aidl new file mode 100644 index 0000000000..dd6c5c8950 --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/PresenceDetection.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@VintfStability +parcelable PresenceDetection { + boolean isOccupantDetected; + long detectionDurationMillis; +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/Role.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/Role.aidl new file mode 100644 index 0000000000..7b2b9077e0 --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/Role.aidl @@ -0,0 +1,35 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@Backing(type="int") @VintfStability +enum Role { + INVALID = 0, + UNKNOWN = 1, + FRONT_PASSENGER = 2, + DRIVER = 4, + ROW_2_PASSENGER_LEFT = 8, + ROW_2_PASSENGER_CENTER = 16, + ROW_2_PASSENGER_RIGHT = 32, + ROW_3_PASSENGER_LEFT = 64, + ROW_3_PASSENGER_CENTER = 128, + ROW_3_PASSENGER_RIGHT = 256, + FRONT_OCCUPANTS = 6, + ROW_2_OCCUPANTS = 56, + ROW_3_OCCUPANTS = 448, + ALL_OCCUPANTS = 511, +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/VehicleRegion.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/VehicleRegion.aidl new file mode 100644 index 0000000000..f96d950bf1 --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/1/android/hardware/automotive/occupant_awareness/VehicleRegion.aidl @@ -0,0 +1,31 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@Backing(type="int") @VintfStability +enum VehicleRegion { + UNKNOWN = 0, + INSTRUMENT_CLUSTER = 1, + REAR_VIEW_MIRROR = 2, + LEFT_SIDE_MIRROR = 3, + RIGHT_SIDE_MIRROR = 4, + FORWARD_ROADWAY = 5, + LEFT_ROADWAY = 6, + RIGHT_ROADWAY = 7, + HEAD_UNIT_DISPLAY = 8, + CUSTOM_TARGET = 200, +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/ConfidenceLevel.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/ConfidenceLevel.aidl new file mode 100644 index 0000000000..6e40d79cab --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/ConfidenceLevel.aidl @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@Backing(type="byte") @VintfStability +enum ConfidenceLevel { + NONE = 0, + LOW = 1, + HIGH = 2, + MAX = 3, +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/DriverMonitoringDetection.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/DriverMonitoringDetection.aidl new file mode 100644 index 0000000000..51d2149aa3 --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/DriverMonitoringDetection.aidl @@ -0,0 +1,24 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@VintfStability +parcelable DriverMonitoringDetection { + android.hardware.automotive.occupant_awareness.ConfidenceLevel confidenceScore; + boolean isLookingOnRoad; + long gazeDurationMillis; +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/GazeDetection.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/GazeDetection.aidl new file mode 100644 index 0000000000..5ce14df9b8 --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/GazeDetection.aidl @@ -0,0 +1,28 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@VintfStability +parcelable GazeDetection { + android.hardware.automotive.occupant_awareness.ConfidenceLevel gazeConfidence; + double[] headPosition; + double[] headAngleUnitVector; + double[] gazeAngleUnitVector; + android.hardware.automotive.occupant_awareness.VehicleRegion gazeTarget; + String customGazeTarget; + long timeOnTargetMillis; +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/IOccupantAwareness.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/IOccupantAwareness.aidl new file mode 100644 index 0000000000..7faff00841 --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/IOccupantAwareness.aidl @@ -0,0 +1,31 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@VintfStability +interface IOccupantAwareness { + android.hardware.automotive.occupant_awareness.OccupantAwarenessStatus startDetection(); + android.hardware.automotive.occupant_awareness.OccupantAwarenessStatus stopDetection(); + int getCapabilityForRole(in android.hardware.automotive.occupant_awareness.Role occupantRole); + android.hardware.automotive.occupant_awareness.OccupantAwarenessStatus getState(in android.hardware.automotive.occupant_awareness.Role occupantRole, in int detectionCapability); + void setCallback(in android.hardware.automotive.occupant_awareness.IOccupantAwarenessClientCallback callback); + void getLatestDetection(out android.hardware.automotive.occupant_awareness.OccupantDetections detections); + const int CAP_NONE = 0; + const int CAP_PRESENCE_DETECTION = 1; + const int CAP_GAZE_DETECTION = 2; + const int CAP_DRIVER_MONITORING_DETECTION = 4; +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/IOccupantAwarenessClientCallback.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/IOccupantAwarenessClientCallback.aidl new file mode 100644 index 0000000000..31b8220d5f --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/IOccupantAwarenessClientCallback.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@VintfStability +interface IOccupantAwarenessClientCallback { + oneway void onSystemStatusChanged(in int detectionFlags, in android.hardware.automotive.occupant_awareness.OccupantAwarenessStatus status); + oneway void onDetectionEvent(in android.hardware.automotive.occupant_awareness.OccupantDetections detections); +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/OccupantAwarenessStatus.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/OccupantAwarenessStatus.aidl new file mode 100644 index 0000000000..26371c9573 --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/OccupantAwarenessStatus.aidl @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@Backing(type="byte") @VintfStability +enum OccupantAwarenessStatus { + READY = 0, + NOT_SUPPORTED = 1, + NOT_INITIALIZED = 2, + FAILURE = 3, +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/OccupantDetection.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/OccupantDetection.aidl new file mode 100644 index 0000000000..42b109550e --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/OccupantDetection.aidl @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@VintfStability +parcelable OccupantDetection { + android.hardware.automotive.occupant_awareness.Role role; + android.hardware.automotive.occupant_awareness.PresenceDetection[] presenceData; + android.hardware.automotive.occupant_awareness.GazeDetection[] gazeData; + android.hardware.automotive.occupant_awareness.DriverMonitoringDetection[] attentionData; +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/OccupantDetections.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/OccupantDetections.aidl new file mode 100644 index 0000000000..9bcad6b4fc --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/OccupantDetections.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@VintfStability +parcelable OccupantDetections { + long timeStampMillis; + android.hardware.automotive.occupant_awareness.OccupantDetection[] detections; +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/PresenceDetection.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/PresenceDetection.aidl new file mode 100644 index 0000000000..dd6c5c8950 --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/PresenceDetection.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@VintfStability +parcelable PresenceDetection { + boolean isOccupantDetected; + long detectionDurationMillis; +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/Role.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/Role.aidl new file mode 100644 index 0000000000..7b2b9077e0 --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/Role.aidl @@ -0,0 +1,35 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@Backing(type="int") @VintfStability +enum Role { + INVALID = 0, + UNKNOWN = 1, + FRONT_PASSENGER = 2, + DRIVER = 4, + ROW_2_PASSENGER_LEFT = 8, + ROW_2_PASSENGER_CENTER = 16, + ROW_2_PASSENGER_RIGHT = 32, + ROW_3_PASSENGER_LEFT = 64, + ROW_3_PASSENGER_CENTER = 128, + ROW_3_PASSENGER_RIGHT = 256, + FRONT_OCCUPANTS = 6, + ROW_2_OCCUPANTS = 56, + ROW_3_OCCUPANTS = 448, + ALL_OCCUPANTS = 511, +} diff --git a/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/VehicleRegion.aidl b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/VehicleRegion.aidl new file mode 100644 index 0000000000..f96d950bf1 --- /dev/null +++ b/automotive/occupant_awareness/aidl/aidl_api/android.hardware.automotive.occupant_awareness/current/android/hardware/automotive/occupant_awareness/VehicleRegion.aidl @@ -0,0 +1,31 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.automotive.occupant_awareness; +@Backing(type="int") @VintfStability +enum VehicleRegion { + UNKNOWN = 0, + INSTRUMENT_CLUSTER = 1, + REAR_VIEW_MIRROR = 2, + LEFT_SIDE_MIRROR = 3, + RIGHT_SIDE_MIRROR = 4, + FORWARD_ROADWAY = 5, + LEFT_ROADWAY = 6, + RIGHT_ROADWAY = 7, + HEAD_UNIT_DISPLAY = 8, + CUSTOM_TARGET = 200, +} diff --git a/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/ConfidenceLevel.aidl b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/ConfidenceLevel.aidl new file mode 100644 index 0000000000..8597ddbbd5 --- /dev/null +++ b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/ConfidenceLevel.aidl @@ -0,0 +1,40 @@ +/* + * 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.occupant_awareness; + +@VintfStability +@Backing(type="byte") +enum ConfidenceLevel { + /** + * No prediction could be made. + */ + NONE, + /** + * Best-guess, low-confidence prediction. Predictions exceeding this threshold are adequate + * for non-critical applications. + */ + LOW, + /** + * High-confidence prediction. Predictions exceeding this threshold are adequate for + * applications that require reliable predictions. + */ + HIGH, + /** + * Highest confidence rate achievable. + */ + MAX, +} diff --git a/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/DriverMonitoringDetection.aidl b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/DriverMonitoringDetection.aidl new file mode 100644 index 0000000000..f5cc9d5a42 --- /dev/null +++ b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/DriverMonitoringDetection.aidl @@ -0,0 +1,36 @@ +/* + * 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.occupant_awareness; + +import android.hardware.automotive.occupant_awareness.ConfidenceLevel; + +@VintfStability +parcelable DriverMonitoringDetection { + /* + * Confidence of the computed attention data. + */ + ConfidenceLevel confidenceScore; + /* + * Is the driver currently looking on-road? + */ + boolean isLookingOnRoad; + /* + * Duration the driver has been looking on or off road, in milliseconds. + */ + long gazeDurationMillis; +} + diff --git a/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/GazeDetection.aidl b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/GazeDetection.aidl new file mode 100644 index 0000000000..fc36e13904 --- /dev/null +++ b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/GazeDetection.aidl @@ -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.automotive.occupant_awareness; + +import android.hardware.automotive.occupant_awareness.VehicleRegion; +import android.hardware.automotive.occupant_awareness.ConfidenceLevel; + +@VintfStability +parcelable GazeDetection { + /* + * Confidence level for the gaze detection. + */ + ConfidenceLevel gazeConfidence; + /* + * Head position, in millimeters. The vehicle coordinate system is specified in the cabin space + * configuration. headPosition is double[3] array. + */ + double[] headPosition; + /* + * Unit vector for the head pose direction. The vehicle coordinate system is specified in the + * cabin space configuration. headAngleUnitVector is double[3] array. + */ + double[] headAngleUnitVector; + /* + * Unit vector for the gaze direction. The vehicle coordinate system is specified in the cabin + * space configuration. gazeAngleUnitVector is double[3] array. + */ + double[] gazeAngleUnitVector; + /* + * Current gaze target. + */ + VehicleRegion gazeTarget; + /* + * Custom gaze target string. This only need to be populated when gazeTarget is CUSTOM_TARGET. + * Vendors should use "com.vendor_name.target_name" format to avoid name collision with other + * vendors. + */ + String customGazeTarget; + /* + * Duration that the subject has been looking at the current gaze target in milliseconds. + */ + long timeOnTargetMillis; +} diff --git a/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/IOccupantAwareness.aidl b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/IOccupantAwareness.aidl new file mode 100644 index 0000000000..1df0bda469 --- /dev/null +++ b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/IOccupantAwareness.aidl @@ -0,0 +1,88 @@ +/* + * 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.occupant_awareness; + +import android.hardware.automotive.occupant_awareness.OccupantAwarenessStatus; +import android.hardware.automotive.occupant_awareness.Role; +import android.hardware.automotive.occupant_awareness.IOccupantAwarenessClientCallback; +import android.hardware.automotive.occupant_awareness.OccupantDetections; + +@VintfStability +interface IOccupantAwareness { + /* + * System not able to generate any occupancy awareness. + */ + const int CAP_NONE = 0; + /* + * System is able to detect the presence of humans. + */ + const int CAP_PRESENCE_DETECTION = 1 << 0; + /* + * System is able to detect the gaze of humans. + */ + const int CAP_GAZE_DETECTION = 1 << 1; + /* + * System is able to compute the attention details of humans. + */ + const int CAP_DRIVER_MONITORING_DETECTION = 1 << 2; + + /** + * Starts the occupant awareness detection system. This is a non-blocking function call. + * Once system is ready, state will be modified. State update can be inrquired using callback + * or getState() function. + * @return status is the current system state. + */ + OccupantAwarenessStatus startDetection(); + + /** + * Stops the occupant awareness detection system. This is a non-blocking function call. + * Once system is reset, state will be modified. State update can be inrquired using callback + * or getState() function. + * @return status is the current system state. + */ + OccupantAwarenessStatus stopDetection(); + + /** + * Returns list of Awareness Capability supported for the given type of occupants. + * + * @param out Bitwise OR of supported capabilities (CAP_* mask). + */ + int getCapabilityForRole(in Role occupantRole); + + /** + * Inquires the current state of the occupant awareness system. + * @param occupantRole specifies the role of occupants of interest. + * @param detectionCapability specifies a single detection capability (CAP_* ) of interest. + * + * @return status is the current system state. + */ + OccupantAwarenessStatus getState(in Role occupantRole, in int detectionCapability); + + /** + * Registers a callback for data streaming. Only single callback is supported. setCallback() + * should replace existing callback with new callback. + * @return: returns ok if successful. + */ + void setCallback(in IOccupantAwarenessClientCallback callback); + + /** + * Returns the most recent set of detections. + * @param OccupantDetections output detections. + * @return returns ok if successful. + */ + void getLatestDetection(out OccupantDetections detections); +} diff --git a/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/IOccupantAwarenessClientCallback.aidl b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/IOccupantAwarenessClientCallback.aidl new file mode 100644 index 0000000000..69f7b0b082 --- /dev/null +++ b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/IOccupantAwarenessClientCallback.aidl @@ -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.automotive.occupant_awareness; + +import android.hardware.automotive.occupant_awareness.OccupantAwarenessStatus; +import android.hardware.automotive.occupant_awareness.OccupantDetections; + +@VintfStability +interface IOccupantAwarenessClientCallback { + /** + * A callback invoked when the system status changes. + * + * @param detectionFlags The detection subsystem(s) whose status has changed. + * @param status The new system status. + */ + oneway void onSystemStatusChanged(in int detectionFlags, in OccupantAwarenessStatus status); + + /** + * A callback invoked when a new set of detections are available. + * + * @param detections Occupant detections. + */ + oneway void onDetectionEvent(in OccupantDetections detections); +} diff --git a/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/OccupantAwarenessStatus.aidl b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/OccupantAwarenessStatus.aidl new file mode 100644 index 0000000000..6a3453d5aa --- /dev/null +++ b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/OccupantAwarenessStatus.aidl @@ -0,0 +1,41 @@ +/* + * 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.occupant_awareness; + +@VintfStability +@Backing(type="byte") +enum OccupantAwarenessStatus { + /* + * System is online and ready to serve requests. + */ + READY = 0, + /** + * Detection is not supported in this vehicle due to a permanent lack of capabilities. Clients + * need not retry. + */ + NOT_SUPPORTED = 1, + /* + * The system has not yet been initialized. No requests can be served until the + * initialization process completes. This state does not indicate any error and + * clients should retry later. + */ + NOT_INITIALIZED = 2, + /* + * A permanent failure has occurred. No detections will be provided. + */ + FAILURE = 3, +} diff --git a/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/OccupantDetection.aidl b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/OccupantDetection.aidl new file mode 100644 index 0000000000..47ca35a6b1 --- /dev/null +++ b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/OccupantDetection.aidl @@ -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.occupant_awareness; + +import android.hardware.automotive.occupant_awareness.Role; +import android.hardware.automotive.occupant_awareness.PresenceDetection; +import android.hardware.automotive.occupant_awareness.GazeDetection; +import android.hardware.automotive.occupant_awareness.DriverMonitoringDetection; + +/* + * A complete detection for a single occupant in the vehicle. Includes data about the subject's + * presence in the vehicle, gaze and attention. + */ + @VintfStability +parcelable OccupantDetection { + /* + * Role of the occupant (e.g., driver, passenger). + */ + Role role; + /* + * Occupant presence state for a single occupant. + * If the vector is empty, no data could be generated. + */ + PresenceDetection[] presenceData; + /* + * Gaze data for a single occupant. + * If the vector is empty, no data could be generated. + */ + GazeDetection[] gazeData; + /* + * Attention data for a single occupant. + * If the vector is empty, no data could be generated. + */ + DriverMonitoringDetection[] attentionData; +} + diff --git a/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/OccupantDetections.aidl b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/OccupantDetections.aidl new file mode 100644 index 0000000000..e8f6621cd2 --- /dev/null +++ b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/OccupantDetections.aidl @@ -0,0 +1,34 @@ +/* + * 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.occupant_awareness; + +import android.hardware.automotive.occupant_awareness.OccupantDetection; + +@VintfStability +parcelable OccupantDetections { + /** + * Timestamp that the underlying source image was captured, in milliseconds since Jan 1, 1970 + * (Unix time). + */ + long timeStampMillis; + /** + * A vector of detections for all occupants in the vehicle. One OccupantDetection will be + * generated per detected face. + */ + OccupantDetection[] detections; +} + diff --git a/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/PresenceDetection.aidl b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/PresenceDetection.aidl new file mode 100644 index 0000000000..a018106280 --- /dev/null +++ b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/PresenceDetection.aidl @@ -0,0 +1,31 @@ +/* + * 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.occupant_awareness; + +@VintfStability +parcelable PresenceDetection { + /* + * Boolean representing whether an occupant was detected. + */ + boolean isOccupantDetected; + /** + * Duration that a particular occupant has been continuously + * detected, in milliseconds. Will be zero duration if the occupant is not + * currently detected. + */ + long detectionDurationMillis; +} diff --git a/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/Role.aidl b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/Role.aidl new file mode 100644 index 0000000000..42e86ad3a5 --- /dev/null +++ b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/Role.aidl @@ -0,0 +1,83 @@ +/* + * 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.occupant_awareness; + +@VintfStability +@Backing(type="int") +enum Role { + /* + * All valid role(s) must have at least 1 bit set. + */ + INVALID = 0, + /* + * System could not determine role for this occupant. + */ + UNKNOWN = 1 << 0, + /* + * Occupants that the system detects as front seat passengers. + */ + FRONT_PASSENGER = 1 << 1, + /* + * Occupants that the system detects as driver(s). + */ + DRIVER = 1 << 2, + /* + * Occupants on left seat of row 2. + */ + ROW_2_PASSENGER_LEFT = 1 << 3, + /* + * Occupants on center seat of row 2. + */ + ROW_2_PASSENGER_CENTER = 1 << 4, + /* + * Occupants on right seat of row 2. + */ + ROW_2_PASSENGER_RIGHT = 1 << 5, + /* + * Occupants on left seat of row 3. + */ + ROW_3_PASSENGER_LEFT = 1 << 6, + /* + * Occupants on center seat of row 3. + */ + ROW_3_PASSENGER_CENTER = 1 << 7, + /* + * Occupants on right seat of row 3. + */ + ROW_3_PASSENGER_RIGHT = 1 << 8, + + /* + * Occupants that the system detects as front seat occupant. + * FRONT_OCCUPANTS = DRIVER | FRONT_PASSENGER + */ + FRONT_OCCUPANTS = 1 << 1 | 1 << 2, + /* + * Occupants of row 2. + * ROW_2_OCCUPANTS = ROW_2_PASSENGER_LEFT | ROW_2_PASSENGER_CENTER | ROW_2_PASSENGER_RIGHT + */ + ROW_2_OCCUPANTS = 1 << 3 | 1 << 4 | 1 << 5, + /* + * Occupants of row 3. + * ROW_3_OCCUPANTS = ROW_3_PASSENGER_LEFT | ROW_3_PASSENGER_CENTER | ROW_3_PASSENGER_RIGHT + */ + ROW_3_OCCUPANTS = 1 << 6 | 1 << 7 | 1 << 8, + /* + * All the occupants in the vehicle. + * ALL_OCCUPANTS = UNKNOWN | FRONT_OCCUPANTS | ROW_2_OCCUPANTS | ROW_3_OCCUPANTS + */ + ALL_OCCUPANTS = 0x1FF, +} diff --git a/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/VehicleRegion.aidl b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/VehicleRegion.aidl new file mode 100644 index 0000000000..9ee9d52f39 --- /dev/null +++ b/automotive/occupant_awareness/aidl/android/hardware/automotive/occupant_awareness/VehicleRegion.aidl @@ -0,0 +1,39 @@ +/* + * 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.occupant_awareness; + +@VintfStability +@Backing(type="int") +enum VehicleRegion { + /* + * List of targets in the car. + */ + UNKNOWN = 0, + INSTRUMENT_CLUSTER = 1, + REAR_VIEW_MIRROR = 2, + LEFT_SIDE_MIRROR = 3, + RIGHT_SIDE_MIRROR = 4, + FORWARD_ROADWAY = 5, + LEFT_ROADWAY = 6, + RIGHT_ROADWAY = 7, + HEAD_UNIT_DISPLAY = 8, + /* + * Vendors can use this value along with customGazeTarget string to uniquely identify their + * custom region. + */ + CUSTOM_TARGET = 200, +} diff --git a/automotive/occupant_awareness/aidl/default/Android.bp b/automotive/occupant_awareness/aidl/default/Android.bp new file mode 100644 index 0000000000..1b2fba2ea4 --- /dev/null +++ b/automotive/occupant_awareness/aidl/default/Android.bp @@ -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. + */ + +cc_binary { + name: "android.hardware.automotive.occupant_awareness@1.0-service", + init_rc: ["android.hardware.automotive.occupant_awareness@1.0-service.rc"], + relative_install_path: "hw", + vendor: true, + srcs: [ + "service.cpp", + "OccupantAwareness.cpp", + ], + shared_libs: [ + "libbase", + "libbinder_ndk", + "libutils", + "android.hardware.automotive.occupant_awareness-ndk_platform", + ], +} diff --git a/automotive/occupant_awareness/aidl/default/OccupantAwareness.cpp b/automotive/occupant_awareness/aidl/default/OccupantAwareness.cpp new file mode 100644 index 0000000000..a156075c09 --- /dev/null +++ b/automotive/occupant_awareness/aidl/default/OccupantAwareness.cpp @@ -0,0 +1,122 @@ +/* + * 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 "OccupantAwareness.h" + +namespace android { +namespace hardware { +namespace automotive { +namespace occupant_awareness { +namespace V1_0 { +namespace implementation { + +using ndk::ScopedAStatus; + +static const int32_t kAllCapabilities = OccupantAwareness::CAP_PRESENCE_DETECTION | + OccupantAwareness::CAP_GAZE_DETECTION | + OccupantAwareness::CAP_DRIVER_MONITORING_DETECTION; + +ScopedAStatus OccupantAwareness::startDetection(OccupantAwarenessStatus* status) { + std::lock_guard lock(mMutex); + if (mStatus != OccupantAwarenessStatus::NOT_SUPPORTED) { + mStatus = OccupantAwarenessStatus::NOT_SUPPORTED; + if (mCallback) { + mCallback->onSystemStatusChanged(kAllCapabilities, + OccupantAwarenessStatus::NOT_SUPPORTED); + } + } + *status = mStatus; + return ScopedAStatus::ok(); +} + +ScopedAStatus OccupantAwareness::stopDetection(OccupantAwarenessStatus* status) { + std::lock_guard lock(mMutex); + if (mStatus != OccupantAwarenessStatus::NOT_INITIALIZED) { + mStatus = OccupantAwarenessStatus::NOT_INITIALIZED; + if (mCallback) { + mCallback->onSystemStatusChanged(kAllCapabilities, + OccupantAwarenessStatus::NOT_INITIALIZED); + } + } + *status = mStatus; + return ScopedAStatus::ok(); +} + +ScopedAStatus OccupantAwareness::getCapabilityForRole(Role occupantRole, int32_t* capabilities) { + if (!isValidRole(occupantRole)) { + return ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED); + } + + // No awareness capability for default HAL. + *capabilities = 0; + return ScopedAStatus::ok(); +} + +ScopedAStatus OccupantAwareness::getState(Role occupantRole, int detectionCapability, + OccupantAwarenessStatus* status) { + if (!isValidRole(occupantRole)) { + return ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED); + } + + if (!isValidDetectionCapabilities(detectionCapability) || + !isSingularCapability(detectionCapability)) { + return ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED); + } + + std::lock_guard lock(mMutex); + *status = mStatus; + return ScopedAStatus::ok(); +} + +ScopedAStatus OccupantAwareness::setCallback( + const std::shared_ptr& callback) { + if (callback == nullptr) { + return ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED); + } + + std::lock_guard lock(mMutex); + mCallback = callback; + return ScopedAStatus::ok(); +} + +ScopedAStatus OccupantAwareness::getLatestDetection(OccupantDetections* detections) { + // No detection generated for default hal. + (void)detections; + return ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED); +} + +bool OccupantAwareness::isValidRole(Role occupantRole) { + int intVal = static_cast(occupantRole); + int allOccupants = static_cast(Role::ALL_OCCUPANTS); + return (occupantRole != Role::INVALID) && ((intVal & (~allOccupants)) == 0); +} + +bool OccupantAwareness::isValidDetectionCapabilities(int detectionCapabilities) { + return (detectionCapabilities != CAP_NONE) && + ((detectionCapabilities & (~kAllCapabilities)) == 0); +} + +bool OccupantAwareness::isSingularCapability(int detectionCapability) { + // Check whether the value is 0, or the value has only one bit set. + return (detectionCapability & (detectionCapability - 1)) == 0; +} + +} // namespace implementation +} // namespace V1_0 +} // namespace occupant_awareness +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/occupant_awareness/aidl/default/OccupantAwareness.h b/automotive/occupant_awareness/aidl/default/OccupantAwareness.h new file mode 100644 index 0000000000..ac51aa4327 --- /dev/null +++ b/automotive/occupant_awareness/aidl/default/OccupantAwareness.h @@ -0,0 +1,67 @@ +/* + * 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 +#include +#include + +namespace android { +namespace hardware { +namespace automotive { +namespace occupant_awareness { +namespace V1_0 { +namespace implementation { + +using ::aidl::android::hardware::automotive::occupant_awareness::BnOccupantAwareness; +using ::aidl::android::hardware::automotive::occupant_awareness::IOccupantAwarenessClientCallback; +using ::aidl::android::hardware::automotive::occupant_awareness::OccupantAwarenessStatus; +using ::aidl::android::hardware::automotive::occupant_awareness::OccupantDetections; +using ::aidl::android::hardware::automotive::occupant_awareness::Role; + +/** + * The default HAL mimics a system which has no Occupant awareness capability. The hal does not + * do any useful work, and returns appropriate failure code / status. + **/ +class OccupantAwareness : public BnOccupantAwareness { + public: + // Methods from ::android::hardware::automotive::occupant_awareness::IOccupantAwareness + // follow. + ndk::ScopedAStatus startDetection(OccupantAwarenessStatus* status) override; + ndk::ScopedAStatus stopDetection(OccupantAwarenessStatus* status) override; + ndk::ScopedAStatus getCapabilityForRole(Role occupantRole, int32_t* capabilities) override; + ndk::ScopedAStatus getState(Role occupantRole, int detectionCapability, + OccupantAwarenessStatus* status) override; + ndk::ScopedAStatus setCallback( + const std::shared_ptr& callback) override; + ndk::ScopedAStatus getLatestDetection(OccupantDetections* detections) override; + + private: + bool isValidRole(Role occupantRole); + bool isValidDetectionCapabilities(int detectionCapabilities); + bool isSingularCapability(int detectionCapability); + + std::mutex mMutex; + std::shared_ptr mCallback = nullptr; + OccupantAwarenessStatus mStatus = OccupantAwarenessStatus::NOT_INITIALIZED; +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace occupant_awareness +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/occupant_awareness/aidl/default/android.hardware.automotive.occupant_awareness@1.0-service.rc b/automotive/occupant_awareness/aidl/default/android.hardware.automotive.occupant_awareness@1.0-service.rc new file mode 100644 index 0000000000..35d5bca66c --- /dev/null +++ b/automotive/occupant_awareness/aidl/default/android.hardware.automotive.occupant_awareness@1.0-service.rc @@ -0,0 +1,4 @@ +service hal_occupant_awareness_default /vendor/bin/hw/android.hardware.automotive.occupant_awareness@1.0-service + class hal + user system + group system diff --git a/automotive/occupant_awareness/aidl/default/service.cpp b/automotive/occupant_awareness/aidl/default/service.cpp new file mode 100644 index 0000000000..44960fb13d --- /dev/null +++ b/automotive/occupant_awareness/aidl/default/service.cpp @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#define LOG_TAG "android.hardware.automotive.occupant_awareness@1.0-service" + +#include + +#include +#include +#include + +#include "OccupantAwareness.h" + +using ::aidl::android::hardware::automotive::occupant_awareness::IOccupantAwareness; +using ::android::hardware::automotive::occupant_awareness::V1_0::implementation::OccupantAwareness; +using ::ndk::ScopedAStatus; +using ::ndk::SharedRefBase; + +const static char kOccupantAwarenessServiceName[] = "default"; + +int main() { + ABinderProcess_setThreadPoolMaxThreadCount(0); + LOG(INFO) << "Occupant Awareness service is starting"; + std::shared_ptr occupantAwareness = SharedRefBase::make(); + + const std::string instance = + std::string() + IOccupantAwareness::descriptor + "/" + kOccupantAwarenessServiceName; + + binder_status_t status = + AServiceManager_addService(occupantAwareness->asBinder().get(), instance.c_str()); + if (status == STATUS_OK) { + LOG(INFO) << "Service " << kOccupantAwarenessServiceName << " is ready"; + ABinderProcess_joinThreadPool(); + } else { + LOG(ERROR) << "Could not register service " << kOccupantAwarenessServiceName + << ", status: " << status; + } + + // In normal operation, we don't expect the thread pool to exit. + LOG(ERROR) << "Occupant Awareness service is shutting down"; + return 1; +} diff --git a/automotive/occupant_awareness/aidl/mock/Android.bp b/automotive/occupant_awareness/aidl/mock/Android.bp new file mode 100644 index 0000000000..4b3086660c --- /dev/null +++ b/automotive/occupant_awareness/aidl/mock/Android.bp @@ -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. + */ + +cc_binary { + name: "android.hardware.automotive.occupant_awareness@1.0-service_mock", + relative_install_path: "hw", + vendor: true, + srcs: [ + "service.cpp", + "OccupantAwareness.cpp", + "DetectionGenerator.cpp", + ], + shared_libs: [ + "libbase", + "libbinder_ndk", + "libutils", + "android.hardware.automotive.occupant_awareness-ndk_platform", + ], +} diff --git a/automotive/occupant_awareness/aidl/mock/DetectionGenerator.cpp b/automotive/occupant_awareness/aidl/mock/DetectionGenerator.cpp new file mode 100644 index 0000000000..79d4dbc25c --- /dev/null +++ b/automotive/occupant_awareness/aidl/mock/DetectionGenerator.cpp @@ -0,0 +1,71 @@ +/* + * 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 + +#include "DetectionGenerator.h" + +namespace android { +namespace hardware { +namespace automotive { +namespace occupant_awareness { +namespace V1_0 { +namespace implementation { + +using ::aidl::android::hardware::automotive::occupant_awareness::ConfidenceLevel; +using ::aidl::android::hardware::automotive::occupant_awareness::DriverMonitoringDetection; +using ::aidl::android::hardware::automotive::occupant_awareness::OccupantDetection; +using ::aidl::android::hardware::automotive::occupant_awareness::PresenceDetection; + +static int64_t kNanoSecondsPerMilliSecond = 1000 * 1000; + +OccupantDetections DetectionGenerator::GetNextDetections() { + OccupantDetections detections; + detections.timeStampMillis = android::elapsedRealtimeNano() / kNanoSecondsPerMilliSecond; + int remainingRoles = getSupportedRoles(); + while (remainingRoles) { + int currentRole = remainingRoles & (~(remainingRoles - 1)); + remainingRoles = remainingRoles & (remainingRoles - 1); + + OccupantDetection occupantDetection; + occupantDetection.role = static_cast(currentRole); + + // Add presence detection object for this occupant. + PresenceDetection presenceDetection; + presenceDetection.isOccupantDetected = true; + presenceDetection.detectionDurationMillis = detections.timeStampMillis; + occupantDetection.presenceData.emplace_back(presenceDetection); + + if (occupantDetection.role == Role::DRIVER) { + // Add driver monitoring detection object for this occupant. + DriverMonitoringDetection driverMonitoringDetection; + driverMonitoringDetection.confidenceScore = ConfidenceLevel::HIGH; + driverMonitoringDetection.isLookingOnRoad = 0; + driverMonitoringDetection.gazeDurationMillis = detections.timeStampMillis; + occupantDetection.attentionData.emplace_back(driverMonitoringDetection); + } + + detections.detections.emplace_back(occupantDetection); + } + return detections; +} + +} // namespace implementation +} // namespace V1_0 +} // namespace occupant_awareness +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/occupant_awareness/aidl/mock/DetectionGenerator.h b/automotive/occupant_awareness/aidl/mock/DetectionGenerator.h new file mode 100644 index 0000000000..0884685124 --- /dev/null +++ b/automotive/occupant_awareness/aidl/mock/DetectionGenerator.h @@ -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. + */ + +#pragma once + +#include + +namespace android { +namespace hardware { +namespace automotive { +namespace occupant_awareness { +namespace V1_0 { +namespace implementation { + +using ::aidl::android::hardware::automotive::occupant_awareness::BnOccupantAwareness; +using ::aidl::android::hardware::automotive::occupant_awareness::OccupantDetections; +using ::aidl::android::hardware::automotive::occupant_awareness::Role; + +class DetectionGenerator { + public: + static int getSupportedRoles() { + return static_cast(Role::DRIVER) | static_cast(Role::FRONT_PASSENGER); + } + static int getSupportedCapabilities() { + return static_cast(BnOccupantAwareness::CAP_PRESENCE_DETECTION) | + static_cast(BnOccupantAwareness::CAP_DRIVER_MONITORING_DETECTION); + } + + OccupantDetections GetNextDetections(); +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace occupant_awareness +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/occupant_awareness/aidl/mock/OccupantAwareness.cpp b/automotive/occupant_awareness/aidl/mock/OccupantAwareness.cpp new file mode 100644 index 0000000000..910760a88c --- /dev/null +++ b/automotive/occupant_awareness/aidl/mock/OccupantAwareness.cpp @@ -0,0 +1,176 @@ +/* + * 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 + +#include "OccupantAwareness.h" + +namespace android { +namespace hardware { +namespace automotive { +namespace occupant_awareness { +namespace V1_0 { +namespace implementation { + +using ndk::ScopedAStatus; + +static const int32_t kAllCapabilities = OccupantAwareness::CAP_PRESENCE_DETECTION | + OccupantAwareness::CAP_GAZE_DETECTION | + OccupantAwareness::CAP_DRIVER_MONITORING_DETECTION; + +constexpr int64_t kNanoSecondsPerMilliSecond = 1000 * 1000; + +ScopedAStatus OccupantAwareness::startDetection(OccupantAwarenessStatus* status) { + std::lock_guard lock(mMutex); + if (mStatus != OccupantAwarenessStatus::NOT_INITIALIZED) { + return ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED); + } + + mStatus = OccupantAwarenessStatus::READY; + mWorkerThread = std::thread(startWorkerThread, this); + if (mCallback) { + mCallback->onSystemStatusChanged(kAllCapabilities, mStatus); + } + + *status = mStatus; + return ScopedAStatus::ok(); +} + +ScopedAStatus OccupantAwareness::stopDetection(OccupantAwarenessStatus* status) { + std::lock_guard lock(mMutex); + if (mStatus != OccupantAwarenessStatus::READY) { + return ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED); + } + + mStatus = OccupantAwarenessStatus::NOT_INITIALIZED; + mWorkerThread.join(); + if (mCallback) { + mCallback->onSystemStatusChanged(kAllCapabilities, mStatus); + } + + *status = mStatus; + return ScopedAStatus::ok(); +} + +ScopedAStatus OccupantAwareness::getCapabilityForRole(Role occupantRole, int32_t* capabilities) { + if (!isValidRole(occupantRole)) { + return ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED); + } + + int intVal = static_cast(occupantRole); + if ((intVal & DetectionGenerator::getSupportedRoles()) == intVal) { + int capabilities_ = DetectionGenerator::getSupportedCapabilities(); + if (occupantRole != Role::DRIVER) { + capabilities_ &= ~CAP_DRIVER_MONITORING_DETECTION; + } + *capabilities = capabilities_; + } else { + *capabilities = 0; + } + + return ScopedAStatus::ok(); +} + +ScopedAStatus OccupantAwareness::getState(Role occupantRole, int detectionCapability, + OccupantAwarenessStatus* status) { + if (!isValidRole(occupantRole)) { + return ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED); + } + + if (!isValidDetectionCapabilities(detectionCapability) || + !isSingularCapability(detectionCapability)) { + return ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED); + } + + int roleVal = static_cast(occupantRole); + + if (((roleVal & DetectionGenerator::getSupportedRoles()) != roleVal) || + ((detectionCapability & DetectionGenerator::getSupportedCapabilities()) != + detectionCapability)) { + *status = OccupantAwarenessStatus::NOT_SUPPORTED; + return ScopedAStatus::ok(); + } + + std::lock_guard lock(mMutex); + *status = mStatus; + return ScopedAStatus::ok(); +} + +ScopedAStatus OccupantAwareness::setCallback( + const std::shared_ptr& callback) { + if (callback == nullptr) { + return ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED); + } + + std::lock_guard lock(mMutex); + mCallback = callback; + return ScopedAStatus::ok(); +} + +ScopedAStatus OccupantAwareness::getLatestDetection(OccupantDetections* detections) { + std::lock_guard lock(mMutex); + + if (mStatus != OccupantAwarenessStatus::READY) { + return ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED); + } + + *detections = mLatestDetections; + return ScopedAStatus::ok(); +} + +bool OccupantAwareness::isValidRole(Role occupantRole) { + int intVal = static_cast(occupantRole); + int allOccupants = static_cast(Role::ALL_OCCUPANTS); + return (occupantRole != Role::INVALID) && ((intVal & (~allOccupants)) == 0); +} + +bool OccupantAwareness::isValidDetectionCapabilities(int detectionCapabilities) { + return (detectionCapabilities != OccupantAwareness::CAP_NONE) && + ((detectionCapabilities & (~kAllCapabilities)) == 0); +} + +bool OccupantAwareness::isSingularCapability(int detectionCapability) { + // Check whether the value is 0, or the value has only one bit set. + return (detectionCapability & (detectionCapability - 1)) == 0; +} + +void OccupantAwareness::startWorkerThread(OccupantAwareness* occupantAwareness) { + occupantAwareness->workerThreadFunction(); +} + +void OccupantAwareness::workerThreadFunction() { + bool isFirstDetection = true; + int64_t prevDetectionTimeMs; + while (mStatus == OccupantAwarenessStatus::READY) { + int64_t currentTimeMs = android::elapsedRealtimeNano() / kNanoSecondsPerMilliSecond; + if ((isFirstDetection) || (currentTimeMs - prevDetectionTimeMs > mDetectionDurationMs)) { + std::lock_guard lock(mMutex); + mLatestDetections = mGenerator.GetNextDetections(); + if (mCallback != nullptr) { + mCallback->onDetectionEvent(mLatestDetections); + } + isFirstDetection = false; + prevDetectionTimeMs = currentTimeMs; + } + } +} + +} // namespace implementation +} // namespace V1_0 +} // namespace occupant_awareness +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/occupant_awareness/aidl/mock/OccupantAwareness.h b/automotive/occupant_awareness/aidl/mock/OccupantAwareness.h new file mode 100644 index 0000000000..c5f6dd637e --- /dev/null +++ b/automotive/occupant_awareness/aidl/mock/OccupantAwareness.h @@ -0,0 +1,83 @@ +/* + * 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 + +#include +#include +#include + +#include "DetectionGenerator.h" + +namespace android { +namespace hardware { +namespace automotive { +namespace occupant_awareness { +namespace V1_0 { +namespace implementation { + +using ::aidl::android::hardware::automotive::occupant_awareness::BnOccupantAwareness; +using ::aidl::android::hardware::automotive::occupant_awareness::IOccupantAwarenessClientCallback; +using ::aidl::android::hardware::automotive::occupant_awareness::OccupantAwarenessStatus; +using ::aidl::android::hardware::automotive::occupant_awareness::OccupantDetections; +using ::aidl::android::hardware::automotive::occupant_awareness::Role; + +/** + * The mock HAL can detect presence of Driver and front passenger, and driver awareness detection + * for driver. + **/ +class OccupantAwareness : public BnOccupantAwareness { + public: + // Methods from ::android::hardware::automotive::occupant_awareness::IOccupantAwareness + // follow. + ndk::ScopedAStatus startDetection(OccupantAwarenessStatus* status) override; + ndk::ScopedAStatus stopDetection(OccupantAwarenessStatus* status) override; + ndk::ScopedAStatus getCapabilityForRole(Role occupantRole, int32_t* capabilities) override; + ndk::ScopedAStatus getState(Role occupantRole, int detectionCapability, + OccupantAwarenessStatus* status) override; + ndk::ScopedAStatus setCallback( + const std::shared_ptr& callback) override; + ndk::ScopedAStatus getLatestDetection(OccupantDetections* detections) override; + + private: + bool isValidRole(Role occupantRole); + bool isValidDetectionCapabilities(int detectionCapabilities); + bool isSingularCapability(int detectionCapability); + + void workerThreadFunction(); + static void startWorkerThread(OccupantAwareness* occupantAwareness); + + std::mutex mMutex; + std::shared_ptr mCallback = nullptr; + OccupantAwarenessStatus mStatus = OccupantAwarenessStatus::NOT_INITIALIZED; + + OccupantDetections mLatestDetections; + std::thread mWorkerThread; + + DetectionGenerator mGenerator; + + // Generate a new detection every 1ms. + const int64_t mDetectionDurationMs = 1; +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace occupant_awareness +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/occupant_awareness/aidl/mock/service.cpp b/automotive/occupant_awareness/aidl/mock/service.cpp new file mode 100644 index 0000000000..d8860dff3d --- /dev/null +++ b/automotive/occupant_awareness/aidl/mock/service.cpp @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#define LOG_TAG "android.hardware.automotive.occupant_awareness@1.0-service_mock" + +#include + +#include +#include +#include + +#include "OccupantAwareness.h" + +using ::aidl::android::hardware::automotive::occupant_awareness::IOccupantAwareness; +using ::android::hardware::automotive::occupant_awareness::V1_0::implementation::OccupantAwareness; +using ::ndk::ScopedAStatus; +using ::ndk::SharedRefBase; + +const static char kOccupantAwarenessServiceName[] = "default"; + +int main() { + ABinderProcess_setThreadPoolMaxThreadCount(0); + LOG(INFO) << "Occupant Awareness service is starting"; + std::shared_ptr occupantAwareness = SharedRefBase::make(); + + const std::string instance = + std::string() + IOccupantAwareness::descriptor + "/" + kOccupantAwarenessServiceName; + + binder_status_t status = + AServiceManager_addService(occupantAwareness->asBinder().get(), instance.c_str()); + if (status == STATUS_OK) { + LOG(INFO) << "Service " << kOccupantAwarenessServiceName << " is ready"; + ABinderProcess_joinThreadPool(); + } else { + LOG(ERROR) << "Could not register service " << kOccupantAwarenessServiceName + << ", status: " << status; + } + + // In normal operation, we don't expect the thread pool to exit. + LOG(ERROR) << "Occupant Awareness service is shutting down"; + return 1; +} diff --git a/automotive/occupant_awareness/aidl/vts/functional/Android.bp b/automotive/occupant_awareness/aidl/vts/functional/Android.bp new file mode 100644 index 0000000000..1256b69c67 --- /dev/null +++ b/automotive/occupant_awareness/aidl/vts/functional/Android.bp @@ -0,0 +1,17 @@ +cc_test { + name: "VtsHalOccupantAwarenessV1_0TargetTest", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + srcs: ["VtsHalOccupantAwarenessV1_0TargetTest.cpp"], + shared_libs: [ + "libbinder", + ], + static_libs: [ + "android.hardware.automotive.occupant_awareness-cpp", + ], + test_suites: [ + "vts-core", + ], +} diff --git a/automotive/occupant_awareness/aidl/vts/functional/VtsHalOccupantAwarenessV1_0TargetTest.cpp b/automotive/occupant_awareness/aidl/vts/functional/VtsHalOccupantAwarenessV1_0TargetTest.cpp new file mode 100644 index 0000000000..c431f9d3b8 --- /dev/null +++ b/automotive/occupant_awareness/aidl/vts/functional/VtsHalOccupantAwarenessV1_0TargetTest.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 "**** HAL log ****" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace android::hardware::automotive::occupant_awareness; +using android::hardware::automotive::occupant_awareness::IOccupantAwareness; + +using android::ProcessState; +using android::sp; +using android::String16; +using android::binder::Status; + +constexpr auto kTimeout = std::chrono::seconds(3); + +#define EXPECT_OK(ret) ASSERT_TRUE((ret).isOk()) + +class OccupantAwarenessCallback : public BnOccupantAwarenessClientCallback { + public: + OccupantAwarenessCallback(const std::function& callback) + : mCallback(callback) {} + Status onSystemStatusChanged(int detectionFlags, OccupantAwarenessStatus status) override { + mCallback(detectionFlags, status); + return Status::ok(); + } + + Status onDetectionEvent(const OccupantDetections& detections) override { + (void)detections; + return Status::ok(); + } + + private: + std::function mCallback; +}; + +class OccupantAwarenessAidl : public testing::TestWithParam { + public: + virtual void SetUp() override { + mOccupantAwarenessService = + android::waitForDeclaredService(String16(GetParam().c_str())); + ASSERT_NE(mOccupantAwarenessService, nullptr); + } + + sp mOccupantAwarenessService; +}; + +// Test that startDetection() returns within the timeout. +TEST_P(OccupantAwarenessAidl, StartDetectionTest) { + auto start = std::chrono::system_clock::now(); + OccupantAwarenessStatus occupantAwarenessStatus; + Status status = mOccupantAwarenessService->startDetection(&occupantAwarenessStatus); + auto elapsed = std::chrono::system_clock::now() - start; + EXPECT_OK(status); + ASSERT_LE(elapsed, kTimeout); + + EXPECT_OK(mOccupantAwarenessService->stopDetection(&occupantAwarenessStatus)); +} + +// Test that getCapabilityForRole() returns supported capabilities for the role. The test only +// verifies that the IPC call returns successfully and does not verify the supported capabilities. +TEST_P(OccupantAwarenessAidl, GetCapabilityTest) { + std::vector rolesToTest = {Role::FRONT_PASSENGER, Role::DRIVER, + Role::ROW_2_PASSENGER_LEFT, Role::ROW_2_PASSENGER_CENTER, + Role::ROW_2_PASSENGER_RIGHT, Role::ROW_3_PASSENGER_LEFT, + Role::ROW_3_PASSENGER_CENTER, Role::ROW_3_PASSENGER_RIGHT, + Role::FRONT_OCCUPANTS, Role::ROW_2_OCCUPANTS, + Role::ROW_3_OCCUPANTS, Role::ALL_OCCUPANTS}; + + for (auto role : rolesToTest) { + int32_t capabilities; + EXPECT_OK(mOccupantAwarenessService->getCapabilityForRole(role, &capabilities)); + } +} + +// Test that getCapabilityForRole() returns failure when arguments are invalid. +TEST_P(OccupantAwarenessAidl, GetCapabilityFailureTest) { + int32_t capabilities; + EXPECT_FALSE( + mOccupantAwarenessService->getCapabilityForRole(Role::INVALID, &capabilities).isOk()); + + Role invalidRole = static_cast(static_cast(Role::ALL_OCCUPANTS) + 1); + EXPECT_FALSE( + mOccupantAwarenessService->getCapabilityForRole(invalidRole, &capabilities).isOk()); +} + +// Test that getState() returns within the timeout. The test do not attempt to verify the state, but +// only checks that the IPC call returns successfully. +TEST_P(OccupantAwarenessAidl, GetStateTest) { + std::vector rolesToTest = {Role::FRONT_PASSENGER, Role::DRIVER, + Role::ROW_2_PASSENGER_LEFT, Role::ROW_2_PASSENGER_CENTER, + Role::ROW_2_PASSENGER_RIGHT, Role::ROW_3_PASSENGER_LEFT, + Role::ROW_3_PASSENGER_CENTER, Role::ROW_3_PASSENGER_RIGHT, + Role::FRONT_OCCUPANTS, Role::ROW_2_OCCUPANTS, + Role::ROW_3_OCCUPANTS, Role::ALL_OCCUPANTS}; + + std::vector detectionCapabilities = {IOccupantAwareness::CAP_PRESENCE_DETECTION, + IOccupantAwareness::CAP_GAZE_DETECTION, + IOccupantAwareness::CAP_DRIVER_MONITORING_DETECTION}; + + for (auto role : rolesToTest) { + for (auto detectionCapability : detectionCapabilities) { + OccupantAwarenessStatus oasStatus; + EXPECT_OK(mOccupantAwarenessService->getState(role, detectionCapability, &oasStatus)); + } + } +} + +// Test that getState() returns failure with invalid args. +TEST_P(OccupantAwarenessAidl, GetStateFailureTest) { + // Verify that getState() returns error when role is invalid (0). + OccupantAwarenessStatus oasStatus; + EXPECT_FALSE(mOccupantAwarenessService + ->getState(Role::INVALID, IOccupantAwareness::CAP_PRESENCE_DETECTION, + &oasStatus) + .isOk()); + + // Verify that getState() returns error when role is invalid (invalid flag). + int invalidRole = static_cast(Role::ALL_OCCUPANTS) + 1; + EXPECT_FALSE(mOccupantAwarenessService + ->getState(static_cast(invalidRole), + IOccupantAwareness::CAP_PRESENCE_DETECTION, &oasStatus) + .isOk()); + + // Verify that getState() returns error when capability is invalid (none). + EXPECT_FALSE(mOccupantAwarenessService + ->getState(Role::FRONT_PASSENGER, IOccupantAwareness::CAP_NONE, &oasStatus) + .isOk()); + + // Verify that getState() returns error when capability is invalid (invalid flag). + int invalidDetectionFlags = 0x10; + EXPECT_FALSE(mOccupantAwarenessService + ->getState(Role::FRONT_PASSENGER, invalidDetectionFlags, &oasStatus) + .isOk()); +} + +// Test that setCallback() returns within the timeout. +TEST_P(OccupantAwarenessAidl, SetCallbackTest) { + sp callback = + new OccupantAwarenessCallback([](int detectionFlags, OccupantAwarenessStatus status) { + (void)detectionFlags; + (void)status; + }); + auto start = std::chrono::system_clock::now(); + Status status = mOccupantAwarenessService->setCallback(callback); + auto elapsed = std::chrono::system_clock::now() - start; + EXPECT_OK(status); + ASSERT_LE(elapsed, kTimeout); +} + +// Test that setCallback() returns failure with invalid args. +TEST_P(OccupantAwarenessAidl, SetCallbackFailureTest) { + sp callback = nullptr; + Status status = mOccupantAwarenessService->setCallback(callback); + EXPECT_FALSE(status.isOk()); +} + +// Test that getLatestDetection() returns within the timeout. +TEST_P(OccupantAwarenessAidl, GetLatestDetectionTest) { + auto start = std::chrono::system_clock::now(); + OccupantDetections detections; + // Do not check status here, since error status is returned when no detection is present. + (void)mOccupantAwarenessService->getLatestDetection(&detections); + auto elapsed = std::chrono::system_clock::now() - start; + ASSERT_LE(elapsed, kTimeout); +} + +INSTANTIATE_TEST_SUITE_P( + InstantiationName, OccupantAwarenessAidl, + testing::ValuesIn(android::getAidlHalInstanceNames(IOccupantAwareness::descriptor)), + android::PrintInstanceNameToString); + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + ProcessState::self()->setThreadPoolMaxThreadCount(1); + ProcessState::self()->startThreadPool(); + return RUN_ALL_TESTS(); +} diff --git a/automotive/sv/1.0/Android.bp b/automotive/sv/1.0/Android.bp new file mode 100644 index 0000000000..3a39148aa0 --- /dev/null +++ b/automotive/sv/1.0/Android.bp @@ -0,0 +1,21 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.automotive.sv@1.0", + root: "android.hardware", + srcs: [ + "types.hal", + "ISurroundView2dSession.hal", + "ISurroundView3dSession.hal", + "ISurroundViewService.hal", + "ISurroundViewSession.hal", + "ISurroundViewStream.hal", + ], + interfaces: [ + "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, +} diff --git a/automotive/sv/1.0/ISurroundView2dSession.hal b/automotive/sv/1.0/ISurroundView2dSession.hal new file mode 100644 index 0000000000..fa4967465e --- /dev/null +++ b/automotive/sv/1.0/ISurroundView2dSession.hal @@ -0,0 +1,84 @@ +/* + * Copyright 2020 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.sv@1.0; + +import ISurroundViewSession; + +/** + * Interface representing a surround view 2d session. + * + * Surround view 2d provides a top/bird's eye view of the car and its surroundings. + */ +interface ISurroundView2dSession extends ISurroundViewSession { + + /** + * Gets mapping information for 2d surround view. + * + * Mapping information maps the output frame of 2d surround view to actual dimensions + * covered on the ground. Mapping information is fixed for a car and is based upon its camera + * coverage. Mapping information can be used for doing overlays of objects in 3d space + * onto the surround view 2d output frame. + * + * @param sv2dConfig Configuration to set. + * @return sv2dMappingInfo mapping information of the 2d surround view. + */ + get2dMappingInfo() generates (Sv2dMappingInfo sv2dMappingInfo); + + /** + * Sets the configuration of 2d surround view. + * + * Configuration is used for supported different target use-cases of the surround view eg. + * fullscreen or preview. Default configuration is FULLSCREEN. + * A set config call can be performed at any time (before or after startStream) of the session. + * Once config change is complete, a CONFIG_CHANGED event is sent, after which + * all frames received will be of the updated config. + * + * @param sv2dConfig Configuration to set. + * @return svResult Returns OK if successful, appropriate error result otherwise. + */ + set2dConfig(Sv2dConfig sv2dConfig) generates (SvResult svResult); + + /** + * Gets the current configuration of the 2d surround view. + * + * Configuration is used for supported different target use-cases of the surround view eg. + * fullscreen view or preview. Use setConfig call to set a configuration. + * + * @return sv2dConfig the active current configuration of the 2d session. + */ + get2dConfig() generates (Sv2dConfig sv2dConfig); + + /** + * Projects points on camera image to surround view 2d image. + * + * Useful for mapping points detected on individual camera frames onto the surround view 2d + * output frame. + * + * @param cameraPoints List of camera pixel points to be projected in range including (0, 0) + * and (width - 1, height -1) of camera frame. If point is outside camera + frame INVALID_ARG error is returned. + * @param cameraId Id of the EvsCamera to use for projecting points. Id must be one of the + * cameras as returned by getCameraIds() else INVALID_ARG error is returned + * @return points2d Returns a list of 2d pixel points projecting into surround view 2d + * frame in the same order as cameraPoints. Point projected maybe outside + * surround view frame i.e. outside (0, 0) and + * (sv_width - 1, sv_height - 1). Points that do not project to ground + * plane are set with inValid true. + */ + projectCameraPoints(vec cameraPoints, string cameraId) generates ( + vec points2d); +}; diff --git a/automotive/sv/1.0/ISurroundView3dSession.hal b/automotive/sv/1.0/ISurroundView3dSession.hal new file mode 100644 index 0000000000..d2b0c53106 --- /dev/null +++ b/automotive/sv/1.0/ISurroundView3dSession.hal @@ -0,0 +1,113 @@ +/* + * Copyright 2020 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.sv@1.0; + +import ISurroundViewSession; + +/** + * Interface representing a surround view 3d session. + * + * Surround view 3d provides a virtual view from any desired position in the 3d space around the + * car. Surround view 3d creates an approximate 3d surface around the car to match the surrounds + * and provides a virtual view as seen on this surface. + */ +interface ISurroundView3dSession extends ISurroundViewSession { + + /** + * Sets the desired views of surround view 3d. + * + * Surround view 3d takes a list of desired virtual view points and provides an output frame + * for each view. Default view list is a single view from behind the car front facing in the + * front direction. + * A call to setViews() results in the views set by a previous call to be discarded. + * Each view set is identified by an Id which is returned with the corresponding output frame + * of that view. + * Clients can call setViews() at any stage of the session (before/after startStream). Client + * may continue to receive frames of previous views after setViews() call for a while and can + * identify updated set of views once effective using the view Id provided in the updated + * views frames. + * + * @param views List of desired views to generate output frames. + * @return svResult Returns OK if successful, appropriate error result otherwise. + */ + setViews(vec views) generates (SvResult svResult); + + /** + * Sets the configuration of 3d surround view. + * + * Configuration is used for supported different target use-cases of the surround view eg. + * fullscreen view or preview. A set config call can be performed at anytime (before or after + * startStream) of the session. + * Once config change is complete, a CONFIG_CHANGED event is sent, after which + * all frames received will be of the updated config. + * + * @param sv3dConfig Configuration to set. + * @return svResult Returns OK if successful, appropriate error result otherwise. + */ + set3dConfig(Sv3dConfig sv3dConfig) generates (SvResult svResult); + + /** + * Gets the current configuration of the 3d surround view. + * + * Configuration is used for supported different target use-cases of the surround view eg. + * fullscreen view or preview. Use setConfig call to set a configuration. + * + * @return sv3dConfig The current active configuration of the 3d session. + */ + get3dConfig() generates (Sv3dConfig sv3dConfig); + + /** + * Updates 3d overlays in scene. + * + * updateOverlays() provides a way to set a 3d overlay object in the scene. An overlay is an + * 3d object in the scene which can be a visual indicator to provide additional information eg. + * parking sensor distance indicators or overlays for objects in scene. + * + * An overlay object is defined by a set of points (forming triangles) with some color and + * transparency values and each overlay is identified by an overlay Id. + * When an overlay with a new Id is passed, a new overlay is added to the scene. + * When an overlay with previous id is passed, its vertices/color are updated with passed data. + * If the overlay data is empty, the overlay is removed from the scene. + * + * @param overlaysData Object with shared memory containing overlays to add/update in the + * scene. Refer to OverlaysData structure for layout in shared memory. + * @return svResult Returns OK if successful, appropriate error result otherwise. + */ + updateOverlays(OverlaysData overlaysData) generates (SvResult svResult); + + /** + * Projects points on camera image to surround view 3D surface. + * + * Useful for mapping points detected on individual camera frames onto the surround view 3D + * surface, these 3d points can then be used to set overlays using the updateOverlays() for + * the detected objects in the scene. + * Note: + * 3d points returned are projected on an approximate 3d surface and do not provide the exact + * 3d location. + * + * @param cameraPoints List of camera pixel points to be projected in range including (0, 0) + * and (width - 1, height -1) of camera frame. If point is outside camera + frame INVALID_ARG error is returned. + * @param cameraId Id of the EvsCamera to use for projecting points. Id must be one of the + * cameras as returned by getCameraIds() else INVALID_ARG error is returned + * @return points3d Returns a list of 3d points on the approximate 3d surface in the + * automotive coordinate system in the same order as cameraPoints. + * Points that do not project to 3d surface are set with inValid true. + */ + projectCameraPointsTo3dSurface(vec cameraPoints, string cameraId) generates ( + vec points3d); +}; diff --git a/automotive/sv/1.0/ISurroundViewService.hal b/automotive/sv/1.0/ISurroundViewService.hal new file mode 100644 index 0000000000..7de0bd13f8 --- /dev/null +++ b/automotive/sv/1.0/ISurroundViewService.hal @@ -0,0 +1,71 @@ +/* + * Copyright 2020 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.sv@1.0; + +import ISurroundView2dSession; +import ISurroundView3dSession; + +/** + * Interface representing entry point for surround view. + * + * Surround view service has two types of sessions 2d and 3d. Refer to their respective interface + * for more details. + */ +interface ISurroundViewService { + + /** + * Gets a list of camera ids that are used for generating surround view. + * For 4 camera configuration, the cameras ids are ordered in clockwise direction + * when viewed from top of the car starting with the front camera. i.e. FRONT, RIGHT, REAR and + * LEFT. All other configurations must follow clockwise order. + * + * @result cameraIds List of camera ids that matching the Id of EVS Cameras used by service. + */ + getCameraIds() generates (vec cameraIds); + + /** + * Starts a surround view 2d session. + * + * @result sv2dSession Returns a new 2d session that was created. + * result Returns OK if successful, appropriate error result otherwise. + */ + start2dSession() generates (ISurroundView2dSession sv2dSession, SvResult result); + + /** + * Stops a surround view 2d session. + * + * @param sv2dSession Valid 2d session to be stopped. + * @return svResult Returns OK if successful, appropriate error result otherwise. + */ + stop2dSession(ISurroundView2dSession sv2dSession) generates (SvResult result); + + /** + * Starts a surround view 3d session. + * + * @result sv3dSession Returns a new 3d session that was created. + * result Returns OK if successful, appropriate error result otherwise. + */ + start3dSession() generates (ISurroundView3dSession sv3dSession, SvResult result); + + /** + * Stops a surround view 2d session. + * + * @param sv2dSession Valid 2d session to be stopped. + * @return svResult Returns OK if successful, appropriate error result otherwise. + */ + stop3dSession(ISurroundView3dSession sv3dSession) generates (SvResult result); +}; diff --git a/automotive/sv/1.0/ISurroundViewSession.hal b/automotive/sv/1.0/ISurroundViewSession.hal new file mode 100644 index 0000000000..62cfac0ab8 --- /dev/null +++ b/automotive/sv/1.0/ISurroundViewSession.hal @@ -0,0 +1,51 @@ +/* + * Copyright 2020 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.sv@1.0; + +import ISurroundViewStream; + +/** + * Common interface for surround view session extended by surround view 2d and 3d + * session. + */ +interface ISurroundViewSession { + /** + * Requests to start receiving surround view frames. + * + * For surround view 3d, setViews() must be set before calling startStream(). + * + * @param stream Stream to receiving callbacks for the session. + * @return svResult Returns OK if successful, returns VIEW_NOT_SET if setViews() is not + * called for surround view 3d, appropriate error results otherwise. + */ + startStream(ISurroundViewStream stream) generates (SvResult svResult); + + /** + * Requests to stop stream. + * + * Frames may continue to arrive after call returns. Each must be returned until + * the closure of the stream is signaled by the ISurroundViewStream. + */ + stopStream(); + + /** + * Signal from client that a frame, which was delivered by the stream, has been consumed. + * + * @param svFramesDesc Descriptor to signal done with frame. + */ + oneway doneWithFrames(SvFramesDesc svFramesDesc); +}; diff --git a/automotive/sv/1.0/ISurroundViewStream.hal b/automotive/sv/1.0/ISurroundViewStream.hal new file mode 100644 index 0000000000..22d610f842 --- /dev/null +++ b/automotive/sv/1.0/ISurroundViewStream.hal @@ -0,0 +1,38 @@ +/* + * Copyright 2020 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.sv@1.0; + +/** + * Interface representing a surround view stream. + * + * This interface is to be implemented by client to receive callback for output frames and events. + */ +interface ISurroundViewStream { + /** + * Receives callback of surround view 2d/3d frames. + * + * @param svFramesDesc Frames descriptor containing the output frames. + */ + oneway receiveFrames(SvFramesDesc svFramesDesc); + + /** + * Receives callback for surround view events. + * + * @param svEvent Surround view event. + */ + oneway notify(SvEvent svEvent); +}; diff --git a/automotive/sv/1.0/default/Android.bp b/automotive/sv/1.0/default/Android.bp new file mode 100644 index 0000000000..8417949eba --- /dev/null +++ b/automotive/sv/1.0/default/Android.bp @@ -0,0 +1,47 @@ +// +// Copyright (C) 2020 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_binary { + name: "android.hardware.automotive.sv@1.0-service", + vendor: true, + relative_install_path: "hw", + srcs: [ + "service.cpp", + "SurroundViewService.cpp", + "SurroundView2dSession.cpp", + "SurroundView3dSession.cpp", + ], + init_rc: ["android.hardware.automotive.sv@1.0-service.rc"], + vintf_fragments: ["android.hardware.automotive.sv@1.0-service.xml"], + shared_libs: [ + "android.hardware.automotive.sv@1.0", + "android.hidl.memory@1.0", + "libbase", + "libbinder", + "libcutils", + "libhardware", + "libhidlbase", + "liblog", + "libui", + "libutils", + "libhidlmemory", + ], + + cflags: [ + "-O0", + "-g", + ], +} diff --git a/automotive/sv/1.0/default/SurroundView2dSession.cpp b/automotive/sv/1.0/default/SurroundView2dSession.cpp new file mode 100644 index 0000000000..4f975987be --- /dev/null +++ b/automotive/sv/1.0/default/SurroundView2dSession.cpp @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2020 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 "SurroundView2dSession.h" + +#include +#include + +namespace android { +namespace hardware { +namespace automotive { +namespace sv { +namespace V1_0 { +namespace implementation { + +SurroundView2dSession::SurroundView2dSession() : + mStreamState(STOPPED) { + mEvsCameraIds = {"0" , "1", "2", "3"}; + + mConfig.width = 640; + mConfig.blending = SvQuality::HIGH; + + framesRecord.frames.svBuffers.resize(1); + framesRecord.frames.svBuffers[0].viewId = 0; + framesRecord.frames.svBuffers[0].hardwareBuffer.nativeHandle = + new native_handle_t(); + framesRecord.frames.svBuffers[0].hardwareBuffer.description[0] = + mConfig.width; + framesRecord.frames.svBuffers[0].hardwareBuffer.description[1] = + mConfig.width * 3 / 4; +} + +// Methods from ::android::hardware::automotive::sv::V1_0::ISurroundViewSession +Return SurroundView2dSession::startStream( + const sp& stream) { + ALOGD("SurroundView2dSession::startStream"); + std::lock_guard lock(mAccessLock); + + if (mStreamState != STOPPED) { + ALOGE("ignoring startVideoStream call" + "when a stream is already running."); + return SvResult::INTERNAL_ERROR; + } + + mStream = stream; + + ALOGD("Notify SvEvent::STREAM_STARTED"); + mStream->notify(SvEvent::STREAM_STARTED); + + // Start the frame generation thread + mStreamState = RUNNING; + mCaptureThread = std::thread([this](){ generateFrames(); }); + + return SvResult::OK; +} + +Return SurroundView2dSession::stopStream() { + ALOGD("SurroundView2dSession::stopStream"); + std::unique_lock lock(mAccessLock); + + if (mStreamState == RUNNING) { + // Tell the GenerateFrames loop we want it to stop + mStreamState = STOPPING; + + // 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..."); + lock.unlock(); + mCaptureThread.join(); + lock.lock(); + + mStreamState = STOPPED; + mStream = nullptr; + ALOGD("Stream marked STOPPED."); + } + + return android::hardware::Void(); +} + +Return SurroundView2dSession::doneWithFrames( + const SvFramesDesc& svFramesDesc){ + ALOGD("SurroundView2dSession::doneWithFrames"); + std::unique_lock lock(mAccessLock); + + framesRecord.inUse = false; + + (void)svFramesDesc; + return android::hardware::Void(); +} + +// Methods from ISurroundView2dSession follow. +Return SurroundView2dSession::get2dMappingInfo( + get2dMappingInfo_cb _hidl_cb) { + ALOGD("SurroundView2dSession::get2dMappingInfo"); + std::unique_lock lock(mAccessLock); + + Sv2dMappingInfo info; + info.width = 8; // keeps ratio to 4:3 + info.height = 6; + info.center.isValid = true; + info.center.x = 0; + info.center.y = 0; + _hidl_cb(info); + return android::hardware::Void(); +} + +Return SurroundView2dSession::set2dConfig( + const Sv2dConfig& sv2dConfig) { + ALOGD("SurroundView2dSession::setConfig"); + std::unique_lock lock(mAccessLock); + + mConfig.width = sv2dConfig.width; + mConfig.blending = sv2dConfig.blending; + ALOGD("Notify SvEvent::CONFIG_UPDATED"); + mStream->notify(SvEvent::CONFIG_UPDATED); + + return SvResult::OK; +} + +Return SurroundView2dSession::get2dConfig(get2dConfig_cb _hidl_cb) { + ALOGD("SurroundView2dSession::getConfig"); + std::unique_lock lock(mAccessLock); + + _hidl_cb(mConfig); + return android::hardware::Void(); +} + +Return SurroundView2dSession::projectCameraPoints( + const hidl_vec& points2dCamera, + const hidl_string& cameraId, + projectCameraPoints_cb _hidl_cb) { + ALOGD("SurroundView2dSession::projectCameraPoints"); + std::unique_lock lock(mAccessLock); + + bool cameraIdFound = false; + for (auto evsCameraId : mEvsCameraIds) { + if (cameraId == evsCameraId) { + cameraIdFound = true; + ALOGI("Camera id found."); + break; + } + } + + if (!cameraIdFound) { + ALOGE("Camera id not found."); + _hidl_cb(hidl_vec()); + return android::hardware::Void(); + } + + hidl_vec outPoints; + outPoints.resize(points2dCamera.size()); + + int width = mConfig.width; + int height = mConfig.width * 3 / 4; + for (int i=0; i width-1 || + points2dCamera[i].x < 0 || points2dCamera[i].y > height-1) { + ALOGW("SurroundView2dSession::projectCameraPoints " + "gets invalid 2d camera points. Ignored"); + outPoints[i].isValid = false; + outPoints[i].x = 10000; + outPoints[i].y = 10000; + } else { + outPoints[i].isValid = true; + outPoints[i].x = 0; + outPoints[i].y = 0; + } + } + + _hidl_cb(outPoints); + return android::hardware::Void(); +} + +void SurroundView2dSession::generateFrames() { + ALOGD("SurroundView2dSession::generateFrames"); + + int sequenceId = 0; + + while(true) { + { + std::lock_guard lock(mAccessLock); + + if (mStreamState != RUNNING) { + // Break out of our main thread loop + break; + } + + framesRecord.frames.svBuffers[0].hardwareBuffer.description[0] = + mConfig.width; + framesRecord.frames.svBuffers[0].hardwareBuffer.description[1] = + mConfig.width * 3 / 4; + } + + usleep(100 * 1000); + + framesRecord.frames.timestampNs = elapsedRealtimeNano(); + framesRecord.frames.sequenceId = sequenceId++; + + { + std::lock_guard lock(mAccessLock); + + if (framesRecord.inUse) { + ALOGD("Notify SvEvent::FRAME_DROPPED"); + mStream->notify(SvEvent::FRAME_DROPPED); + } else { + framesRecord.inUse = true; + mStream->receiveFrames(framesRecord.frames); + } + } + } + + // If we've been asked to stop, send an event to signal the actual + // end of stream + ALOGD("Notify SvEvent::STREAM_STOPPED"); + mStream->notify(SvEvent::STREAM_STOPPED); +} + +} // namespace implementation +} // namespace V1_0 +} // namespace sv +} // namespace automotive +} // namespace hardware +} // namespace android + diff --git a/automotive/sv/1.0/default/SurroundView2dSession.h b/automotive/sv/1.0/default/SurroundView2dSession.h new file mode 100644 index 0000000000..ee751e7d78 --- /dev/null +++ b/automotive/sv/1.0/default/SurroundView2dSession.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2020 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 +#include +#include +#include +#include + +#include + +using namespace ::android::hardware::automotive::sv::V1_0; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::hidl_vec; +using ::android::sp; +using ::std::mutex; + +namespace android { +namespace hardware { +namespace automotive { +namespace sv { +namespace V1_0 { +namespace implementation { + +class SurroundView2dSession : public ISurroundView2dSession { +public: + SurroundView2dSession(); + + // Methods from ::android::hardware::automotive::sv::V1_0::ISurroundViewSession. + Return startStream( + const sp& stream) override; + Return stopStream() override; + Return doneWithFrames(const SvFramesDesc& svFramesDesc) override; + + // Methods from ISurroundView2dSession follow. + Return get2dMappingInfo(get2dMappingInfo_cb _hidl_cb) override; + Return set2dConfig(const Sv2dConfig& sv2dConfig) override; + Return get2dConfig(get2dConfig_cb _hidl_cb) override; + Return projectCameraPoints( + const hidl_vec& points2dCamera, + const hidl_string& cameraId, + projectCameraPoints_cb _hidl_cb) override; + + // TODO(tanmayp): Make private and add set/get method. + // Stream subscribed for the session. + sp mStream; + +private: + void generateFrames(); + + enum StreamStateValues { + STOPPED, + RUNNING, + STOPPING, + DEAD, + }; + StreamStateValues mStreamState; + + Sv2dConfig mConfig; + + std::thread mCaptureThread; // The thread we'll use to synthesize frames + + struct FramesRecord { + SvFramesDesc frames; + bool inUse = false; + }; + + FramesRecord framesRecord; + + // Synchronization necessary to deconflict mCaptureThread from the main service thread + std::mutex mAccessLock; + + std::vector mEvsCameraIds; +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace sv +} // namespace automotive +} // namespace hardware +} // namespace android + diff --git a/automotive/sv/1.0/default/SurroundView3dSession.cpp b/automotive/sv/1.0/default/SurroundView3dSession.cpp new file mode 100644 index 0000000000..da36f329aa --- /dev/null +++ b/automotive/sv/1.0/default/SurroundView3dSession.cpp @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2020 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 "SurroundView3dSession.h" + +#include + +#include +#include + +#include +#include + +using ::android::hidl::memory::V1_0::IMemory; +using ::android::hardware::hidl_memory; + +namespace android { +namespace hardware { +namespace automotive { +namespace sv { +namespace V1_0 { +namespace implementation { + +SurroundView3dSession::SurroundView3dSession() : + mStreamState(STOPPED){ + + mEvsCameraIds = {"0" , "1", "2", "3"}; + + mConfig.width = 640; + mConfig.height = 480; + mConfig.carDetails = SvQuality::HIGH; + + framesRecord.frames.svBuffers.resize(1); + framesRecord.frames.svBuffers[0].viewId = 0; + framesRecord.frames.svBuffers[0].hardwareBuffer.nativeHandle = new native_handle_t(); + framesRecord.frames.svBuffers[0].hardwareBuffer.description[0] = mConfig.width; + framesRecord.frames.svBuffers[0].hardwareBuffer.description[1] = mConfig.height; +} + +// Methods from ::android::hardware::automotive::sv::V1_0::ISurroundViewSession. +Return SurroundView3dSession::startStream( + const sp& stream) { + ALOGD("SurroundView3dSession::startStream"); + std::lock_guard lock(mAccessLock); + + if (mStreamState != STOPPED) { + ALOGE("ignoring startVideoStream call when a stream is already running."); + return SvResult::INTERNAL_ERROR; + } + + if (mViews.empty()) { + ALOGE("No views have been set for current Surround View 3d Session. " + "Please call setViews before starting the stream."); + return SvResult::VIEW_NOT_SET; + } + + mStream = stream; + + ALOGD("Notify SvEvent::STREAM_STARTED"); + mStream->notify(SvEvent::STREAM_STARTED); + + // Start the frame generation thread + mStreamState = RUNNING; + mCaptureThread = std::thread([this](){ generateFrames(); }); + + return SvResult::OK; +} + +Return SurroundView3dSession::stopStream() { + ALOGD("SurroundView3dSession::stopStream"); + std::unique_lock lock(mAccessLock); + + if (mStreamState == RUNNING) { + // Tell the GenerateFrames loop we want it to stop + mStreamState = STOPPING; + + // 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..."); + lock.unlock(); + mCaptureThread.join(); + lock.lock(); + + mStreamState = STOPPED; + mStream = nullptr; + ALOGD("Stream marked STOPPED."); + } + + return android::hardware::Void(); +} + +Return SurroundView3dSession::doneWithFrames( + const SvFramesDesc& svFramesDesc){ + ALOGD("SurroundView3dSession::doneWithFrames"); + std::unique_lock lock(mAccessLock); + + framesRecord.inUse = false; + + (void)svFramesDesc; + return android::hardware::Void(); +} + +// Methods from ISurroundView3dSession follow. +Return SurroundView3dSession::setViews(const hidl_vec& views) { + ALOGD("SurroundView3dSession::stopStream"); + std::unique_lock lock(mAccessLock); + + mViews.resize(views.size()); + for (int i=0; i SurroundView3dSession::set3dConfig(const Sv3dConfig& sv3dConfig) { + ALOGD("SurroundView3dSession::set3dConfig"); + std::unique_lock lock(mAccessLock); + + mConfig.width = sv3dConfig.width; + mConfig.height = sv3dConfig.height; + mConfig.carDetails = sv3dConfig.carDetails; + ALOGD("Notify SvEvent::CONFIG_UPDATED"); + mStream->notify(SvEvent::CONFIG_UPDATED); + + return SvResult::OK; +} + +Return SurroundView3dSession::get3dConfig(get3dConfig_cb _hidl_cb) { + ALOGD("SurroundView3dSession::get3dConfig"); + std::unique_lock lock(mAccessLock); + + _hidl_cb(mConfig); + return android::hardware::Void(); +} + +bool VerifyOverlayData(const OverlaysData& overlaysData) { + // Check size of shared memory matches overlaysMemoryDesc. + const int kVertexSize = 16; + const int kIdSize = 2; + int memDescSize = 0; + for (auto overlayMemDesc : overlaysData.overlaysMemoryDesc) { + memDescSize += kIdSize + kVertexSize * overlayMemDesc.verticesCount; + } + if (memDescSize != overlaysData.overlaysMemory.size()) { + ALOGE("shared memory and overlaysMemoryDesc size mismatch."); + return false; + } + + // Map memory. + sp pSharedMemory = mapMemory(overlaysData.overlaysMemory); + if(pSharedMemory.get() == nullptr) { + ALOGE("mapMemory failed."); + return false; + } + + // Get Data pointer. + uint8_t* pData = (uint8_t*)((void*)pSharedMemory->getPointer()); + if (pData == nullptr) { + ALOGE("Shared memory getPointer() failed."); + return false; + } + + int idOffset = 0; + std::set overlayIdSet; + for (auto overlayMemDesc : overlaysData.overlaysMemoryDesc) { + + if (overlayIdSet.find(overlayMemDesc.id) != overlayIdSet.end()) { + ALOGE("Duplicate id within memory descriptor."); + return false; + } + overlayIdSet.insert(overlayMemDesc.id); + + if(overlayMemDesc.verticesCount < 3) { + ALOGE("Less than 3 vertices."); + return false; + } + + if (overlayMemDesc.overlayPrimitive == OverlayPrimitive::TRIANGLES && + overlayMemDesc.verticesCount % 3 != 0) { + ALOGE("Triangles primitive does not have vertices multiple of 3."); + return false; + } + + uint16_t overlayId = *((uint16_t*)(pData + idOffset)); + + if (overlayId != overlayMemDesc.id) { + ALOGE("Overlay id mismatch %d , %d", overlayId, overlayMemDesc.id); + return false; + } + + idOffset += kIdSize + (kVertexSize * overlayMemDesc.verticesCount); + } + + return true; +} + +Return SurroundView3dSession::updateOverlays( + const OverlaysData& overlaysData) { + + if(!VerifyOverlayData(overlaysData)) { + ALOGE("VerifyOverlayData failed."); + return SvResult::INVALID_ARG; + } + + return SvResult::OK; +} + +Return SurroundView3dSession::projectCameraPointsTo3dSurface( + const hidl_vec& cameraPoints, + const hidl_string& cameraId, + projectCameraPointsTo3dSurface_cb _hidl_cb) { + + std::vector points3d; + bool cameraIdFound = false; + for (auto evsCameraId : mEvsCameraIds) { + if (cameraId == evsCameraId) { + cameraIdFound = true; + ALOGI("Camera id found."); + break; + } + } + + if (!cameraIdFound) { + ALOGE("Camera id not found."); + _hidl_cb(points3d); + return android::hardware::Void(); + } + + for (const auto cameraPoint : cameraPoints) { + Point3dFloat point3d; + point3d.isValid = true; + + if (cameraPoint.x < 0 || cameraPoint.x >= mConfig.width-1 || + cameraPoint.y < 0 || cameraPoint.y >= mConfig.height-1) { + ALOGE("Camera point out of bounds."); + point3d.isValid = false; + } + points3d.push_back(point3d); + } + _hidl_cb(points3d); + return android::hardware::Void(); +} + +void SurroundView3dSession::generateFrames() { + ALOGD("SurroundView3dSession::generateFrames"); + + int sequenceId = 0; + + while(true) { + { + std::lock_guard lock(mAccessLock); + + if (mStreamState != RUNNING) { + // Break out of our main thread loop + break; + } + } + + usleep(100 * 1000); + + framesRecord.frames.timestampNs = elapsedRealtimeNano(); + framesRecord.frames.sequenceId = sequenceId++; + + framesRecord.frames.svBuffers.resize(mViews.size()); + for (int i=0; i lock(mAccessLock); + + if (framesRecord.inUse) { + ALOGD("Notify SvEvent::FRAME_DROPPED"); + mStream->notify(SvEvent::FRAME_DROPPED); + } else { + framesRecord.inUse = true; + mStream->receiveFrames(framesRecord.frames); + } + } + } + + // If we've been asked to stop, send an event to signal the actual end of stream + ALOGD("Notify SvEvent::STREAM_STOPPED"); + mStream->notify(SvEvent::STREAM_STOPPED); +} + +} // namespace implementation +} // namespace V1_0 +} // namespace sv +} // namespace automotive +} // namespace hardware +} // namespace android + diff --git a/automotive/sv/1.0/default/SurroundView3dSession.h b/automotive/sv/1.0/default/SurroundView3dSession.h new file mode 100644 index 0000000000..5c638dbb9a --- /dev/null +++ b/automotive/sv/1.0/default/SurroundView3dSession.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 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 +#include +#include +#include +#include + +#include + +using namespace ::android::hardware::automotive::sv::V1_0; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::hidl_vec; +using ::android::sp; +using ::std::mutex; + +namespace android { +namespace hardware { +namespace automotive { +namespace sv { +namespace V1_0 { +namespace implementation { + +class SurroundView3dSession : public ISurroundView3dSession { +public: + SurroundView3dSession(); + + // Methods from ::android::hardware::automotive::sv::V1_0::ISurroundViewSession. + Return startStream( + const sp& stream) override; + Return stopStream() override; + Return doneWithFrames(const SvFramesDesc& svFramesDesc) override; + + // Methods from ISurroundView3dSession follow. + Return setViews(const hidl_vec& views) override; + Return set3dConfig(const Sv3dConfig& sv3dConfig) override; + Return get3dConfig(get3dConfig_cb _hidl_cb) override; + Return updateOverlays(const OverlaysData& overlaysData); + Return projectCameraPointsTo3dSurface( + const hidl_vec& cameraPoints, + const hidl_string& cameraId, + projectCameraPointsTo3dSurface_cb _hidl_cb); + + // Stream subscribed for the session. + // TODO(tanmayp): Make private and add set/get method. + sp mStream; + +private: + void generateFrames(); + + enum StreamStateValues { + STOPPED, + RUNNING, + STOPPING, + DEAD, + }; + StreamStateValues mStreamState; + + std::thread mCaptureThread; // The thread we'll use to synthesize frames + + struct FramesRecord { + SvFramesDesc frames; + bool inUse = false; + }; + + FramesRecord framesRecord; + + // Synchronization necessary to deconflict mCaptureThread from the main service thread + std::mutex mAccessLock; + + std::vector mViews; + + Sv3dConfig mConfig; + + std::vector mEvsCameraIds; +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace sv +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/sv/1.0/default/SurroundViewService.cpp b/automotive/sv/1.0/default/SurroundViewService.cpp new file mode 100644 index 0000000000..fe89dd5ee4 --- /dev/null +++ b/automotive/sv/1.0/default/SurroundViewService.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2020 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 "SurroundViewService.h" + +#include + +namespace android { +namespace hardware { +namespace automotive { +namespace sv { +namespace V1_0 { +namespace implementation { + +const std::string kCameraIds[] = {"0", "1", "2", "3"}; + +Return SurroundViewService::getCameraIds(getCameraIds_cb _hidl_cb) { + std::vector cameraIds = {kCameraIds[0], kCameraIds[1], + kCameraIds[2], kCameraIds[3]}; + _hidl_cb(cameraIds); + return android::hardware::Void(); +} + +Return SurroundViewService::start2dSession(start2dSession_cb _hidl_cb) { + ALOGD("SurroundViewService::start2dSession"); + if (mSurroundView2dSession != nullptr) { + ALOGW("Only one 2d session is supported at the same time"); + _hidl_cb(nullptr, SvResult::INTERNAL_ERROR); + } else { + mSurroundView2dSession = new SurroundView2dSession(); + _hidl_cb(mSurroundView2dSession, SvResult::OK); + } + return android::hardware::Void(); +} + +Return SurroundViewService::stop2dSession( + const sp& sv2dSession) { + ALOGD("SurroundViewService::stop2dSession"); + if (sv2dSession != nullptr && sv2dSession == mSurroundView2dSession) { + mSurroundView2dSession = nullptr; + return SvResult::OK; + } else { + ALOGE("Invalid arg for stop2dSession"); + return SvResult::INVALID_ARG; + } +} + +Return SurroundViewService::start3dSession(start3dSession_cb _hidl_cb) { + ALOGD("SurroundViewService::start3dSession"); + if (mSurroundView3dSession != nullptr) { + ALOGW("Only one 3d session is supported at the same time"); + _hidl_cb(nullptr, SvResult::INTERNAL_ERROR); + } else { + mSurroundView3dSession = new SurroundView3dSession(); + _hidl_cb(mSurroundView3dSession, SvResult::OK); + } + return android::hardware::Void(); +} + +Return SurroundViewService::stop3dSession( + const sp& sv3dSession) { + ALOGD("SurroundViewService::stop3dSession"); + if (sv3dSession != nullptr && sv3dSession == mSurroundView3dSession) { + mSurroundView3dSession = nullptr; + return SvResult::OK; + } else { + ALOGE("Invalid arg for stop3dSession"); + return SvResult::INVALID_ARG; + } +} + +} // namespace implementation +} // namespace V1_0 +} // namespace sv +} // namespace automotive +} // namespace hardware +} // namespace android + diff --git a/automotive/sv/1.0/default/SurroundViewService.h b/automotive/sv/1.0/default/SurroundViewService.h new file mode 100644 index 0000000000..9e0e151308 --- /dev/null +++ b/automotive/sv/1.0/default/SurroundViewService.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2020 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 "SurroundView2dSession.h" +#include "SurroundView3dSession.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace ::android::hardware::automotive::sv::V1_0; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::sp; + +namespace android { +namespace hardware { +namespace automotive { +namespace sv { +namespace V1_0 { +namespace implementation { + +class SurroundViewService : public ISurroundViewService { +public: + // Methods from ::android::hardware::automotive::sv::V1_0::ISurroundViewService follow. + Return getCameraIds(getCameraIds_cb _hidl_cb) override; + Return start2dSession(start2dSession_cb _hidl_cb) override; + Return stop2dSession( + const sp& sv2dSession) override; + + Return start3dSession(start3dSession_cb _hidl_cb) override; + Return stop3dSession( + const sp& sv3dSession) override; + +private: + sp mSurroundView2dSession; + sp mSurroundView3dSession; +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace sv +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/sv/1.0/default/android.hardware.automotive.sv@1.0-service.rc b/automotive/sv/1.0/default/android.hardware.automotive.sv@1.0-service.rc new file mode 100644 index 0000000000..e822017cf9 --- /dev/null +++ b/automotive/sv/1.0/default/android.hardware.automotive.sv@1.0-service.rc @@ -0,0 +1,5 @@ +service sv_service /vendor/bin/hw/android.hardware.automotive.sv@1.0-service + class hal + user automotive_evs + group automotive_evs + disabled diff --git a/automotive/sv/1.0/default/android.hardware.automotive.sv@1.0-service.xml b/automotive/sv/1.0/default/android.hardware.automotive.sv@1.0-service.xml new file mode 100644 index 0000000000..ba8e5ac93f --- /dev/null +++ b/automotive/sv/1.0/default/android.hardware.automotive.sv@1.0-service.xml @@ -0,0 +1,11 @@ + + + android.hardware.automotive.sv + hwbinder + 1.0 + + ISurroundViewService + default + + + diff --git a/automotive/sv/1.0/default/service.cpp b/automotive/sv/1.0/default/service.cpp new file mode 100644 index 0000000000..fae7425176 --- /dev/null +++ b/automotive/sv/1.0/default/service.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 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.automotive.sv@1.0-service" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "SurroundViewService.h" + +// libhidl: +using android::hardware::configureRpcThreadpool; +using android::hardware::joinRpcThreadpool; + +// implementation: +using android::hardware::automotive::sv::V1_0::implementation::SurroundViewService; + +int main() { + ALOGI("ISurroundViewService default implementation is starting"); + android::sp service = new SurroundViewService(); + + configureRpcThreadpool(1, true /* callerWillJoin */); + + // Register our service -- if somebody is already registered by our name, + // they will be killed (their thread pool will throw an exception). + android::status_t status = service->registerAsService(); + + LOG_ALWAYS_FATAL_IF(status != android::OK, + "Could not register default Surround View Service (%d)", + status); + + joinRpcThreadpool(); + + // In normal operation, we don't expect the thread pool to exit + ALOGE("Surround View Service is shutting down"); + return 1; +} diff --git a/automotive/sv/1.0/types.hal b/automotive/sv/1.0/types.hal new file mode 100644 index 0000000000..573bf11322 --- /dev/null +++ b/automotive/sv/1.0/types.hal @@ -0,0 +1,351 @@ +/* + * Copyright 2020 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.sv@1.0; + +import android.hardware.graphics.common@1.2::HardwareBuffer; + +/** Structure for translation with x, y and z units. */ +struct Translation { + float x; + float y; + float z; +}; + +/** + * Structure for rotation expressed as quaternions. + * Convention used: Unit quaternion with hamilton convention. + */ +struct RotationQuat { + float x; + float y; + float z; + float w; +}; + +/** Structure representing a 2D point with integers. Units are pixels. */ +struct Point2dInt { + uint32_t x; + uint32_t y; +}; + +/** Structure representing a 2D point with floats. */ +struct Point2dFloat { + /** Boolean flag to indicate the (x, y) data is valid. */ + bool isValid; + + /** (x, y) data is only valid if isValid is true. Units are pixels or milli-meters. */ + float x; + float y; +}; + +/** Structure representing a 3D point with floats. */ +struct Point3dFloat { + /** Boolean flag to indicate the (x, y, z) data is valid. */ + bool isValid; + + /** + * (x, y, z) data is only valid if isValid is true. Units are milli-meters. + */ + float x; + float y; + float z; +}; + +/** + * Structure defining the pose in 3D space. + */ +struct Pose { + /** + * Rotation part of the pose, expressed as a unit quaternion. + */ + RotationQuat rotation; + + /** + * Translation part of the pose, in (x, y, z) format with milli-meter units. + */ + Translation translation; +}; + +/** + * Struct defining a virtual view in the 3d space around the car. + */ +struct View3d { + /** + * Id to identify each custom view, this is passed along in each result SvBuffer. + * Recommend client to have a unique id for each different view. + */ + uint32_t viewId; + + /** + * Pose of the view. Describes the orientation and location of a virtual view relative to the + * android automotive coordinate system: + * https://source.android.com/devices/sensors/sensor-types#auto_axes + * The virtual view axes are defined as +Y as look-at direction, +X as right direction and + * +Z as up direction. + * The rotation and translation of the virtual view axes w.r.t the android automotive axes is + * specified by the rotation and tranlation component of the pose respectively. + * Example: A virtual view points to the right face of the car, located on right side of + * the car at (4, 2, 0) and is upright w.r.t the ground : + * ______ + * front | | + * | car | ↑X + * | ↑Y | Y←∘ view + * rear | ∘→X | (4,2) + * |(0,0) | + * |______| + * + * Here the view axes are rotated by 90 counter-clockwise w.r.t android automotive axes. + * For this example the rotation and translation will be: + * Rotation = + 90 degrees around Z axis = (0.7071, 0, 0, 0.7071) as a unit quaternion. + * Translation = (4, 2, 0) in meters = (2000, 4000, 0) in milli-meters. + */ + Pose pose; + + /** + * Horizontal field of view of the virtual view in degrees. Vertical fov is scaled accordingly + * to maintain the aspect ratio of the output frame. Must be in range (20, + */ + float horizontalFov; +}; + +/** + * Memory Buffer that stores the output of a single view from 2d/3d surround view. + */ +struct SvBuffer { + /** + * viewId identifying the view as passed by the client in setViews() call for + * surround view 3d. Id value is 0 for 2d surround view frame. + */ + uint32_t viewId; + + /** Hardware buffer containing the surround view 2d/3d result. */ + HardwareBuffer hardwareBuffer; +}; + +/** + * Structure describing a set of frames to be returned as output from 2d/3d surround view. + */ +struct SvFramesDesc { + /** + * Elapsed real-time nanoseconds of earliest camera frame from the set of camera + * frames used to generate the view. + */ + uint64_t timestampNs; + + /** + * Incremental counter for client to keep track of frames. + */ + uint32_t sequenceId; + + /** + * Frames generated with different views. + * 2d surround view has only a single svBuffer with Id 0. + */ + vec svBuffers; +}; + +/** + * Enumerator for list of result returns by surround view . + */ +enum SvResult : uint32_t { + /** Operation was successful. */ + OK = 0, + + /** Invalid argument to function was provided. */ + INVALID_ARG, + + /** Error indicating the particular operation is not supported. */ + NOT_SUPPORTED, + + /** Error indicating view not set before starting stream. */ + VIEW_NOT_SET, + + /** + * Error indicating system does not currently have enough resources to + * allocate for a new requested session. + * Clients may retry request for session if resources become available. + */ + NO_RESOURCE, + + /** Internal error in surround view service. */ + INTERNAL_ERROR, +}; + +/** + * Enumerator listing events for surround view. + */ +enum SvEvent : uint32_t { + STREAM_STARTED = 1, + + STREAM_STOPPED, + + /** + * Event sent after service switches to an updated config, all frames + * streamed after this event are of the updated config. + */ + CONFIG_UPDATED, + + /** Each frame dropped will be notified with this event. */ + FRAME_DROPPED, + + /** + * Timeout event occurs if any individual camera stream has a timeout. + * Frames will not be delivered and clients must stop the stream. + */ + TIMEOUT, +}; + +/** + * Structure defining the mapping information for 2d surround view. + * + * Mapping information provides the area on ground (width and height) and + * position w.r.t the car that the surround view 2d covers. This can be used for + * mapping (linear transformation) with other sensors whose data is available in + * the car coordinate system (eg. Ultrasonics). + * Axes and origin are as per the android automotive axes: + * https://source.android.com/devices/sensors/sensor-types#auto_axes + */ +struct Sv2dMappingInfo { + /** Width in milli-meters of the 2d surround view along the ground plane. */ + float width; + + /** Height in milli-meters of the 2d surround view along the ground plane. */ + float height; + + /** + * Coordinates (x, y) of the center of the view in android automotive coordinate system on the + * ground plane. Units are milli-meters. + */ + Point2dFloat center; +}; + +/** + * Enumerator for quality presets for 2d/3d surround view. + * Details of each preset are specified in the respective 2d/3d config structures. + */ +enum SvQuality : uint32_t { + HIGH = 0, + LOW, +}; + +/** Structure for surround view 2d configuration. */ +struct Sv2dConfig { + /** + * Desired output width in pixels. Must be in range (0, 4096]. + * Height is computed keeping the aspect ratio of the mapping info, + * Example: If width = 1080 px and mapping_width = 5000 mm, mapping_height = 10000 mm. + * then, height = width * (mapping_height / mapping_width) = 2160 px. + * Height is set to the floor value in case of (mapping_height / mapping_width) is not integer. + * Mapping width, height is fixed for a car and is based on camera parameters and their ground + * coverage. + */ + uint32_t width; + + /** + * Blending quality preset to use. + * HIGH: High quality blending (eg. multiband blending) that consumes more resources. + * LOW: Low quality blending (eg. alpha blending) that consumes less resources. + */ + SvQuality blending; +}; + +/** Structure for surround view 3d configuration. */ +struct Sv3dConfig { + /** Desired output width in pixels. Must be in range (0, 4096]. */ + uint32_t width; + + /** Desired output height in pixels. Must be in range (0, 4096]. */ + uint32_t height; + + /** + * Car model rendering details level. + * HIGH: Rendering includes shadows and reflections. Default option. + * LOW: Rendering with no shadows and reflections. + */ + SvQuality carDetails; +}; + +/** + * Enumerator for a list of overlay primitives. + * + * Given a list of vertices for an overlay, a primitive type defines which vertices are used to form + * the surfaces of the overlay object. + */ +enum OverlayPrimitive : uint32_t { + /** + * Consecutive vertices picked in order 3 at a time form a triangle. + * Eg: In a list of vertices (V1, V2, V3, V4, V5, V6) + * (V1, V2, V3) form a triangle and (V4, V5, V6) form a triangle. + */ + TRIANGLES = 0, + + /** + * Every 3 consecutive vertices form a triangle. + * Example in a list of vertices V1, V2, V3, V4, V5, V6 + * (V1, V2, V3), (V2, V3, V4), (V3, V4, V5) and (V4, V5, V6) form triangles. + */ + TRIANGLES_STRIP, +}; + +/** + * Structure identifying an overlay and describing the size and arrangement of its data in + * shared memory. + */ +struct OverlayMemoryDesc { + /** Identifier of the overlay. */ + uint16_t id; + + /** Number of vertices in the overlay. */ + uint32_t verticesCount; + + /** Primitive for the overlay. */ + OverlayPrimitive overlayPrimitive; +}; + +/** + * Structure containing the overlays data in shared memory. + */ +struct OverlaysData { + /** List of overlay memory descriptors, describing the data in the shared memory */ + vec overlaysMemoryDesc; + + /** + * Shared memory object containing a list of vertices for each overlay as described by + * overlaysMemoryDesc. + * + * Each vertex comprises of: + * | PositionX | PositionY | PositionZ | RGBA | + * | float | float | float | 4 * uint8_t | + * + * Each vertex is of 3 floats and 4 bytes = 16 bytes. + * + * Layout of vertices in shared memory is in order: + * + * Bytes: | 0-1 | 2-18 | 19-34 | 35-50 | 51-66 | 67-68 | 69-84 | 85-100 | 101-116 |... + * Data: | id1 | V1 | V2 | V3 | V4 | id2 | V1 | V2 | V3 |... + * | overlay1 | overlay 2 | + * + * The order of overlays must match the order as specified in the overlaysMemoryDesc. + * The number of vertices each overlay has must match the verticesCount in overlaysMemoryDesc. + * The id must match the id specificed in the OverlayMemoryDesc. This is used for verification. + * For each overlay the number of vertices must be 3 or greater. + * For TRIANGLES primitive the number of vertices must be a multiple of 3. + * The overlay vertices are grouped as per the overlayPrimitive specified in overlaysMemoryDesc, + * eg: If primitive is TRIANGLES, (V1, V2, V3) and (V4, V5, V6) form a triangle. + */ + memory overlaysMemory; +}; diff --git a/automotive/sv/1.0/vts/functional/Android.bp b/automotive/sv/1.0/vts/functional/Android.bp new file mode 100644 index 0000000000..0e5d3df9a7 --- /dev/null +++ b/automotive/sv/1.0/vts/functional/Android.bp @@ -0,0 +1,41 @@ +// +// Copyright (C) 2020 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: "VtsHalSurroundViewV1_0TargetTest", + srcs: [ + "VtsHalSurroundViewV1_0TargetTest.cpp", + "SurroundViewStreamHandler.cpp", + ], + defaults: ["VtsHalTargetTestDefaults"], + static_libs: [ + "libnativewindow", + "android.hardware.automotive.sv@1.0", + "android.hardware.graphics.common@1.0", + "android.hardware.graphics.common@1.1", + "android.hardware.graphics.common@1.2", + ], + shared_libs: [ + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libhidlmemory", + ], + test_suites: ["general-tests", "vts-core"], + cflags: [ + "-O0", + "-g", + ], +} diff --git a/automotive/sv/1.0/vts/functional/SurroundViewStreamHandler.cpp b/automotive/sv/1.0/vts/functional/SurroundViewStreamHandler.cpp new file mode 100644 index 0000000000..cb45caab59 --- /dev/null +++ b/automotive/sv/1.0/vts/functional/SurroundViewStreamHandler.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2020 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 "SurroundViewStreamHandler.h" + +#include + +using std::lock_guard; + +SurroundViewServiceHandler::SurroundViewServiceHandler(sp pSession) : + mSession(pSession), + mReceiveFramesCount(0), + mDoNotReturnFrames(false) { + // Nothing but member initialization +} + +Return SurroundViewServiceHandler::notify(SvEvent svEvent) { + ALOGD("SurroundViewServiceHandler::notify %d", svEvent); + + lock_guard lock(mLock); + switch (svEvent) { + case SvEvent::STREAM_STARTED: + case SvEvent::CONFIG_UPDATED: + case SvEvent::STREAM_STOPPED: + case SvEvent::FRAME_DROPPED: + case SvEvent::TIMEOUT: + mReceivedEvents.emplace_back(svEvent); + break; + default: + ALOGI("[SurroundViewLog] Received other event"); + } + + return android::hardware::Void(); +} + +Return SurroundViewServiceHandler::receiveFrames(const SvFramesDesc& svFramesDesc) { + ALOGD("SurroundViewServiceHandler::receiveFrames"); + + lock_guard lock(mLock); + unsigned long timestampNs = svFramesDesc.timestampNs; + unsigned sequenceId = svFramesDesc.sequenceId; + ALOGD("receiveFrames count: %d", mReceiveFramesCount); + ALOGD("timestampNs: %lu, sequenceId: %u", timestampNs, sequenceId); + if (mReceiveFramesCount != 0 + && (mLastReceivedFrames.timestampNs >= svFramesDesc.timestampNs + || mLastReceivedFrames.sequenceId >= svFramesDesc.sequenceId)) { + mAllFramesValid = false; + ALOGD("The incoming frames are with invalid timestamp or sequenceId!"); + } + + for (int i=0; idoneWithFrames(svFramesDesc); + } + + return android::hardware::Void(); +} + +bool SurroundViewServiceHandler::checkEventReceived(SvEvent svEvent) { + ALOGD("SurroundViewServiceHandler::checkEventReceived"); + int size = mReceivedEvents.size(); // work around + ALOGD("Received event number: %d", size); + auto iter = find(mReceivedEvents.begin(), mReceivedEvents.end(), svEvent); + return iter != mReceivedEvents.end(); +} + +SvFramesDesc SurroundViewServiceHandler::getLastReceivedFrames() { + return mLastReceivedFrames; +} + +int SurroundViewServiceHandler::getReceiveFramesCount() { + return mReceiveFramesCount; +} + +bool SurroundViewServiceHandler::areAllFramesValid() { + return mAllFramesValid; +} + +void SurroundViewServiceHandler::setDoNotReturnFrames(bool flag) { + mDoNotReturnFrames = flag; +} diff --git a/automotive/sv/1.0/vts/functional/SurroundViewStreamHandler.h b/automotive/sv/1.0/vts/functional/SurroundViewStreamHandler.h new file mode 100644 index 0000000000..7d3f61d47c --- /dev/null +++ b/automotive/sv/1.0/vts/functional/SurroundViewStreamHandler.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 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 SURROUND_VIEW_STREAM_HANDLER_H +#define SURROUND_VIEW_STREAM_HANDLER_H + +#include +#include +#include + +#include +#include + +using std::vector; +using std::mutex; +using android::hardware::Return; +using android::sp; +using namespace ::android::hardware::automotive::sv::V1_0; + +class SurroundViewServiceHandler : public ISurroundViewStream { +public: + SurroundViewServiceHandler(sp session); + + Return notify(SvEvent svEvent) override; + Return receiveFrames(const SvFramesDesc& svFramesDesc) override; + + bool checkEventReceived(SvEvent svEvent); + SvFramesDesc getLastReceivedFrames(); + int getReceiveFramesCount(); + bool areAllFramesValid(); + void setDoNotReturnFrames(bool flag); + +private: + mutex mLock; + + vector mReceivedEvents; + sp mSession; + SvFramesDesc mLastReceivedFrames; // only use timestampNs and sequenceId + int mReceiveFramesCount; // TODO(haoxiangl): figure out a better name + bool mAllFramesValid = true; + bool mDoNotReturnFrames; +}; + +#endif //SURROUND_VIEW_STREAM_HANDLER_H diff --git a/automotive/sv/1.0/vts/functional/VtsHalSurroundViewV1_0TargetTest.cpp b/automotive/sv/1.0/vts/functional/VtsHalSurroundViewV1_0TargetTest.cpp new file mode 100644 index 0000000000..b1b9d16bc2 --- /dev/null +++ b/automotive/sv/1.0/vts/functional/VtsHalSurroundViewV1_0TargetTest.cpp @@ -0,0 +1,1137 @@ +// +// Copyright (C) 2020 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 "VtsHalSurroundViewTest" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "SurroundViewStreamHandler.h" + +using namespace ::android::hardware::automotive::sv::V1_0; +using ::android::sp; +using ::android::hardware::hidl_vec; +using ::android::hardware::hidl_string; +using ::android::hidl::allocator::V1_0::IAllocator; +using ::android::hidl::memory::V1_0::IMemory; +using ::android::hardware::hidl_memory; + +const int kVertexByteSize = (3 * sizeof(float)) + 4; +const int kIdByteSize = 2; + +// The main test class for Surround View Service +class SurroundViewHidlTest : public ::testing::TestWithParam { +public: + virtual void SetUp() override { + mSurroundViewService = ISurroundViewService::getService(GetParam()); + ASSERT_NE(mSurroundViewService.get(), nullptr); + } + + virtual void TearDown() override {} + + sp mSurroundViewService; // Every test needs access to the service +}; + +TEST_P(SurroundViewHidlTest, startAndStop2dSession) { + ALOGD("SurroundViewHidlTest::startAndStop2dSession"); + sp surroundView2dSession; + mSurroundViewService->start2dSession( + [&surroundView2dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView2dSession = session; + }); + + SvResult result = mSurroundViewService->stop2dSession(surroundView2dSession); + ASSERT_EQ(result, SvResult::OK); +} + +TEST_P(SurroundViewHidlTest, stopInvalid2dSession) { + ALOGD("SurroundViewHidlTest::stopInvalid2dSession"); + sp surroundView2dSession; + SvResult result = mSurroundViewService->stop2dSession(surroundView2dSession); + ASSERT_NE(result, SvResult::OK); +} + +TEST_P(SurroundViewHidlTest, startAndStop2dStream) { + ALOGD("SurroundViewHidlTest::startAndStop2dStream"); + sp surroundView2dSession; + mSurroundViewService->start2dSession( + [&surroundView2dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView2dSession = session; + }); + + sp handler = + new SurroundViewServiceHandler(surroundView2dSession); + + SvResult result = surroundView2dSession->startStream(handler); + EXPECT_EQ(result, SvResult::OK); + + sleep(5); + + EXPECT_TRUE(handler->checkEventReceived(SvEvent::STREAM_STARTED)); + EXPECT_GT(handler->getReceiveFramesCount(), 0); + + surroundView2dSession->stopStream(); + + sleep(1); + EXPECT_TRUE(handler->checkEventReceived(SvEvent::STREAM_STOPPED)); + + result = mSurroundViewService->stop2dSession(surroundView2dSession); + EXPECT_EQ(result, SvResult::OK); +} + +TEST_P(SurroundViewHidlTest, start2dStreamWithoutReturningFrames) { + ALOGD("SurroundViewHidlTest::start2dStreamWithoutReturningFrames"); + sp surroundView2dSession; + mSurroundViewService->start2dSession( + [&surroundView2dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView2dSession = session; + }); + + sp handler = + new SurroundViewServiceHandler(surroundView2dSession); + handler->setDoNotReturnFrames(true); + + SvResult result = surroundView2dSession->startStream(handler); + EXPECT_EQ(result, SvResult::OK); + + sleep(5); + + EXPECT_TRUE(handler->checkEventReceived(SvEvent::STREAM_STARTED)); + EXPECT_TRUE(handler->checkEventReceived(SvEvent::FRAME_DROPPED)); + EXPECT_GT(handler->getReceiveFramesCount(), 0); + + surroundView2dSession->stopStream(); + + sleep(1); + EXPECT_TRUE(handler->checkEventReceived(SvEvent::STREAM_STOPPED)); + + result = mSurroundViewService->stop2dSession(surroundView2dSession); + EXPECT_EQ(result, SvResult::OK); +} + +TEST_P(SurroundViewHidlTest, duplicateStart2dStream) { + ALOGD("SurroundViewHidlTest, duplicateStart2dStream"); + sp surroundView2dSession; + mSurroundViewService->start2dSession( + [&surroundView2dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView2dSession = session; + }); + + sp handler = + new SurroundViewServiceHandler(surroundView2dSession); + + SvResult result = surroundView2dSession->startStream(handler); + EXPECT_EQ(result, SvResult::OK); + + result = surroundView2dSession->startStream(handler); + EXPECT_NE(result, SvResult::OK); + + surroundView2dSession->stopStream(); + mSurroundViewService->stop2dSession(surroundView2dSession); +} + +TEST_P(SurroundViewHidlTest, stopInvalid2dStream) { + ALOGD("SurroundViewHidlTest, stopInvalid2dStream"); + sp surroundView2dSession; + mSurroundViewService->start2dSession( + [&surroundView2dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView2dSession = session; + }); + + sp handler = + new SurroundViewServiceHandler(surroundView2dSession); + + surroundView2dSession->stopStream(); + mSurroundViewService->stop2dSession(surroundView2dSession); +} + +TEST_P(SurroundViewHidlTest, validate2dSvFramesDesc) { + ALOGD("SurroundViewHidlTest, validate2dSvFramesDesc"); + sp surroundView2dSession; + mSurroundViewService->start2dSession( + [&surroundView2dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView2dSession = session; + }); + + sp handler = + new SurroundViewServiceHandler(surroundView2dSession); + + SvResult result = surroundView2dSession->startStream(handler); + EXPECT_EQ(result, SvResult::OK); + + sleep(5); + + // Validate timestampNs and sequenceId + EXPECT_GT(handler->getReceiveFramesCount(), 0); + EXPECT_TRUE(handler->areAllFramesValid()); + + // Validate 2d SvFramesDesc. Do not compare nativeHandle since it is not + // stored and already verified on the fly. + SvFramesDesc frames = handler->getLastReceivedFrames(); + EXPECT_EQ(frames.svBuffers.size(), 1); + + SvBuffer svBuffer2d = frames.svBuffers[0]; + EXPECT_EQ(svBuffer2d.viewId, 0); + + const AHardwareBuffer_Desc* pDesc = + reinterpret_cast(&svBuffer2d.hardwareBuffer.description); + float mapWidth, mapHeight; + surroundView2dSession->get2dMappingInfo([&mapWidth, &mapHeight] (Sv2dMappingInfo info) { + mapWidth = info.width; + mapHeight = info.height; + }); + EXPECT_EQ(pDesc->height, floor(pDesc->width * (mapHeight / mapWidth))); + + // Clean up + surroundView2dSession->stopStream(); + result = mSurroundViewService->stop2dSession(surroundView2dSession); +} + +TEST_P(SurroundViewHidlTest, get2dMappingInfo) { + ALOGD("SurroundViewHidlTest, get2dMappingInfo"); + sp surroundView2dSession; + mSurroundViewService->start2dSession( + [&surroundView2dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView2dSession = session; + }); + + surroundView2dSession->get2dMappingInfo([] (Sv2dMappingInfo info) { + EXPECT_GT(info.width, 0); + EXPECT_GT(info.height, 0); + }); + + mSurroundViewService->stop2dSession(surroundView2dSession); +} + +TEST_P(SurroundViewHidlTest, set2dConfigResolution) { + ALOGD("SurroundViewHidlTest, set2dConfigResolution"); + sp surroundView2dSession; + mSurroundViewService->start2dSession( + [&surroundView2dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView2dSession = session; + }); + + sp handler = + new SurroundViewServiceHandler(surroundView2dSession); + + SvResult result = surroundView2dSession->startStream(handler); + EXPECT_EQ(result, SvResult::OK); + + sleep(1); + + // Change config + Sv2dConfig config; + config.width = 1920; + config.blending = SvQuality::HIGH; + surroundView2dSession->set2dConfig(config); + + sleep(1); + + EXPECT_TRUE(handler->checkEventReceived(SvEvent::CONFIG_UPDATED)); + + // Check width has been changed but not the ratio + SvFramesDesc frames = handler->getLastReceivedFrames(); + EXPECT_EQ(frames.svBuffers.size(), 1); + SvBuffer svBuffer2d = frames.svBuffers[0]; + EXPECT_EQ(svBuffer2d.viewId, 0); + const AHardwareBuffer_Desc* pDesc = + reinterpret_cast(&svBuffer2d.hardwareBuffer.description); + EXPECT_EQ(pDesc->width, config.width); + + float mapWidth, mapHeight; + surroundView2dSession->get2dMappingInfo([&mapWidth, &mapHeight] (Sv2dMappingInfo info) { + mapWidth = info.width; + mapHeight = info.height; + }); + EXPECT_EQ(pDesc->height, floor (pDesc->width * (mapHeight / mapWidth))); + + // Clean up + surroundView2dSession->stopStream(); + mSurroundViewService->stop2dSession(surroundView2dSession); +} + +TEST_P(SurroundViewHidlTest, set2dConfigBlending) { + ALOGD("SurroundViewHidlTest, set2dConfigBlending"); + sp surroundView2dSession; + mSurroundViewService->start2dSession( + [&surroundView2dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView2dSession = session; + }); + + sp handler = + new SurroundViewServiceHandler(surroundView2dSession); + + SvResult result = surroundView2dSession->startStream(handler); + EXPECT_EQ(result, SvResult::OK); + + sleep(1); + + // Get the width before config changed + int oldWidth; + SvFramesDesc frames = handler->getLastReceivedFrames(); + EXPECT_EQ(frames.svBuffers.size(), 1); + SvBuffer svBuffer2d = frames.svBuffers[0]; + const AHardwareBuffer_Desc* pDesc = + reinterpret_cast(&svBuffer2d.hardwareBuffer.description); + oldWidth = pDesc->width; + + // Change config + Sv2dConfig config; + config.width = oldWidth; + config.blending = SvQuality::LOW; + surroundView2dSession->set2dConfig(config); + + sleep(1); + + EXPECT_TRUE(handler->checkEventReceived(SvEvent::CONFIG_UPDATED)); + + Sv2dConfig retConfig; + surroundView2dSession->get2dConfig([&retConfig] (Sv2dConfig config) { + retConfig.width = config.width; + retConfig.blending = config.blending; + }); + + // Check config blending has been changed but not the width + EXPECT_EQ(retConfig.blending, config.blending); + EXPECT_EQ(retConfig.width, oldWidth); + + // Clean up + surroundView2dSession->stopStream(); + mSurroundViewService->stop2dSession(surroundView2dSession); +} + +TEST_P(SurroundViewHidlTest, projectCameraPointsWithValidCameraId) { + ALOGD("SurroundViewHidlTest, projectCameraPointsWithValidCameraId"); + sp surroundView2dSession; + mSurroundViewService->start2dSession( + [&surroundView2dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView2dSession = session; + }); + + hidl_vec cameraIds; + mSurroundViewService->getCameraIds([&cameraIds]( + const hidl_vec& camIds) { + cameraIds = camIds; + }); + + sp handler = + new SurroundViewServiceHandler(surroundView2dSession); + + SvResult result = surroundView2dSession->startStream(handler); + EXPECT_EQ(result, SvResult::OK); + + sleep(1); + + // Get the width and height of the frame + int width, height; + SvFramesDesc frames = handler->getLastReceivedFrames(); + EXPECT_EQ(frames.svBuffers.size(), 1); + SvBuffer svBuffer2d = frames.svBuffers[0]; + const AHardwareBuffer_Desc* pDesc = + reinterpret_cast(&svBuffer2d.hardwareBuffer.description); + width = pDesc->width; + height = pDesc->height; + + float mapWidth, mapHeight, mapCenter[2]; + surroundView2dSession->get2dMappingInfo( + [&mapWidth, &mapHeight, &mapCenter] (Sv2dMappingInfo info) { + mapWidth = info.width; + mapHeight = info.height; + mapCenter[0] = info.center.x; + mapCenter[1] = info.center.y; + }); + + // Set one valid point and one invalid point + hidl_vec points2dCamera; + points2dCamera.resize(2); + points2dCamera[0].x = 0; + points2dCamera[0].y = 0; + points2dCamera[1].x = width * 2; + points2dCamera[1].y = height * 2; + + surroundView2dSession->projectCameraPoints( + points2dCamera, + cameraIds[0], + [&mapWidth, &mapHeight, &mapCenter] ( + const hidl_vec& outPoints) { + // Make sure point[0] is valid. + EXPECT_TRUE(outPoints[0].isValid); + EXPECT_GE(outPoints[0].x, mapCenter[0] - mapWidth); + EXPECT_LE(outPoints[0].x, mapCenter[0] + mapWidth); + EXPECT_GE(outPoints[0].y, mapCenter[1] - mapHeight); + EXPECT_LE(outPoints[0].y, mapCenter[1] + mapHeight); + + // Make sure point[1] is invalid. + EXPECT_FALSE(outPoints[1].isValid); + }); + + // Clean up + surroundView2dSession->stopStream(); + mSurroundViewService->stop2dSession(surroundView2dSession); +} + +TEST_P(SurroundViewHidlTest, projectCameraPointsWithInvalidCameraId) { + ALOGD("SurroundViewHidlTest, projectCameraPointsWithInvalidCameraId"); + sp surroundView2dSession; + mSurroundViewService->start2dSession( + [&surroundView2dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView2dSession = session; + }); + + hidl_vec cameraIds; + mSurroundViewService->getCameraIds([&cameraIds]( + const hidl_vec& camIds) { + cameraIds = camIds; + }); + + hidl_string invalidCameraId = "INVALID_CAMERA_ID"; + + // In case one of the camera id happens to be identical to + // the invalid camera id. + for (auto cameraId : cameraIds) { + ASSERT_NE(cameraId, invalidCameraId); + } + + // Set one valid point + hidl_vec points2dCamera; + points2dCamera.resize(1); + points2dCamera[0].x = 0; + points2dCamera[0].y = 0; + + surroundView2dSession->projectCameraPoints( + points2dCamera, + invalidCameraId, + [] (const hidl_vec& outPoints) { + // No points are return due to invalid camera id + EXPECT_EQ(outPoints.size(), 0); + }); + + // Clean up + surroundView2dSession->stopStream(); + mSurroundViewService->stop2dSession(surroundView2dSession); +} + +TEST_P(SurroundViewHidlTest, startAndStop3dSession) { + ALOGD("SurroundViewHidlTest, startAndStop3dSession"); + sp surroundView3dSession; + mSurroundViewService->start3dSession( + [&surroundView3dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView3dSession = session; + }); + + SvResult result = mSurroundViewService->stop3dSession(surroundView3dSession); + EXPECT_EQ(result, SvResult::OK); +} + +TEST_P(SurroundViewHidlTest, stopInvalid3dSession) { + ALOGD("SurroundViewHidlTest, stopInvalid3dSession"); + sp surroundView3dSession; + SvResult result = mSurroundViewService->stop3dSession(surroundView3dSession); + EXPECT_NE(result, SvResult::OK); +} + +TEST_P(SurroundViewHidlTest, startAndStop3dStream) { + ALOGD("SurroundViewHidlTest::startAndStop3dStream"); + sp surroundView3dSession; + mSurroundViewService->start3dSession( + [&surroundView3dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView3dSession = session; + }); + + std::vector views(1); + SvResult setViewResult = surroundView3dSession->setViews(views); + EXPECT_EQ(setViewResult, SvResult::OK); + + sp handler = + new SurroundViewServiceHandler(surroundView3dSession); + + SvResult result = surroundView3dSession->startStream(handler); + EXPECT_EQ(result, SvResult::OK); + + sleep(5); + + EXPECT_TRUE(handler->checkEventReceived(SvEvent::STREAM_STARTED)); + EXPECT_GT(handler->getReceiveFramesCount(), 0); + + surroundView3dSession->stopStream(); + + sleep(1); + EXPECT_TRUE(handler->checkEventReceived(SvEvent::STREAM_STOPPED)); + + result = mSurroundViewService->stop3dSession(surroundView3dSession); + EXPECT_EQ(result, SvResult::OK); +} + +TEST_P(SurroundViewHidlTest, start3dStreamWithoutReturningFrames) { + ALOGD("SurroundViewHidlTest::start3dStreamWithoutReturningFrames"); + sp surroundView3dSession; + mSurroundViewService->start3dSession( + [&surroundView3dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView3dSession = session; + }); + + std::vector views(1); + SvResult setViewResult = surroundView3dSession->setViews(views); + EXPECT_EQ(setViewResult, SvResult::OK); + + sp handler = + new SurroundViewServiceHandler(surroundView3dSession); + handler->setDoNotReturnFrames(true); + + SvResult result = surroundView3dSession->startStream(handler); + EXPECT_EQ(result, SvResult::OK); + + sleep(5); + + EXPECT_TRUE(handler->checkEventReceived(SvEvent::STREAM_STARTED)); + EXPECT_TRUE(handler->checkEventReceived(SvEvent::FRAME_DROPPED)); + EXPECT_GT(handler->getReceiveFramesCount(), 0); + + surroundView3dSession->stopStream(); + + sleep(1); + EXPECT_TRUE(handler->checkEventReceived(SvEvent::STREAM_STOPPED)); + + result = mSurroundViewService->stop3dSession(surroundView3dSession); + EXPECT_EQ(result, SvResult::OK); +} + +TEST_P(SurroundViewHidlTest, duplicateStart3dStream) { + ALOGD("SurroundViewHidlTest, duplicateStart3dStream"); + sp surroundView3dSession; + mSurroundViewService->start3dSession( + [&surroundView3dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView3dSession = session; + }); + + std::vector views(1); + SvResult setViewResult = surroundView3dSession->setViews(views); + EXPECT_EQ(setViewResult, SvResult::OK); + + sp handler = + new SurroundViewServiceHandler(surroundView3dSession); + + SvResult result = surroundView3dSession->startStream(handler); + EXPECT_EQ(result, SvResult::OK); + + result = surroundView3dSession->startStream(handler); + EXPECT_NE(result, SvResult::OK); + + surroundView3dSession->stopStream(); + mSurroundViewService->stop3dSession(surroundView3dSession); +} + +TEST_P(SurroundViewHidlTest, start3dStreamNoViewSetFail) { + ALOGD("SurroundViewHidlTest, start3dStreamNoViewSetFail"); + sp surroundView3dSession; + mSurroundViewService->start3dSession( + [&surroundView3dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView3dSession = session; + }); + + sp handler = + new SurroundViewServiceHandler(surroundView3dSession); + + SvResult result = surroundView3dSession->startStream(handler); + EXPECT_EQ(result, SvResult::VIEW_NOT_SET); + + mSurroundViewService->stop3dSession(surroundView3dSession); +} + +TEST_P(SurroundViewHidlTest, validate3dSvFramesDesc) { + ALOGD("SurroundViewHidlTest, validate3dSvFramesDesc"); + sp surroundView3dSession; + mSurroundViewService->start3dSession( + [&surroundView3dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView3dSession = session; + }); + + sp handler = + new SurroundViewServiceHandler(surroundView3dSession); + + std::vector views(1); + views[0].viewId = 0; + SvResult setViewResult = surroundView3dSession->setViews(views); + EXPECT_EQ(setViewResult, SvResult::OK); + + SvResult result = surroundView3dSession->startStream(handler); + EXPECT_EQ(result, SvResult::OK); + + sleep(5); + + EXPECT_GT(handler->getReceiveFramesCount(), 0); + EXPECT_TRUE(handler->areAllFramesValid()); + + // Validate 3d SvFramesDesc when only one view is set. + SvFramesDesc frames = handler->getLastReceivedFrames(); + EXPECT_EQ(frames.svBuffers.size(), 1); + EXPECT_EQ(frames.svBuffers[0].viewId, 0); + + views.resize(3); + views[0].viewId = 0; + views[1].viewId = 1; + views[2].viewId = 2; + setViewResult = surroundView3dSession->setViews(views); + EXPECT_EQ(setViewResult, SvResult::OK); + + sleep(1); + + // Validate 3d SvFramesDesc when multiple views are set. + EXPECT_TRUE(handler->areAllFramesValid()); + frames = handler->getLastReceivedFrames(); + EXPECT_EQ(frames.svBuffers.size(), 3); + EXPECT_EQ(frames.svBuffers[0].viewId, 0); + EXPECT_EQ(frames.svBuffers[1].viewId, 1); + EXPECT_EQ(frames.svBuffers[2].viewId, 2); + EXPECT_EQ(frames.svBuffers[0].hardwareBuffer.description[0], + frames.svBuffers[1].hardwareBuffer.description[0]); + EXPECT_EQ(frames.svBuffers[0].hardwareBuffer.description[1], + frames.svBuffers[1].hardwareBuffer.description[1]); + EXPECT_EQ(frames.svBuffers[1].hardwareBuffer.description[0], + frames.svBuffers[2].hardwareBuffer.description[0]); + EXPECT_EQ(frames.svBuffers[1].hardwareBuffer.description[1], + frames.svBuffers[2].hardwareBuffer.description[1]); + + // Clean up + surroundView3dSession->stopStream(); + result = mSurroundViewService->stop3dSession(surroundView3dSession); +} + +TEST_P(SurroundViewHidlTest, set3dConfigResolution) { + ALOGD("SurroundViewHidlTest, set3dConfigResolution"); + sp surroundView3dSession; + mSurroundViewService->start3dSession( + [&surroundView3dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView3dSession = session; + }); + + sp handler = + new SurroundViewServiceHandler(surroundView3dSession); + + std::vector views(1); + views[0].viewId = 0; + SvResult setViewResult = surroundView3dSession->setViews(views); + EXPECT_EQ(setViewResult, SvResult::OK); + SvResult result = surroundView3dSession->startStream(handler); + EXPECT_EQ(result, SvResult::OK); + + sleep(1); + + // Change config + Sv3dConfig config; + config.width = 1920; + config.height = 1080; + config.carDetails = SvQuality::HIGH; + surroundView3dSession->set3dConfig(config); + + sleep(1); + + EXPECT_TRUE(handler->checkEventReceived(SvEvent::CONFIG_UPDATED)); + + // Check width has been changed but not the ratio + SvFramesDesc frames = handler->getLastReceivedFrames(); + EXPECT_EQ(frames.svBuffers.size(), 1); + SvBuffer svBuffer3d = frames.svBuffers[0]; + EXPECT_EQ(svBuffer3d.viewId, 0); + const AHardwareBuffer_Desc* pDesc = + reinterpret_cast(&svBuffer3d.hardwareBuffer.description); + EXPECT_EQ(pDesc->width, config.width); + EXPECT_EQ(pDesc->height, config.height); + + // Clean up + surroundView3dSession->stopStream(); + mSurroundViewService->stop3dSession(surroundView3dSession); +} + +TEST_P(SurroundViewHidlTest, set3dConfigCarDetails) { + ALOGD("SurroundViewHidlTest, set3dConfigCarDetails"); + sp surroundView3dSession; + mSurroundViewService->start3dSession( + [&surroundView3dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView3dSession = session; + }); + + sp handler = + new SurroundViewServiceHandler(surroundView3dSession); + + std::vector views(1); + views[0].viewId = 0; + SvResult setViewResult = surroundView3dSession->setViews(views); + EXPECT_EQ(setViewResult, SvResult::OK); + SvResult result = surroundView3dSession->startStream(handler); + EXPECT_EQ(result, SvResult::OK); + + sleep(1); + + // Get the width before config changed + int oldWidth, oldHeight; + SvFramesDesc frames = handler->getLastReceivedFrames(); + EXPECT_EQ(frames.svBuffers.size(), 1); + SvBuffer svBuffer3d = frames.svBuffers[0]; + const AHardwareBuffer_Desc* pDesc = + reinterpret_cast(&svBuffer3d.hardwareBuffer.description); + oldWidth = pDesc->width; + oldHeight = pDesc->height; + + // Change config + Sv3dConfig config; + config.width = oldWidth; + config.height = oldHeight; + config.carDetails = SvQuality::LOW; + surroundView3dSession->set3dConfig(config); + + sleep(1); + + EXPECT_TRUE(handler->checkEventReceived(SvEvent::CONFIG_UPDATED)); + + Sv3dConfig retConfig; + surroundView3dSession->get3dConfig([&retConfig] (Sv3dConfig config) { + retConfig.width = config.width; + retConfig.height = config.height; + retConfig.carDetails = config.carDetails; + }); + + // Check config blending has been changed but not the width + EXPECT_EQ(retConfig.carDetails, config.carDetails); + EXPECT_EQ(retConfig.width, oldWidth); + EXPECT_EQ(retConfig.height, oldHeight); + + // Clean up + surroundView3dSession->stopStream(); + mSurroundViewService->stop3dSession(surroundView3dSession); +} + +std::pair> GetMappedSharedMemory(int bytesSize) { + + const auto nullResult = std::make_pair(hidl_memory(), nullptr); + + sp ashmemAllocator = IAllocator::getService("ashmem"); + if (ashmemAllocator.get() == nullptr) { + ALOGE("SurroundViewHidlTest getService ashmem failed"); + return nullResult; + } + + // Allocate shared memory. + hidl_memory hidlMemory; + bool allocateSuccess = false; + Return result = ashmemAllocator->allocate(bytesSize, + [&](bool success, const hidl_memory& hidlMem) { + if (!success) { + return; + } + allocateSuccess = success; + hidlMemory = hidlMem; + }); + + // Check result of allocated memory. + if (!result.isOk() || !allocateSuccess) { + ALOGE("SurroundViewHidlTest allocate shared memory failed"); + return nullResult; + } + + // Map shared memory. + sp pIMemory = mapMemory(hidlMemory); + if (pIMemory.get() == nullptr) { + ALOGE("SurroundViewHidlTest map shared memory failed"); + return nullResult; + } + + return std::make_pair(hidlMemory, pIMemory); +} + +void SetIndexOfOverlaysMemory( + const std::vector& overlaysMemDesc, + sp pIMemory, int indexPosition, uint16_t indexValue) { + + // Count the number of vertices until the index. + int totalVerticesCount = 0; + for (int i = 0; i < indexPosition; i++) { + totalVerticesCount += overlaysMemDesc[i].verticesCount; + } + + const int indexBytePosition = (indexPosition * kIdByteSize) + + (kVertexByteSize * totalVerticesCount); + + uint8_t* pSharedMemoryData = (uint8_t*)((void*)pIMemory->getPointer()); + pSharedMemoryData += indexBytePosition; + uint16_t* pIndex16bit = (uint16_t*)pSharedMemoryData; + + ALOGD("Setting index at pos %d", indexBytePosition); + + // Modify shared memory. + pIMemory->update(); + *pIndex16bit = indexValue; + pIMemory->commit(); +} + +std::pair> GetSampleOverlaysData() { + OverlaysData overlaysData; + overlaysData.overlaysMemoryDesc.resize(2); + + int sharedMemBytesSize = 0; + OverlayMemoryDesc overlayMemDesc1, overlayMemDesc2; + overlayMemDesc1.id = 0; + overlayMemDesc1.verticesCount = 6; + overlayMemDesc1.overlayPrimitive = OverlayPrimitive::TRIANGLES; + overlaysData.overlaysMemoryDesc[0] = overlayMemDesc1; + sharedMemBytesSize += kIdByteSize + + kVertexByteSize * overlayMemDesc1.verticesCount; + + overlayMemDesc2.id = 1; + overlayMemDesc2.verticesCount = 4; + overlayMemDesc2.overlayPrimitive = OverlayPrimitive::TRIANGLES_STRIP; + overlaysData.overlaysMemoryDesc[1] = overlayMemDesc2; + sharedMemBytesSize += kIdByteSize + + kVertexByteSize * overlayMemDesc2.verticesCount; + + std::pair> sharedMem = + GetMappedSharedMemory(sharedMemBytesSize); + sp pIMemory = sharedMem.second; + if (pIMemory.get() == nullptr) { + return std::make_pair(OverlaysData(), nullptr); + } + + // Get pointer to shared memory data and set all bytes to 0. + uint8_t* pSharedMemoryData = (uint8_t*)((void*)pIMemory->getPointer()); + pIMemory->update(); + memset(pSharedMemoryData, 0, sharedMemBytesSize); + pIMemory->commit(); + + std::vector overlaysDesc = {overlayMemDesc1, + overlayMemDesc2}; + + // Set indexes in shared memory. + SetIndexOfOverlaysMemory(overlaysDesc, pIMemory, 0, overlayMemDesc1.id); + SetIndexOfOverlaysMemory(overlaysDesc, pIMemory, 1, overlayMemDesc2.id); + + overlaysData.overlaysMemoryDesc = overlaysDesc; + overlaysData.overlaysMemory = sharedMem.first; + + return std::make_pair(overlaysData, pIMemory); +} + +TEST_P(SurroundViewHidlTest, updateOverlaysSuccess) { + ALOGD("SurroundViewHidlTest, updateOverlaysSuccess"); + + sp surroundView3dSession; + mSurroundViewService->start3dSession( + [&surroundView3dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView3dSession = session; + }); + + std::pair> overlaysData = GetSampleOverlaysData(); + ASSERT_NE(overlaysData.second, nullptr); + + SvResult result = surroundView3dSession->updateOverlays(overlaysData.first); + EXPECT_EQ(result, SvResult::OK); + + mSurroundViewService->stop3dSession(surroundView3dSession); +} + +TEST_P(SurroundViewHidlTest, overlaysDataMismatchIdFail) { + ALOGD("SurroundViewHidlTest, overlaysDataMismatchIdFail"); + + sp surroundView3dSession; + mSurroundViewService->start3dSession( + [&surroundView3dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView3dSession = session; + }); + + std::pair> overlaysDataMismatchId + = GetSampleOverlaysData(); + ASSERT_NE(overlaysDataMismatchId.second, nullptr); + + // Set id of second overlay in shared memory to 2 (expected is 1). + auto& overlaysDesc = overlaysDataMismatchId.first.overlaysMemoryDesc; + auto& pIMemory = overlaysDataMismatchId.second; + SetIndexOfOverlaysMemory(overlaysDesc, pIMemory, 1, 2); + + SvResult result = surroundView3dSession->updateOverlays( + overlaysDataMismatchId.first); + EXPECT_EQ(result, SvResult::INVALID_ARG); + + mSurroundViewService->stop3dSession(surroundView3dSession); +} + +TEST_P(SurroundViewHidlTest, overlaysDataNullMemoryFail) { + ALOGD("SurroundViewHidlTest, overlaysDataNullMemoryFail"); + + sp surroundView3dSession; + mSurroundViewService->start3dSession( + [&surroundView3dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView3dSession = session; + }); + + std::pair> overlaysDataNullMemory + = GetSampleOverlaysData(); + + // Set shared memory to null. + overlaysDataNullMemory.first.overlaysMemory = hidl_memory(); + + SvResult result = surroundView3dSession->updateOverlays( + overlaysDataNullMemory.first); + EXPECT_EQ(result, SvResult::INVALID_ARG); + + mSurroundViewService->stop3dSession(surroundView3dSession); +} + +TEST_P(SurroundViewHidlTest, overlaysDataLessThan3VerticesFail) { + ALOGD("SurroundViewHidlTest, overlaysDataLessThan3VerticesFail"); + + sp surroundView3dSession; + mSurroundViewService->start3dSession( + [&surroundView3dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView3dSession = session; + }); + + std::pair> overlaysData2Vertices + = GetSampleOverlaysData(); + + // Set vertices count of second overlay to 2. + overlaysData2Vertices.first.overlaysMemoryDesc[1].verticesCount = 2; + + SvResult result = surroundView3dSession->updateOverlays( + overlaysData2Vertices.first); + EXPECT_EQ(result, SvResult::INVALID_ARG); + + mSurroundViewService->stop3dSession(surroundView3dSession); +} + +TEST_P(SurroundViewHidlTest, overlaysDataVerticesCountFail) { + ALOGD("SurroundViewHidlTest, overlaysDataVerticesNotMultipleOf3Fail"); + + sp surroundView3dSession; + mSurroundViewService->start3dSession( + [&surroundView3dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView3dSession = session; + }); + + OverlaysData overlaysData; + overlaysData.overlaysMemoryDesc.resize(1); + + OverlayMemoryDesc overlayMemDesc1; + overlayMemDesc1.id = 0; + overlayMemDesc1.verticesCount = 4; // Invalid count for TRIANGLES primitive. + overlayMemDesc1.overlayPrimitive = OverlayPrimitive::TRIANGLES; + overlaysData.overlaysMemoryDesc[0] = overlayMemDesc1; + + const int sharedMemBytesSize = + 2 + sizeof(float) * overlayMemDesc1.verticesCount; + auto sharedMem = GetMappedSharedMemory(sharedMemBytesSize); + + // Set index in shared memory. + auto& overlaysDesc = overlaysData.overlaysMemoryDesc; + auto& pIMemory = sharedMem.second; + SetIndexOfOverlaysMemory(overlaysDesc, pIMemory, 0, overlayMemDesc1.id); + + SvResult result = surroundView3dSession->updateOverlays(overlaysData); + EXPECT_EQ(result, SvResult::INVALID_ARG); + + mSurroundViewService->stop3dSession(surroundView3dSession); +} + +TEST_P(SurroundViewHidlTest, overlaysDataSameIdFail) { + ALOGD("SurroundViewHidlTest, overlaysDataSameIdFail"); + + sp surroundView3dSession; + mSurroundViewService->start3dSession( + [&surroundView3dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView3dSession = session; + }); + + std::pair> overlaysDataSameId + = GetSampleOverlaysData(); + ASSERT_NE(overlaysDataSameId.second, nullptr); + + // Set id of second overlay as id of first. + auto& overlaysDesc = overlaysDataSameId.first.overlaysMemoryDesc; + auto& pIMemory = overlaysDataSameId.second; + SetIndexOfOverlaysMemory(overlaysDesc, pIMemory, 1, overlaysDesc[0].id); + + SvResult result = surroundView3dSession->updateOverlays( + overlaysDataSameId.first); + EXPECT_EQ(result, SvResult::INVALID_ARG); + + mSurroundViewService->stop3dSession(surroundView3dSession); +} + +TEST_P(SurroundViewHidlTest, projectPointsIncorrectCameraIdFail) { + ALOGD("SurroundViewHidlTest, projectPointsIncorrectCameraIdFail"); + + hidl_vec cameraIds; + mSurroundViewService->getCameraIds([&cameraIds]( + const hidl_vec& camIds) { + cameraIds = camIds; + }); + EXPECT_GT(cameraIds.size(), 0); + + sp surroundView3dSession; + mSurroundViewService->start3dSession( + [&surroundView3dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView3dSession = session; + }); + + Point2dInt cameraPoint; + cameraPoint.x = 0; + cameraPoint.y = 0; + std::vector cameraPoints = {cameraPoint}; + + hidl_string invalidCameraId = "INVALID_CAMERA_ID"; + + std::vector points3d; + surroundView3dSession->projectCameraPointsTo3dSurface( + cameraPoints, invalidCameraId, + [&points3d](const hidl_vec& points3dproj) { + points3d = points3dproj; + }); + + EXPECT_TRUE(points3d.empty()); + + mSurroundViewService->stop3dSession(surroundView3dSession); +} + +TEST_P(SurroundViewHidlTest, projectPointsInvalidPointsFail) { + ALOGD("SurroundViewHidlTest, projectPointsInvalidPointsFail"); + + hidl_vec cameraIds; + mSurroundViewService->getCameraIds([&cameraIds]( + const hidl_vec& camIds) { + cameraIds = camIds; + }); + + EXPECT_GT(cameraIds.size(), 0); + + sp surroundView3dSession; + mSurroundViewService->start3dSession( + [&surroundView3dSession]( + const sp& session, SvResult result) { + ASSERT_EQ(result, SvResult::OK); + surroundView3dSession = session; + }); + + sp handler = + new SurroundViewServiceHandler(surroundView3dSession); + + std::vector views(1); + views[0].viewId = 0; + SvResult setViewResult = surroundView3dSession->setViews(views); + EXPECT_EQ(setViewResult, SvResult::OK); + SvResult result = surroundView3dSession->startStream(handler); + EXPECT_EQ(result, SvResult::OK); + + sleep(1); + + // Get the width and height of the frame + int width, height; + SvFramesDesc frames = handler->getLastReceivedFrames(); + EXPECT_EQ(frames.svBuffers.size(), 1); + SvBuffer svBuffer2d = frames.svBuffers[0]; + const AHardwareBuffer_Desc* pDesc = + reinterpret_cast(&svBuffer2d.hardwareBuffer.description); + width = pDesc->width; + height = pDesc->height; + + Point2dInt cameraPoint; + cameraPoint.x = width * 2; + cameraPoint.y = height * 2; + std::vector cameraPoints = {cameraPoint}; + + std::vector points3d; + surroundView3dSession->projectCameraPointsTo3dSurface( + cameraPoints, cameraIds[0], + [&points3d](const hidl_vec& points3dproj) { + points3d = points3dproj; + }); + + EXPECT_FALSE(points3d[0].isValid); + + surroundView3dSession->stopStream(); + mSurroundViewService->stop3dSession(surroundView3dSession); +} + +INSTANTIATE_TEST_SUITE_P( + PerInstance, + SurroundViewHidlTest, + testing::ValuesIn( + android::hardware::getAllHalInstanceNames(ISurroundViewService::descriptor) + ), + android::hardware::PrintInstanceNameToString +); diff --git a/automotive/vehicle/2.0/default/Android.bp b/automotive/vehicle/2.0/default/Android.bp index ed09859443..d9ac239f62 100644 --- a/automotive/vehicle/2.0/default/Android.bp +++ b/automotive/vehicle/2.0/default/Android.bp @@ -27,6 +27,15 @@ cc_defaults { ], } +cc_defaults { + name: "vhal_v2_0_target_defaults", + defaults: ["vhal_v2_0_defaults"], + shared_libs: [ + "libbinder_ndk", + "carwatchdog_aidl_interface-ndk_platform", + ], +} + cc_library_headers { name: "vhal_v2_0_common_headers", vendor: true, @@ -37,7 +46,7 @@ cc_library_headers { cc_library { name: "android.hardware.automotive.vehicle@2.0-manager-lib", vendor: true, - defaults: ["vhal_v2_0_defaults"], + defaults: ["vhal_v2_0_target_defaults"], srcs: [ "common/src/Obd2SensorStore.cpp", "common/src/SubscriptionManager.cpp", @@ -46,6 +55,10 @@ cc_library { "common/src/VehiclePropertyStore.cpp", "common/src/VehicleUtils.cpp", "common/src/VmsUtils.cpp", + "common/src/WatchdogClient.cpp", + ], + shared_libs: [ + "libbase", ], local_include_dirs: ["common/include/vhal_v2_0"], export_include_dirs: ["common/include"], @@ -55,12 +68,16 @@ cc_library { cc_library_static { name: "android.hardware.automotive.vehicle@2.0-default-impl-lib", vendor: true, - defaults: ["vhal_v2_0_defaults"], + defaults: ["vhal_v2_0_target_defaults"], srcs: [ "impl/vhal_v2_0/CommConn.cpp", + "impl/vhal_v2_0/EmulatedVehicleConnector.cpp", "impl/vhal_v2_0/EmulatedVehicleHal.cpp", + "impl/vhal_v2_0/VehicleHalClient.cpp", + "impl/vhal_v2_0/VehicleHalServer.cpp", "impl/vhal_v2_0/VehicleEmulator.cpp", "impl/vhal_v2_0/PipeComm.cpp", + "impl/vhal_v2_0/ProtoMessageConverter.cpp", "impl/vhal_v2_0/SocketComm.cpp", "impl/vhal_v2_0/LinearFakeValueGenerator.cpp", "impl/vhal_v2_0/JsonFakeValueGenerator.cpp", @@ -68,7 +85,10 @@ cc_library_static { ], local_include_dirs: ["common/include/vhal_v2_0"], export_include_dirs: ["impl"], - whole_static_libs: ["android.hardware.automotive.vehicle@2.0-manager-lib"], + whole_static_libs: [ + "android.hardware.automotive.vehicle@2.0-emulated-user-hal-lib", + "android.hardware.automotive.vehicle@2.0-manager-lib", + ], shared_libs: [ "libbase", "libjsoncpp", @@ -80,10 +100,63 @@ cc_library_static { ], } +// Library used to emulate User HAL behavior through lshal debug requests. +cc_library_static { + name: "android.hardware.automotive.vehicle@2.0-emulated-user-hal-lib", + vendor: true, + defaults: ["vhal_v2_0_target_defaults"], + srcs: [ + "impl/vhal_v2_0/EmulatedUserHal.cpp", + ], +} + +// Vehicle HAL Server reference impl lib +cc_library_static { + name: "android.hardware.automotive.vehicle@2.0-server-common-lib", + vendor: true, + host_supported: true, + defaults: ["vhal_v2_0_defaults"], + local_include_dirs: ["common/include/vhal_v2_0"], + export_include_dirs: ["common/include"], + srcs: [ + "common/src/Obd2SensorStore.cpp", + "common/src/VehicleObjectPool.cpp", + "common/src/VehicleUtils.cpp", + ], +} + +// Vehicle HAL Server default implementation +cc_library_static { + name: "android.hardware.automotive.vehicle@2.0-server-impl-lib", + vendor: true, + host_supported: true, + defaults: ["vhal_v2_0_defaults"], + local_include_dirs: ["common/include/vhal_v2_0"], + export_include_dirs: ["impl"], + srcs: [ + "impl/vhal_v2_0/EmulatedUserHal.cpp", + "impl/vhal_v2_0/GeneratorHub.cpp", + "impl/vhal_v2_0/JsonFakeValueGenerator.cpp", + "impl/vhal_v2_0/LinearFakeValueGenerator.cpp", + "impl/vhal_v2_0/ProtoMessageConverter.cpp", + "impl/vhal_v2_0/VehicleHalServer.cpp", + ], + whole_static_libs: [ + "android.hardware.automotive.vehicle@2.0-server-common-lib", + ], + static_libs: [ + "android.hardware.automotive.vehicle@2.0-libproto-native", + ], + shared_libs: [ + "libbase", + "libjsoncpp", + ], +} + cc_test { name: "android.hardware.automotive.vehicle@2.0-manager-unit-tests", vendor: true, - defaults: ["vhal_v2_0_defaults"], + defaults: ["vhal_v2_0_target_defaults"], whole_static_libs: ["android.hardware.automotive.vehicle@2.0-manager-lib"], srcs: [ "tests/RecurrentTimer_test.cpp", @@ -93,13 +166,34 @@ cc_test { "tests/VehiclePropConfigIndex_test.cpp", "tests/VmsUtils_test.cpp", ], + shared_libs: [ + "libbase", + ], header_libs: ["libbase_headers"], test_suites: ["general-tests"], } +cc_test { + name: "android.hardware.automotive.vehicle@2.0-default-impl-unit-tests", + vendor: true, + defaults: ["vhal_v2_0_target_defaults"], + srcs: [ + "impl/vhal_v2_0/tests/ProtoMessageConverter_test.cpp", + ], + static_libs: [ + "android.hardware.automotive.vehicle@2.0-default-impl-lib", + "android.hardware.automotive.vehicle@2.0-libproto-native", + "libprotobuf-cpp-lite", + ], + test_suites: ["general-tests"], +} + cc_binary { name: "android.hardware.automotive.vehicle@2.0-service", - defaults: ["vhal_v2_0_defaults"], + defaults: ["vhal_v2_0_target_defaults"], + vintf_fragments: [ + "android.hardware.automotive.vehicle@2.0-service.xml", + ], init_rc: ["android.hardware.automotive.vehicle@2.0-service.rc"], vendor: true, relative_install_path: "hw", diff --git a/automotive/vehicle/2.0/default/VehicleService.cpp b/automotive/vehicle/2.0/default/VehicleService.cpp index d1fd55567e..47133fd162 100644 --- a/automotive/vehicle/2.0/default/VehicleService.cpp +++ b/automotive/vehicle/2.0/default/VehicleService.cpp @@ -20,8 +20,13 @@ #include -#include +#include +#include +#include +#include #include +#include +#include using namespace android; using namespace android::hardware; @@ -29,11 +34,14 @@ using namespace android::hardware::automotive::vehicle::V2_0; int main(int /* argc */, char* /* argv */ []) { auto store = std::make_unique(); - auto hal = std::make_unique(store.get()); + auto connector = impl::makeEmulatedPassthroughConnector(); + auto userHal = connector->getEmulatedUserHal(); + auto hal = std::make_unique(store.get(), connector.get(), userHal); auto emulator = std::make_unique(hal.get()); auto service = std::make_unique(hal.get()); + connector->setValuePool(hal->getValuePool()); - configureRpcThreadpool(4, true /* callerWillJoin */); + configureRpcThreadpool(4, false /* callerWillJoin */); ALOGI("Registering as service..."); status_t status = service->registerAsService(); @@ -43,8 +51,22 @@ int main(int /* argc */, char* /* argv */ []) { return 1; } + // Setup a binder thread pool to be a car watchdog client. + ABinderProcess_setThreadPoolMaxThreadCount(1); + ABinderProcess_startThreadPool(); + sp looper(Looper::prepare(0 /* opts */)); + std::shared_ptr watchdogClient = + ndk::SharedRefBase::make(looper, service.get()); + // The current health check is done in the main thread, so it falls short of capturing the real + // situation. Checking through HAL binder thread should be considered. + if (!watchdogClient->initialize()) { + ALOGE("Failed to initialize car watchdog client"); + return 1; + } ALOGI("Ready"); - joinRpcThreadpool(); + while (true) { + looper->pollAll(-1 /* timeoutMillis */); + } return 1; } diff --git a/automotive/vehicle/2.0/manifest.vehicle.xml b/automotive/vehicle/2.0/default/android.hardware.automotive.vehicle@2.0-service.xml similarity index 83% rename from automotive/vehicle/2.0/manifest.vehicle.xml rename to automotive/vehicle/2.0/default/android.hardware.automotive.vehicle@2.0-service.xml index 832b302dfa..660b03d9cf 100644 --- a/automotive/vehicle/2.0/manifest.vehicle.xml +++ b/automotive/vehicle/2.0/default/android.hardware.automotive.vehicle@2.0-service.xml @@ -1,4 +1,4 @@ - + android.hardware.automotive.vehicle hwbinder diff --git a/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleClient.h b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleClient.h new file mode 100644 index 0000000000..5e4bf27d6b --- /dev/null +++ b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleClient.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 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 + +#include + +namespace android::hardware::automotive::vehicle::V2_0 { + +/** + * Vehicle HAL talks to the vehicle through a client, instead of accessing + * the car bus directly, to give us more flexibility on the implementation. + * Android OS do not need direct access to the vehicle, and the communication + * channel is also customizable. + * + * Client lives on the Android (HAL) side to talk to the vehicle + */ +class IVehicleClient { + public: + IVehicleClient() = default; + + IVehicleClient(const IVehicleClient&) = delete; + + IVehicleClient& operator=(const IVehicleClient&) = delete; + + IVehicleClient(IVehicleClient&&) = default; + + virtual ~IVehicleClient() = default; + + // Get configuration of all properties from server + virtual std::vector getAllPropertyConfig() const = 0; + + // Send the set property request to server + // updateStatus indicate if VHal should change the status of the value + // it should be false except injecting values for e2e tests + virtual StatusCode setProperty(const VehiclePropValue& value, bool updateStatus) = 0; + + // Receive a new property value from server + // updateStatus is true if and only if the value is + // generated by car (ECU/fake generator/injected) + virtual void onPropertyValue(const VehiclePropValue& value, bool updateStatus) = 0; + + // Dump method forwarded from HIDL's debug() + // If implemented, it must return whether the caller should dump its state. + virtual bool dump(const hidl_handle& /* handle */, const hidl_vec& /* options */) { + return true; + } +}; + +} // namespace android::hardware::automotive::vehicle::V2_0 diff --git a/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleConnector.h b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleConnector.h new file mode 100644 index 0000000000..2908a55c25 --- /dev/null +++ b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleConnector.h @@ -0,0 +1,90 @@ +/* + * 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_automotive_vehicle_V2_0_VehicleConnector_H_ +#define android_hardware_automotive_vehicle_V2_0_VehicleConnector_H_ + +#include + +#include + +#include "VehicleClient.h" +#include "VehicleServer.h" + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +/** + * This file defines the interface of client/server pair for HAL-vehicle + * communication. Vehicle HAL may use this interface to talk to the vehicle + * regardless of the underlying communication channels. + */ + +/** + * If Android has direct access to the vehicle, then the client and + * the server may act in passthrough mode to avoid extra IPC + * + * Template is used here for spliting the logic of operating Android objects (VehicleClientType), + * talking to cars (VehicleServerType) and the commucation between client and server (passthrough + * mode in this case), so that we can easily combine different parts together without duplicating + * codes (for example, in Google VHAL, the server talks to the fake car in the same way no matter + * if it is on top of passthrough connector or VSOCK or any other communication channels between + * client and server) + * + * The alternative may be factoring the common logic of every operations for both client and + * server. Which is not always the case. Making sure different non-template connectors calling + * the same method is hard, especially when the engineer maintaining the code may not be aware + * of it when making changes. Template is a clean and easy way to solve this problem in this + * case. + */ +template +class IPassThroughConnector : public VehicleClientType, public VehicleServerType { + static_assert(std::is_base_of_v); + static_assert(std::is_base_of_v); + + public: + std::vector getAllPropertyConfig() const override { + return this->onGetAllPropertyConfig(); + } + + StatusCode setProperty(const VehiclePropValue& value, bool updateStatus) override { + return this->onSetProperty(value, updateStatus); + } + + void onPropertyValueFromCar(const VehiclePropValue& value, bool updateStatus) override { + return this->onPropertyValue(value, updateStatus); + } + + bool dump(const hidl_handle& handle, const hidl_vec& options) override { + return this->onDump(handle, options); + } + + // To be implemented: + // virtual std::vector onGetAllPropertyConfig() = 0; + // virtual void onPropertyValue(const VehiclePropValue& value) = 0; + // virtual StatusCode onSetProperty(const VehiclePropValue& value) = 0; +}; + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // android_hardware_automotive_vehicle_V2_0_VehicleConnector_H_ diff --git a/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleHal.h b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleHal.h index fd28483a4e..fe01867611 100644 --- a/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleHal.h +++ b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleHal.h @@ -70,6 +70,26 @@ public: */ virtual void onCreate() {} + /** + * Dump method forwarded from HIDL's debug(). + * + * By default it doesn't dump anything and let caller dump its properties, but it could be + * override to change the behavior. For example: + * + * - To augment caller's dump, it should dump its state and return true. + * - To not dump anything at all, it should just return false. + * - To provide custom dump (like dumping just specific state or executing a custom command), + * it should check if options is not empty, handle the options accordingly, then return false. + * + * @param handle handle used to dump the contents. + * @param options options passed to dump. + * + * @return whether the caller should dump its state. + */ + virtual bool dump(const hidl_handle& /* handle */, const hidl_vec& /* options */) { + return true; + } + void init( VehiclePropValuePool* valueObjectPool, const HalEventFunction& onHalEvent, diff --git a/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleHalManager.h b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleHalManager.h index c1e9e88609..fcfe7612ac 100644 --- a/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleHalManager.h +++ b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleHalManager.h @@ -73,7 +73,9 @@ public: int32_t propId) override; Return debugDump(debugDump_cb _hidl_cb = nullptr) override; -private: + Return debug(const hidl_handle& fd, const hidl_vec& options) override; + + private: using VehiclePropValuePtr = VehicleHal::VehiclePropValuePtr; // Returns true if needs to call again shortly. using RetriableAction = std::function; @@ -96,6 +98,22 @@ private: bool checkReadPermission(const VehiclePropConfig &config) const; void onAllClientsUnsubscribed(int32_t propertyId); + // Dump and commands + // TODO: most functions below (exception dump() and cmdSetOne()) should be const, but they rely + // on IVehicle.get(), which isn't... + void cmdDump(int fd, const hidl_vec& options); + void cmdDumpOneProperty(int fd, int32_t prop, int32_t areaId); + void cmdDumpOneProperty(int fd, int rowNumber, const VehiclePropConfig& config); + + static bool checkArgumentsSize(int fd, const hidl_vec& options, size_t minSize); + static bool checkCallerHasWritePermissions(int fd); + static bool safelyParseInt(int fd, int index, std::string s, int* out); + void cmdHelp(int fd) const; + void cmdListAllProperties(int fd) const; + void cmdDumpAllProperties(int fd); + void cmdDumpSpecificProperties(int fd, const hidl_vec& options); + void cmdSetOneProperty(int fd, const hidl_vec& options); + static bool isSubscribable(const VehiclePropConfig& config, SubscribeFlags flags); static bool isSampleRateFixed(VehiclePropertyChangeMode mode); diff --git a/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleObjectPool.h b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleObjectPool.h index 946e74ddda..f2d3c1361e 100644 --- a/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleObjectPool.h +++ b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleObjectPool.h @@ -21,6 +21,7 @@ #include #include #include +#include #include @@ -205,7 +206,7 @@ private: InternalPool(VehiclePropertyType type, size_t vectorSize) : mPropType(type), mVectorSize(vectorSize) {} - RecyclableType obtain() { + RecyclableType obtain() override { return ObjectPool::obtain(); } protected: diff --git a/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleServer.h b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleServer.h new file mode 100644 index 0000000000..ba9799af1b --- /dev/null +++ b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleServer.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 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 + +#include + +namespace android::hardware::automotive::vehicle::V2_0 { + +/** + * Server lives on the vehicle side to talk to Android HAL. + * Note that the server may not be run on Android + */ +class IVehicleServer { + public: + IVehicleServer() = default; + + IVehicleServer(const IVehicleServer&) = delete; + + IVehicleServer& operator=(const IVehicleServer&) = delete; + + IVehicleServer(IVehicleServer&&) = default; + + virtual ~IVehicleServer() = default; + + // Receive the get property configuration request from HAL. + // Return a list of all property config + virtual std::vector onGetAllPropertyConfig() const = 0; + + // Receive the set property request from HAL. + // Process the setting and return the status code + // updateStatus indicate if VHal should change the status of the value + // it should be false except injecting values for e2e tests + virtual StatusCode onSetProperty(const VehiclePropValue& value, bool updateStatus) = 0; + + // Receive a new property value from car (via direct connection to the car bus or the emulator) + // and forward the value to HAL + // updateStatus is true if and only if the value is + // generated by car (ECU/fake generator/injected) + virtual void onPropertyValueFromCar(const VehiclePropValue& value, bool updateStatus) = 0; + + // TODO (chenhaosjtuacm): fix this since there are no HIDL in non-Android OS +#ifdef __ANDROID__ + // Dump method forwarded from HIDL's debug() + // If implemented, it must return whether the caller should dump its state. + virtual bool onDump(const hidl_handle& /* handle */, + const hidl_vec& /* options */) { + return true; + } +#endif // __ANDROID__ +}; + +} // namespace android::hardware::automotive::vehicle::V2_0 diff --git a/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleUtils.h b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleUtils.h index f97dfa1bba..955341504c 100644 --- a/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleUtils.h +++ b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/VehicleUtils.h @@ -19,7 +19,9 @@ #include +#ifdef __ANDROID__ #include +#endif #include @@ -69,6 +71,8 @@ size_t getVehicleRawValueVectorSize( void copyVehicleRawValue(VehiclePropValue::RawValue* dest, const VehiclePropValue::RawValue& src); +#ifdef __ANDROID__ + template void shallowCopyHidlVec(hidl_vec* dest, const hidl_vec& src); @@ -76,6 +80,8 @@ void shallowCopyHidlStr(hidl_string* dest, const hidl_string& src); void shallowCopy(VehiclePropValue* dest, const VehiclePropValue& src); +#endif // __ANDROID__ + } // namespace V2_0 } // namespace vehicle } // namespace automotive diff --git a/automotive/vehicle/2.0/default/common/include/vhal_v2_0/WatchdogClient.h b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/WatchdogClient.h new file mode 100644 index 0000000000..578606dd21 --- /dev/null +++ b/automotive/vehicle/2.0/default/common/include/vhal_v2_0/WatchdogClient.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2020 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_automotive_vehicle_V2_0_WatchdogClient_H_ +#define android_hardware_automotive_vehicle_V2_0_WatchdogClient_H_ + +#include "VehicleHalManager.h" + +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +class WatchdogClient : public aidl::android::automotive::watchdog::BnCarWatchdogClient { + public: + explicit WatchdogClient(const ::android::sp<::android::Looper>& handlerLooper, + VehicleHalManager* vhalManager); + + ndk::ScopedAStatus checkIfAlive( + int32_t sessionId, aidl::android::automotive::watchdog::TimeoutLength timeout) override; + ndk::ScopedAStatus prepareProcessTermination() override; + + bool initialize(); + + private: + class MessageHandlerImpl : public ::android::MessageHandler { + public: + explicit MessageHandlerImpl(WatchdogClient* client); + void handleMessage(const ::android::Message& message) override; + + private: + WatchdogClient* mClient; + }; + + private: + void respondToWatchdog(); + bool isClientHealthy() const; + + private: + ::android::sp<::android::Looper> mHandlerLooper; + ::android::sp mMessageHandler; + std::shared_ptr mWatchdogServer; + std::shared_ptr mTestClient; + VehicleHalManager* mVhalManager; + ::android::Mutex mMutex; + int mCurrentSessionId GUARDED_BY(mMutex); +}; + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // android_hardware_automotive_vehicle_V2_0_WatchdogClient_H_ diff --git a/automotive/vehicle/2.0/default/common/src/VehicleHalManager.cpp b/automotive/vehicle/2.0/default/common/src/VehicleHalManager.cpp index 393d3ecc5e..b09e9bfba0 100644 --- a/automotive/vehicle/2.0/default/common/src/VehicleHalManager.cpp +++ b/automotive/vehicle/2.0/default/common/src/VehicleHalManager.cpp @@ -21,8 +21,14 @@ #include #include -#include +#include +#include #include +#include + +#include +#include +#include #include "VehicleUtils.h" @@ -34,6 +40,10 @@ namespace V2_0 { using namespace std::placeholders; +using ::android::base::EqualsIgnoreCase; +using ::android::hardware::hidl_handle; +using ::android::hardware::hidl_string; + constexpr std::chrono::milliseconds kHalEventBatchingTimeWindow(10); const VehiclePropValue kEmptyValue{}; @@ -172,6 +182,251 @@ Return VehicleHalManager::debugDump(IVehicle::debugDump_cb _hidl_cb) { return Void(); } +Return VehicleHalManager::debug(const hidl_handle& fd, const hidl_vec& options) { + if (fd.getNativeHandle() == nullptr || fd->numFds == 0) { + ALOGE("Invalid parameters passed to debug()"); + return Void(); + } + + bool shouldContinue = mHal->dump(fd, options); + if (!shouldContinue) { + ALOGI("Dumped HAL only"); + return Void(); + } + + // Do our dump + cmdDump(fd->data[0], options); + return Void(); +} + +void VehicleHalManager::cmdDump(int fd, const hidl_vec& options) { + if (options.size() == 0) { + cmdDumpAllProperties(fd); + return; + } + std::string option = options[0]; + if (EqualsIgnoreCase(option, "--help")) { + cmdHelp(fd); + } else if (EqualsIgnoreCase(option, "--list")) { + cmdListAllProperties(fd); + } else if (EqualsIgnoreCase(option, "--get")) { + cmdDumpSpecificProperties(fd, options); + } else if (EqualsIgnoreCase(option, "--set")) { + cmdSetOneProperty(fd, options); + } else { + dprintf(fd, "Invalid option: %s\n", option.c_str()); + } +} + +bool VehicleHalManager::checkCallerHasWritePermissions(int fd) { + // Double check that's only called by root - it should be be blocked at the HIDL debug() level, + // but it doesn't hurt to make sure... + if (hardware::IPCThreadState::self()->getCallingUid() != AID_ROOT) { + dprintf(fd, "Must be root\n"); + return false; + } + return true; +} + +bool VehicleHalManager::checkArgumentsSize(int fd, const hidl_vec& options, + size_t minSize) { + size_t size = options.size(); + if (size >= minSize) { + return true; + } + dprintf(fd, "Invalid number of arguments: required at least %zu, got %zu\n", minSize, size); + return false; +} + +bool VehicleHalManager::safelyParseInt(int fd, int index, std::string s, int* out) { + if (!android::base::ParseInt(s, out)) { + dprintf(fd, "non-integer argument at index %d: %s\n", index, s.c_str()); + return false; + } + return true; +} + +void VehicleHalManager::cmdHelp(int fd) const { + dprintf(fd, "Usage: \n\n"); + dprintf(fd, "[no args]: dumps (id and value) all supported properties \n"); + dprintf(fd, "--help: shows this help\n"); + dprintf(fd, "--list: lists the ids of all supported properties\n"); + dprintf(fd, "--get [PROP2] [PROPN]: dumps the value of specific properties \n"); + // TODO: support other formats (int64, float, bytes) + dprintf(fd, + "--set [ ] [a AREA_ID] : sets the value of " + "property PROP, using arbitrary number of key/value parameters (i for int32, " + "s for string) and an optional area.\n" + "Notice that the string value can be set just once, while the other can have multiple " + "values (so they're used in the respective array)\n"); +} + +void VehicleHalManager::cmdListAllProperties(int fd) const { + auto& halConfig = mConfigIndex->getAllConfigs(); + size_t size = halConfig.size(); + if (size == 0) { + dprintf(fd, "no properties to list\n"); + return; + } + int i = 0; + dprintf(fd, "listing %zu properties\n", size); + for (const auto& config : halConfig) { + dprintf(fd, "%d: %d\n", ++i, config.prop); + } +} + +void VehicleHalManager::cmdDumpAllProperties(int fd) { + auto& halConfig = mConfigIndex->getAllConfigs(); + size_t size = halConfig.size(); + if (size == 0) { + dprintf(fd, "no properties to dump\n"); + return; + } + int rowNumber = 0; + dprintf(fd, "dumping %zu properties\n", size); + for (auto& config : halConfig) { + cmdDumpOneProperty(fd, ++rowNumber, config); + } +} + +void VehicleHalManager::cmdDumpOneProperty(int fd, int rowNumber, const VehiclePropConfig& config) { + size_t numberAreas = config.areaConfigs.size(); + if (numberAreas == 0) { + if (rowNumber > 0) { + dprintf(fd, "%d: ", rowNumber); + } + cmdDumpOneProperty(fd, config.prop, /* areaId= */ 0); + return; + } + for (size_t j = 0; j < numberAreas; ++j) { + if (rowNumber > 0) { + if (numberAreas > 1) { + dprintf(fd, "%d/%zu: ", rowNumber, j); + } else { + dprintf(fd, "%d: ", rowNumber); + } + } + cmdDumpOneProperty(fd, config.prop, config.areaConfigs[j].areaId); + } +} + +void VehicleHalManager::cmdDumpSpecificProperties(int fd, const hidl_vec& options) { + if (!checkArgumentsSize(fd, options, 2)) return; + + // options[0] is the command itself... + int rowNumber = 0; + size_t size = options.size(); + for (size_t i = 1; i < size; ++i) { + int prop; + if (!safelyParseInt(fd, i, options[i], &prop)) return; + const auto* config = getPropConfigOrNull(prop); + if (config == nullptr) { + dprintf(fd, "No property %d\n", prop); + continue; + } + if (size > 2) { + // Only show row number if there's more than 1 + rowNumber++; + } + cmdDumpOneProperty(fd, rowNumber, *config); + } +} + +void VehicleHalManager::cmdDumpOneProperty(int fd, int32_t prop, int32_t areaId) { + VehiclePropValue input; + input.prop = prop; + input.areaId = areaId; + auto callback = [&](StatusCode status, const VehiclePropValue& output) { + if (status == StatusCode::OK) { + dprintf(fd, "%s\n", toString(output).c_str()); + } else { + dprintf(fd, "Could not get property %d. Error: %s\n", prop, toString(status).c_str()); + } + }; + get(input, callback); +} + +void VehicleHalManager::cmdSetOneProperty(int fd, const hidl_vec& options) { + if (!checkCallerHasWritePermissions(fd) || !checkArgumentsSize(fd, options, 3)) return; + + size_t size = options.size(); + + // Syntax is --set PROP Type1 Value1 TypeN ValueN, so number of arguments must be even + if (size % 2 != 0) { + dprintf(fd, "must pass even number of arguments (passed %zu)\n", size); + return; + } + int numberValues = (size - 2) / 2; + + VehiclePropValue prop; + if (!safelyParseInt(fd, 1, options[1], &prop.prop)) return; + prop.timestamp = elapsedRealtimeNano(); + prop.status = VehiclePropertyStatus::AVAILABLE; + + // First pass: calculate sizes + int sizeInt32 = 0; + int stringIndex = 0; + int areaIndex = 0; + for (int i = 2, kv = 1; kv <= numberValues; kv++) { + // iterate through the kv=1..n key/value pairs, accessing indexes i / i+1 at each step + std::string type = options[i]; + std::string value = options[i + 1]; + if (EqualsIgnoreCase(type, "i")) { + sizeInt32++; + } else if (EqualsIgnoreCase(type, "s")) { + if (stringIndex != 0) { + dprintf(fd, + "defining string value (%s) again at index %d (already defined at %d=%s" + ")\n", + value.c_str(), i, stringIndex, options[stringIndex + 1].c_str()); + return; + } + stringIndex = i; + } else if (EqualsIgnoreCase(type, "a")) { + if (areaIndex != 0) { + dprintf(fd, + "defining area value (%s) again at index %d (already defined at %d=%s" + ")\n", + value.c_str(), i, areaIndex, options[areaIndex + 1].c_str()); + return; + } + areaIndex = i; + } else { + dprintf(fd, "invalid (%s) type at index %d\n", type.c_str(), i); + return; + } + i += 2; + } + prop.value.int32Values.resize(sizeInt32); + + // Second pass: populate it + int indexInt32 = 0; + for (int i = 2, kv = 1; kv <= numberValues; kv++) { + // iterate through the kv=1..n key/value pairs, accessing indexes i / i+1 at each step + int valueIndex = i + 1; + std::string type = options[i]; + std::string value = options[valueIndex]; + if (EqualsIgnoreCase(type, "i")) { + int safeInt; + if (!safelyParseInt(fd, valueIndex, value, &safeInt)) return; + prop.value.int32Values[indexInt32++] = safeInt; + } else if (EqualsIgnoreCase(type, "s")) { + prop.value.stringValue = value; + } else if (EqualsIgnoreCase(type, "a")) { + if (!safelyParseInt(fd, valueIndex, value, &prop.areaId)) return; + } + i += 2; + } + ALOGD("Setting prop %s", toString(prop).c_str()); + auto status = set(prop); + if (status == StatusCode::OK) { + dprintf(fd, "Set property %s\n", toString(prop).c_str()); + } else { + dprintf(fd, "Failed to set property %s: %s\n", toString(prop).c_str(), + toString(status).c_str()); + } +} + void VehicleHalManager::init() { ALOGI("VehicleHalManager::init"); diff --git a/automotive/vehicle/2.0/default/common/src/VehicleObjectPool.cpp b/automotive/vehicle/2.0/default/common/src/VehicleObjectPool.cpp index 40dd56e73d..0947c9fe8c 100644 --- a/automotive/vehicle/2.0/default/common/src/VehicleObjectPool.cpp +++ b/automotive/vehicle/2.0/default/common/src/VehicleObjectPool.cpp @@ -131,7 +131,7 @@ void VehiclePropValuePool::InternalPool::recycle(VehiclePropValue* o) { ALOGE("Discarding value for prop 0x%x because it contains " "data that is not consistent with this pool. " "Expected type: %d, vector size: %zu", - o->prop, mPropType, mVectorSize); + o->prop, toInt(mPropType), mVectorSize); delete o; } else { ObjectPool::recycle(o); diff --git a/automotive/vehicle/2.0/default/common/src/VehiclePropertyStore.cpp b/automotive/vehicle/2.0/default/common/src/VehiclePropertyStore.cpp index 94ace455cc..6087bfa526 100644 --- a/automotive/vehicle/2.0/default/common/src/VehiclePropertyStore.cpp +++ b/automotive/vehicle/2.0/default/common/src/VehiclePropertyStore.cpp @@ -50,12 +50,20 @@ bool VehiclePropertyStore::writeValue(const VehiclePropValue& propValue, VehiclePropValue* valueToUpdate = const_cast(getValueOrNullLocked(recId)); if (valueToUpdate == nullptr) { mPropertyValues.insert({ recId, propValue }); - } else { - valueToUpdate->timestamp = propValue.timestamp; - valueToUpdate->value = propValue.value; - if (updateStatus) { - valueToUpdate->status = propValue.status; - } + return true; + } + + // propValue is outdated and drops it. + if (valueToUpdate->timestamp > propValue.timestamp) { + return false; + } + // update the propertyValue. + // The timestamp in propertyStore should only be updated by the server side. It indicates + // the time when the event is generated by the server. + valueToUpdate->timestamp = propValue.timestamp; + valueToUpdate->value = propValue.value; + if (updateStatus) { + valueToUpdate->status = propValue.status; } return true; } diff --git a/automotive/vehicle/2.0/default/common/src/VehicleUtils.cpp b/automotive/vehicle/2.0/default/common/src/VehicleUtils.cpp index 5b6816ee21..c16b29a2e7 100644 --- a/automotive/vehicle/2.0/default/common/src/VehicleUtils.cpp +++ b/automotive/vehicle/2.0/default/common/src/VehicleUtils.cpp @@ -52,7 +52,7 @@ std::unique_ptr createVehiclePropValue( case VehiclePropertyType::MIXED: break; // Valid, but nothing to do. default: - ALOGE("createVehiclePropValue: unknown type: %d", type); + ALOGE("createVehiclePropValue: unknown type: %d", toInt(type)); val.reset(nullptr); } return val; @@ -78,13 +78,6 @@ size_t getVehicleRawValueVectorSize( } } -template -inline void copyHidlVec(hidl_vec * dest, const hidl_vec & src) { - for (size_t i = 0; i < std::min(dest->size(), src.size()); i++) { - (*dest)[i] = src[i]; - } -} - void copyVehicleRawValue(VehiclePropValue::RawValue* dest, const VehiclePropValue::RawValue& src) { dest->int32Values = src.int32Values; @@ -94,6 +87,15 @@ void copyVehicleRawValue(VehiclePropValue::RawValue* dest, dest->stringValue = src.stringValue; } +#ifdef __ANDROID__ + +template +inline void copyHidlVec(hidl_vec * dest, const hidl_vec & src) { + for (size_t i = 0; i < std::min(dest->size(), src.size()); i++) { + (*dest)[i] = src[i]; + } +} + template void shallowCopyHidlVec(hidl_vec * dest, const hidl_vec & src) { if (src.size() > 0) { @@ -123,6 +125,7 @@ void shallowCopy(VehiclePropValue* dest, const VehiclePropValue& src) { shallowCopyHidlStr(&dest->value.stringValue, src.value.stringValue); } +#endif // __ANDROID__ //} // namespace utils diff --git a/automotive/vehicle/2.0/default/common/src/WatchdogClient.cpp b/automotive/vehicle/2.0/default/common/src/WatchdogClient.cpp new file mode 100644 index 0000000000..c067216668 --- /dev/null +++ b/automotive/vehicle/2.0/default/common/src/WatchdogClient.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2020 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 "automotive.vehicle@2.0-watchdog" + +#include + +#include +#include + +using aidl::android::automotive::watchdog::ICarWatchdog; +using aidl::android::automotive::watchdog::TimeoutLength; + +namespace { + +enum { WHAT_CHECK_ALIVE = 1 }; + +} // namespace + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +WatchdogClient::WatchdogClient(const sp& handlerLooper, VehicleHalManager* vhalManager) + : mHandlerLooper(handlerLooper), mVhalManager(vhalManager), mCurrentSessionId(-1) { + mMessageHandler = new MessageHandlerImpl(this); +} + +ndk::ScopedAStatus WatchdogClient::checkIfAlive(int32_t sessionId, TimeoutLength /*timeout*/) { + mHandlerLooper->removeMessages(mMessageHandler, WHAT_CHECK_ALIVE); + { + Mutex::Autolock lock(mMutex); + mCurrentSessionId = sessionId; + } + mHandlerLooper->sendMessage(mMessageHandler, Message(WHAT_CHECK_ALIVE)); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus WatchdogClient::prepareProcessTermination() { + return ndk::ScopedAStatus::ok(); +} + +bool WatchdogClient::initialize() { + ndk::SpAIBinder binder( + AServiceManager_getService("android.automotive.watchdog.ICarWatchdog/default")); + if (binder.get() == nullptr) { + ALOGE("Failed to get carwatchdog daemon"); + return false; + } + std::shared_ptr server = ICarWatchdog::fromBinder(binder); + if (server == nullptr) { + ALOGE("Failed to connect to carwatchdog daemon"); + return false; + } + mWatchdogServer = server; + + binder = this->asBinder(); + if (binder.get() == nullptr) { + ALOGE("Failed to get car watchdog client binder object"); + return false; + } + std::shared_ptr client = ICarWatchdogClient::fromBinder(binder); + if (client == nullptr) { + ALOGE("Failed to get ICarWatchdogClient from binder"); + return false; + } + mTestClient = client; + mWatchdogServer->registerClient(client, TimeoutLength::TIMEOUT_NORMAL); + ALOGI("Successfully registered the client to car watchdog server"); + return true; +} + +void WatchdogClient::respondToWatchdog() { + if (mWatchdogServer == nullptr) { + ALOGW("Cannot respond to car watchdog daemon: car watchdog daemon is not connected"); + return; + } + int sessionId; + { + Mutex::Autolock lock(mMutex); + sessionId = mCurrentSessionId; + } + if (isClientHealthy()) { + ndk::ScopedAStatus status = mWatchdogServer->tellClientAlive(mTestClient, sessionId); + if (!status.isOk()) { + ALOGE("Failed to call tellClientAlive(session id = %d): %d", sessionId, + status.getStatus()); + return; + } + } +} + +bool WatchdogClient::isClientHealthy() const { + // We consider that default vehicle HAL is healthy if we can get PERF_VEHICLE_SPEED value. + StatusCode status = StatusCode::TRY_AGAIN; + VehiclePropValue propValue = {.prop = (int32_t)VehicleProperty::PERF_VEHICLE_SPEED}; + while (status == StatusCode::TRY_AGAIN) { + mVhalManager->get(propValue, + [&propValue, &status](StatusCode s, const VehiclePropValue& v) { + status = s; + if (s == StatusCode::OK) { + propValue = v; + } + }); + } + return status == StatusCode::OK; +} + +WatchdogClient::MessageHandlerImpl::MessageHandlerImpl(WatchdogClient* client) : mClient(client) {} + +void WatchdogClient::MessageHandlerImpl::handleMessage(const Message& message) { + switch (message.what) { + case WHAT_CHECK_ALIVE: + mClient->respondToWatchdog(); + break; + default: + ALOGW("Unknown message: %d", message.what); + } +} + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/CommConn.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/CommConn.cpp index bf1de8165a..136b2e0758 100644 --- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/CommConn.cpp +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/CommConn.cpp @@ -41,7 +41,7 @@ void CommConn::stop() { } } -void CommConn::sendMessage(emulator::EmulatorMessage const& msg) { +void CommConn::sendMessage(vhal_proto::EmulatorMessage const& msg) { int numBytes = msg.ByteSize(); std::vector buffer(static_cast(numBytes)); if (!msg.SerializeToArray(buffer.data(), numBytes)) { @@ -61,9 +61,9 @@ void CommConn::readThread() { break; } - emulator::EmulatorMessage rxMsg; + vhal_proto::EmulatorMessage rxMsg; if (rxMsg.ParseFromArray(buffer.data(), static_cast(buffer.size()))) { - emulator::EmulatorMessage respMsg; + vhal_proto::EmulatorMessage respMsg; mMessageProcessor->processMessage(rxMsg, respMsg); sendMessage(respMsg); diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/CommConn.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/CommConn.h index 87b0dfc17d..6d36da417f 100644 --- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/CommConn.h +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/CommConn.h @@ -44,8 +44,8 @@ class MessageProcessor { * Process a single message received over a CommConn. Populate the given respMsg with the reply * message we should send. */ - virtual void processMessage(emulator::EmulatorMessage const& rxMsg, - emulator::EmulatorMessage& respMsg) = 0; + virtual void processMessage(vhal_proto::EmulatorMessage const& rxMsg, + vhal_proto::EmulatorMessage& respMsg) = 0; }; /** @@ -93,7 +93,7 @@ class CommConn { /** * Serialized and send the given message to the other side. */ - void sendMessage(emulator::EmulatorMessage const& msg); + void sendMessage(vhal_proto::EmulatorMessage const& msg); protected: std::unique_ptr mReadThread; 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 3070171325..16c33b9d19 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 @@ -17,9 +17,11 @@ #ifndef android_hardware_automotive_vehicle_V2_0_impl_DefaultConfig_H_ #define android_hardware_automotive_vehicle_V2_0_impl_DefaultConfig_H_ -#include +#include #include +#include + namespace android { namespace hardware { namespace automotive { @@ -70,6 +72,7 @@ constexpr int VENDOR_EXTENSION_STRING_PROPERTY = (int)(0x104 | VehiclePropertyGroup::VENDOR | VehiclePropertyType::STRING | VehicleArea::GLOBAL); constexpr int FUEL_DOOR_REAR_LEFT = (int)PortLocationType::REAR_LEFT; constexpr int CHARGE_PORT_FRONT_LEFT = (int)PortLocationType::FRONT_LEFT; +constexpr int CHARGE_PORT_REAR_LEFT = (int)PortLocationType::REAR_LEFT; constexpr int LIGHT_STATE_ON = (int)VehicleLightState::ON; constexpr int LIGHT_SWITCH_AUTO = (int)VehicleLightSwitch::AUTOMATIC; constexpr int WHEEL_FRONT_LEFT = (int)VehicleAreaWheel::LEFT_FRONT; @@ -84,6 +87,34 @@ constexpr int WHEEL_REAR_RIGHT = (int)VehicleAreaWheel::RIGHT_REAR; 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 kSetPropertyFromVehicleForTest 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 kSetIntPropertyFromVehicleForTest = + 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 kSetFloatPropertyFromVehicleForTest = + 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 kSetBooleanPropertyFromVehicleForTest = + 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. + */ +const int32_t kMixedTypePropertyForTest = + 0x1111 | VehiclePropertyGroup::VENDOR | VehicleArea::GLOBAL | VehiclePropertyType::MIXED; /** * FakeDataCommand enum defines the supported command type for kGenerateFakeDataControllingProperty. * All those commands can be send independently with each other. And each will override the one sent @@ -167,7 +198,6 @@ const ConfigDeclaration kVehicleProperties[]{ .prop = toInt(VehicleProperty::INFO_FUEL_CAPACITY), .access = VehiclePropertyAccess::READ, .changeMode = VehiclePropertyChangeMode::STATIC, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, }, .initialValue = {.floatValues = {15000.0f}}}, @@ -177,14 +207,13 @@ const ConfigDeclaration kVehicleProperties[]{ .access = VehiclePropertyAccess::READ, .changeMode = VehiclePropertyChangeMode::STATIC, }, - .initialValue = {.int32Values = {1}}}, + .initialValue = {.int32Values = {(int)FuelType::FUEL_TYPE_UNLEADED}}}, {.config = { .prop = toInt(VehicleProperty::INFO_EV_BATTERY_CAPACITY), .access = VehiclePropertyAccess::READ, .changeMode = VehiclePropertyChangeMode::STATIC, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, }, .initialValue = {.floatValues = {150000.0f}}}, @@ -194,14 +223,13 @@ const ConfigDeclaration kVehicleProperties[]{ .access = VehiclePropertyAccess::READ, .changeMode = VehiclePropertyChangeMode::STATIC, }, - .initialValue = {.int32Values = {1}}}, + .initialValue = {.int32Values = {(int)EvConnectorType::IEC_TYPE_1_AC}}}, {.config = { .prop = toInt(VehicleProperty::INFO_DRIVER_SEAT), .access = VehiclePropertyAccess::READ, .changeMode = VehiclePropertyChangeMode::STATIC, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, }, .initialValue = {.int32Values = {SEAT_1_LEFT}}}, @@ -210,7 +238,6 @@ const ConfigDeclaration kVehicleProperties[]{ .prop = toInt(VehicleProperty::INFO_FUEL_DOOR_LOCATION), .access = VehiclePropertyAccess::READ, .changeMode = VehiclePropertyChangeMode::STATIC, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, }, .initialValue = {.int32Values = {FUEL_DOOR_REAR_LEFT}}}, @@ -219,10 +246,17 @@ const ConfigDeclaration kVehicleProperties[]{ .prop = toInt(VehicleProperty::INFO_EV_PORT_LOCATION), .access = VehiclePropertyAccess::READ, .changeMode = VehiclePropertyChangeMode::STATIC, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, }, .initialValue = {.int32Values = {CHARGE_PORT_FRONT_LEFT}}}, + {.config = + { + .prop = toInt(VehicleProperty::INFO_MULTI_EV_PORT_LOCATIONS), + .access = VehiclePropertyAccess::READ, + .changeMode = VehiclePropertyChangeMode::STATIC, + }, + .initialValue = {.int32Values = {CHARGE_PORT_FRONT_LEFT, CHARGE_PORT_REAR_LEFT}}}, + {.config = { .prop = toInt(VehicleProperty::INFO_MAKE), @@ -230,6 +264,13 @@ const ConfigDeclaration kVehicleProperties[]{ .changeMode = VehiclePropertyChangeMode::STATIC, }, .initialValue = {.stringValue = "Toy Vehicle"}}, + {.config = + { + .prop = toInt(VehicleProperty::INFO_EXTERIOR_DIMENSIONS), + .access = VehiclePropertyAccess::READ, + .changeMode = VehiclePropertyChangeMode::STATIC, + }, + .initialValue = {.floatValues = {1776, 4950, 2008, 2140, 2984, 1665, 1667, 11800}}}, {.config = { .prop = toInt(VehicleProperty::PERF_VEHICLE_SPEED), @@ -265,10 +306,29 @@ const ConfigDeclaration kVehicleProperties[]{ { .prop = toInt(VehicleProperty::PERF_ODOMETER), .access = VehiclePropertyAccess::READ, - .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + .changeMode = VehiclePropertyChangeMode::CONTINUOUS, + .minSampleRate = 0.0f, + .maxSampleRate = 10.0f, + }, + .initialValue = {.floatValues = {0.0f}}}, + {.config = + { + .prop = toInt(VehicleProperty::PERF_STEERING_ANGLE), + .access = VehiclePropertyAccess::READ, + .changeMode = VehiclePropertyChangeMode::CONTINUOUS, + .minSampleRate = 0.0f, + .maxSampleRate = 10.0f, + }, + .initialValue = {.floatValues = {0.0f}}}, + {.config = + { + .prop = toInt(VehicleProperty::PERF_REAR_STEERING_ANGLE), + .access = VehiclePropertyAccess::READ, + .changeMode = VehiclePropertyChangeMode::CONTINUOUS, + .minSampleRate = 0.0f, + .maxSampleRate = 10.0f, }, .initialValue = {.floatValues = {0.0f}}}, - { .config = { @@ -285,8 +345,9 @@ const ConfigDeclaration kVehicleProperties[]{ { .prop = toInt(VehicleProperty::FUEL_LEVEL), .access = VehiclePropertyAccess::READ, - .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, + .changeMode = VehiclePropertyChangeMode::CONTINUOUS, + .minSampleRate = 0.0f, + .maxSampleRate = 100.0f, }, .initialValue = {.floatValues = {15000.0f}}}, @@ -295,7 +356,6 @@ const ConfigDeclaration kVehicleProperties[]{ .prop = toInt(VehicleProperty::FUEL_DOOR_OPEN), .access = VehiclePropertyAccess::READ_WRITE, .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, }, .initialValue = {.int32Values = {0}}}, @@ -303,8 +363,9 @@ const ConfigDeclaration kVehicleProperties[]{ { .prop = toInt(VehicleProperty::EV_BATTERY_LEVEL), .access = VehiclePropertyAccess::READ, - .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, + .changeMode = VehiclePropertyChangeMode::CONTINUOUS, + .minSampleRate = 0.0f, + .maxSampleRate = 100.0f, }, .initialValue = {.floatValues = {150000.0f}}}, @@ -313,7 +374,6 @@ const ConfigDeclaration kVehicleProperties[]{ .prop = toInt(VehicleProperty::EV_CHARGE_PORT_OPEN), .access = VehiclePropertyAccess::READ_WRITE, .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, }, .initialValue = {.int32Values = {0}}}, @@ -322,7 +382,6 @@ const ConfigDeclaration kVehicleProperties[]{ .prop = toInt(VehicleProperty::EV_CHARGE_PORT_CONNECTED), .access = VehiclePropertyAccess::READ, .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, }, .initialValue = {.int32Values = {0}}}, @@ -330,17 +389,17 @@ const ConfigDeclaration kVehicleProperties[]{ { .prop = toInt(VehicleProperty::EV_BATTERY_INSTANTANEOUS_CHARGE_RATE), .access = VehiclePropertyAccess::READ, - .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, + .changeMode = VehiclePropertyChangeMode::CONTINUOUS, + .minSampleRate = 1.0f, + .maxSampleRate = 10.0f, }, .initialValue = {.floatValues = {0.0f}}}, {.config = { .prop = toInt(VehicleProperty::RANGE_REMAINING), - .access = VehiclePropertyAccess::READ, + .access = VehiclePropertyAccess::READ_WRITE, .changeMode = VehiclePropertyChangeMode::CONTINUOUS, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, .minSampleRate = 1.0f, .maxSampleRate = 2.0f, }, @@ -374,7 +433,17 @@ const ConfigDeclaration kVehicleProperties[]{ .minSampleRate = 1.0f, .maxSampleRate = 2.0f, }, - .initialValue = {.floatValues = {200}}}, // units in kPa + .initialValue = {.floatValues = {200.0f}}}, // units in kPa + + {.config = + { + .prop = toInt(VehicleProperty::TIRE_PRESSURE_DISPLAY_UNITS), + .access = VehiclePropertyAccess::READ_WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + .configArray = {(int)VehicleUnit::KILOPASCAL, (int)VehicleUnit::PSI, + (int)VehicleUnit::BAR}, + }, + .initialValue = {.int32Values = {toInt(VehicleUnit::PSI)}}}, {.config = { @@ -397,7 +466,6 @@ const ConfigDeclaration kVehicleProperties[]{ .prop = toInt(VehicleProperty::FUEL_LEVEL_LOW), .access = VehiclePropertyAccess::READ, .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, }, .initialValue = {.int32Values = {0}}}, @@ -409,6 +477,14 @@ const ConfigDeclaration kVehicleProperties[]{ }, .initialValue = {.int32Values = {0, 0, 0}}}, + {.config = + { + .prop = toInt(VehicleProperty::HW_ROTARY_INPUT), + .access = VehiclePropertyAccess::READ, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + }, + .initialValue = {.int32Values = {0, 0, 0}}}, + {.config = {.prop = toInt(VehicleProperty::HVAC_POWER_ON), .access = VehiclePropertyAccess::READ_WRITE, .changeMode = VehiclePropertyChangeMode::ON_CHANGE, @@ -430,6 +506,17 @@ const ConfigDeclaration kVehicleProperties[]{ .areaId = toInt(VehicleAreaWindow::REAR_WINDSHIELD)}}}, .initialValue = {.int32Values = {0}} // Will be used for all areas. }, + { + .config = {.prop = toInt(VehicleProperty::HVAC_ELECTRIC_DEFROSTER_ON), + .access = VehiclePropertyAccess::READ_WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + .areaConfigs = + {VehicleAreaConfig{ + .areaId = toInt(VehicleAreaWindow::FRONT_WINDSHIELD)}, + VehicleAreaConfig{ + .areaId = toInt(VehicleAreaWindow::REAR_WINDSHIELD)}}}, + .initialValue = {.int32Values = {0}} // Will be used for all areas. + }, {.config = {.prop = toInt(VehicleProperty::HVAC_MAX_DEFROST_ON), .access = VehiclePropertyAccess::READ_WRITE, @@ -558,14 +645,10 @@ const ConfigDeclaration kVehicleProperties[]{ }, .initialValue = {.floatValues = {25.0f}}}, - {.config = - { - .prop = toInt(VehicleProperty::HVAC_TEMPERATURE_DISPLAY_UNITS), - .access = VehiclePropertyAccess::READ_WRITE, - .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, - .configArray = {(int)VehicleUnit::FAHRENHEIT, (int)VehicleUnit::CELSIUS}, - }, + {.config = {.prop = toInt(VehicleProperty::HVAC_TEMPERATURE_DISPLAY_UNITS), + .access = VehiclePropertyAccess::READ_WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + .configArray = {(int)VehicleUnit::FAHRENHEIT, (int)VehicleUnit::CELSIUS}}, .initialValue = {.int32Values = {(int)VehicleUnit::FAHRENHEIT}}}, {.config = @@ -573,8 +656,8 @@ const ConfigDeclaration kVehicleProperties[]{ .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}, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}} }, .initialValue = {.int32Values = {(int)VehicleUnit::MILE}}}, @@ -594,6 +677,14 @@ const ConfigDeclaration kVehicleProperties[]{ }, .initialValue = {.int32Values = {toInt(VehicleGear::GEAR_PARK)}}}, + {.config = + { + .prop = toInt(VehicleProperty::TURN_SIGNAL_STATE), + .access = VehiclePropertyAccess::READ, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + }, + .initialValue = {.int32Values = {toInt(VehicleTurnSignal::NONE)}}}, + {.config = { .prop = toInt(VehicleProperty::IGNITION_STATE), @@ -626,6 +717,50 @@ const ConfigDeclaration kVehicleProperties[]{ .prop = kGenerateFakeDataControllingProperty, .access = VehiclePropertyAccess::WRITE, .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + .configArray = {1, 0, 0, 2, 0, 0, 0, 0, 0}, + }, + }, + + { + .config = + { + .prop = kSetIntPropertyFromVehicleForTest, + .access = VehiclePropertyAccess::WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + .configArray = {0, 0, 0, 2, 1, 0, 0, 0, 0}, + }, + }, + + { + .config = + { + .prop = kSetFloatPropertyFromVehicleForTest, + .access = VehiclePropertyAccess::WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + .configArray = {0, 0, 1, 0, 1, 0, 1, 0, 0}, + }, + }, + + { + .config = + { + .prop = kSetBooleanPropertyFromVehicleForTest, + .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", }, }, @@ -757,7 +892,6 @@ const ConfigDeclaration kVehicleProperties[]{ .prop = toInt(VehicleProperty::HEADLIGHTS_STATE), .access = VehiclePropertyAccess::READ, .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, }, .initialValue = {.int32Values = {LIGHT_STATE_ON}}}, @@ -766,7 +900,6 @@ const ConfigDeclaration kVehicleProperties[]{ .prop = toInt(VehicleProperty::HIGH_BEAM_LIGHTS_STATE), .access = VehiclePropertyAccess::READ, .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, }, .initialValue = {.int32Values = {LIGHT_STATE_ON}}}, @@ -775,7 +908,6 @@ const ConfigDeclaration kVehicleProperties[]{ .prop = toInt(VehicleProperty::FOG_LIGHTS_STATE), .access = VehiclePropertyAccess::READ, .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, }, .initialValue = {.int32Values = {LIGHT_STATE_ON}}}, @@ -784,7 +916,6 @@ const ConfigDeclaration kVehicleProperties[]{ .prop = toInt(VehicleProperty::HAZARD_LIGHTS_STATE), .access = VehiclePropertyAccess::READ, .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, }, .initialValue = {.int32Values = {LIGHT_STATE_ON}}}, @@ -793,7 +924,6 @@ const ConfigDeclaration kVehicleProperties[]{ .prop = toInt(VehicleProperty::HEADLIGHTS_SWITCH), .access = VehiclePropertyAccess::READ_WRITE, .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, }, .initialValue = {.int32Values = {LIGHT_SWITCH_AUTO}}}, @@ -802,7 +932,6 @@ const ConfigDeclaration kVehicleProperties[]{ .prop = toInt(VehicleProperty::HIGH_BEAM_LIGHTS_SWITCH), .access = VehiclePropertyAccess::READ_WRITE, .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, }, .initialValue = {.int32Values = {LIGHT_SWITCH_AUTO}}}, @@ -811,7 +940,6 @@ const ConfigDeclaration kVehicleProperties[]{ .prop = toInt(VehicleProperty::FOG_LIGHTS_SWITCH), .access = VehiclePropertyAccess::READ_WRITE, .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, }, .initialValue = {.int32Values = {LIGHT_SWITCH_AUTO}}}, @@ -820,7 +948,6 @@ const ConfigDeclaration kVehicleProperties[]{ .prop = toInt(VehicleProperty::HAZARD_LIGHTS_SWITCH), .access = VehiclePropertyAccess::READ_WRITE, .changeMode = VehiclePropertyChangeMode::ON_CHANGE, - .areaConfigs = {VehicleAreaConfig{.areaId = (0)}}, }, .initialValue = {.int32Values = {LIGHT_SWITCH_AUTO}}}, @@ -874,6 +1001,65 @@ const ConfigDeclaration kVehicleProperties[]{ .access = VehiclePropertyAccess::READ_WRITE, .changeMode = VehiclePropertyChangeMode::ON_CHANGE}, .initialValue = {.stringValue = "Vendor String Property"}}, + + {.config = + { + .prop = toInt(VehicleProperty::SUPPORT_CUSTOMIZE_VENDOR_PERMISSION), + .access = VehiclePropertyAccess::READ_WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + .configArray = + {kMixedTypePropertyForTest, + (int)VehicleVendorPermission::PERMISSION_GET_VENDOR_CATEGORY_INFO, + (int)VehicleVendorPermission::PERMISSION_SET_VENDOR_CATEGORY_INFO, + VENDOR_EXTENSION_INT_PROPERTY, + (int)VehicleVendorPermission::PERMISSION_GET_VENDOR_CATEGORY_SEAT, + (int)VehicleVendorPermission::PERMISSION_NOT_ACCESSIBLE, + VENDOR_EXTENSION_FLOAT_PROPERTY, + (int)VehicleVendorPermission::PERMISSION_DEFAULT, + (int)VehicleVendorPermission::PERMISSION_DEFAULT}, + }, + .initialValue = {.int32Values = {1}}}, + + { + .config = + { + .prop = toInt(VehicleProperty::INITIAL_USER_INFO), + .access = VehiclePropertyAccess::READ_WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + }, + }, + { + .config = + { + .prop = toInt(VehicleProperty::SWITCH_USER), + .access = VehiclePropertyAccess::READ_WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + }, + }, + { + .config = + { + .prop = toInt(VehicleProperty::CREATE_USER), + .access = VehiclePropertyAccess::READ_WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + }, + }, + { + .config = + { + .prop = toInt(VehicleProperty::REMOVE_USER), + .access = VehiclePropertyAccess::READ_WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + }, + }, + { + .config = + { + .prop = toInt(VehicleProperty::USER_IDENTIFICATION_ASSOCIATION), + .access = VehiclePropertyAccess::READ_WRITE, + .changeMode = VehiclePropertyChangeMode::ON_CHANGE, + }, + }, }; } // impl diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.cpp new file mode 100644 index 0000000000..ea38cb3941 --- /dev/null +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.cpp @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2020 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 "EmulatedUserHal" + +#include +#include + +#include "EmulatedUserHal.h" + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +namespace impl { + +constexpr int INITIAL_USER_INFO = static_cast(VehicleProperty::INITIAL_USER_INFO); +constexpr int SWITCH_USER = static_cast(VehicleProperty::SWITCH_USER); +constexpr int CREATE_USER = static_cast(VehicleProperty::CREATE_USER); +constexpr int REMOVE_USER = static_cast(VehicleProperty::REMOVE_USER); +constexpr int USER_IDENTIFICATION_ASSOCIATION = + static_cast(VehicleProperty::USER_IDENTIFICATION_ASSOCIATION); + +bool EmulatedUserHal::isSupported(int32_t prop) { + switch (prop) { + case INITIAL_USER_INFO: + case SWITCH_USER: + case CREATE_USER: + case REMOVE_USER: + case USER_IDENTIFICATION_ASSOCIATION: + return true; + default: + return false; + } +} + +android::base::Result> EmulatedUserHal::onSetProperty( + const VehiclePropValue& value) { + ALOGV("onSetProperty(): %s", toString(value).c_str()); + + switch (value.prop) { + case INITIAL_USER_INFO: + return onSetInitialUserInfoResponse(value); + case SWITCH_USER: + return onSetSwitchUserResponse(value); + case CREATE_USER: + return onSetCreateUserResponse(value); + case REMOVE_USER: + ALOGI("REMOVE_USER is FYI only, nothing to do..."); + return {}; + case USER_IDENTIFICATION_ASSOCIATION: + return onSetUserIdentificationAssociation(value); + default: + return android::base::Error(static_cast(StatusCode::INVALID_ARG)) + << "Unsupported property: " << toString(value); + } +} + +android::base::Result> EmulatedUserHal::onGetProperty( + const VehiclePropValue& value) { + ALOGV("onGetProperty(%s)", toString(value).c_str()); + switch (value.prop) { + case INITIAL_USER_INFO: + case SWITCH_USER: + case CREATE_USER: + case REMOVE_USER: + ALOGE("onGetProperty(): %d is only supported on SET", value.prop); + return android::base::Error(static_cast(StatusCode::INVALID_ARG)) + << "only supported on SET"; + case USER_IDENTIFICATION_ASSOCIATION: + return onGetUserIdentificationAssociation(value); + default: + ALOGE("onGetProperty(): %d is not supported", value.prop); + return android::base::Error(static_cast(StatusCode::INVALID_ARG)) + << "not supported by User HAL"; + } +} + +android::base::Result> +EmulatedUserHal::onGetUserIdentificationAssociation(const VehiclePropValue& value) { + if (mSetUserIdentificationAssociationResponseFromCmd != nullptr) { + ALOGI("get(USER_IDENTIFICATION_ASSOCIATION): returning %s", + toString(*mSetUserIdentificationAssociationResponseFromCmd).c_str()); + auto newValue = std::unique_ptr( + new VehiclePropValue(*mSetUserIdentificationAssociationResponseFromCmd)); + // Must use the same requestId + if (value.value.int32Values.size() > 0) { + newValue->value.int32Values[0] = value.value.int32Values[0]; + } else { + ALOGE("get(USER_IDENTIFICATION_ASSOCIATION): no requestId on %s", + toString(value).c_str()); + } + return newValue; + } + return defaultUserIdentificationAssociation(value); +} + +android::base::Result> +EmulatedUserHal::onSetInitialUserInfoResponse(const VehiclePropValue& value) { + if (value.value.int32Values.size() == 0) { + ALOGE("set(INITIAL_USER_INFO): no int32values, ignoring it: %s", toString(value).c_str()); + return android::base::Error(static_cast(StatusCode::INVALID_ARG)) + << "no int32values on " << toString(value); + } + + if (value.areaId != 0) { + ALOGD("set(INITIAL_USER_INFO) called from lshal; storing it: %s", toString(value).c_str()); + mInitialUserResponseFromCmd.reset(new VehiclePropValue(value)); + return {}; + } + + ALOGD("set(INITIAL_USER_INFO) called from Android: %s", toString(value).c_str()); + + int32_t requestId = value.value.int32Values[0]; + if (mInitialUserResponseFromCmd != nullptr) { + ALOGI("replying INITIAL_USER_INFO with lshal value: %s", + toString(*mInitialUserResponseFromCmd).c_str()); + return sendUserHalResponse(std::move(mInitialUserResponseFromCmd), requestId); + } + + // Returns default response + auto updatedValue = std::unique_ptr(new VehiclePropValue); + updatedValue->prop = INITIAL_USER_INFO; + updatedValue->timestamp = elapsedRealtimeNano(); + updatedValue->value.int32Values.resize(2); + updatedValue->value.int32Values[0] = requestId; + updatedValue->value.int32Values[1] = (int32_t)InitialUserInfoResponseAction::DEFAULT; + + ALOGI("no lshal response; replying with InitialUserInfoResponseAction::DEFAULT: %s", + toString(*updatedValue).c_str()); + + return updatedValue; +} + +android::base::Result> EmulatedUserHal::onSetSwitchUserResponse( + const VehiclePropValue& value) { + if (value.value.int32Values.size() == 0) { + ALOGE("set(SWITCH_USER): no int32values, ignoring it: %s", toString(value).c_str()); + return android::base::Error(static_cast(StatusCode::INVALID_ARG)) + << "no int32values on " << toString(value); + } + + if (value.areaId != 0) { + ALOGD("set(SWITCH_USER) called from lshal; storing it: %s", toString(value).c_str()); + mSwitchUserResponseFromCmd.reset(new VehiclePropValue(value)); + return {}; + } + ALOGD("set(SWITCH_USER) called from Android: %s", toString(value).c_str()); + + int32_t requestId = value.value.int32Values[0]; + if (mSwitchUserResponseFromCmd != nullptr) { + ALOGI("replying SWITCH_USER with lshal value: %s", + toString(*mSwitchUserResponseFromCmd).c_str()); + return sendUserHalResponse(std::move(mSwitchUserResponseFromCmd), requestId); + } + + if (value.value.int32Values.size() > 1) { + auto messageType = static_cast(value.value.int32Values[1]); + switch (messageType) { + case SwitchUserMessageType::LEGACY_ANDROID_SWITCH: + ALOGI("request is LEGACY_ANDROID_SWITCH; ignoring it"); + return {}; + case SwitchUserMessageType::ANDROID_POST_SWITCH: + ALOGI("request is ANDROID_POST_SWITCH; ignoring it"); + return {}; + default: + break; + } + } + + // Returns default response + auto updatedValue = std::unique_ptr(new VehiclePropValue); + updatedValue->prop = SWITCH_USER; + updatedValue->timestamp = elapsedRealtimeNano(); + updatedValue->value.int32Values.resize(3); + updatedValue->value.int32Values[0] = requestId; + updatedValue->value.int32Values[1] = (int32_t)SwitchUserMessageType::VEHICLE_RESPONSE; + updatedValue->value.int32Values[2] = (int32_t)SwitchUserStatus::SUCCESS; + + ALOGI("no lshal response; replying with VEHICLE_RESPONSE / SUCCESS: %s", + toString(*updatedValue).c_str()); + + return updatedValue; +} + +android::base::Result> EmulatedUserHal::onSetCreateUserResponse( + const VehiclePropValue& value) { + if (value.value.int32Values.size() == 0) { + ALOGE("set(CREATE_USER): no int32values, ignoring it: %s", toString(value).c_str()); + return android::base::Error(static_cast(StatusCode::INVALID_ARG)) + << "no int32values on " << toString(value); + } + + if (value.areaId != 0) { + ALOGD("set(CREATE_USER) called from lshal; storing it: %s", toString(value).c_str()); + mCreateUserResponseFromCmd.reset(new VehiclePropValue(value)); + return {}; + } + ALOGD("set(CREATE_USER) called from Android: %s", toString(value).c_str()); + + int32_t requestId = value.value.int32Values[0]; + if (mCreateUserResponseFromCmd != nullptr) { + ALOGI("replying CREATE_USER with lshal value: %s", + toString(*mCreateUserResponseFromCmd).c_str()); + return sendUserHalResponse(std::move(mCreateUserResponseFromCmd), requestId); + } + + // Returns default response + auto updatedValue = std::unique_ptr(new VehiclePropValue); + updatedValue->prop = CREATE_USER; + updatedValue->timestamp = elapsedRealtimeNano(); + updatedValue->value.int32Values.resize(2); + updatedValue->value.int32Values[0] = requestId; + updatedValue->value.int32Values[1] = (int32_t)CreateUserStatus::SUCCESS; + + ALOGI("no lshal response; replying with SUCCESS: %s", toString(*updatedValue).c_str()); + + return updatedValue; +} + +android::base::Result> +EmulatedUserHal::onSetUserIdentificationAssociation(const VehiclePropValue& value) { + if (value.value.int32Values.size() == 0) { + ALOGE("set(USER_IDENTIFICATION_ASSOCIATION): no int32values, ignoring it: %s", + toString(value).c_str()); + return android::base::Error(static_cast(StatusCode::INVALID_ARG)) + << "no int32values on " << toString(value); + } + + if (value.areaId != 0) { + ALOGD("set(USER_IDENTIFICATION_ASSOCIATION) called from lshal; storing it: %s", + toString(value).c_str()); + mSetUserIdentificationAssociationResponseFromCmd.reset(new VehiclePropValue(value)); + return {}; + } + ALOGD("set(USER_IDENTIFICATION_ASSOCIATION) called from Android: %s", toString(value).c_str()); + + int32_t requestId = value.value.int32Values[0]; + if (mSetUserIdentificationAssociationResponseFromCmd != nullptr) { + ALOGI("replying USER_IDENTIFICATION_ASSOCIATION with lshal value: %s", + toString(*mSetUserIdentificationAssociationResponseFromCmd).c_str()); + // Not moving response so it can be used on GET requests + auto copy = std::unique_ptr( + new VehiclePropValue(*mSetUserIdentificationAssociationResponseFromCmd)); + return sendUserHalResponse(std::move(copy), requestId); + } + + // Returns default response + return defaultUserIdentificationAssociation(value); +} + +android::base::Result> +EmulatedUserHal::defaultUserIdentificationAssociation(const VehiclePropValue& request) { + // TODO(b/159498909): return a response with NOT_ASSOCIATED_ANY_USER for all requested types + ALOGE("no lshal response for %s; replying with NOT_AVAILABLE", toString(request).c_str()); + return android::base::Error(static_cast(StatusCode::NOT_AVAILABLE)) << "not set by lshal"; +} + +android::base::Result> EmulatedUserHal::sendUserHalResponse( + std::unique_ptr response, int32_t requestId) { + switch (response->areaId) { + case 1: + ALOGD("returning response with right request id"); + response->value.int32Values[0] = requestId; + break; + case 2: + ALOGD("returning response with wrong request id"); + response->value.int32Values[0] = -requestId; + break; + case 3: + ALOGD("not generating a property change event because of lshal prop: %s", + toString(*response).c_str()); + return android::base::Error(static_cast(StatusCode::NOT_AVAILABLE)) + << "not generating a property change event because of lshal prop: " + << toString(*response); + default: + ALOGE("invalid action on lshal response: %s", toString(*response).c_str()); + return android::base::Error(static_cast(StatusCode::INTERNAL_ERROR)) + << "invalid action on lshal response: " << toString(*response); + } + + ALOGD("updating property to: %s", toString(*response).c_str()); + + return response; +} + +void EmulatedUserHal::showDumpHelp(int fd) { + dprintf(fd, "%s: dumps state used for user management\n", kUserHalDumpOption); +} + +void EmulatedUserHal::dump(int fd, std::string indent) { + if (mInitialUserResponseFromCmd != nullptr) { + dprintf(fd, "%sInitialUserInfo response: %s\n", indent.c_str(), + toString(*mInitialUserResponseFromCmd).c_str()); + } else { + dprintf(fd, "%sNo InitialUserInfo response\n", indent.c_str()); + } + if (mSwitchUserResponseFromCmd != nullptr) { + dprintf(fd, "%sSwitchUser response: %s\n", indent.c_str(), + toString(*mSwitchUserResponseFromCmd).c_str()); + } else { + dprintf(fd, "%sNo SwitchUser response\n", indent.c_str()); + } + if (mCreateUserResponseFromCmd != nullptr) { + dprintf(fd, "%sCreateUser response: %s\n", indent.c_str(), + toString(*mCreateUserResponseFromCmd).c_str()); + } else { + dprintf(fd, "%sNo CreateUser response\n", indent.c_str()); + } + if (mSetUserIdentificationAssociationResponseFromCmd != nullptr) { + dprintf(fd, "%sSetUserIdentificationAssociation response: %s\n", indent.c_str(), + toString(*mSetUserIdentificationAssociationResponseFromCmd).c_str()); + } else { + dprintf(fd, "%sNo SetUserIdentificationAssociation response\n", indent.c_str()); + } +} + +} // namespace impl + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.h new file mode 100644 index 0000000000..db2f117e3e --- /dev/null +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedUserHal.h @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2020 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_automotive_vehicle_V2_0_impl_EmulatedUserHal_H_ +#define android_hardware_automotive_vehicle_V2_0_impl_EmulatedUserHal_H_ + +#include + +#include + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +namespace impl { + +constexpr char kUserHalDumpOption[] = "--user-hal"; + +/** + * Class used to emulate User HAL behavior through lshal debug requests. + */ +class EmulatedUserHal { + public: + EmulatedUserHal() {} + + ~EmulatedUserHal() = default; + + /** + * Checks if the emulator can handle the property. + */ + bool isSupported(int32_t prop); + + /** + * Lets the emulator set the property. + * + * @return updated property and StatusCode + */ + android::base::Result> onSetProperty( + const VehiclePropValue& value); + + /** + * Gets the property value from the emulator. + * + * @return property value and StatusCode + */ + android::base::Result> onGetProperty( + const VehiclePropValue& value); + + /** + * Shows the User HAL emulation help. + */ + void showDumpHelp(int fd); + + /** + * Dump its contents. + */ + void dump(int fd, std::string indent); + + private: + /** + * INITIAL_USER_INFO is called by Android when it starts, and it's expecting a property change + * indicating what the initial user should be. + * + * During normal circumstances, the emulator will reply right away, passing a response if + * InitialUserInfoResponseAction::DEFAULT (so Android could use its own logic to decide which + * user to boot). + * + * But during development / testing, the behavior can be changed using lshal dump, which must + * use the areaId to indicate what should happen next. + * + * So, the behavior of set(INITIAL_USER_INFO) is: + * + * - if it has an areaId, store the property into mInitialUserResponseFromCmd (as it was called + * by lshal). + * - else if mInitialUserResponseFromCmd is not set, return a response with the same request id + * and InitialUserInfoResponseAction::DEFAULT + * - else the behavior is defined by the areaId on mInitialUserResponseFromCmd: + * - if it's 1, reply with mInitialUserResponseFromCmd and the right request id + * - if it's 2, reply with mInitialUserResponseFromCmd but a wrong request id (so Android can + * test this error scenario) + * - if it's 3, then don't send a property change (so Android can emulate a timeout) + * + */ + android::base::Result> onSetInitialUserInfoResponse( + const VehiclePropValue& value); + + /** + * Used to emulate SWITCH_USER - see onSetInitialUserInfoResponse() for usage. + */ + android::base::Result> onSetSwitchUserResponse( + const VehiclePropValue& value); + + /** + * Used to emulate CREATE_USER - see onSetInitialUserInfoResponse() for usage. + */ + android::base::Result> onSetCreateUserResponse( + const VehiclePropValue& value); + + /** + * Used to emulate set USER_IDENTIFICATION_ASSOCIATION - see onSetInitialUserInfoResponse() for + * usage. + */ + android::base::Result> onSetUserIdentificationAssociation( + const VehiclePropValue& value); + + /** + * Used to emulate get USER_IDENTIFICATION_ASSOCIATION - see onSetInitialUserInfoResponse() for + * usage. + */ + android::base::Result> onGetUserIdentificationAssociation( + const VehiclePropValue& value); + + /** + * Creates a default USER_IDENTIFICATION_ASSOCIATION when it was not set by lshal. + */ + android::base::Result> defaultUserIdentificationAssociation( + const VehiclePropValue& request); + + android::base::Result> sendUserHalResponse( + std::unique_ptr response, int32_t requestId); + + std::unique_ptr mInitialUserResponseFromCmd; + std::unique_ptr mSwitchUserResponseFromCmd; + std::unique_ptr mCreateUserResponseFromCmd; + std::unique_ptr mSetUserIdentificationAssociationResponseFromCmd; +}; + +} // namespace impl + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // android_hardware_automotive_vehicle_V2_0_impl_EmulatedUserHal_H_ diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleConnector.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleConnector.cpp new file mode 100644 index 0000000000..7f9362fdf4 --- /dev/null +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleConnector.cpp @@ -0,0 +1,81 @@ +/* + * 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 "automotive.vehicle@2.0-connector" + +#include + +#include +#include + +#include "DefaultConfig.h" +#include "EmulatedVehicleConnector.h" +#include "JsonFakeValueGenerator.h" +#include "LinearFakeValueGenerator.h" +#include "Obd2SensorStore.h" + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +namespace impl { + +class EmulatedPassthroughConnector : public PassthroughConnector { + public: + bool onDump(const hidl_handle& fd, const hidl_vec& options) override; +}; + +bool EmulatedPassthroughConnector::onDump(const hidl_handle& handle, + const hidl_vec& options) { + int fd = handle->data[0]; + + if (options.size() > 0) { + if (options[0] == "--help") { + dprintf(fd, "Emulator-specific usage:\n"); + mEmulatedUserHal.showDumpHelp(fd); + dprintf(fd, "\n"); + // Include caller's help options + return true; + } else if (options[0] == kUserHalDumpOption) { + mEmulatedUserHal.dump(fd, ""); + return false; + + } else { + // Let caller handle the options... + return true; + } + } + + dprintf(fd, "Emulator-specific state:\n"); + mEmulatedUserHal.dump(fd, " "); + dprintf(fd, "\n"); + + return true; +} + +PassthroughConnectorPtr makeEmulatedPassthroughConnector() { + return std::make_unique(); +} + +} // namespace impl + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleConnector.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleConnector.h new file mode 100644 index 0000000000..57cbb8b893 --- /dev/null +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleConnector.h @@ -0,0 +1,46 @@ +/* + * 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_automotive_vehicle_V2_0_impl_EmulatedVehicleConnector_H_ +#define android_hardware_automotive_vehicle_V2_0_impl_EmulatedVehicleConnector_H_ + +#include + +#include "VehicleHalClient.h" +#include "VehicleHalServer.h" + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +namespace impl { + +using PassthroughConnector = IPassThroughConnector; +using PassthroughConnectorPtr = std::unique_ptr; + +PassthroughConnectorPtr makeEmulatedPassthroughConnector(); + +} // namespace impl + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // android_hardware_automotive_vehicle_V2_0_impl_EmulatedVehicleConnector_H_ 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 9b687ec361..a0b566d1ee 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 @@ -16,8 +16,12 @@ #define LOG_TAG "DefaultVehicleHal_v2_0" #include +#include #include +#include #include +#include +#include #include "EmulatedVehicleHal.h" #include "JsonFakeValueGenerator.h" @@ -88,22 +92,52 @@ static std::unique_ptr fillDefaultObd2Frame(size_t numVendorInt return sensorStore; } -EmulatedVehicleHal::EmulatedVehicleHal(VehiclePropertyStore* propStore) +EmulatedVehicleHal::EmulatedVehicleHal(VehiclePropertyStore* propStore, VehicleHalClient* client, + EmulatedUserHal* emulatedUserHal) : mPropStore(propStore), mHvacPowerProps(std::begin(kHvacPowerProperties), std::end(kHvacPowerProperties)), - mRecurrentTimer( - std::bind(&EmulatedVehicleHal::onContinuousPropertyTimer, this, std::placeholders::_1)), - mGeneratorHub( - std::bind(&EmulatedVehicleHal::onFakeValueGenerated, this, std::placeholders::_1)) { + mRecurrentTimer(std::bind(&EmulatedVehicleHal::onContinuousPropertyTimer, this, + std::placeholders::_1)), + mVehicleClient(client), + mEmulatedUserHal(emulatedUserHal) { initStaticConfig(); for (size_t i = 0; i < arraysize(kVehicleProperties); i++) { mPropStore->registerProperty(kVehicleProperties[i].config); } + mVehicleClient->registerPropertyValueCallback(std::bind(&EmulatedVehicleHal::onPropertyValue, + this, std::placeholders::_1, + std::placeholders::_2)); + + mInitVhalValueOverride = + android::base::GetBoolProperty("persist.vendor.vhal_init_value_override", false); + if (mInitVhalValueOverride) { + getAllPropertiesOverride(); + } +} + +void EmulatedVehicleHal::getAllPropertiesOverride() { + if (auto dir = opendir("/vendor/etc/vhaloverride/")) { + std::regex reg_json(".*[.]json", std::regex::icase); + while (auto f = readdir(dir)) { + if (!regex_match(f->d_name, reg_json)) { + continue; + } + std::string file = "/vendor/etc/vhaloverride/" + std::string(f->d_name); + JsonFakeValueGenerator tmpGenerator(file); + + std::vector propvalues = tmpGenerator.getAllEvents(); + mVehiclePropertiesOverride.insert(std::end(mVehiclePropertiesOverride), + std::begin(propvalues), std::end(propvalues)); + } + closedir(dir); + } } VehicleHal::VehiclePropValuePtr EmulatedVehicleHal::get( const VehiclePropValue& requestedPropValue, StatusCode* outStatus) { auto propId = requestedPropValue.prop; + ALOGV("get(%d)", propId); + auto& pool = *getValuePool(); VehiclePropValuePtr v = nullptr; @@ -117,6 +151,26 @@ VehicleHal::VehiclePropValuePtr EmulatedVehicleHal::get( *outStatus = fillObd2DtcInfo(v.get()); break; default: + if (mEmulatedUserHal != nullptr && mEmulatedUserHal->isSupported(propId)) { + ALOGI("get(): getting value for prop %d from User HAL", propId); + const auto& ret = mEmulatedUserHal->onGetProperty(requestedPropValue); + if (!ret.ok()) { + ALOGE("get(): User HAL returned error: %s", ret.error().message().c_str()); + *outStatus = StatusCode(ret.error().code()); + } else { + auto value = ret.value().get(); + if (value != nullptr) { + ALOGI("get(): User HAL returned value: %s", toString(*value).c_str()); + v = getValuePool()->obtain(*value); + *outStatus = StatusCode::OK; + } else { + ALOGE("get(): User HAL returned null value"); + *outStatus = StatusCode::INTERNAL_ERROR; + } + } + break; + } + auto internalPropValue = mPropStore->readValueOrNull(requestedPropValue); if (internalPropValue != nullptr) { v = getValuePool()->obtain(*internalPropValue); @@ -125,18 +179,27 @@ VehicleHal::VehiclePropValuePtr EmulatedVehicleHal::get( *outStatus = v != nullptr ? StatusCode::OK : StatusCode::INVALID_ARG; break; } - + if (v.get()) { + v->timestamp = elapsedRealtimeNano(); + } return v; } +bool EmulatedVehicleHal::dump(const hidl_handle& fd, const hidl_vec& options) { + return mVehicleClient->dump(fd, options); +} + StatusCode EmulatedVehicleHal::set(const VehiclePropValue& propValue) { - static constexpr bool shouldUpdateStatus = false; + constexpr bool updateStatus = false; if (propValue.prop == kGenerateFakeDataControllingProperty) { - StatusCode status = handleGenerateFakeDataRequest(propValue); - if (status != StatusCode::OK) { - return status; - } + // Send the generator controlling request to the server. + // 'updateStatus' flag is only for the value sent by setProperty (propValue in this case) + // instead of the generated values triggered by it. 'propValue' works as a control signal + // here, since we never send the control signal back, the value of 'updateStatus' flag + // does not matter here. + auto status = mVehicleClient->setProperty(propValue, updateStatus); + return status; } else if (mHvacPowerProps.count(propValue.prop)) { auto hvacPowerOn = mPropStore->readValueOrNull( toInt(VehicleProperty::HVAC_POWER_ON), @@ -157,29 +220,6 @@ StatusCode EmulatedVehicleHal::set(const VehiclePropValue& propValue) { // Placeholder for future implementation of VMS property in the default hal. For // now, just returns OK; otherwise, hal clients crash with property not supported. return StatusCode::OK; - case AP_POWER_STATE_REPORT: - switch (propValue.value.int32Values[0]) { - case toInt(VehicleApPowerStateReport::DEEP_SLEEP_EXIT): - case toInt(VehicleApPowerStateReport::SHUTDOWN_CANCELLED): - case toInt(VehicleApPowerStateReport::WAIT_FOR_VHAL): - // CPMS is in WAIT_FOR_VHAL state, simply move to ON - doHalEvent(createApPowerStateReq(VehicleApPowerStateReq::ON, 0)); - break; - case toInt(VehicleApPowerStateReport::DEEP_SLEEP_ENTRY): - case toInt(VehicleApPowerStateReport::SHUTDOWN_START): - // CPMS is in WAIT_FOR_FINISH state, send the FINISHED command - doHalEvent(createApPowerStateReq(VehicleApPowerStateReq::FINISHED, 0)); - break; - case toInt(VehicleApPowerStateReport::ON): - case toInt(VehicleApPowerStateReport::SHUTDOWN_POSTPONE): - case toInt(VehicleApPowerStateReport::SHUTDOWN_PREPARE): - // Do nothing - break; - default: - // Unknown state - break; - } - break; } } @@ -199,12 +239,6 @@ StatusCode EmulatedVehicleHal::set(const VehiclePropValue& propValue) { return StatusCode::NOT_AVAILABLE; } - if (!mPropStore->writeValue(propValue, shouldUpdateStatus)) { - return StatusCode::INVALID_ARG; - } - - getEmulatorOrDie()->doSetValueFromClient(propValue); - if (mInEmulator && propValue.prop == toInt(VehicleProperty::DISPLAY_BRIGHTNESS)) { // Emulator does not support remote brightness control, b/139959479 // do not send it down so that it does not bring unnecessary property change event @@ -213,7 +247,17 @@ StatusCode EmulatedVehicleHal::set(const VehiclePropValue& propValue) { return StatusCode::OK; } - doHalEvent(getValuePool()->obtain(propValue)); + /** + * After checking all conditions, such as the property is available, a real vhal will + * sent the events to Car ECU to take actions. + */ + + // Send the value to the vehicle server, the server will talk to the (real or emulated) car + auto setValueStatus = mVehicleClient->setProperty(propValue, updateStatus); + if (setValueStatus != StatusCode::OK) { + return setValueStatus; + } + return StatusCode::OK; } @@ -283,6 +327,13 @@ void EmulatedVehicleHal::onCreate() { } } else { prop.value = it.initialValue; + if (mInitVhalValueOverride) { + for (auto& itOverride : mVehiclePropertiesOverride) { + if (itOverride.prop == cfg.prop) { + prop.value = itOverride.value; + } + } + } } mPropStore->writeValue(prop, shouldUpdateStatus); } @@ -346,144 +397,20 @@ bool EmulatedVehicleHal::isContinuousProperty(int32_t propId) const { } bool EmulatedVehicleHal::setPropertyFromVehicle(const VehiclePropValue& propValue) { - static constexpr bool shouldUpdateStatus = true; - - if (propValue.prop == kGenerateFakeDataControllingProperty) { - StatusCode status = handleGenerateFakeDataRequest(propValue); - if (status != StatusCode::OK) { - return false; - } - } - - if (mPropStore->writeValue(propValue, shouldUpdateStatus)) { - doHalEvent(getValuePool()->obtain(propValue)); - return true; - } else { - return false; - } + constexpr bool updateStatus = true; + return mVehicleClient->setProperty(propValue, updateStatus) == StatusCode::OK; } std::vector EmulatedVehicleHal::getAllProperties() const { return mPropStore->readAllValues(); } -StatusCode EmulatedVehicleHal::handleGenerateFakeDataRequest(const VehiclePropValue& request) { - ALOGI("%s", __func__); - const auto& v = request.value; - if (!v.int32Values.size()) { - ALOGE("%s: expected at least \"command\" field in int32Values", __func__); - return StatusCode::INVALID_ARG; - } - - FakeDataCommand command = static_cast(v.int32Values[0]); - - switch (command) { - case FakeDataCommand::StartLinear: { - ALOGI("%s, FakeDataCommand::StartLinear", __func__); - if (v.int32Values.size() < 2) { - ALOGE("%s: expected property ID in int32Values", __func__); - return StatusCode::INVALID_ARG; - } - if (!v.int64Values.size()) { - ALOGE("%s: interval is not provided in int64Values", __func__); - return StatusCode::INVALID_ARG; - } - if (v.floatValues.size() < 3) { - ALOGE("%s: expected at least 3 elements in floatValues, got: %zu", __func__, - v.floatValues.size()); - return StatusCode::INVALID_ARG; - } - int32_t cookie = v.int32Values[1]; - mGeneratorHub.registerGenerator(cookie, - std::make_unique(request)); - break; - } - case FakeDataCommand::StartJson: { - ALOGI("%s, FakeDataCommand::StartJson", __func__); - if (v.stringValue.empty()) { - ALOGE("%s: path to JSON file is missing", __func__); - return StatusCode::INVALID_ARG; - } - int32_t cookie = std::hash()(v.stringValue); - mGeneratorHub.registerGenerator(cookie, - std::make_unique(request)); - break; - } - case FakeDataCommand::StopLinear: { - ALOGI("%s, FakeDataCommand::StopLinear", __func__); - if (v.int32Values.size() < 2) { - ALOGE("%s: expected property ID in int32Values", __func__); - return StatusCode::INVALID_ARG; - } - int32_t cookie = v.int32Values[1]; - mGeneratorHub.unregisterGenerator(cookie); - break; - } - case FakeDataCommand::StopJson: { - ALOGI("%s, FakeDataCommand::StopJson", __func__); - if (v.stringValue.empty()) { - ALOGE("%s: path to JSON file is missing", __func__); - return StatusCode::INVALID_ARG; - } - int32_t cookie = std::hash()(v.stringValue); - mGeneratorHub.unregisterGenerator(cookie); - break; - } - case FakeDataCommand::KeyPress: { - ALOGI("%s, FakeDataCommand::KeyPress", __func__); - int32_t keyCode = request.value.int32Values[2]; - int32_t display = request.value.int32Values[3]; - doHalEvent( - createHwInputKeyProp(VehicleHwKeyInputAction::ACTION_DOWN, keyCode, display)); - doHalEvent(createHwInputKeyProp(VehicleHwKeyInputAction::ACTION_UP, keyCode, display)); - break; - } - default: { - ALOGE("%s: unexpected command: %d", __func__, command); - return StatusCode::INVALID_ARG; - } - } - return StatusCode::OK; -} - -VehicleHal::VehiclePropValuePtr EmulatedVehicleHal::createApPowerStateReq( - VehicleApPowerStateReq state, int32_t param) { - auto req = getValuePool()->obtain(VehiclePropertyType::INT32_VEC, 2); - req->prop = toInt(VehicleProperty::AP_POWER_STATE_REQ); - req->areaId = 0; - req->timestamp = elapsedRealtimeNano(); - req->status = VehiclePropertyStatus::AVAILABLE; - req->value.int32Values[0] = toInt(state); - req->value.int32Values[1] = param; - return req; -} - -VehicleHal::VehiclePropValuePtr EmulatedVehicleHal::createHwInputKeyProp( - VehicleHwKeyInputAction action, int32_t keyCode, int32_t targetDisplay) { - auto keyEvent = getValuePool()->obtain(VehiclePropertyType::INT32_VEC, 3); - keyEvent->prop = toInt(VehicleProperty::HW_KEY_INPUT); - keyEvent->areaId = 0; - keyEvent->timestamp = elapsedRealtimeNano(); - keyEvent->status = VehiclePropertyStatus::AVAILABLE; - keyEvent->value.int32Values[0] = toInt(action); - keyEvent->value.int32Values[1] = keyCode; - keyEvent->value.int32Values[2] = targetDisplay; - return keyEvent; -} - -void EmulatedVehicleHal::onFakeValueGenerated(const VehiclePropValue& value) { - ALOGD("%s: %s", __func__, toString(value).c_str()); - static constexpr bool shouldUpdateStatus = false; - +void EmulatedVehicleHal::onPropertyValue(const VehiclePropValue& value, bool updateStatus) { VehiclePropValuePtr updatedPropValue = getValuePool()->obtain(value); - if (updatedPropValue) { - updatedPropValue->timestamp = elapsedRealtimeNano(); - updatedPropValue->status = VehiclePropertyStatus::AVAILABLE; - mPropStore->writeValue(*updatedPropValue, shouldUpdateStatus); - auto changeMode = mPropStore->getConfigOrDie(value.prop)->changeMode; - if (VehiclePropertyChangeMode::ON_CHANGE == changeMode) { - doHalEvent(std::move(updatedPropValue)); - } + + if (mPropStore->writeValue(*updatedPropValue, updateStatus)) { + getEmulatorOrDie()->doSetValueFromClient(*updatedPropValue); + doHalEvent(std::move(updatedPropValue)); } } diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleHal.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleHal.h index 367a6ec96e..eb38d7de89 100644 --- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleHal.h +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/EmulatedVehicleHal.h @@ -30,6 +30,8 @@ #include "vhal_v2_0/VehiclePropertyStore.h" #include "DefaultConfig.h" +#include "EmulatedUserHal.h" +#include "EmulatedVehicleConnector.h" #include "GeneratorHub.h" #include "VehicleEmulator.h" @@ -44,7 +46,8 @@ namespace impl { /** Implementation of VehicleHal that connected to emulator instead of real vehicle network. */ class EmulatedVehicleHal : public EmulatedVehicleHalIface { public: - EmulatedVehicleHal(VehiclePropertyStore* propStore); + EmulatedVehicleHal(VehiclePropertyStore* propStore, VehicleHalClient* client, + EmulatedUserHal* emulatedUserHal = nullptr); ~EmulatedVehicleHal() = default; // Methods from VehicleHal @@ -55,10 +58,12 @@ public: StatusCode set(const VehiclePropValue& propValue) override; StatusCode subscribe(int32_t property, float sampleRate) override; StatusCode unsubscribe(int32_t property) override; + bool dump(const hidl_handle& fd, const hidl_vec& options) override; // Methods from EmulatedVehicleHalIface bool setPropertyFromVehicle(const VehiclePropValue& propValue) override; std::vector getAllProperties() const override; + void getAllPropertiesOverride(); private: constexpr std::chrono::nanoseconds hertzToNanoseconds(float hz) const { @@ -66,10 +71,7 @@ private: } StatusCode handleGenerateFakeDataRequest(const VehiclePropValue& request); - void onFakeValueGenerated(const VehiclePropValue& value); - VehiclePropValuePtr createApPowerStateReq(VehicleApPowerStateReq req, int32_t param); - VehiclePropValuePtr createHwInputKeyProp(VehicleHwKeyInputAction action, int32_t keyCode, - int32_t targetDisplay); + void onPropertyValue(const VehiclePropValue& value, bool updateStatus); void onContinuousPropertyTimer(const std::vector& properties); bool isContinuousProperty(int32_t propId) const; @@ -85,8 +87,11 @@ private: VehiclePropertyStore* mPropStore; std::unordered_set mHvacPowerProps; RecurrentTimer mRecurrentTimer; - GeneratorHub mGeneratorHub; + VehicleHalClient* mVehicleClient; bool mInEmulator; + bool mInitVhalValueOverride; + std::vector mVehiclePropertiesOverride; + EmulatedUserHal* mEmulatedUserHal; }; } // impl diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/FakeValueGenerator.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/FakeValueGenerator.h index d6ad77df73..2dc502b9e0 100644 --- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/FakeValueGenerator.h +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/FakeValueGenerator.h @@ -19,6 +19,8 @@ #include +#include + namespace android { namespace hardware { namespace automotive { 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 8677f837f5..890eb33643 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 @@ -48,6 +48,22 @@ JsonFakeValueGenerator::JsonFakeValueGenerator(const VehiclePropValue& request) mNumOfIterations = v.int32Values.size() < 2 ? -1 : v.int32Values[1]; } +JsonFakeValueGenerator::JsonFakeValueGenerator(std::string path) { + std::ifstream ifs(path); + if (!ifs) { + ALOGE("%s: couldn't open %s for parsing.", __func__, path.c_str()); + } + mGenCfg = { + .index = 0, + .events = parseFakeValueJson(ifs), + }; + mNumOfIterations = mGenCfg.events.size(); +} + +std::vector JsonFakeValueGenerator::getAllEvents() { + return mGenCfg.events; +} + VehiclePropValue JsonFakeValueGenerator::nextEvent() { VehiclePropValue generatedValue; if (!hasNext()) { @@ -109,6 +125,7 @@ std::vector JsonFakeValueGenerator::parseFakeValueJson(std::is Json::Value rawEventValue = rawEvent["value"]; auto& value = event.value; + int32_t count; switch (getPropType(event.prop)) { case VehiclePropertyType::BOOLEAN: case VehiclePropertyType::INT32: @@ -126,6 +143,13 @@ std::vector JsonFakeValueGenerator::parseFakeValueJson(std::is case VehiclePropertyType::STRING: value.stringValue = rawEventValue.asString(); break; + case VehiclePropertyType::INT32_VEC: + value.int32Values.resize(rawEventValue.size()); + count = 0; + for (auto& it : rawEventValue) { + value.int32Values[count++] = it.asInt(); + } + break; case VehiclePropertyType::MIXED: copyMixedValueJson(value, rawEventValue); if (isDiagnosticProperty(event.prop)) { diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/JsonFakeValueGenerator.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/JsonFakeValueGenerator.h index 70575f77bf..dc8ff6680c 100644 --- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/JsonFakeValueGenerator.h +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/JsonFakeValueGenerator.h @@ -41,9 +41,12 @@ private: public: JsonFakeValueGenerator(const VehiclePropValue& request); + JsonFakeValueGenerator(std::string path); + ~JsonFakeValueGenerator() = default; VehiclePropValue nextEvent(); + std::vector getAllEvents(); bool hasNext(); diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/LinearFakeValueGenerator.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/LinearFakeValueGenerator.cpp index 7bdc97cd2b..96aaafe0b0 100644 --- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/LinearFakeValueGenerator.cpp +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/LinearFakeValueGenerator.cpp @@ -46,7 +46,8 @@ VehiclePropValue LinearFakeValueGenerator::nextEvent() { if (mGenCfg.currentValue > mGenCfg.initialValue + mGenCfg.dispersion) { mGenCfg.currentValue = mGenCfg.initialValue - mGenCfg.dispersion; } - VehiclePropValue event = {.prop = mGenCfg.propId}; + // TODO: (chenhaosjtuacm) remove "{}" if AGL compiler updated + VehiclePropValue event = {.timestamp = {}, .areaId = {}, .prop = mGenCfg.propId}; auto& value = event.value; switch (getPropType(event.prop)) { case VehiclePropertyType::INT32: diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/ProtoMessageConverter.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/ProtoMessageConverter.cpp new file mode 100644 index 0000000000..77cb114164 --- /dev/null +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/ProtoMessageConverter.cpp @@ -0,0 +1,216 @@ +/* + * 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 "ProtoMsgConverter" + +#include +#include + +#include + +#include + +#include "ProtoMessageConverter.h" + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +namespace impl { + +namespace proto_msg_converter { + +// If protobuf class PROTO_VALUE has value in field PROTO_VARNAME, +// then casting the value by CAST and copying it to VHAL_TYPE_VALUE->VHAL_TYPE_VARNAME +#define CHECK_CAST_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(PROTO_VALUE, PROTO_VARNAME, VHAL_TYPE_VALUE, \ + VHAL_TYPE_VARNAME, CAST) \ + if (PROTO_VALUE.has_##PROTO_VARNAME()) { \ + (VHAL_TYPE_VALUE)->VHAL_TYPE_VARNAME = CAST(PROTO_VALUE.PROTO_VARNAME()); \ + } + +// Copying the vector PROTO_VECNAME of protobuf class PROTO_VALUE to +// VHAL_TYPE_VALUE->VHAL_TYPE_VECNAME, every element of PROTO_VECNAME +// is casted by CAST +#define CAST_COPY_PROTOBUF_VEC_TO_VHAL_TYPE(PROTO_VALUE, PROTO_VECNAME, VHAL_TYPE_VALUE, \ + VHAL_TYPE_VECNAME, CAST) \ + do { \ + (VHAL_TYPE_VALUE)->VHAL_TYPE_VECNAME.resize(PROTO_VALUE.PROTO_VECNAME##_size()); \ + size_t idx = 0; \ + for (auto& value : PROTO_VALUE.PROTO_VECNAME()) { \ + VHAL_TYPE_VALUE->VHAL_TYPE_VECNAME[idx++] = CAST(value); \ + } \ + } while (0) + +// If protobuf message has value in field PROTO_VARNAME, +// then copying it to VHAL_TYPE_VALUE->VHAL_TYPE_VARNAME +#define CHECK_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(PROTO_VALUE, PROTO_VARNAME, VHAL_TYPE_VALUE, \ + VHAL_TYPE_VARNAME) \ + CHECK_CAST_COPY_PROTOBUF_VAR_TO_VHAL_TYPE( \ + PROTO_VALUE, PROTO_VARNAME, VHAL_TYPE_VALUE, VHAL_TYPE_VARNAME, /*NO CAST*/) + +// Copying the vector PROTO_VECNAME of protobuf class PROTO_VALUE to +// VHAL_TYPE_VALUE->VHAL_TYPE_VECNAME +#define COPY_PROTOBUF_VEC_TO_VHAL_TYPE(PROTO_VALUE, PROTO_VECNAME, VHAL_TYPE_VALUE, \ + VHAL_TYPE_VECNAME) \ + CAST_COPY_PROTOBUF_VEC_TO_VHAL_TYPE( \ + PROTO_VALUE, PROTO_VECNAME, VHAL_TYPE_VALUE, VHAL_TYPE_VECNAME, /*NO CAST*/) + +void toProto(vhal_proto::VehiclePropConfig* protoCfg, const VehiclePropConfig& cfg) { + protoCfg->set_prop(cfg.prop); + protoCfg->set_access(toInt(cfg.access)); + protoCfg->set_change_mode(toInt(cfg.changeMode)); + protoCfg->set_value_type(toInt(getPropType(cfg.prop))); + + for (auto& configElement : cfg.configArray) { + protoCfg->add_config_array(configElement); + } + + if (cfg.configString.size() > 0) { + protoCfg->set_config_string(cfg.configString.c_str(), cfg.configString.size()); + } + + protoCfg->clear_area_configs(); + for (auto& areaConfig : cfg.areaConfigs) { + auto* protoACfg = protoCfg->add_area_configs(); + protoACfg->set_area_id(areaConfig.areaId); + + switch (getPropType(cfg.prop)) { + case VehiclePropertyType::STRING: + case VehiclePropertyType::BOOLEAN: + case VehiclePropertyType::INT32_VEC: + case VehiclePropertyType::INT64_VEC: + case VehiclePropertyType::FLOAT_VEC: + case VehiclePropertyType::BYTES: + case VehiclePropertyType::MIXED: + // Do nothing. These types don't have min/max values + break; + case VehiclePropertyType::INT64: + protoACfg->set_min_int64_value(areaConfig.minInt64Value); + protoACfg->set_max_int64_value(areaConfig.maxInt64Value); + break; + case VehiclePropertyType::FLOAT: + protoACfg->set_min_float_value(areaConfig.minFloatValue); + protoACfg->set_max_float_value(areaConfig.maxFloatValue); + break; + case VehiclePropertyType::INT32: + protoACfg->set_min_int32_value(areaConfig.minInt32Value); + protoACfg->set_max_int32_value(areaConfig.maxInt32Value); + break; + default: + ALOGW("%s: Unknown property type: 0x%x", __func__, toInt(getPropType(cfg.prop))); + break; + } + } + + protoCfg->set_min_sample_rate(cfg.minSampleRate); + protoCfg->set_max_sample_rate(cfg.maxSampleRate); +} + +void fromProto(VehiclePropConfig* cfg, const vhal_proto::VehiclePropConfig& protoCfg) { + CHECK_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoCfg, prop, cfg, prop); + CHECK_CAST_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoCfg, access, cfg, access, + static_cast); + CHECK_CAST_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoCfg, change_mode, cfg, changeMode, + static_cast); + COPY_PROTOBUF_VEC_TO_VHAL_TYPE(protoCfg, config_array, cfg, configArray); + CHECK_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoCfg, config_string, cfg, configString); + + auto cast_to_acfg = [](const vhal_proto::VehicleAreaConfig& protoAcfg) { + VehicleAreaConfig acfg; + CHECK_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoAcfg, area_id, &acfg, areaId); + CHECK_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoAcfg, min_int32_value, &acfg, minInt32Value); + CHECK_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoAcfg, max_int32_value, &acfg, maxInt32Value); + CHECK_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoAcfg, min_int64_value, &acfg, minInt64Value); + CHECK_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoAcfg, max_int64_value, &acfg, maxInt64Value); + CHECK_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoAcfg, min_float_value, &acfg, minFloatValue); + CHECK_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoAcfg, max_float_value, &acfg, maxFloatValue); + return acfg; + }; + + CAST_COPY_PROTOBUF_VEC_TO_VHAL_TYPE(protoCfg, area_configs, cfg, areaConfigs, cast_to_acfg); + + CHECK_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoCfg, min_sample_rate, cfg, minSampleRate); + CHECK_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoCfg, max_sample_rate, cfg, maxSampleRate); +} + +void toProto(vhal_proto::VehiclePropValue* protoVal, const VehiclePropValue& val) { + protoVal->set_prop(val.prop); + protoVal->set_value_type(toInt(getPropType(val.prop))); + protoVal->set_timestamp(val.timestamp); + protoVal->set_status((vhal_proto::VehiclePropStatus)(val.status)); + protoVal->set_area_id(val.areaId); + + // Copy value data if it is set. + // - for bytes and strings, this is indicated by size > 0 + // - for int32, int64, and float, copy the values if vectors have data + if (val.value.stringValue.size() > 0) { + protoVal->set_string_value(val.value.stringValue.c_str(), val.value.stringValue.size()); + } + + if (val.value.bytes.size() > 0) { + protoVal->set_bytes_value(val.value.bytes.data(), val.value.bytes.size()); + } + + for (auto& int32Value : val.value.int32Values) { + protoVal->add_int32_values(int32Value); + } + + for (auto& int64Value : val.value.int64Values) { + protoVal->add_int64_values(int64Value); + } + + for (auto& floatValue : val.value.floatValues) { + protoVal->add_float_values(floatValue); + } +} + +void fromProto(VehiclePropValue* val, const vhal_proto::VehiclePropValue& protoVal) { + CHECK_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoVal, prop, val, prop); + CHECK_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoVal, timestamp, val, timestamp); + CHECK_CAST_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoVal, status, val, status, + static_cast); + CHECK_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoVal, area_id, val, areaId); + + // Copy value data + CHECK_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoVal, string_value, val, value.stringValue); + + auto cast_proto_bytes_to_vec = [](auto&& bytes) { + return std::vector(bytes.begin(), bytes.end()); + }; + CHECK_CAST_COPY_PROTOBUF_VAR_TO_VHAL_TYPE(protoVal, bytes_value, val, value.bytes, + cast_proto_bytes_to_vec); + + COPY_PROTOBUF_VEC_TO_VHAL_TYPE(protoVal, int32_values, val, value.int32Values); + COPY_PROTOBUF_VEC_TO_VHAL_TYPE(protoVal, int64_values, val, value.int64Values); + COPY_PROTOBUF_VEC_TO_VHAL_TYPE(protoVal, float_values, val, value.floatValues); +} + +#undef COPY_PROTOBUF_VEC_TO_VHAL_TYPE +#undef CHECK_COPY_PROTOBUF_VAR_TO_VHAL_TYPE +#undef CAST_COPY_PROTOBUF_VEC_TO_VHAL_TYPE +#undef CHECK_CAST_COPY_PROTOBUF_VAR_TO_VHAL_TYPE + +} // namespace proto_msg_converter + +} // namespace impl + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/ProtoMessageConverter.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/ProtoMessageConverter.h new file mode 100644 index 0000000000..01f3beb49b --- /dev/null +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/ProtoMessageConverter.h @@ -0,0 +1,56 @@ +/* + * 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_automotive_vehicle_V2_0_impl_ProtoMessageConverter_H_ +#define android_hardware_automotive_vehicle_V2_0_impl_ProtoMessageConverter_H_ + +#include + +#include "VehicleHalProto.pb.h" + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { + +namespace impl { + +namespace proto_msg_converter { + +// VehiclePropConfig + +void toProto(vhal_proto::VehiclePropConfig* protoCfg, const VehiclePropConfig& cfg); + +void fromProto(VehiclePropConfig* cfg, const vhal_proto::VehiclePropConfig& protoCfg); + +// VehiclePropValue + +void toProto(vhal_proto::VehiclePropValue* protoVal, const VehiclePropValue& val); + +void fromProto(VehiclePropValue* val, const vhal_proto::VehiclePropValue& protoVal); + +} // namespace proto_msg_converter + +} // namespace impl + +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android + +#endif // android_hardware_automotive_vehicle_V2_0_impl_VehicleHalEmulator_H_ diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/SocketComm.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/SocketComm.cpp index 9eb8894385..916c320b90 100644 --- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/SocketComm.cpp +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/SocketComm.cpp @@ -60,7 +60,7 @@ void SocketComm::stop() { } } -void SocketComm::sendMessage(emulator::EmulatorMessage const& msg) { +void SocketComm::sendMessage(vhal_proto::EmulatorMessage const& msg) { std::lock_guard lock(mMutex); for (std::unique_ptr const& conn : mOpenConnections) { conn->sendMessage(msg); @@ -92,7 +92,10 @@ bool SocketComm::listen() { } ALOGI("%s: Listening for connections on port %d", __FUNCTION__, DEBUG_SOCKET); - ::listen(mListenFd, 1); + if (::listen(mListenFd, 1) == -1) { + ALOGE("%s: Error on listening: errno: %d: %s", __FUNCTION__, errno, strerror(errno)); + return false; + } return true; } diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/SocketComm.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/SocketComm.h index 88b852bb33..52326b9fc7 100644 --- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/SocketComm.h +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/SocketComm.h @@ -47,7 +47,7 @@ class SocketComm { /** * Serialized and send the given message to all connected clients. */ - void sendMessage(emulator::EmulatorMessage const& msg); + void sendMessage(vhal_proto::EmulatorMessage const& msg); private: int mListenFd; 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 9dc70859c9..263ca62f42 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 @@ -24,6 +24,7 @@ #include #include "PipeComm.h" +#include "ProtoMessageConverter.h" #include "SocketComm.h" #include "VehicleEmulator.h" @@ -62,11 +63,11 @@ VehicleEmulator::~VehicleEmulator() { * changed. */ void VehicleEmulator::doSetValueFromClient(const VehiclePropValue& propValue) { - emulator::EmulatorMessage msg; - emulator::VehiclePropValue *val = msg.add_value(); + vhal_proto::EmulatorMessage msg; + vhal_proto::VehiclePropValue* val = msg.add_value(); populateProtoVehiclePropValue(val, &propValue); - msg.set_status(emulator::RESULT_OK); - msg.set_msg_type(emulator::SET_PROPERTY_ASYNC); + msg.set_status(vhal_proto::RESULT_OK); + msg.set_msg_type(vhal_proto::SET_PROPERTY_ASYNC); mSocketComm->sendMessage(msg); if (mPipeComm) { @@ -77,17 +78,17 @@ void VehicleEmulator::doSetValueFromClient(const VehiclePropValue& propValue) { void VehicleEmulator::doGetConfig(VehicleEmulator::EmulatorMessage const& rxMsg, VehicleEmulator::EmulatorMessage& respMsg) { std::vector configs = mHal->listProperties(); - emulator::VehiclePropGet getProp = rxMsg.prop(0); + vhal_proto::VehiclePropGet getProp = rxMsg.prop(0); - respMsg.set_msg_type(emulator::GET_CONFIG_RESP); - respMsg.set_status(emulator::ERROR_INVALID_PROPERTY); + respMsg.set_msg_type(vhal_proto::GET_CONFIG_RESP); + respMsg.set_status(vhal_proto::ERROR_INVALID_PROPERTY); for (auto& config : configs) { // Find the config we are looking for if (config.prop == getProp.prop()) { - emulator::VehiclePropConfig* protoCfg = respMsg.add_config(); + vhal_proto::VehiclePropConfig* protoCfg = respMsg.add_config(); populateProtoVehicleConfig(protoCfg, config); - respMsg.set_status(emulator::RESULT_OK); + respMsg.set_status(vhal_proto::RESULT_OK); break; } } @@ -97,11 +98,11 @@ void VehicleEmulator::doGetConfigAll(VehicleEmulator::EmulatorMessage const& /* VehicleEmulator::EmulatorMessage& respMsg) { std::vector configs = mHal->listProperties(); - respMsg.set_msg_type(emulator::GET_CONFIG_ALL_RESP); - respMsg.set_status(emulator::RESULT_OK); + respMsg.set_msg_type(vhal_proto::GET_CONFIG_ALL_RESP); + respMsg.set_status(vhal_proto::RESULT_OK); for (auto& config : configs) { - emulator::VehiclePropConfig* protoCfg = respMsg.add_config(); + vhal_proto::VehiclePropConfig* protoCfg = respMsg.add_config(); populateProtoVehicleConfig(protoCfg, config); } } @@ -109,11 +110,11 @@ void VehicleEmulator::doGetConfigAll(VehicleEmulator::EmulatorMessage const& /* void VehicleEmulator::doGetProperty(VehicleEmulator::EmulatorMessage const& rxMsg, VehicleEmulator::EmulatorMessage& respMsg) { int32_t areaId = 0; - emulator::VehiclePropGet getProp = rxMsg.prop(0); + vhal_proto::VehiclePropGet getProp = rxMsg.prop(0); int32_t propId = getProp.prop(); - emulator::Status status = emulator::ERROR_INVALID_PROPERTY; + vhal_proto::Status status = vhal_proto::ERROR_INVALID_PROPERTY; - respMsg.set_msg_type(emulator::GET_PROPERTY_RESP); + respMsg.set_msg_type(vhal_proto::GET_PROPERTY_RESP); if (getProp.has_area_id()) { areaId = getProp.area_id(); @@ -127,9 +128,9 @@ void VehicleEmulator::doGetProperty(VehicleEmulator::EmulatorMessage const& rxMs StatusCode halStatus; auto val = mHal->get(request, &halStatus); if (val != nullptr) { - emulator::VehiclePropValue* protoVal = respMsg.add_value(); + vhal_proto::VehiclePropValue* protoVal = respMsg.add_value(); populateProtoVehiclePropValue(protoVal, val.get()); - status = emulator::RESULT_OK; + status = vhal_proto::RESULT_OK; } } @@ -138,12 +139,12 @@ void VehicleEmulator::doGetProperty(VehicleEmulator::EmulatorMessage const& rxMs void VehicleEmulator::doGetPropertyAll(VehicleEmulator::EmulatorMessage const& /* rxMsg */, VehicleEmulator::EmulatorMessage& respMsg) { - respMsg.set_msg_type(emulator::GET_PROPERTY_ALL_RESP); - respMsg.set_status(emulator::RESULT_OK); + respMsg.set_msg_type(vhal_proto::GET_PROPERTY_ALL_RESP); + respMsg.set_status(vhal_proto::RESULT_OK); { for (const auto& prop : mHal->getAllProperties()) { - emulator::VehiclePropValue* protoVal = respMsg.add_value(); + vhal_proto::VehiclePropValue* protoVal = respMsg.add_value(); populateProtoVehiclePropValue(protoVal, &prop); } } @@ -151,7 +152,7 @@ void VehicleEmulator::doGetPropertyAll(VehicleEmulator::EmulatorMessage const& / void VehicleEmulator::doSetProperty(VehicleEmulator::EmulatorMessage const& rxMsg, VehicleEmulator::EmulatorMessage& respMsg) { - emulator::VehiclePropValue protoVal = rxMsg.value(0); + vhal_proto::VehiclePropValue protoVal = rxMsg.value(0); VehiclePropValue val = { .timestamp = elapsedRealtimeNano(), .areaId = protoVal.area_id(), @@ -159,7 +160,7 @@ void VehicleEmulator::doSetProperty(VehicleEmulator::EmulatorMessage const& rxMs .status = (VehiclePropertyStatus)protoVal.status(), }; - respMsg.set_msg_type(emulator::SET_PROPERTY_RESP); + respMsg.set_msg_type(vhal_proto::SET_PROPERTY_RESP); // Copy value data if it is set. This automatically handles complex data types if needed. if (protoVal.has_string_value()) { @@ -187,120 +188,42 @@ void VehicleEmulator::doSetProperty(VehicleEmulator::EmulatorMessage const& rxMs } bool halRes = mHal->setPropertyFromVehicle(val); - respMsg.set_status(halRes ? emulator::RESULT_OK : emulator::ERROR_INVALID_PROPERTY); + respMsg.set_status(halRes ? vhal_proto::RESULT_OK : vhal_proto::ERROR_INVALID_PROPERTY); } -void VehicleEmulator::processMessage(emulator::EmulatorMessage const& rxMsg, - emulator::EmulatorMessage& respMsg) { +void VehicleEmulator::processMessage(vhal_proto::EmulatorMessage const& rxMsg, + vhal_proto::EmulatorMessage& respMsg) { switch (rxMsg.msg_type()) { - case emulator::GET_CONFIG_CMD: + case vhal_proto::GET_CONFIG_CMD: doGetConfig(rxMsg, respMsg); break; - case emulator::GET_CONFIG_ALL_CMD: + case vhal_proto::GET_CONFIG_ALL_CMD: doGetConfigAll(rxMsg, respMsg); break; - case emulator::GET_PROPERTY_CMD: + case vhal_proto::GET_PROPERTY_CMD: doGetProperty(rxMsg, respMsg); break; - case emulator::GET_PROPERTY_ALL_CMD: + case vhal_proto::GET_PROPERTY_ALL_CMD: doGetPropertyAll(rxMsg, respMsg); break; - case emulator::SET_PROPERTY_CMD: + case vhal_proto::SET_PROPERTY_CMD: doSetProperty(rxMsg, respMsg); break; default: ALOGW("%s: Unknown message received, type = %d", __func__, rxMsg.msg_type()); - respMsg.set_status(emulator::ERROR_UNIMPLEMENTED_CMD); + respMsg.set_status(vhal_proto::ERROR_UNIMPLEMENTED_CMD); break; } } -void VehicleEmulator::populateProtoVehicleConfig(emulator::VehiclePropConfig* protoCfg, +void VehicleEmulator::populateProtoVehicleConfig(vhal_proto::VehiclePropConfig* protoCfg, const VehiclePropConfig& cfg) { - protoCfg->set_prop(cfg.prop); - protoCfg->set_access(toInt(cfg.access)); - protoCfg->set_change_mode(toInt(cfg.changeMode)); - protoCfg->set_value_type(toInt(getPropType(cfg.prop))); - - for (auto& configElement : cfg.configArray) { - protoCfg->add_config_array(configElement); - } - - if (cfg.configString.size() > 0) { - protoCfg->set_config_string(cfg.configString.c_str(), cfg.configString.size()); - } - - // Populate the min/max values based on property type - switch (getPropType(cfg.prop)) { - case VehiclePropertyType::STRING: - case VehiclePropertyType::BOOLEAN: - case VehiclePropertyType::INT32_VEC: - case VehiclePropertyType::INT64_VEC: - case VehiclePropertyType::FLOAT_VEC: - case VehiclePropertyType::BYTES: - case VehiclePropertyType::MIXED: - // Do nothing. These types don't have min/max values - break; - case VehiclePropertyType::INT64: - if (cfg.areaConfigs.size() > 0) { - emulator::VehicleAreaConfig* aCfg = protoCfg->add_area_configs(); - aCfg->set_min_int64_value(cfg.areaConfigs[0].minInt64Value); - aCfg->set_max_int64_value(cfg.areaConfigs[0].maxInt64Value); - } - break; - case VehiclePropertyType::FLOAT: - if (cfg.areaConfigs.size() > 0) { - emulator::VehicleAreaConfig* aCfg = protoCfg->add_area_configs(); - aCfg->set_min_float_value(cfg.areaConfigs[0].minFloatValue); - aCfg->set_max_float_value(cfg.areaConfigs[0].maxFloatValue); - } - break; - case VehiclePropertyType::INT32: - if (cfg.areaConfigs.size() > 0) { - emulator::VehicleAreaConfig* aCfg = protoCfg->add_area_configs(); - aCfg->set_min_int32_value(cfg.areaConfigs[0].minInt32Value); - aCfg->set_max_int32_value(cfg.areaConfigs[0].maxInt32Value); - } - break; - default: - ALOGW("%s: Unknown property type: 0x%x", __func__, toInt(getPropType(cfg.prop))); - break; - } - - protoCfg->set_min_sample_rate(cfg.minSampleRate); - protoCfg->set_max_sample_rate(cfg.maxSampleRate); + return proto_msg_converter::toProto(protoCfg, cfg); } -void VehicleEmulator::populateProtoVehiclePropValue(emulator::VehiclePropValue* protoVal, +void VehicleEmulator::populateProtoVehiclePropValue(vhal_proto::VehiclePropValue* protoVal, const VehiclePropValue* val) { - protoVal->set_prop(val->prop); - protoVal->set_value_type(toInt(getPropType(val->prop))); - protoVal->set_timestamp(val->timestamp); - protoVal->set_status((emulator::VehiclePropStatus)(val->status)); - protoVal->set_area_id(val->areaId); - - // Copy value data if it is set. - // - for bytes and strings, this is indicated by size > 0 - // - for int32, int64, and float, copy the values if vectors have data - if (val->value.stringValue.size() > 0) { - protoVal->set_string_value(val->value.stringValue.c_str(), val->value.stringValue.size()); - } - - if (val->value.bytes.size() > 0) { - protoVal->set_bytes_value(val->value.bytes.data(), val->value.bytes.size()); - } - - for (auto& int32Value : val->value.int32Values) { - protoVal->add_int32_values(int32Value); - } - - for (auto& int64Value : val->value.int64Values) { - protoVal->add_int64_values(int64Value); - } - - for (auto& floatValue : val->value.floatValues) { - protoVal->add_float_values(floatValue); - } + return proto_msg_converter::toProto(protoVal, *val); } } // impl diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleEmulator.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleEmulator.h index 58e387a749..82947beef1 100644 --- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleEmulator.h +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleEmulator.h @@ -72,21 +72,21 @@ class VehicleEmulator : public MessageProcessor { virtual ~VehicleEmulator(); void doSetValueFromClient(const VehiclePropValue& propValue); - void processMessage(emulator::EmulatorMessage const& rxMsg, - emulator::EmulatorMessage& respMsg) override; + void processMessage(vhal_proto::EmulatorMessage const& rxMsg, + vhal_proto::EmulatorMessage& respMsg) override; private: friend class ConnectionThread; - using EmulatorMessage = emulator::EmulatorMessage; + using EmulatorMessage = vhal_proto::EmulatorMessage; void doGetConfig(EmulatorMessage const& rxMsg, EmulatorMessage& respMsg); void doGetConfigAll(EmulatorMessage const& rxMsg, EmulatorMessage& respMsg); void doGetProperty(EmulatorMessage const& rxMsg, EmulatorMessage& respMsg); void doGetPropertyAll(EmulatorMessage const& rxMsg, EmulatorMessage& respMsg); void doSetProperty(EmulatorMessage const& rxMsg, EmulatorMessage& respMsg); - void populateProtoVehicleConfig(emulator::VehiclePropConfig* protoCfg, + void populateProtoVehicleConfig(vhal_proto::VehiclePropConfig* protoCfg, const VehiclePropConfig& cfg); - void populateProtoVehiclePropValue(emulator::VehiclePropValue* protoVal, + void populateProtoVehiclePropValue(vhal_proto::VehiclePropValue* protoVal, const VehiclePropValue* val); private: diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalClient.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalClient.cpp new file mode 100644 index 0000000000..25ffc6d70d --- /dev/null +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalClient.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 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 "VehicleHalClient.h" + +#include + +namespace android::hardware::automotive::vehicle::V2_0::impl { + +void VehicleHalClient::onPropertyValue(const VehiclePropValue& value, bool updateStatus) { + if (!mPropCallback) { + LOG(ERROR) << __func__ << ": PropertyCallBackType is not registered!"; + return; + } + return mPropCallback(value, updateStatus); +} + +void VehicleHalClient::registerPropertyValueCallback(PropertyCallBackType&& callback) { + if (mPropCallback) { + LOG(ERROR) << __func__ << ": Cannot register multiple callbacks!"; + return; + } + mPropCallback = std::move(callback); +} + +} // namespace android::hardware::automotive::vehicle::V2_0::impl diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalClient.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalClient.h new file mode 100644 index 0000000000..6559e2aa84 --- /dev/null +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalClient.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 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 + +namespace android::hardware::automotive::vehicle::V2_0::impl { + +// The common client operations that may be used by both native and +// virtualized VHAL clients. +class VehicleHalClient : public IVehicleClient { + public: + // Type of callback function for handling the new property values + using PropertyCallBackType = std::function; + + // Method from IVehicleClient + void onPropertyValue(const VehiclePropValue& value, bool updateStatus) override; + + void registerPropertyValueCallback(PropertyCallBackType&& callback); + + private: + PropertyCallBackType mPropCallback; +}; + +} // namespace android::hardware::automotive::vehicle::V2_0::impl diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.cpp new file mode 100644 index 0000000000..36f25345ae --- /dev/null +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.cpp @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2020 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 "VehicleHalServer" + +#include "VehicleHalServer.h" + +#include + +#include +#include + +#include "DefaultConfig.h" +#include "JsonFakeValueGenerator.h" +#include "LinearFakeValueGenerator.h" +#include "Obd2SensorStore.h" + +namespace android::hardware::automotive::vehicle::V2_0::impl { + +GeneratorHub* VehicleHalServer::getGenerator() { + return &mGeneratorHub; +} + +VehiclePropValuePool* VehicleHalServer::getValuePool() const { + if (!mValuePool) { + LOG(WARNING) << __func__ << ": Value pool not set!"; + } + return mValuePool; +} + +EmulatedUserHal* VehicleHalServer::getEmulatedUserHal() { + return &mEmulatedUserHal; +} + +void VehicleHalServer::setValuePool(VehiclePropValuePool* valuePool) { + if (!valuePool) { + LOG(WARNING) << __func__ << ": Setting value pool to nullptr!"; + } + mValuePool = valuePool; +} + +void VehicleHalServer::onFakeValueGenerated(const VehiclePropValue& value) { + constexpr bool updateStatus = true; + LOG(DEBUG) << __func__ << ": " << toString(value); + auto updatedPropValue = getValuePool()->obtain(value); + if (updatedPropValue) { + updatedPropValue->timestamp = value.timestamp; + updatedPropValue->status = VehiclePropertyStatus::AVAILABLE; + onPropertyValueFromCar(*updatedPropValue, updateStatus); + } +} + +std::vector VehicleHalServer::onGetAllPropertyConfig() const { + std::vector vehiclePropConfigs; + constexpr size_t numOfVehiclePropConfigs = + sizeof(kVehicleProperties) / sizeof(kVehicleProperties[0]); + vehiclePropConfigs.reserve(numOfVehiclePropConfigs); + for (auto& it : kVehicleProperties) { + vehiclePropConfigs.emplace_back(it.config); + } + return vehiclePropConfigs; +} + +StatusCode VehicleHalServer::handleGenerateFakeDataRequest(const VehiclePropValue& request) { + constexpr bool updateStatus = true; + + LOG(INFO) << __func__; + const auto& v = request.value; + if (!v.int32Values.size()) { + LOG(ERROR) << __func__ << ": expected at least \"command\" field in int32Values"; + return StatusCode::INVALID_ARG; + } + + FakeDataCommand command = static_cast(v.int32Values[0]); + + switch (command) { + case FakeDataCommand::StartLinear: { + LOG(INFO) << __func__ << ", FakeDataCommand::StartLinear"; + if (v.int32Values.size() < 2) { + LOG(ERROR) << __func__ << ": expected property ID in int32Values"; + return StatusCode::INVALID_ARG; + } + if (!v.int64Values.size()) { + LOG(ERROR) << __func__ << ": interval is not provided in int64Values"; + return StatusCode::INVALID_ARG; + } + if (v.floatValues.size() < 3) { + LOG(ERROR) << __func__ << ": expected at least 3 elements in floatValues, got: " + << v.floatValues.size(); + return StatusCode::INVALID_ARG; + } + int32_t cookie = v.int32Values[1]; + getGenerator()->registerGenerator(cookie, + std::make_unique(request)); + break; + } + case FakeDataCommand::StartJson: { + LOG(INFO) << __func__ << ", FakeDataCommand::StartJson"; + if (v.stringValue.empty()) { + LOG(ERROR) << __func__ << ": path to JSON file is missing"; + return StatusCode::INVALID_ARG; + } + int32_t cookie = std::hash()(v.stringValue); + getGenerator()->registerGenerator(cookie, + std::make_unique(request)); + break; + } + case FakeDataCommand::StopLinear: { + LOG(INFO) << __func__ << ", FakeDataCommand::StopLinear"; + if (v.int32Values.size() < 2) { + LOG(ERROR) << __func__ << ": expected property ID in int32Values"; + return StatusCode::INVALID_ARG; + } + int32_t cookie = v.int32Values[1]; + getGenerator()->unregisterGenerator(cookie); + break; + } + case FakeDataCommand::StopJson: { + LOG(INFO) << __func__ << ", FakeDataCommand::StopJson"; + if (v.stringValue.empty()) { + LOG(ERROR) << __func__ << ": path to JSON file is missing"; + return StatusCode::INVALID_ARG; + } + int32_t cookie = std::hash()(v.stringValue); + getGenerator()->unregisterGenerator(cookie); + break; + } + case FakeDataCommand::KeyPress: { + LOG(INFO) << __func__ << ", FakeDataCommand::KeyPress"; + int32_t keyCode = request.value.int32Values[2]; + int32_t display = request.value.int32Values[3]; + // Send back to HAL + onPropertyValueFromCar( + *createHwInputKeyProp(VehicleHwKeyInputAction::ACTION_DOWN, keyCode, display), + updateStatus); + onPropertyValueFromCar( + *createHwInputKeyProp(VehicleHwKeyInputAction::ACTION_UP, keyCode, display), + updateStatus); + break; + } + default: { + LOG(ERROR) << __func__ << ": unexpected command: " << toInt(command); + return StatusCode::INVALID_ARG; + } + } + return StatusCode::OK; +} + +VehicleHalServer::VehiclePropValuePtr VehicleHalServer::createApPowerStateReq( + VehicleApPowerStateReq state, int32_t param) { + auto req = getValuePool()->obtain(VehiclePropertyType::INT32_VEC, 2); + req->prop = toInt(VehicleProperty::AP_POWER_STATE_REQ); + req->areaId = 0; + req->timestamp = elapsedRealtimeNano(); + req->status = VehiclePropertyStatus::AVAILABLE; + req->value.int32Values[0] = toInt(state); + req->value.int32Values[1] = param; + return req; +} + +VehicleHalServer::VehiclePropValuePtr VehicleHalServer::createHwInputKeyProp( + VehicleHwKeyInputAction action, int32_t keyCode, int32_t targetDisplay) { + auto keyEvent = getValuePool()->obtain(VehiclePropertyType::INT32_VEC, 3); + keyEvent->prop = toInt(VehicleProperty::HW_KEY_INPUT); + keyEvent->areaId = 0; + keyEvent->timestamp = elapsedRealtimeNano(); + keyEvent->status = VehiclePropertyStatus::AVAILABLE; + keyEvent->value.int32Values[0] = toInt(action); + keyEvent->value.int32Values[1] = keyCode; + keyEvent->value.int32Values[2] = targetDisplay; + return keyEvent; +} + +StatusCode VehicleHalServer::onSetProperty(const VehiclePropValue& value, bool updateStatus) { + if (mEmulatedUserHal.isSupported(value.prop)) { + LOG(INFO) << "onSetProperty(): property " << value.prop << " will be handled by UserHal"; + + const auto& ret = mEmulatedUserHal.onSetProperty(value); + if (!ret.ok()) { + LOG(ERROR) << "onSetProperty(): HAL returned error: " << ret.error().message(); + return StatusCode(ret.error().code()); + } + auto updatedValue = ret.value().get(); + if (updatedValue != nullptr) { + LOG(INFO) << "onSetProperty(): updating property returned by HAL: " + << toString(*updatedValue); + onPropertyValueFromCar(*updatedValue, updateStatus); + } + return StatusCode::OK; + } + LOG(DEBUG) << "onSetProperty(" << value.prop << ")"; + + // Some properties need to be treated non-trivially + switch (value.prop) { + case kGenerateFakeDataControllingProperty: + return handleGenerateFakeDataRequest(value); + + // set the value from vehicle side, used in end to end test. + case kSetIntPropertyFromVehicleForTest: { + auto updatedPropValue = createVehiclePropValue(VehiclePropertyType::INT32, 1); + updatedPropValue->prop = value.value.int32Values[0]; + updatedPropValue->value.int32Values[0] = value.value.int32Values[1]; + updatedPropValue->timestamp = value.value.int64Values[0]; + updatedPropValue->areaId = value.areaId; + onPropertyValueFromCar(*updatedPropValue, updateStatus); + return StatusCode::OK; + } + case kSetFloatPropertyFromVehicleForTest: { + auto updatedPropValue = createVehiclePropValue(VehiclePropertyType::FLOAT, 1); + updatedPropValue->prop = value.value.int32Values[0]; + updatedPropValue->value.floatValues[0] = value.value.floatValues[0]; + updatedPropValue->timestamp = value.value.int64Values[0]; + updatedPropValue->areaId = value.areaId; + onPropertyValueFromCar(*updatedPropValue, updateStatus); + return StatusCode::OK; + } + case kSetBooleanPropertyFromVehicleForTest: { + auto updatedPropValue = createVehiclePropValue(VehiclePropertyType::BOOLEAN, 1); + updatedPropValue->prop = value.value.int32Values[1]; + updatedPropValue->value.int32Values[0] = value.value.int32Values[0]; + updatedPropValue->timestamp = value.value.int64Values[0]; + updatedPropValue->areaId = value.areaId; + onPropertyValueFromCar(*updatedPropValue, updateStatus); + return StatusCode::OK; + } + + case AP_POWER_STATE_REPORT: + switch (value.value.int32Values[0]) { + case toInt(VehicleApPowerStateReport::DEEP_SLEEP_EXIT): + case toInt(VehicleApPowerStateReport::SHUTDOWN_CANCELLED): + case toInt(VehicleApPowerStateReport::WAIT_FOR_VHAL): + // CPMS is in WAIT_FOR_VHAL state, simply move to ON + // Send back to HAL + // ALWAYS update status for generated property value + onPropertyValueFromCar(*createApPowerStateReq(VehicleApPowerStateReq::ON, 0), + true /* updateStatus */); + break; + case toInt(VehicleApPowerStateReport::DEEP_SLEEP_ENTRY): + case toInt(VehicleApPowerStateReport::SHUTDOWN_START): + // CPMS is in WAIT_FOR_FINISH state, send the FINISHED command + // Send back to HAL + // ALWAYS update status for generated property value + onPropertyValueFromCar( + *createApPowerStateReq(VehicleApPowerStateReq::FINISHED, 0), + true /* updateStatus */); + break; + case toInt(VehicleApPowerStateReport::ON): + case toInt(VehicleApPowerStateReport::SHUTDOWN_POSTPONE): + case toInt(VehicleApPowerStateReport::SHUTDOWN_PREPARE): + // Do nothing + break; + default: + // Unknown state + break; + } + break; + default: + break; + } + + // In the real vhal, the value will be sent to Car ECU. + // We just pretend it is done here and send back to HAL + auto updatedPropValue = getValuePool()->obtain(value); + updatedPropValue->timestamp = elapsedRealtimeNano(); + + onPropertyValueFromCar(*updatedPropValue, updateStatus); + return StatusCode::OK; +} + +} // namespace android::hardware::automotive::vehicle::V2_0::impl diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.h b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.h new file mode 100644 index 0000000000..fca78bc822 --- /dev/null +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/VehicleHalServer.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 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 +#include + +#include "EmulatedUserHal.h" +#include "GeneratorHub.h" + +namespace android::hardware::automotive::vehicle::V2_0::impl { + +// This contains the common server operations that will be used by +// both native and virtualized VHAL server. Notice that in the virtualized +// scenario, the server may be run on a different OS than Android. +class VehicleHalServer : public IVehicleServer { + public: + // Methods from IVehicleServer + + std::vector onGetAllPropertyConfig() const override; + + StatusCode onSetProperty(const VehiclePropValue& value, bool updateStatus) override; + + // Set the Property Value Pool used in this server + void setValuePool(VehiclePropValuePool* valuePool); + + EmulatedUserHal* getEmulatedUserHal(); + + private: + using VehiclePropValuePtr = recyclable_ptr; + + GeneratorHub* getGenerator(); + + VehiclePropValuePool* getValuePool() const; + + void onFakeValueGenerated(const VehiclePropValue& value); + + StatusCode handleGenerateFakeDataRequest(const VehiclePropValue& request); + + VehiclePropValuePtr createApPowerStateReq(VehicleApPowerStateReq req, int32_t param); + + VehiclePropValuePtr createHwInputKeyProp(VehicleHwKeyInputAction action, int32_t keyCode, + int32_t targetDisplay); + + // data members + + protected: + EmulatedUserHal mEmulatedUserHal; + + private: + GeneratorHub mGeneratorHub{ + std::bind(&VehicleHalServer::onFakeValueGenerated, this, std::placeholders::_1)}; + + VehiclePropValuePool* mValuePool{nullptr}; +}; + +} // namespace android::hardware::automotive::vehicle::V2_0::impl diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/proto/Android.bp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/proto/Android.bp index 6754843bf5..c5b9ed6b0a 100644 --- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/proto/Android.bp +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/proto/Android.bp @@ -16,6 +16,7 @@ cc_library_static { name: "android.hardware.automotive.vehicle@2.0-libproto-native", vendor: true, + host_supported: true, proto: { export_proto_headers: true, type: "lite", @@ -27,5 +28,47 @@ cc_library_static { "-Wall", "-Werror", ], - srcs: ["VehicleHalProto.proto"] + srcs: ["VehicleHalProto.proto"], +} + +filegroup { + name: "vhal-proto-src", + visibility: [ + "//device/google/trout/hal/vehicle/2.0:__subpackages__", + ], + srcs: [ + "VehicleHalProto.proto", + ], +} + +genrule { + name: "DefaultVehicleHalProtoStub_h", + tools: [ + "aprotoc", + "protoc-gen-grpc-cpp-plugin", + ], + cmd: "$(location aprotoc) -I$$(dirname $(in)) -Iexternal/protobuf/src --plugin=protoc-gen-grpc=$(location protoc-gen-grpc-cpp-plugin) $(in) --grpc_out=$(genDir) --cpp_out=$(genDir)", + srcs: [ + "VehicleHalProto.proto", + ], + out: [ + "VehicleHalProto.pb.h", + "VehicleHalProto.grpc.pb.h", + ], +} + +genrule { + name: "DefaultVehicleHalProtoStub_cc", + tools: [ + "aprotoc", + "protoc-gen-grpc-cpp-plugin", + ], + cmd: "$(location aprotoc) -I$$(dirname $(in)) -Iexternal/protobuf/src --plugin=protoc-gen-grpc=$(location protoc-gen-grpc-cpp-plugin) $(in) --grpc_out=$(genDir) --cpp_out=$(genDir)", + srcs: [ + "VehicleHalProto.proto", + ], + out: [ + "VehicleHalProto.pb.cc", + "VehicleHalProto.grpc.pb.cc", + ], } diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/proto/VehicleHalProto.proto b/automotive/vehicle/2.0/default/impl/vhal_v2_0/proto/VehicleHalProto.proto index 2ef64fbfab..4902a5d8fb 100644 --- a/automotive/vehicle/2.0/default/impl/vhal_v2_0/proto/VehicleHalProto.proto +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/proto/VehicleHalProto.proto @@ -15,9 +15,8 @@ */ syntax = "proto2"; -option optimize_for = LITE_RUNTIME; -package emulator; +package vhal_proto; // CMD messages are from workstation --> VHAL // RESP messages are from VHAL --> workstation diff --git a/automotive/vehicle/2.0/default/impl/vhal_v2_0/tests/ProtoMessageConverter_test.cpp b/automotive/vehicle/2.0/default/impl/vhal_v2_0/tests/ProtoMessageConverter_test.cpp new file mode 100644 index 0000000000..3817e4406e --- /dev/null +++ b/automotive/vehicle/2.0/default/impl/vhal_v2_0/tests/ProtoMessageConverter_test.cpp @@ -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. + */ + +#include + +#include + +#include "vhal_v2_0/DefaultConfig.h" +#include "vhal_v2_0/ProtoMessageConverter.h" +#include "vhal_v2_0/VehicleUtils.h" + +namespace android { +namespace hardware { +namespace automotive { +namespace vehicle { +namespace V2_0 { +namespace impl { +namespace proto_msg_converter { + +namespace { + +void CheckPropConfigConversion(const VehiclePropConfig& config) { + vhal_proto::VehiclePropConfig protoCfg; + VehiclePropConfig tmpConfig; + + toProto(&protoCfg, config); + fromProto(&tmpConfig, protoCfg); + + EXPECT_EQ(config.prop, tmpConfig.prop); + EXPECT_EQ(config.access, tmpConfig.access); + EXPECT_EQ(config.changeMode, tmpConfig.changeMode); + EXPECT_EQ(config.configString, tmpConfig.configString); + EXPECT_EQ(config.minSampleRate, tmpConfig.minSampleRate); + EXPECT_EQ(config.maxSampleRate, tmpConfig.maxSampleRate); + EXPECT_EQ(config.configArray, tmpConfig.configArray); + + EXPECT_EQ(config.areaConfigs.size(), tmpConfig.areaConfigs.size()); + + auto cfgType = getPropType(config.prop); + for (size_t idx = 0; idx < std::min(config.areaConfigs.size(), tmpConfig.areaConfigs.size()); + ++idx) { + auto& lhs = config.areaConfigs[idx]; + auto& rhs = tmpConfig.areaConfigs[idx]; + EXPECT_EQ(lhs.areaId, rhs.areaId); + switch (cfgType) { + case VehiclePropertyType::INT64: + EXPECT_EQ(lhs.minInt64Value, rhs.minInt64Value); + EXPECT_EQ(lhs.maxInt64Value, rhs.maxInt64Value); + break; + case VehiclePropertyType::FLOAT: + EXPECT_EQ(lhs.minFloatValue, rhs.minFloatValue); + EXPECT_EQ(lhs.maxFloatValue, rhs.maxFloatValue); + break; + case VehiclePropertyType::INT32: + EXPECT_EQ(lhs.minInt32Value, rhs.minInt32Value); + EXPECT_EQ(lhs.maxInt32Value, rhs.maxInt32Value); + break; + default: + // ignore min/max values + break; + } + } +} + +void CheckPropValueConversion(const VehiclePropValue& val) { + vhal_proto::VehiclePropValue protoVal; + VehiclePropValue tmpVal; + + toProto(&protoVal, val); + fromProto(&tmpVal, protoVal); + + EXPECT_EQ(val, tmpVal); +} + +TEST(ProtoMessageConverterTest, basic) { + for (auto& property : impl::kVehicleProperties) { + CheckPropConfigConversion(property.config); + + VehiclePropValue prop; + prop.timestamp = elapsedRealtimeNano(); + prop.areaId = 123; + prop.prop = property.config.prop; + prop.value = property.initialValue; + prop.status = VehiclePropertyStatus::ERROR; + CheckPropValueConversion(prop); + } +} + +} // namespace + +} // namespace proto_msg_converter +} // namespace impl +} // namespace V2_0 +} // namespace vehicle +} // namespace automotive +} // namespace hardware +} // namespace android diff --git a/automotive/vehicle/2.0/types.hal b/automotive/vehicle/2.0/types.hal index 0f20dd18c0..f7a42e959b 100644 --- a/automotive/vehicle/2.0/types.hal +++ b/automotive/vehicle/2.0/types.hal @@ -35,6 +35,21 @@ enum VehiclePropertyType : int32_t { /** * Any combination of scalar or vector types. The exact format must be * provided in the description of the property. + * + * For vendor MIXED type properties, configArray needs to be formatted in this + * structure. + * configArray[0], 1 indicates the property has a String value + * configArray[1], 1 indicates the property has a Boolean value . + * configArray[2], 1 indicates the property has an Integer value. + * configArray[3], the number indicates the size of Integer[] in the property. + * configArray[4], 1 indicates the property has a Long value. + * configArray[5], the number indicates the size of Long[] in the property. + * configArray[6], 1 indicates the property has a Float value. + * configArray[7], the number indicates the size of Float[] in the property. + * configArray[8], the number indicates the size of byte[] in the property. + * For example: + * {@code configArray = {1, 1, 1, 3, 0, 0, 0, 0, 0}} indicates the property has + * a String value, a Boolean value, an Integer value and an array with 3 integers. */ MIXED = 0x00e00000, @@ -50,8 +65,8 @@ enum VehiclePropertyType : int32_t { * particular door, thus this property must be marked with * VehicleArea:DOOR flag. * - * Other properties may not be associated with particular vehicle area, - * these kind of properties must have VehicleArea:GLOBAL flag. + * Other properties may not be associated with particular vehicle area. + * These kinds of properties must have VehicleArea:GLOBAL flag. * * [Definition] Area: An area represents a unique element of an AreaType. * For instance, if AreaType is WINDOW, then an area may be FRONT_WINDSHIELD. @@ -64,9 +79,9 @@ enum VehiclePropertyType : int32_t { * Rules for mapping a zoned property to AreaIDs: * - A property must be mapped to an array of AreaIDs that are impacted when * the property value changes. - * - Each element in the array must represent an AreaID, in which, the + * - Each element in the array must represent an AreaID, in which the * property value can only be changed together in all the areas within - * an AreaID and never independently. That is, when the property value + * the AreaID and never independently. That is, when the property value * changes in one of the areas in an AreaID in the array, then it must * automatically change in all other areas in the AreaID. * - The property value must be independently controllable in any two @@ -125,7 +140,7 @@ enum VehiclePropertyGroup : int32_t { * - vehicle area (VehicleArea) * * Vendors are allowed to extend this enum with their own properties. In this - * case they must use VehiclePropertyGroup:VENDOR flag when property is + * case they must use VehiclePropertyGroup:VENDOR flag when the property is * declared. * * When a property's status field is not set to AVAILABLE: @@ -282,6 +297,47 @@ enum VehicleProperty : int32_t { | VehiclePropertyType:INT32 | VehicleArea:SEAT), + /** + * Exterior dimensions of vehicle. + * + * int32Values[0] = height + * int32Values[1] = length + * int32Values[2] = width + * int32Values[3] = width including mirrors + * int32Values[4] = wheel base + * int32Values[5] = track width front + * int32Values[6] = track width rear + * int32Values[7] = curb to curb turning radius + * + * @change_mode VehiclePropertyChangeMode:STATIC + * @access VehiclePropertyAccess:READ + * @unit VehicleUnit:MILLIMETER + */ + INFO_EXTERIOR_DIMENSIONS = ( + 0x010B + | VehiclePropertyGroup:SYSTEM + | VehiclePropertyType:INT32_VEC + | VehicleArea:GLOBAL), + + /** + * Multiple EV port locations + * + * Implement this property if the vehicle has multiple EV ports. + * Port locations are defined in PortLocationType. + * For example, a car has one port in front left and one port in rear left: + * int32Values[0] = PortLocationType::FRONT_LEFT + * int32Values[0] = PortLocationType::REAR_LEFT + * + * @change_mode VehiclePropertyChangeMode:STATIC + * @access VehiclePropertyAccess:READ + * @data_enum PortLocationType + */ + INFO_MULTI_EV_PORT_LOCATIONS = ( + 0x010C + | VehiclePropertyGroup:SYSTEM + | VehiclePropertyType:INT32_VEC + | VehicleArea:GLOBAL), + /** * Current odometer value of the vehicle * @@ -325,7 +381,7 @@ enum VehicleProperty : int32_t { | VehicleArea:GLOBAL), /** - * Steering angle of the vehicle + * Front bicycle model steering angle for vehicle * * Angle is in degrees. Left is negative. * @@ -339,6 +395,21 @@ enum VehicleProperty : int32_t { | VehiclePropertyType:FLOAT | VehicleArea:GLOBAL), + /** + * Rear bicycle model steering angle for vehicle + * + * Angle is in degrees. Left is negative. + * + * @change_mode VehiclePropertyChangeMode:CONTINUOUS + * @access VehiclePropertyAccess:READ + * @unit VehicleUnit:DEGREES + */ + PERF_REAR_STEERING_ANGLE = ( + 0x0210 + | VehiclePropertyGroup:SYSTEM + | VehiclePropertyType:FLOAT + | VehicleArea:GLOBAL), + /** * Temperature of engine coolant * @@ -535,8 +606,23 @@ enum VehicleProperty : int32_t { /** * Tire pressure * - * min/max value indicates tire pressure sensor range. Each tire will have a separate min/max - * value denoted by its areaConfig.areaId. + * Each tires is identified by its areaConfig.areaId config and their + * minFloatValue/maxFloatValue are used to store OEM recommended pressure + * range. + * The Min value in the areaConfig data represents the lower bound of + * the recommended tire pressure. + * The Max value in the areaConfig data represents the upper bound of + * the recommended tire pressure. + * For example: + * The following areaConfig indicates the recommended tire pressure + * of left_front tire is from 200.0 KILOPASCAL to 240.0 KILOPASCAL. + * .areaConfigs = { + * VehicleAreaConfig { + * .areaId = VehicleAreaWheel::LEFT_FRONT, + * .minFloatValue = 200.0, + * .maxFloatValue = 240.0, + * } + * }, * * @change_mode VehiclePropertyChangeMode:CONTINUOUS * @access VehiclePropertyAccess:READ @@ -715,7 +801,8 @@ enum VehicleProperty : int32_t { /* * HVAC Properties * - * Additional rules for mapping a zoned HVAC property to AreaIDs: + * Additional rules for mapping a zoned HVAC property (except + * HVAC_MAX_DEFROST_ON) to AreaIDs: * - Every seat in VehicleAreaSeat that is available in the car, must be * part of an AreaID in the AreaID array. * @@ -799,7 +886,7 @@ enum VehicleProperty : int32_t { | VehicleArea:SEAT), /** - * On/off defrost for designated window + * Fan-based defrost for designated window. * * @change_mode VehiclePropertyChangeMode:ON_CHANGE * @access VehiclePropertyAccess:READ_WRITE @@ -848,6 +935,11 @@ enum VehicleProperty : int32_t { * possible. Any parameters modified as a side effect of turning on/off * the MAX DEFROST parameter shall generate onPropertyEvent() callbacks to * the VHAL. + * The AreaIDs for HVAC_MAX_DEFROST_ON indicate MAX DEFROST can be controlled + * in the area. + * For example: + * areaConfig.areaId = {ROW_1_LEFT | ROW_1_RIGHT} indicates HVAC_MAX_DEFROST_ON + * only can be controlled for the front rows. * * @change_mode VehiclePropertyChangeMode:ON_CHANGE * @access VehiclePropertyAccess:READ_WRITE @@ -1076,6 +1168,7 @@ enum VehicleProperty : int32_t { * * @change_mode VehiclePropertyChangeMode:STATIC * @access VehiclePropertyAccess:READ + * @data_enum VehicleHvacFanDirection */ HVAC_FAN_DIRECTION_AVAILABLE = ( 0x0511 @@ -1118,6 +1211,18 @@ enum VehicleProperty : int32_t { | VehiclePropertyType:INT32 | VehicleArea:SEAT), + /** + * Electric defrosters' status + * + * @change_mode VehiclePropertyChangeMode:ON_CHANGE + * @access VehiclePropertyAccess:READ_WRITE + */ + HVAC_ELECTRIC_DEFROSTER_ON = ( + 0x0514 + | VehiclePropertyGroup:SYSTEM + | VehiclePropertyType:BOOLEAN + | VehicleArea:WINDOW), + /** * Distance units for display * @@ -1348,6 +1453,33 @@ enum VehicleProperty : int32_t { | VehiclePropertyType:INT32_VEC | VehicleArea:GLOBAL), + /** + * Property to feed H/W rotary events to android + * + * int32Values[0] : RotaryInputType identifying which rotary knob rotated + * int32Values[1] : number of detents (clicks), positive for clockwise, + * negative for counterclockwise + * int32Values[2] : target display defined in VehicleDisplay. Events not + * tied to specific display must be sent to + * VehicleDisplay#MAIN. + * int32values[3 .. 3 + abs(number of detents) - 2]: + * nanosecond deltas between pairs of consecutive detents, + * if the number of detents is > 1 or < -1 + * + * VehiclePropValue.timestamp: when the rotation occurred. If the number of + * detents is > 1 or < -1, this is when the + * first detent of rotation occurred. + * + * @change_mode VehiclePropertyChangeMode:ON_CHANGE + * @data_enum RotaryInputType + * @access VehiclePropertyAccess:READ + */ + HW_ROTARY_INPUT = ( + 0x0A20 + | VehiclePropertyGroup:SYSTEM + | VehiclePropertyType:INT32_VEC + | VehicleArea:GLOBAL), + /*************************************************************************** * Most Car Cabin properties have both a POSition and MOVE parameter. These * are used to control the various movements for seats, doors, and windows @@ -2321,6 +2453,503 @@ enum VehicleProperty : int32_t { | VehiclePropertyType:INT32 | VehicleArea:SEAT), + /** + * Support customize permissions for vendor properties + * + * Implement this property if vehicle hal support customize vendor permissions feature. + * VehiclePropConfig.configArray is used to indicate vendor properties and permissions + * which selected for this vendor property. The permission must be one of enum in + * VehicleVendorPermission. + * The configArray is set as follows: + * configArray[n] = propId : property ID for the vendor property + * configArray[n+1] = one of enums in VehicleVendorPermission. It indicates the permission + * for reading value of the property. + * configArray[n+2] = one of enums in VehicleVendorPermission. It indicates the permission + * for writing value of the property. + * + * For example: + * configArray = { + * vendor_prop_1, PERMISSION_VENDOR_SEAT_READ, PERMISSION_VENDOR_SEAT_WRITE, + * vendor_prop_2, PERMISSION_VENDOR_INFO, PERMISSION_NOT_ACCESSIBLE, + * } + * If vendor properties are not in this array, they will have the default vendor permission. + * If vendor chose PERMISSION_NOT_ACCESSIBLE, android will not have access to the property. In + * the example, Android can not write value for vendor_prop_2. + * + * @change_mode VehiclePropertyChangeMode:STATIC + * @access VehiclePropertyAccess:READ + */ + SUPPORT_CUSTOMIZE_VENDOR_PERMISSION = ( + 0x0F05 + | VehiclePropertyGroup:SYSTEM + | VehiclePropertyType:BOOLEAN + | VehicleArea:GLOBAL), + + /** + * Allow disabling optional featurs from vhal. + * + * This property reports optional features that should be disabled. + * All allowed optional features for the system is declared in Car service overlay, + * config_allowed_optional_car_features. + * This property allows disabling features defined in the overlay. Without this property, + * all the features declared in the overlay will be enabled. + * + * Value read should include all features disabled with ',' separation. + * ex) "com.android.car.user.CarUserNoticeService,storage_monitoring" + * @change_mode VehiclePropertyChangeMode:STATIC + * @access VehiclePropertyAccess:READ + */ + DISABLED_OPTIONAL_FEATURES = ( + 0x0F06 + | VehiclePropertyGroup:SYSTEM + | VehiclePropertyType:STRING + | VehicleArea:GLOBAL), + + /** + * Defines the initial Android user to be used during initialization. + * + * This property is called by the Android system when it initializes and it lets the HAL + * define which Android user should be started. + * + * This request is made by setting a VehiclePropValue (defined by InitialUserInfoRequest), + * and the HAL must respond with a property change event (defined by InitialUserInfoResponse). + * If the HAL doesn't respond after some time (defined by the Android system), the Android + * system will proceed as if HAL returned a response of action + * InitialUserInfoResponseAction:DEFAULT. + * + * For example, on first boot, the request could be: + * + * int32[0]: 42 // request id (arbitrary number set by Android system) + * int32[1]: 1 // InitialUserInfoRequestType::FIRST_BOOT + * int32[2]: 0 // id of current user (usersInfo.currentUser.userId) + * int32[3]: 1 // flag of current user (usersInfo.currentUser.flags = SYSTEM) + * int32[4]: 1 // number of existing users (usersInfo.numberUsers); + * int32[5]: 0 // user #0 (usersInfo.existingUsers[0].userId) + * int32[6]: 1 // flags of user #0 (usersInfo.existingUsers[0].flags) + * + * And if the HAL want to respond with the creation of an admin user called "Owner", the + * response would be: + * + * int32[0]: 42 // must match the request id from the request + * int32[1]: 2 // action = InitialUserInfoResponseAction::CREATE + * int32[2]: -10000 // userToSwitchOrCreate.userId (not used as user will be created) + * int32[3]: 8 // userToSwitchOrCreate.flags = ADMIN + * string: "||Owner" // userLocales + separator + userNameToCreate + * + * Notice the string value represents multiple values, separated by ||. The first value is the + * (optional) system locales for the user to be created (in this case, it's empty, meaning it + * will use Android's default value), while the second value is the (also optional) name of the + * to user to be created (when the type of response is InitialUserInfoResponseAction:CREATE). + * For example, to create the same "Owner" user with "en-US" and "pt-BR" locales, the string + * value of the response would be "en-US,pt-BR||Owner". As such, neither the locale nor the + * name can have || on it, although a single | is fine. + * + * NOTE: if the HAL doesn't support user management, then it should not define this property, + * which in turn would disable the other user-related properties (for example, the Android + * system would never issue them and user-related requests from the HAL layer would be ignored + * by the Android System). But if it supports user management, then it must support all core + * user-related properties (INITIAL_USER_INFO, SWITCH_USER, CREATE_USER, and REMOVE_USER). + * + * @change_mode VehiclePropertyChangeMode:ON_CHANGE + * @access VehiclePropertyAccess:READ_WRITE + */ + INITIAL_USER_INFO = ( + 0x0F07 + | VehiclePropertyGroup:SYSTEM + | VehiclePropertyType:MIXED + | VehicleArea:GLOBAL), + + /** + * Defines a request to switch the foreground Android user. + * + * This property is used primarily by the Android System to inform the HAL that the + * current foreground Android user is switching, but it could also be used by the HAL to request + * the Android system to switch users - the + * + * When the request is made by Android, it sets a VehiclePropValue and the HAL must responde + * with a property change event; when the HAL is making the request, it must also do it through + * a property change event (the main difference is that the request id will be positive in the + * former case, and negative in the latter; the SwitchUserMessageType will also be different). + * + * The format of both request is defined by SwitchUserRequest and the format of the response + * (when needed) is defined by SwitchUserResponse. How the HAL (or Android System) should + * proceed depends on the message type (which is defined by the SwitchUserMessageType + * parameter), as defined below. + * + * 1.LEGACY_ANDROID_SWITCH + * ----------------------- + * + * Called by the Android System to indicate the Android user is about to change, when the change + * request was made in a way that is not integrated with the HAL (for example, through + * adb shell am switch-user). + * + * The HAL can switch its internal user once it receives this request, but it doesn't need to + * reply back to the Android System. If its internal user cannot be changed for some reason, + * then it must wait for the SWITCH_USER(type=ANDROID_POST_SWITCH) call to recover + * (for example, it could issue a SWITCH_USER(type=VEHICLE_REQUEST) to switch back to + * the previous user), but ideally it should never fail (as switching back could result in a + * confusing experience for the end user). + * + * For example, if the system have users (0, 10, 11) and it's switching from 0 to 11 (where none + * of them have any special flag), the request would be: + * + * int32[0]: 42 // request id + * int32[1]: 1 // SwitchUserMessageType::LEGACY_ANDROID_SWITCH + * int32[2]: 11 // target user id + * int32[3]: 0 // target user flags (none) + * int32[4]: 10 // current user + * int32[5]: 0 // current user flags (none) + * int32[6]: 3 // number of users + * int32[7]: 0 // user #0 (Android user id 0) + * int32[8]: 0 // flags of user #0 (none) + * int32[9]: 10 // user #1 (Android user id 10) + * int32[10]: 0 // flags of user #1 (none) + * int32[11]: 11 // user #2 (Android user id 11) + * int32[12]: 0 // flags of user #2 (none) + * + * 2.ANDROID_SWITCH + * ---------------- + * Called by the Android System to indicate the Android user is about to change, but Android + * will wait for the HAL's response (up to some time) before proceeding. + * + * The HAL must switch its internal user once it receives this request, then respond back to + * Android with a SWITCH_USER(type=VEHICLE_RESPONSE) indicating whether its internal + * user was switched or not (through the SwitchUserStatus enum). + * + * For example, if Android has users (0, 10, 11) and it's switching from 10 to 11 (where + * none of them have any special flag), the request would be: + * + * int32[0]: 42 // request id + * int32[1]: 2 // SwitchUserMessageType::ANDROID_SWITCH + * int32[2]: 11 // target user id + * int32[3]: 0 // target user flags (none) + * int32[4]: 10 // current user + * int32[5]: 0 // current user flags (none) + * int32[6]: 3 // number of users + * int32[7]: 0 // 1st user (user 0) + * int32[8]: 1 // 1st user flags (SYSTEM) + * int32[9]: 10 // 2nd user (user 10) + * int32[10]: 0 // 2nd user flags (none) + * int32[11]: 11 // 3rd user (user 11) + * int32[12]: 0 // 3rd user flags (none) + * + * If the request succeeded, the HAL must update the propery with: + * + * int32[0]: 42 // request id + * int32[1]: 3 // messageType = SwitchUserMessageType::VEHICLE_RESPONSE + * int32[2]: 1 // status = SwitchUserStatus::SUCCESS + * + * But if it failed, the response would be something like: + * + * int32[0]: 42 // request id + * int32[1]: 3 // messageType = SwitchUserMessageType::VEHICLE_RESPONSE + * int32[2]: 2 // status = SwitchUserStatus::FAILURE + * string: "108-D'OH!" // OEM-spefic error message + * + * 3.VEHICLE_RESPONSE + * ------------------ + * Called by the HAL to indicate whether a request of type ANDROID_SWITCH should proceed or + * abort - see the ANDROID_SWITCH section above for more info. + * + * 4.VEHICLE_REQUEST + * ------------------ + * Called by the HAL to request that the current foreground Android user is switched. + * + * This is useful in situations where Android started as one user, but the vehicle identified + * the driver as another user. For example, user A unlocked the car using the key fob of user B; + * the INITIAL_USER_INFO request returned user B, but then a face recognition subsubsystem + * identified the user as A. + * + * The HAL makes this request by a property change event (passing a negative request id), and + * the Android system will response by issue an ANDROID_POST_SWITCH call which the same + * request id. + * + * For example, if the current foreground Android user is 10 and the HAL asked it to switch to + * 11, the request would be: + * + * int32[0]: -108 // request id + * int32[1]: 4 // messageType = SwitchUserMessageType::VEHICLE_REQUEST + * int32[2]: 11 // Android user id + * + * If the request succeeded and Android has 3 users (0, 10, 11), the response would be: + * + * int32[0]: -108 // request id + * int32[1]: 5 // messageType = SwitchUserMessageType::ANDROID_POST_SWITCH + * int32[2]: 11 // target user id + * int32[3]: 0 // target user id flags (none) + * int32[4]: 11 // current user + * int32[5]: 0 // current user flags (none) + * int32[6]: 3 // number of users + * int32[7]: 0 // 1st user (user 0) + * int32[8]: 0 // 1st user flags (none) + * int32[9]: 10 // 2nd user (user 10) + * int32[10]: 4 // 2nd user flags (none) + * int32[11]: 11 // 3rd user (user 11) + * int32[12]: 3 // 3rd user flags (none) + * + * Notice that both the current and target user ids are the same - if the request failed, then + * they would be different (i.e, target user would be 11, but current user would still be 10). + * + * 5.ANDROID_POST_SWITCH + * --------------------- + * Called by the Android System after a request to switch a user was made. + * + * This property is called after switch requests of any type (i.e., LEGACY_ANDROID_SWITCH, + * ANDROID_SWITCH, or VEHICLE_REQUEST) and can be used to determine if the request succeeded or + * failed: + * + * 1. When it succeeded, it's called when the Android user is in the unlocked state and the + * value of the current and target users ids in the response are the same. This would be + * equivalent to receiving an Intent.ACTION_USER_UNLOCKED in an Android app. + * 2. When it failed it's called right away and the value of the current and target users ids + * in the response are different (as the current user didn't change to the target). + * 3. If a new switch request is made before the HAL responded to the previous one or before + * the user was unlocked, then the ANDROID_POST_SWITCH request is not made. For example, + * the driver could accidentally switch to the wrong user which has lock credentials, then + * switch to the right one before entering the credentials. + * + * The HAL can update its internal state once it receives this request, but it doesn't need to + * reply back to the Android System. + * + * Request: the first N values as defined by INITIAL_USER_INFO (where the request-specific + * value at index 1 is SwitchUserMessageType::ANDROID_POST_SWITCH), then 2 more values for the + * target user id (i.e., the Android user id that was requested to be switched to) and its flags + * (as defined by UserFlags). + * + * Response: none. + * + * Example: see VEHICLE_REQUEST section above. + * + * @change_mode VehiclePropertyChangeMode:ON_CHANGE + * @access VehiclePropertyAccess:READ_WRITE + */ + SWITCH_USER = ( + 0x0F08 + | VehiclePropertyGroup:SYSTEM + | VehiclePropertyType:MIXED + | VehicleArea:GLOBAL), + + /** + * Called by the Android System after an Android user was created. + * + * The HAL can use this property to create its equivalent user. + * + * This is an async request: Android makes the request by setting a VehiclePropValue, and HAL + * must respond with a property change indicating whether the request succeeded or failed. If + * it failed, the Android system will remove the user. + * + * The format of the request is defined by CreateUserRequest and the format of the response by + * CreateUserResponse. + * + * For example, if system had 2 users (0 and 10) and a 3rd one (which is an ephemeral guest) was + * created, the request would be: + * + * int32[0]: 42 // request id + * int32[1]: 11 // Android id of the created user + * int32[2]: 3 // Android flags (ephemeral guest) of the created user + * int32[3]: 10 // current user + * int32[4]: 0 // current user flags (none) + * int32[5]: 3 // number of users + * int32[6]: 0 // 1st user (user 0) + * int32[7]: 0 // 1st user flags (none) + * int32[8]: 10 // 2nd user (user 10) + * int32[9]: 0 // 2nd user flags (none) + * int32[19]: 11 // 3rd user (user 11) + * int32[11]: 3 // 3rd user flags (ephemeral guest) + * string: "ElGuesto" // name of the new user + * + * Then if the request succeeded, the HAL would return: + * + * int32[0]: 42 // request id + * int32[1]: 1 // CreateUserStatus::SUCCESS + * + * But if it failed: + * + * int32[0]: 42 // request id + * int32[1]: 2 // CreateUserStatus::FAILURE + * string: "D'OH!" // The meaning is a blackbox - it's passed to the caller (like Settings UI), + * // which in turn can take the proper action. + * + * @change_mode VehiclePropertyChangeMode:ON_CHANGE + * @access VehiclePropertyAccess:READ_WRITE + */ + CREATE_USER = ( + 0x0F09 + | VehiclePropertyGroup:SYSTEM + | VehiclePropertyType:MIXED + | VehicleArea:GLOBAL), + + /** + * Called by the Android System after an Android user was removed. + * + * The HAL can use this property to remove its equivalent user. + * + * This is write-only call - the Android System is not expecting a reply from the HAL. Hence, + * this request should not fail - if the equivalent HAL user cannot be removed, then HAL should + * mark it as inactive or recover in some other way. + * + * The request is made by setting the VehiclePropValue with the contents defined by + * RemoveUserRequest. + * + * For example, if system had 3 users (0, 10, and 11) and user 11 was removed, the request + * would be: + * + * int32[0]: 42 // request id + * int32[1]: 11 // (Android user id of the removed user) + * int32[2]: 0 // (Android user flags of the removed user) + * int32[3]: 10 // current user + * int32[4]: 0 // current user flags (none) + * int32[5]: 2 // number of users + * int32[6]: 0 // 1st user (user 0) + * int32[7]: 0 // 1st user flags (none) + * int32[8]: 10 // 2nd user (user 10) + * int32[9]: 0 // 2nd user flags (none) + * + * @change_mode VehiclePropertyChangeMode:STATIC + * @access VehiclePropertyAccess:WRITE + */ + REMOVE_USER = ( + 0x0F0A + | VehiclePropertyGroup:SYSTEM + | VehiclePropertyType:MIXED + | VehicleArea:GLOBAL), + + /** + * Property used to associate (or query the association) the current user with vehicle-specific + * identification mechanisms (such as key FOB). + * + * This is an optional user management property - the OEM could still support user management + * without defining it. In fact, this property could be used without supporting the core + * user-related functions described on INITIAL_USER_INFO. + * + * To query the association, the Android system gets the property, passing a VehiclePropValue + * containing the types of associations are being queried, as defined by + * UserIdentificationGetRequest. The HAL must return right away, returning a VehiclePropValue + * with a UserIdentificationResponse. Notice that user identification should have already + * happened while system is booting up and the VHAL implementation should only return the + * already identified association (like the key FOB used to unlock the car), instead of starting + * a new association from the get call. + * + * To associate types, the Android system sets the property, passing a VehiclePropValue + * containing the types and values of associations being set, as defined by the + * UserIdentificationSetRequest. The HAL will then use a property change event (whose + * VehiclePropValue is defined by UserIdentificationResponse) indicating the current status of + * the types after the request. + * + * For example, to query if the current user (10) is associated with the FOB that unlocked the + * car and a custom mechanism provided by the OEM, the request would be: + * + * int32[0]: 42 // request id + * int32[1]: 10 (Android user id) + * int32[2]: 0 (Android user flags) + * int32[3]: 2 (number of types queried) + * int32[4]: 1 (1st type queried, UserIdentificationAssociationType::KEY_FOB) + * int32[5]: 101 (2nd type queried, UserIdentificationAssociationType::CUSTOM_1) + * + * If the user is associated with the FOB but not with the custom mechanism, the response would + * be: + * + * int32[0]: 42 // request id + * int32[1]: 2 (number of associations in the response) + * int32[2]: 1 (1st type: UserIdentificationAssociationType::KEY_FOB) + * int32[3]: 2 (1st value: UserIdentificationAssociationValue::ASSOCIATED_CURRENT_USER) + * int32[4]: 101 (2st type: UserIdentificationAssociationType::CUSTOM_1) + * int32[5]: 4 (2nd value: UserIdentificationAssociationValue::NOT_ASSOCIATED_ANY_USER) + * + * Then to associate the user with the custom mechanism, a set request would be made: + * + * int32[0]: 43 // request id + * int32[1]: 10 (Android user id) + * int32[2]: 0 (Android user flags) + * int32[3]: 1 (number of associations being set) + * int32[4]: 101 (1st type: UserIdentificationAssociationType::CUSTOM_1) + * int32[5]: 1 (1st value: UserIdentificationAssociationSetValue::ASSOCIATE_CURRENT_USER) + * + * If the request succeeded, the response would be simply: + * + * int32[0]: 43 // request id + * int32[1]: 1 (number of associations in the response) + * int32[2]: 101 (1st type: UserIdentificationAssociationType::CUSTOM_1) + * int32[3]: 1 (1st value: UserIdentificationAssociationValue::ASSOCIATED_CURRENT_USER) + * + * Notice that the set request adds associations, but doesn't remove the existing ones. In the + * example above, the end state would be 2 associations (FOB and CUSTOM_1). If we wanted to + * associate the user with just CUSTOM_1 but not FOB, then the request should have been: + * + * int32[0]: 43 // request id + * int32[1]: 10 (Android user id) + * int32[2]: 2 (number of types set) + * int32[3]: 1 (1st type: UserIdentificationAssociationType::KEY_FOB) + * int32[4]: 2 (1st value: UserIdentificationAssociationValue::DISASSOCIATE_CURRENT_USER) + * int32[5]: 101 (2nd type: UserIdentificationAssociationType::CUSTOM_1) + * int32[6]: 1 (2nd value: UserIdentificationAssociationValue::ASSOCIATE_CURRENT_USER) + * + * @change_mode VehiclePropertyChangeMode:ON_CHANGE + * @access VehiclePropertyAccess:READ_WRITE + */ + USER_IDENTIFICATION_ASSOCIATION = ( + 0x0F0B + | VehiclePropertyGroup:SYSTEM + | VehiclePropertyType:MIXED + | VehicleArea:GLOBAL), +}; + +/** + * Used by SUPPORT_CUSTOMIZE_VENDOR_PERMISSION to indicate the permission of vendor properties. + */ +enum VehicleVendorPermission : int32_t { + PERMISSION_DEFAULT = 0x00000000, + + // permissions for the property related with window + PERMISSION_SET_VENDOR_CATEGORY_WINDOW= 0X00000001, + PERMISSION_GET_VENDOR_CATEGORY_WINDOW = 0x00000002, + // permissions for the property related with door + PERMISSION_SET_VENDOR_CATEGORY_DOOR = 0x00000003, + PERMISSION_GET_VENDOR_CATEGORY_DOOR = 0x00000004, + // permissions for the property related with seat + PERMISSION_SET_VENDOR_CATEGORY_SEAT = 0x00000005, + PERMISSION_GET_VENDOR_CATEGORY_SEAT = 0x00000006, + // permissions for the property related with mirror + PERMISSION_SET_VENDOR_CATEGORY_MIRROR= 0x00000007, + PERMISSION_GET_VENDOR_CATEGORY_MIRROR = 0x00000008, + + // permissions for the property related with car's information + PERMISSION_SET_VENDOR_CATEGORY_INFO = 0x00000009, + PERMISSION_GET_VENDOR_CATEGORY_INFO = 0x0000000A, + // permissions for the property related with car's engine + PERMISSION_SET_VENDOR_CATEGORY_ENGINE= 0x0000000B, + PERMISSION_GET_VENDOR_CATEGORY_ENGINE = 0x0000000C, + // permissions for the property related with car's HVAC + PERMISSION_SET_VENDOR_CATEGORY_HVAC = 0x0000000D, + PERMISSION_GET_VENDOR_CATEGORY_HVAC = 0x0000000E, + // permissions for the property related with car's light + PERMISSION_SET_VENDOR_CATEGORY_LIGHT = 0x0000000F, + PERMISSION_GET_VENDOR_CATEGORY_LIGHT = 0x00000010, + + // permissions reserved for other vendor permission + PERMISSION_SET_VENDOR_CATEGORY_1 = 0x00010000, + PERMISSION_GET_VENDOR_CATEGORY_1 = 0x00011000, + PERMISSION_SET_VENDOR_CATEGORY_2 = 0x00020000, + PERMISSION_GET_VENDOR_CATEGORY_2 = 0x00021000, + PERMISSION_SET_VENDOR_CATEGORY_3 = 0x00030000, + PERMISSION_GET_VENDOR_CATEGORY_3 = 0x00031000, + PERMISSION_SET_VENDOR_CATEGORY_4 = 0x00040000, + PERMISSION_GET_VENDOR_CATEGORY_4 = 0x00041000, + PERMISSION_SET_VENDOR_CATEGORY_5 = 0x00050000, + PERMISSION_GET_VENDOR_CATEGORY_5 = 0x00051000, + PERMISSION_SET_VENDOR_CATEGORY_6 = 0x00060000, + PERMISSION_GET_VENDOR_CATEGORY_6 = 0x00061000, + PERMISSION_SET_VENDOR_CATEGORY_7 = 0x00070000, + PERMISSION_GET_VENDOR_CATEGORY_7 = 0x00071000, + PERMISSION_SET_VENDOR_CATEGORY_8 = 0x00080000, + PERMISSION_GET_VENDOR_CATEGORY_8 = 0x00081000, + PERMISSION_SET_VENDOR_CATEGORY_9 = 0x00090000, + PERMISSION_GET_VENDOR_CATEGORY_9 = 0x00091000, + PERMISSION_SET_VENDOR_CATEGORY_10 = 0x000A0000, + PERMISSION_GET_VENDOR_CATEGORY_10 = 0x000A1000, + + // Indicate not available for android to access. + PERMISSION_NOT_ACCESSIBLE = 0xF0000000 }; /** @@ -2458,9 +3087,19 @@ enum FuelType : int32_t { * Bit flags for fan direction */ enum VehicleHvacFanDirection : int32_t { + UNKNOWN = 0x0, + FACE = 0x1, FLOOR = 0x2, + /** + * FACE_AND_FLOOR = FACE | FLOOR + */ + FACE_AND_FLOOR = 0x3, DEFROST = 0x4, + /** + * DEFROST_AND_FLOOR = DEFROST | FLOOR + */ + DEFROST_AND_FLOOR = 0x06, }; enum VehicleOilLevel : int32_t { @@ -2489,26 +3128,52 @@ enum VehicleApPowerStateConfigFlag : int32_t { }; enum VehicleApPowerStateReq : int32_t { - /** Transition Android from WAIT_FOR_VHAL to ON state */ + /** + * This requests Android to enter its normal operating state. + * This may be sent after the AP has reported + * VehicleApPowerStateReport#DEEP_SLEEP_EXIT, + * VehicleApPowerStateReport#SHUTDOWN_CANCELLED, or + * VehicleApPowerStateReport#WAIT_FOR_VHAL. + */ ON = 0, /** - * The power controller has requested AP to shutdown. AP can either enter - * sleep state or start full shutdown. AP can also request postponing - * shutdown by sending VehicleApPowerSetState#SHUTDOWN_POSTPONE message. The - * power controller must change power state to this state to shutdown - * system. + * The power controller issues this request to shutdown the system. + * This may be sent after the AP has reported + * VehicleApPowerStateReport#DEEP_SLEEP_EXIT, + * VehicleApPowerStateReport#ON, + * VehicleApPowerStateReport#SHUTDOWN_CANCELLED, + * VehicleApPowerStateReport#SHUTDOWN_POSTPONE, + * VehicleApPowerStateReport#SHUTDOWN_PREPARE, or + * VehicleApPowerStateReport#WAIT_FOR_VHAL. * - * int32Values[1] : one of enum_vehicle_ap_power_state_shutdown_param_type - * - * SHUTDOWN_PRPARE may be requested from either WAIT_FOR_VHAL or ON states. + * int32Values[1] : One of VehicleApPowerStateShutdownParam. + * This parameter indicates if the AP should shut + * down fully or sleep. This parameter also + * indicates if the shutdown should be immediate + * or if it can be postponed. If the shutdown can + * be postponed, AP requests postponing by sending + * VehicleApPowerStateReport#SHUTDOWN_POSTPONE. */ SHUTDOWN_PREPARE = 1, - /** Cancel the shutdown and transition from SHUTDOWN_PREPARE to WAIT_FOR_VHAL state */ + /** + * Cancel the shutdown. + * This may be sent after the AP has reported + * VehicleApPowerStateReport#SHUTDOWN_POSTPONE or + * VehicleApPowerStateReport#SHUTDOWN_PREPARE. + * After receiving this request, the AP will report + * VehicleApPowerStateReport#WAIT_FOR_VHAL in preparation to going ON. + */ CANCEL_SHUTDOWN = 2, - /** VHAL is finished with shutdown procedures and ready for Android to suspend/shutdown */ + /** + * Completes the shutdown process. + * This may be sent after the AP has reported + * VehicleApPowerStateReport#DEEP_SLEEP_ENTRY or + * VehicleApPowerStateReport#SHUTDOWN_START. The AP will not report new + * state information after receiving this request. + */ FINISHED = 3, }; @@ -2531,65 +3196,89 @@ 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 { /** - * Device has booted, CarService has initialized and is ready to accept commands from VHAL. - * Device starts in WAIT_FOR_VHAL state. The user is not logged in, and vendor apps/services - * are expected to control the display and audio. + * The device has booted. CarService has initialized and is ready to accept commands + * from VHAL. The user is not logged in, and vendor apps and services are expected to + * control the display and audio. + * After reporting this state, AP will accept VehicleApPowerStateReq#ON or + * VehicleApPowerStateReq#SHUTDOWN_PREPARE. Other power state requests are ignored. */ WAIT_FOR_VHAL = 0x1, /** - * AP is ready to suspend and has entered WAIT_FOR_FINISHED state. + * AP is ready to suspend. + * The AP will not send any more state reports after this. + * After reporting this state, AP will accept VehicleApPowerStateReq#FINISHED. + * Other power state requests are ignored. * - * int32Values[1]: Time to turn on AP in secs. Power controller may turn on - * AP after specified time so that AP can run tasks like - * update. If it is set to 0, there is no wake up, and power - * controller may not necessarily support wake-up. + * int32Values[1]: Time to turn AP back on, in seconds. Power controller should turn on + * AP after the specified time has elapsed, so AP can run tasks like + * update. If this value is 0, no wake up is requested. The power + * controller may not necessarily support timed wake-up. */ DEEP_SLEEP_ENTRY = 0x2, /** - * AP is exiting from deep sleep state, and is in WAIT_FOR_VHAL state. + * AP is exiting from deep sleep state. + * After reporting this state, AP will accept VehicleApPowerStateReq#ON or + * VehicleApPowerStateReq#SHUTDOWN_PREPARE. Other power state requests are ignored. */ DEEP_SLEEP_EXIT = 0x3, /** - * AP remains in SHUTDOWN_PREPARE state as idle and cleanup tasks execute. + * AP sends this message repeatedly while cleanup and idle tasks execute. + * After reporting this state, AP will accept VehicleApPowerStateReq#SHUTDOWN_PREPARE + * requesting immediate shutdown or VehicleApPowerStateReq#CANCEL_SHUTDOWN. Other + * power state requests are ignored. * - * int32Values[1]: Time to postpone shutdown in ms. Maximum value can be + * int32Values[1]: Time to postpone shutdown in ms. Maximum value is * 5000 ms. - * If AP needs more time, it will send another POSTPONE + * If AP needs more time, it will send another SHUTDOWN_POSTPONE * message before the previous one expires. */ SHUTDOWN_POSTPONE = 0x4, /** - * AP is ready to shutdown and has entered WAIT_FOR_FINISHED state. + * AP is ready to shutdown. + * The AP will not send any more state reports after this. + * After reporting this state, AP will accept VehicleApPowerStateReq#FINISHED. + * Other power state requests are ignored. * - * int32Values[1]: Time to turn on AP in secs. Power controller may turn on - * AP after specified time so that AP can run tasks like - * update. If it is set to 0, there is no wake up, and power - * controller may not necessarily support wake-up. + * int32Values[1]: Time to turn AP back on, in seconds. Power controller should turn on + * AP after the specified time has elapsed so AP can run tasks like + * update. If this value is 0, no wake up is specified. The power + * controller may not necessarily support timed wake-up. */ SHUTDOWN_START = 0x5, /** - * AP has transitioned from WAIT_FOR_VHAL state to ON. + * AP is entering its normal operating state. + * After reporting this state, AP will accept VehicleApPowerStateReq#SHUTDOWN_PREPARE. + * Other power state requests are ignored. */ ON = 0x6, /** - * AP has transitions to SHUTDOWN_PREPARE state. In this state, Garage Mode will execute idle - * tasks, and other services that have registered for this state transition may execute - * cleanup activities. + * AP is preparing to shut down. In this state, Garage Mode is active and idle + * tasks are allowed to run. + * After reporting this state, AP will accept VehicleApPowerStateReq#SHUTDOWN_PREPARE + * requesting immediate shutdown or VehicleApPowerStateReq#CANCEL_SHUTDOWN. Other + * power state requests are ignored. */ SHUTDOWN_PREPARE = 0x7, /** - * AP has transitioned from SHUTDOWN_PREPARE state to WAIT_FOR_VHAL. + * AP has stopped preparing to shut down. + * After reporting this state, AP will accept VehicleApPowerStateReq#ON or + * VehicleApPowerStateReq#SHUTDOWN_PREPARE. Other power state requests are ignored. */ SHUTDOWN_CANCELLED = 0x8, }; @@ -2723,6 +3412,8 @@ enum VehiclePropertyStatus : int32_t { * Various gears which can be selected by user and chosen in system. */ enum VehicleGear : int32_t { + GEAR_UNKNOWN = 0x0000, + GEAR_NEUTRAL = 0x0001, GEAR_REVERSE = 0x0002, GEAR_PARK = 0x0004, @@ -3542,3 +4233,560 @@ enum VmsPublisherInformationIntegerValuesIndex : VmsBaseMessageIntegerValuesInde PUBLISHER_ID = 1, }; +/** + * Information about a specific Android user. + */ +struct UserInfo { + + UserId userId; + + UserFlags flags; +}; + +/** + * Id of an Android user. + * + * Must be > 0 for valid ids, or -10000 (which is the same as Android.UserHandle.USER_NULL) when + * it's not used. + */ +typedef int32_t UserId; + +/** + * Flags used to define the characteristics of an Android user. + */ +enum UserFlags: int32_t { + /** + * No flags. + */ + NONE = 0x0, + + /** + * System user. + * On automotive, that user is always running, although never on foreground (except during + * boot or exceptional circumstances). + */ + SYSTEM = 0x01, + + /** + * Guest users have restrictions. + */ + GUEST = 0x02, + + /** + * Ephemeral users have non-persistent state. + */ + EPHEMERAL = 0x04, + + /** + * Admin users have additional privileges such as permission to create other users. + */ + ADMIN = 0x08, + + /** + * Disabled users are marked for deletion. + */ + DISABLED = 0x10, + + /** + * Profile user is a profile of another user. + */ + PROFILE = 0x20, +}; + +/** + * Information about all Android users. + * + * NOTE: this struct is not used in the HAL properties directly, it's part of other structs, which + * in turn are converted to a VehiclePropValue.RawValue through libraries provided by the default + * Vehicle HAL implementation. + */ +struct UsersInfo { + + /** The current foreground user. */ + UserInfo currentUser; + + /** + * Number of existing users; includes the current user, recently removed users (with DISABLED + * flag), and profile users (with PROFILE flag). + */ + int32_t numberUsers; + + /** + * List of existing users; includes the current user, recently removed users (with DISABLED + * flag), and profile users (with PROFILE flag). + */ + vec existingUsers; + }; + +/** + * Id of a request related to user management. + * + * This id can be used by the Android system to map responses sent by the HAL, and vice-versa. + * + * For requests originated by Android, the value is positive (> 0), while for requests originated by + * the HAL it must be negative (< 0). + */ +typedef int32_t UserRequestId; + +/** + * Defines the format of a INITIAL_USER_INFO request made by the Android system. + * + * NOTE: this struct is not used in the HAL properties directly, it must be converted to + * VehiclePropValue.RawValue through libraries provided by the default Vehicle HAL implementation. + */ +struct InitialUserInfoRequest { + /** + * Arbitrary id used to map the HAL response to the request. + */ + UserRequestId requestId; + + /** + * Type of request. + */ + InitialUserInfoRequestType requestType; + + /** + * Information about the current state of the Android system. + */ + UsersInfo usersInfo; +}; + +/** + * Defines when a INITIAL_USER_INFO request was made. + */ +enum InitialUserInfoRequestType : int32_t { + /** At the first time Android was booted (or after a factory reset). */ + FIRST_BOOT = 1, + + /** At the first time Android was booted after the system was updated. */ + FIRST_BOOT_AFTER_OTA = 2, + + /** When Android was booted "from scratch". */ + COLD_BOOT = 3, + + /** When Android was resumed after the system was suspended to memory. */ + RESUME = 4, +}; + +/** + * Defines the format of a HAL response to a INITIAL_USER_INFO request. + * + * NOTE: this struct is not used in the HAL properties directly, it must be converted to + * VehiclePropValue.RawValue through libraries provided by the default Vehicle HAL implementation. + */ +struct InitialUserInfoResponse { + /** + * Id of the request being responded. + */ + UserRequestId requestId; + + /** + * which action the Android system should take. + */ + InitialUserInfoResponseAction action; + + /** + * Information about the user that should be switched to or created. + */ + UserInfo userToSwitchOrCreate; + + /** + * System locales of the initial user (value will be passed as-is to + * android.provider.Settings.System.SYSTEM_LOCALES) + */ + string userLocales; + + /** + * Name of the user that should be created. + */ + string userNameToCreate; +}; + +/** + * Defines which action the Android system should take in an INITIAL_USER_INFO request. + */ +enum InitialUserInfoResponseAction : int32_t { + /** + * Let the Android System decide what to do. + * + * For example, it might create a new user on first boot, and switch to the last + * active user afterwards. + */ + DEFAULT = 0, + + /** + * Switch to an existing Android user. + */ + SWITCH = 1, + + /** + * Create a new Android user (and switch to it). + */ + CREATE = 2, +}; + +/** + * Defines the format of a SWITCH_USER property. + * + * NOTE: this struct is not used in the HAL properties directly, it must be converted to + * VehiclePropValue.RawValue through libraries provided by the default Vehicle HAL implementation. + */ +struct SwitchUserRequest { + /** + * Arbitrary id used to map the response to the request. + */ + UserRequestId requestId; + + /** + * Type of message. + */ + SwitchUserMessageType messageType; + + /** + * Information about the Android user being switched to. + * + * Only the user id (but not the flags) should be set when the request is made by HAL. + */ + UserInfo targetUser; + + /** + * Information about the current state of the Android system. + * + * Should not be set when the request is made by HAL. + */ + UsersInfo usersInfo; +}; + +/** + * Defines the reason a SWITCH_USER call was made. + * + * The meaning of each constant is explained in that property. + */ +enum SwitchUserMessageType: int32_t { + LEGACY_ANDROID_SWITCH = 1, + ANDROID_SWITCH = 2, + VEHICLE_RESPONSE = 3, + VEHICLE_REQUEST = 4, + ANDROID_POST_SWITCH = 5, +}; + +/** + * Defines the result of a SwitchUserRequest. + * + * NOTE: this struct is not used in the HAL properties directly, it must be converted to + * VehiclePropValue.RawValue through libraries provided by the default Vehicle HAL implementation. + */ +struct SwitchUserResponse { + /** + * Id of the request being responded. + */ + UserRequestId requestId; + + /** + * Type of message. + */ + SwitchUserMessageType messageType; + + /** + * Status of the request. + */ + SwitchUserStatus status; + + /** + * HAL-specific error message. + * + * This argument is optional, and when defined, it's passed "as-is" to the caller. It could be + * used to show custom error messages to the end user. + */ + string errorMessage; +}; + +/** + * Status of the response to a SwitchUserRequest. + */ +enum SwitchUserStatus : int32_t { + /** The request succeeded and the HAL user was switched. */ + SUCCESS = 1, + /** The request failed and the HAL user remained the same. */ + FAILURE = 2, +}; + +/** + * Defines the format of a CREATE_USER property. + * + * NOTE: this struct is not used in the HAL properties directly, it must be converted to + * VehiclePropValue.RawValue through libraries provided by the default Vehicle HAL implementation. + */ +struct CreateUserRequest { + /** + * Arbitrary id used to map the response to the request. + */ + UserRequestId requestId; + + /** + * Basic information about Android user that was created. + */ + UserInfo newUserInfo; + + /** + * Name of the new Android user. + */ + string newUserName; + + /** + * Information about the current state of the Android system. + */ + UsersInfo usersInfo; +}; + +/** + * Defines the result of a CreateUserRequest. + * + * NOTE: this struct is not used in the HAL properties directly, it must be converted to + * VehiclePropValue.RawValue through libraries provided by the default Vehicle HAL implementation. + */ +struct CreateUserResponse { + /** + * Id of the request being responded. + */ + UserRequestId requestId; + + /** + * Status of the request. + */ + CreateUserStatus status; + + /** + * HAL-specific error message. + * + * This argument is optional, and when defined, it's passed "as-is" to the caller. It could be + * used to show custom error messages to the end user. + */ + string errorMessage; +}; + +/** + * Status of the response to a CreateUserRequest. + */ +enum CreateUserStatus : int32_t { + /** + * The request succeeded (for example, HAL created a new internal user, or associated the + * Android user to an existing internal user). + */ + SUCCESS = 1, + + /** + * The request failed (and Android will remove the Android user). + */ + FAILURE = 2, +}; + +/** + * Defines the format of a REMOVE_USER property. + * + * NOTE: this struct is not used in the HAL properties directly, it must be converted to + * VehiclePropValue.RawValue through libraries provided by the default Vehicle HAL implementation. + */ +struct RemoveUserRequest { + /** + * Arbitrary id used to map the response to the request. + */ + UserRequestId requestId; + + /** + * Information about the Android user that was removed. + */ + UserInfo removedUserInfo; + + /** + * Information about the current state of the Android system. + */ + UsersInfo usersInfo; +}; + +/** + * Types of mechanisms used to identify an Android user. + * + * See USER_IDENTIFICATION_ASSOCIATION for more details and example. + */ +enum UserIdentificationAssociationType: int32_t { + /** Key used to unlock the car. */ + KEY_FOB = 1, + /** Custom mechanism defined by the OEM. */ + CUSTOM_1 = 101, + /** Custom mechanism defined by the OEM. */ + CUSTOM_2 = 102, + /** Custom mechanism defined by the OEM. */ + CUSTOM_3 = 103, + /** Custom mechanism defined by the OEM. */ + CUSTOM_4 = 104, +}; + +/** + * Whether a UserIdentificationAssociationType is associate with an Android user. + */ +enum UserIdentificationAssociationValue : int32_t { + /** + * Used when the status of an association could not be determined. + * + * For example, in a set() request, it would indicate a failure to set the given type. + */ + UNKNOWN = 1, + + /** + * The identification type is associated with the current foreground Android user. + */ + ASSOCIATED_CURRENT_USER = 2, + + /** + * The identification type is associated with another Android user. + */ + ASSOCIATED_ANOTHER_USER = 3, + + /** + * The identification type is not associated with any Android user. + */ + NOT_ASSOCIATED_ANY_USER = 4, +}; + +/** + * Used to set a UserIdentificationAssociationType with an Android user. + */ +enum UserIdentificationAssociationSetValue : int32_t { + /** + * Associate the identification type with the current foreground Android user. + */ + ASSOCIATE_CURRENT_USER = 1, + + /** + * Disassociate the identification type from the current foreground Android user. + */ + DISASSOCIATE_CURRENT_USER = 2, + + /** + * Disassociate the identification type from all Android users. + */ + DISASSOCIATE_ALL_USERS = 3, +}; + +/** + * Defines the format of a get() call to USER_IDENTIFICATION_ASSOCIATION. + * + * NOTE: this struct is not used in the HAL properties directly, it must be converted to + * VehiclePropValue.RawValue through libraries provided by the default Vehicle HAL implementation. + */ +struct UserIdentificationGetRequest { + /** + * Id of the request being responded. + */ + UserRequestId requestId; + + /** + * Information about the current foreground Android user. + */ + UserInfo userInfo; + + /** + * Number of association being queried. + */ + int32_t numberAssociationTypes; + + /** + * Types of association being queried. + */ + vec associationTypes; +}; + +/** + * Defines the format of a set() call to USER_IDENTIFICATION_ASSOCIATION. + * + * NOTE: this struct is not used in the HAL properties directly, it must be converted to + * VehiclePropValue.RawValue through libraries provided by the default Vehicle HAL implementation. + */ +struct UserIdentificationSetRequest { + /** + * Id of the request being responded. + */ + UserRequestId requestId; + + /** + * Information about the current foreground Android user. + */ + UserInfo userInfo; + + /** + * Number of association being set. + */ + int32_t numberAssociations; + + /** + * Associations being set. + */ + vec associations; +}; + +/** + * Defines the result of a USER_IDENTIFICATION_ASSOCIATION - both for get() and set(). + * + * NOTE: this struct is not used in the HAL properties directly, it must be converted to + * VehiclePropValue.RawValue through libraries provided by the default Vehicle HAL implementation. + */ +struct UserIdentificationResponse { + /** + * Id of the request being responded. + */ + UserRequestId requestId; + + /** + * Number of associations being returned. + */ + int32_t numberAssociation; + + /** + * Values associated with the user. + */ + vec associations; + + /** + * HAL-specific error message. + * + * This argument is optional, and when defined, it's passed "as-is" to the caller. It could be + * used to show custom error messages to the end user. + */ + string errorMessage; +}; + +/** + * Helper struct used when getting a user/identification association type. + */ +struct UserIdentificationAssociation { + + UserIdentificationAssociationType type; + + UserIdentificationAssociationValue value; +}; + +/** + * Helper struct used when setting a user/identification association type. + */ +struct UserIdentificationSetAssociation { + + UserIdentificationAssociationType type; + + UserIdentificationAssociationSetValue value; +}; + +/** + * A rotary control which can rotate without limits. These controls use HW_ROTARY_INPUT to report + * relative clockwise or counterclockwise motion. They have no absolute position. + */ +enum RotaryInputType : int32_t { + /** + * Main rotary control, typically in the center console, used to navigate the user interface. + */ + ROTARY_INPUT_TYPE_SYSTEM_NAVIGATION = 0, + + /** Volume control for adjusting audio volume. */ + ROTARY_INPUT_TYPE_AUDIO_VOLUME = 1, +}; + diff --git a/biometrics/face/1.0/default/Android.bp b/biometrics/face/1.0/default/Android.bp new file mode 100644 index 0000000000..d6ff087ee6 --- /dev/null +++ b/biometrics/face/1.0/default/Android.bp @@ -0,0 +1,35 @@ +/* + * Copyright 2020 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_binary { + name: "android.hardware.biometrics.face@1.0-service.example", + defaults: ["hidl_defaults"], + vendor: true, + init_rc: ["android.hardware.biometrics.face@1.0-service.rc"], + vintf_fragments: ["manifest_face_default.xml"], + relative_install_path: "hw", + proprietary: true, + srcs: [ + "BiometricsFace.cpp", + "service.cpp", + ], + shared_libs: [ + "libhidlbase", + "libutils", + "liblog", + "android.hardware.biometrics.face@1.0", + ], +} diff --git a/biometrics/face/1.0/default/BiometricsFace.cpp b/biometrics/face/1.0/default/BiometricsFace.cpp new file mode 100644 index 0000000000..2dd64764bb --- /dev/null +++ b/biometrics/face/1.0/default/BiometricsFace.cpp @@ -0,0 +1,113 @@ +/* + * Copyright 2020 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 "BiometricsFace.h" + +namespace android::hardware::biometrics::face::implementation { +using android::hardware::biometrics::face::V1_0::FaceError; +using android::hardware::biometrics::face::V1_0::OptionalUint64; + +// Arbitrary value. +constexpr uint64_t kDeviceId = 123; +// Arbitrary value. +constexpr uint64_t kAuthenticatorId = 987; +// Arbitrary value. +constexpr uint64_t kLockoutDuration = 555; + +BiometricsFace::BiometricsFace() : mRandom(std::mt19937::default_seed) {} + +// Methods from IBiometricsFace follow. +Return BiometricsFace::setCallback(const sp& clientCallback, + setCallback_cb _hidl_cb) { + mClientCallback = clientCallback; + _hidl_cb({Status::OK, kDeviceId}); + return Void(); +} + +Return BiometricsFace::setActiveUser(int32_t userId, const hidl_string& storePath) { + if (userId < 0 || storePath.empty() || std::string(storePath).find("/data") != 0) { + return Status::ILLEGAL_ARGUMENT; + } + mUserId = userId; + mClientCallback->onLockoutChanged(kLockoutDuration); + return Status::OK; +} + +Return BiometricsFace::generateChallenge(uint32_t /* challengeTimeoutSec */, + generateChallenge_cb _hidl_cb) { + std::uniform_int_distribution dist; + _hidl_cb({Status::OK, dist(mRandom)}); + return Void(); +} + +Return BiometricsFace::enroll(const hidl_vec& /* hat */, uint32_t /* timeoutSec */, + const hidl_vec& /* disabledFeatures */) { + // hat can never be valid in this implementation. + mClientCallback->onError(kDeviceId, mUserId, FaceError::UNABLE_TO_PROCESS, 0 /* vendorCode */); + return Status::OK; +} + +Return BiometricsFace::revokeChallenge() { + return Status::OK; +} + +Return BiometricsFace::setFeature(Feature /* feature */, bool /* enabled */, + const hidl_vec& /* hat */, + uint32_t /* faceId */) { + // hat can never be valid in this implementation. + return Status::ILLEGAL_ARGUMENT; +} + +Return BiometricsFace::getFeature(Feature /* feature */, uint32_t /* faceId */, + getFeature_cb _hidl_cb) { + // hat can never be valid in this implementation. + _hidl_cb({Status::ILLEGAL_ARGUMENT, false}); + return Void(); +} + +Return BiometricsFace::getAuthenticatorId(getAuthenticatorId_cb _hidl_cb) { + _hidl_cb({Status::OK, kAuthenticatorId}); + return Void(); +} + +Return BiometricsFace::cancel() { + mClientCallback->onError(kDeviceId, mUserId, FaceError::CANCELED, 0 /* vendorCode */); + return Status::OK; +} + +Return BiometricsFace::enumerate() { + mClientCallback->onEnumerate(kDeviceId, {}, mUserId); + return Status::OK; +} + +Return BiometricsFace::remove(uint32_t /* faceId */) { + return Status::OK; +} + +Return BiometricsFace::authenticate(uint64_t /* operationId */) { + mClientCallback->onError(kDeviceId, mUserId, FaceError::HW_UNAVAILABLE, 0 /* vendorCode */); + return Status::OK; +} + +Return BiometricsFace::userActivity() { + return Status::OK; +} + +Return BiometricsFace::resetLockout(const hidl_vec& /* hat */) { + return Status::OK; +} + +} // namespace android::hardware::biometrics::face::implementation diff --git a/biometrics/face/1.0/default/BiometricsFace.h b/biometrics/face/1.0/default/BiometricsFace.h new file mode 100644 index 0000000000..1d99ed26bc --- /dev/null +++ b/biometrics/face/1.0/default/BiometricsFace.h @@ -0,0 +1,80 @@ +/* + * Copyright 2020 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 +#include +#include +#include + +namespace android::hardware::biometrics::face::implementation { + +using ::android::sp; +using ::android::hardware::hidl_array; +using ::android::hardware::hidl_memory; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::biometrics::face::V1_0::Feature; +using ::android::hardware::biometrics::face::V1_0::IBiometricsFaceClientCallback; +using ::android::hardware::biometrics::face::V1_0::Status; + +class BiometricsFace : public V1_0::IBiometricsFace { + public: + BiometricsFace(); + + // Methods from ::android::hardware::biometrics::face::V1_0::IBiometricsFace follow. + Return setCallback(const sp& clientCallback, + setCallback_cb _hidl_cb) override; + + Return setActiveUser(int32_t userId, const hidl_string& storePath) override; + + Return generateChallenge(uint32_t challengeTimeoutSec, + generateChallenge_cb _hidl_cb) override; + + Return enroll(const hidl_vec& hat, uint32_t timeoutSec, + const hidl_vec& disabledFeatures) override; + + Return revokeChallenge() override; + + Return setFeature(Feature feature, bool enabled, const hidl_vec& hat, + uint32_t faceId) override; + + Return getFeature(Feature feature, uint32_t faceId, getFeature_cb _hidl_cb) override; + + Return getAuthenticatorId(getAuthenticatorId_cb _hidl_cb) override; + + Return cancel() override; + + Return enumerate() override; + + Return remove(uint32_t faceId) override; + + Return authenticate(uint64_t operationId) override; + + Return userActivity() override; + + Return resetLockout(const hidl_vec& hat) override; + + private: + std::mt19937 mRandom; + int32_t mUserId; + sp mClientCallback; +}; + +} // namespace android::hardware::biometrics::face::implementation diff --git a/biometrics/face/1.0/default/android.hardware.biometrics.face@1.0-service.rc b/biometrics/face/1.0/default/android.hardware.biometrics.face@1.0-service.rc new file mode 100644 index 0000000000..6c7362f2bb --- /dev/null +++ b/biometrics/face/1.0/default/android.hardware.biometrics.face@1.0-service.rc @@ -0,0 +1,10 @@ +service vendor.face-hal-1-0-default /vendor/bin/hw/android.hardware.biometrics.face@1.0-service.example + # "class hal" causes a race condition on some devices due to files created + # in /data. As a workaround, postpone startup until later in boot once + # /data is mounted. + class late_start + user system + group system + writepid /dev/cpuset/foreground/tasks + capabilities SYS_NICE + rlimit rtprio 10 10 diff --git a/biometrics/face/1.0/default/manifest_face_default.xml b/biometrics/face/1.0/default/manifest_face_default.xml new file mode 100644 index 0000000000..380ae49e93 --- /dev/null +++ b/biometrics/face/1.0/default/manifest_face_default.xml @@ -0,0 +1,11 @@ + + + android.hardware.biometrics.face + hwbinder + 1.0 + + IBiometricsFace + default + + + diff --git a/biometrics/face/1.0/default/service.cpp b/biometrics/face/1.0/default/service.cpp new file mode 100644 index 0000000000..9818c959d6 --- /dev/null +++ b/biometrics/face/1.0/default/service.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 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.biometrics.face@1.0-service" + +#include +#include +#include +#include +#include +#include "BiometricsFace.h" + +using android::sp; +using android::hardware::configureRpcThreadpool; +using android::hardware::joinRpcThreadpool; +using android::hardware::biometrics::face::implementation::BiometricsFace; +using android::hardware::biometrics::face::V1_0::IBiometricsFace; + +int main() { + ALOGI("BiometricsFace HAL is being started."); + + configureRpcThreadpool(1, true /*callerWillJoin*/); + + android::sp face = new BiometricsFace(); + const android::status_t status = face->registerAsService(); + + if (status != android::OK) { + ALOGE("Error starting the BiometricsFace HAL."); + return 1; + } + + ALOGI("BiometricsFace HAL has started successfully."); + joinRpcThreadpool(); + + ALOGI("BiometricsFace HAL is terminating."); + return 1; // should never get here +} diff --git a/biometrics/face/1.0/vts/functional/VtsHalBiometricsFaceV1_0TargetTest.cpp b/biometrics/face/1.0/vts/functional/VtsHalBiometricsFaceV1_0TargetTest.cpp index c21d0a641e..379f37faa9 100644 --- a/biometrics/face/1.0/vts/functional/VtsHalBiometricsFaceV1_0TargetTest.cpp +++ b/biometrics/face/1.0/vts/functional/VtsHalBiometricsFaceV1_0TargetTest.cpp @@ -28,6 +28,7 @@ #include #include #include +#include using android::sp; using android::hardware::hidl_vec; @@ -144,7 +145,10 @@ class FaceHidlTest : public ::testing::TestWithParam { ASSERT_EQ(Status::OK, static_cast(ret2)); } - void TearDown() override {} + void TearDown() override { + // Hack to allow the asynchronous operations to finish on time. + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + } sp mService; sp mCallback; diff --git a/biometrics/fingerprint/2.1/vts/functional/VtsHalBiometricsFingerprintV2_1TargetTest.cpp b/biometrics/fingerprint/2.1/vts/functional/VtsHalBiometricsFingerprintV2_1TargetTest.cpp index c4cb356b84..b92831438d 100644 --- a/biometrics/fingerprint/2.1/vts/functional/VtsHalBiometricsFingerprintV2_1TargetTest.cpp +++ b/biometrics/fingerprint/2.1/vts/functional/VtsHalBiometricsFingerprintV2_1TargetTest.cpp @@ -49,7 +49,7 @@ static const uint32_t kTimeout = 3; static const std::chrono::seconds kTimeoutInSeconds = std::chrono::seconds(kTimeout); static const uint32_t kGroupId = 99; static std::string kTmpDir = ""; -static const uint32_t kIterations = 1000; +static const uint32_t kIterations = 10; // Wait for a callback to occur (signaled by the given future) up to the // provided timeout. If the future is invalid or the callback does not come diff --git a/biometrics/fingerprint/2.2/Android.bp b/biometrics/fingerprint/2.2/Android.bp new file mode 100644 index 0000000000..a8f202c2c0 --- /dev/null +++ b/biometrics/fingerprint/2.2/Android.bp @@ -0,0 +1,16 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.biometrics.fingerprint@2.2", + root: "android.hardware", + srcs: [ + "types.hal", + "IBiometricsFingerprint.hal", + "IBiometricsFingerprintClientCallback.hal", + ], + interfaces: [ + "android.hardware.biometrics.fingerprint@2.1", + "android.hidl.base@1.0", + ], + gen_java: true, +} diff --git a/biometrics/fingerprint/2.2/IBiometricsFingerprint.hal b/biometrics/fingerprint/2.2/IBiometricsFingerprint.hal new file mode 100644 index 0000000000..249830a111 --- /dev/null +++ b/biometrics/fingerprint/2.2/IBiometricsFingerprint.hal @@ -0,0 +1,28 @@ +/* + * Copyright 2020 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.biometrics.fingerprint@2.2; + +import @2.1::IBiometricsFingerprint; + +/** + * The HAL interface for biometric fingerprint authentication. + * + * This interface is required because all top-level interfaces need to be + * updated in a minor uprev. + */ +interface IBiometricsFingerprint extends @2.1::IBiometricsFingerprint { +}; diff --git a/biometrics/fingerprint/2.2/IBiometricsFingerprintClientCallback.hal b/biometrics/fingerprint/2.2/IBiometricsFingerprintClientCallback.hal new file mode 100644 index 0000000000..14c2b12b76 --- /dev/null +++ b/biometrics/fingerprint/2.2/IBiometricsFingerprintClientCallback.hal @@ -0,0 +1,35 @@ +/* + * Copyright 2020 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.biometrics.fingerprint@2.2; + +import @2.1::IBiometricsFingerprintClientCallback; + +/* + * This HAL interface communicates asynchronous results from the + * fingerprint driver in response to user actions on the fingerprint sensor + */ +interface IBiometricsFingerprintClientCallback extends @2.1::IBiometricsFingerprintClientCallback { + /** + * Sent when a fingerprint image is acquired by the sensor + * @param deviceId the instance of this fingerprint device + * @param acquiredInfo a message about the quality of the acquired image + * @param vendorCode a vendor-specific message about the quality of the image. Only + * valid when acquiredInfo == ACQUIRED_VENDOR + */ + oneway onAcquired_2_2(uint64_t deviceId, FingerprintAcquiredInfo acquiredInfo, + int32_t vendorCode); +}; diff --git a/biometrics/fingerprint/2.2/types.hal b/biometrics/fingerprint/2.2/types.hal new file mode 100644 index 0000000000..2c1d3f374c --- /dev/null +++ b/biometrics/fingerprint/2.2/types.hal @@ -0,0 +1,42 @@ +/* + * Copyright 2020 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.biometrics.fingerprint@2.2; + +import @2.1::FingerprintAcquiredInfo; + +/** + * Fingerprint acquisition info is meant as feedback for the current operation. + * Anything but START and ACQUIRED_GOOD must be shown to the user as feedback on + * how to take action on the current operation. For example, + * ACQUIRED_IMAGER_DIRTY may be used to tell the user to clean the sensor if it + * is detected to be dirty. + * If this causes the current operation to fail, an additional ERROR_CANCELED + * must be sent to stop the operation in progress (e.g. enrollment). + * In general, these messages will result in a "Try again" message. + */ +enum FingerprintAcquiredInfo : @2.1::FingerprintAcquiredInfo { + /** + * This message represents the earliest message sent at the beginning of the + * authentication pipeline. It is expected to be used to measure latency. For + * example, in a camera-based authentication system it's expected to be sent + * prior to camera initialization. Note this should be sent whenever + * authentication is restarted (see IBiometricsFace#userActivity). + * The framework will measure latency based on the time between the last START + * message and the onAuthenticated callback. + */ + START = 7, +}; diff --git a/biometrics/fingerprint/2.2/vts/functional/Android.bp b/biometrics/fingerprint/2.2/vts/functional/Android.bp new file mode 100644 index 0000000000..496570c64c --- /dev/null +++ b/biometrics/fingerprint/2.2/vts/functional/Android.bp @@ -0,0 +1,29 @@ +/* + * Copyright 2020 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: "VtsHalBiometricsFingerprintV2_2TargetTest", + defaults: ["VtsHalTargetTestDefaults"], + srcs: ["VtsHalBiometricsFingerprintV2_2TargetTest.cpp"], + static_libs: [ + "android.hardware.biometrics.fingerprint@2.1", + "android.hardware.biometrics.fingerprint@2.2", + ], + test_suites: [ + "general-tests", + "vts-core", + ], +} diff --git a/biometrics/fingerprint/2.2/vts/functional/VtsHalBiometricsFingerprintV2_2TargetTest.cpp b/biometrics/fingerprint/2.2/vts/functional/VtsHalBiometricsFingerprintV2_2TargetTest.cpp new file mode 100644 index 0000000000..df29fd4179 --- /dev/null +++ b/biometrics/fingerprint/2.2/vts/functional/VtsHalBiometricsFingerprintV2_2TargetTest.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2020 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 "fingerprint_hidl_hal_test" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +namespace hidl_interface = android::hardware::biometrics::fingerprint::V2_1; +namespace hidl_interface_2_2 = android::hardware::biometrics::fingerprint::V2_2; + +using hidl_interface::FingerprintError; +using hidl_interface::IBiometricsFingerprint; +using hidl_interface::RequestStatus; + +using android::sp; +using android::base::GetUintProperty; +using android::hardware::hidl_vec; +using android::hardware::Return; +using android::hardware::Void; + +constexpr uint32_t kTimeoutSec = 3; +constexpr auto kTimeout = std::chrono::seconds(kTimeoutSec); +constexpr uint32_t kGroupId = 99; +constexpr char kCallbackNameOnAcquired[] = "onAcquired"; + +// Callback arguments that need to be captured for the tests. +struct FingerprintCallbackArgs { + // The info passed to the last onAcquired() callback. + hidl_interface_2_2::FingerprintAcquiredInfo info; +}; + +// Test callback class for the BiometricsFingerprint HAL. +// The HAL will call these callback methods to notify about completed operations +// or encountered errors. +class FingerprintCallback : public ::testing::VtsHalHidlTargetCallbackBase, + public hidl_interface_2_2::IBiometricsFingerprintClientCallback { + public: + Return onEnrollResult(uint64_t, uint32_t, uint32_t, uint32_t) override { return Void(); } + + Return onAcquired(uint64_t, hidl_interface::FingerprintAcquiredInfo, int32_t) override { + return Void(); + } + + Return onAcquired_2_2(uint64_t, hidl_interface_2_2::FingerprintAcquiredInfo info, + int32_t) override { + FingerprintCallbackArgs args = {}; + args.info = info; + NotifyFromCallback(kCallbackNameOnAcquired, args); + return Void(); + } + + Return onAuthenticated(uint64_t, uint32_t, uint32_t, const hidl_vec&) override { + return Void(); + } + + Return onError(uint64_t, FingerprintError, int32_t) override { return Void(); } + + Return onRemoved(uint64_t, uint32_t, uint32_t, uint32_t) override { return Void(); } + + Return onEnumerate(uint64_t, uint32_t, uint32_t, uint32_t) override { return Void(); } +}; + +class FingerprintHidlTest : public ::testing::TestWithParam { + public: + void SetUp() override { + mService = IBiometricsFingerprint::getService(GetParam()); + ASSERT_NE(mService, nullptr); + mCallback = new FingerprintCallback(); + mCallback->SetWaitTimeoutDefault(kTimeout); + Return ret1 = mService->setNotify(mCallback); + ASSERT_NE(0UL, static_cast(ret1)); + + /* + * Devices shipped from now on will instead store + * fingerprint data under /data/vendor_de//fpdata. + * Support for /data/vendor_de and /data/vendor_ce has been added to vold. + */ + + auto api_level = GetUintProperty("ro.product.first_api_level", 0); + if (api_level == 0) { + api_level = GetUintProperty("ro.build.version.sdk", 0); + } + ASSERT_NE(api_level, 0); + + // 27 is the API number for O-MR1 + string tmpDir; + if (api_level <= 27) { + tmpDir = "/data/system/users/0/fpdata/"; + } else { + tmpDir = "/data/vendor_de/0/fpdata/"; + } + + Return res = mService->setActiveGroup(kGroupId, tmpDir); + ASSERT_EQ(RequestStatus::SYS_OK, static_cast(res)); + } + + sp mService; + sp mCallback; +}; + +// The START message and onAcquired_2_2 method should exist and work together correctly. +// Note, this test doesn't use the HAL. It just makes sure that the newly added constant and +// callback compile. Unfortunately, there is no way to test the usage of the constant within the +// actual HAL. +TEST_P(FingerprintHidlTest, acquiredInfoStartTest) { + mCallback->SetWaitTimeoutDefault(kTimeout); + mCallback->onAcquired_2_2(0 /* deviceId */, hidl_interface_2_2::FingerprintAcquiredInfo::START, + 0 /* vendorCode */); + auto res = mCallback->WaitForCallback(kCallbackNameOnAcquired); + ASSERT_EQ(hidl_interface_2_2::FingerprintAcquiredInfo::START, res.args->info); +} + +} // anonymous namespace + +INSTANTIATE_TEST_SUITE_P(PerInstance, FingerprintHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames( + IBiometricsFingerprint::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/bluetooth/1.0/vts/functional/Android.bp b/bluetooth/1.0/vts/functional/Android.bp index 463ed849f6..e9f867f5f0 100644 --- a/bluetooth/1.0/vts/functional/Android.bp +++ b/bluetooth/1.0/vts/functional/Android.bp @@ -26,4 +26,5 @@ cc_test { "general-tests", "vts", ], + disable_framework: true, } diff --git a/boot/1.1/vts/functional/Android.bp b/boot/1.1/vts/functional/Android.bp index 49ea09a15e..9f0c74a482 100644 --- a/boot/1.1/vts/functional/Android.bp +++ b/boot/1.1/vts/functional/Android.bp @@ -23,6 +23,8 @@ cc_test { "android.hardware.boot@1.1", "libgmock", ], - test_suites: ["device-tests"], + test_suites: [ + "device-tests", + "vts", + ], } - diff --git a/broadcastradio/1.0/vts/functional/Android.bp b/broadcastradio/1.0/vts/functional/Android.bp index 9ba9fbe209..2a4f9420ec 100644 --- a/broadcastradio/1.0/vts/functional/Android.bp +++ b/broadcastradio/1.0/vts/functional/Android.bp @@ -22,5 +22,8 @@ cc_test { "android.hardware.broadcastradio@1.0", "android.hardware.broadcastradio@vts-utils-lib", ], - test_suites: ["general-tests"], + test_suites: [ + "general-tests", + "vts", + ], } diff --git a/broadcastradio/1.0/vts/functional/VtsHalBroadcastradioV1_0TargetTest.cpp b/broadcastradio/1.0/vts/functional/VtsHalBroadcastradioV1_0TargetTest.cpp index 90c8463755..9897ab7af5 100644 --- a/broadcastradio/1.0/vts/functional/VtsHalBroadcastradioV1_0TargetTest.cpp +++ b/broadcastradio/1.0/vts/functional/VtsHalBroadcastradioV1_0TargetTest.cpp @@ -15,11 +15,13 @@ */ #define LOG_TAG "BroadcastRadioHidlHalTest" -#include #include #include #include +#include +#include #include +#include #include #include @@ -27,28 +29,28 @@ #include #include #include -#include +#include -using ::android::sp; -using ::android::Mutex; using ::android::Condition; +using ::android::Mutex; +using ::android::sp; using ::android::hardware::Return; using ::android::hardware::Void; -using ::android::hardware::broadcastradio::V1_0::IBroadcastRadioFactory; -using ::android::hardware::broadcastradio::V1_0::IBroadcastRadio; -using ::android::hardware::broadcastradio::V1_0::ITuner; -using ::android::hardware::broadcastradio::V1_0::ITunerCallback; -using ::android::hardware::broadcastradio::V1_0::Result; -using ::android::hardware::broadcastradio::V1_0::Class; -using ::android::hardware::broadcastradio::V1_0::Properties; using ::android::hardware::broadcastradio::V1_0::Band; using ::android::hardware::broadcastradio::V1_0::BandConfig; +using ::android::hardware::broadcastradio::V1_0::Class; using ::android::hardware::broadcastradio::V1_0::Direction; -using ::android::hardware::broadcastradio::V1_0::ProgramInfo; +using ::android::hardware::broadcastradio::V1_0::IBroadcastRadio; +using ::android::hardware::broadcastradio::V1_0::IBroadcastRadioFactory; +using ::android::hardware::broadcastradio::V1_0::ITuner; +using ::android::hardware::broadcastradio::V1_0::ITunerCallback; using ::android::hardware::broadcastradio::V1_0::MetaData; using ::android::hardware::broadcastradio::V1_0::MetadataKey; using ::android::hardware::broadcastradio::V1_0::MetadataType; -using ::android::hardware::broadcastradio::vts::BroadcastRadioHidlEnvironment; +using ::android::hardware::broadcastradio::V1_0::ProgramInfo; +using ::android::hardware::broadcastradio::V1_0::Properties; +using ::android::hardware::broadcastradio::V1_0::Result; +using ::android::hardware::broadcastradio::V1_0::vts::RadioClassFromString; #define RETURN_IF_SKIPPED \ if (skipped) { \ @@ -56,19 +58,19 @@ using ::android::hardware::broadcastradio::vts::BroadcastRadioHidlEnvironment; return; \ } -static BroadcastRadioHidlEnvironment* gEnv = nullptr; // The main test class for Broadcast Radio HIDL HAL. -class BroadcastRadioHidlTest : public ::testing::VtsHalHidlTargetTestBase, - public ::testing::WithParamInterface { - protected: +class BroadcastRadioHidlTest + : public ::testing::TestWithParam> { + protected: virtual void SetUp() override { ASSERT_EQ(nullptr, mRadio.get()); - radioClass = GetParam(); + radioClass = RadioClassFromString(std::get<1>(GetParam())); + skipped = false; sp factory = - getService(gEnv->getServiceName()); + IBroadcastRadioFactory::getService(std::get<0>(GetParam())); ASSERT_NE(nullptr, factory.get()); Result connectResult; @@ -727,16 +729,8 @@ TEST_P(BroadcastRadioHidlTest, IbImagesOnly) { } INSTANTIATE_TEST_CASE_P( - BroadcastRadioHidlTestCases, - BroadcastRadioHidlTest, - ::testing::Values(Class::AM_FM, Class::SAT, Class::DT)); - -int main(int argc, char** argv) { - gEnv = new BroadcastRadioHidlEnvironment; - ::testing::AddGlobalTestEnvironment(gEnv); - ::testing::InitGoogleTest(&argc, argv); - gEnv->init(&argc, argv); - int status = RUN_ALL_TESTS(); - ALOGI("Test result = %d", status); - return status; -} + PerInstance, BroadcastRadioHidlTest, + testing::Combine(testing::ValuesIn(android::hardware::getAllHalInstanceNames( + IBroadcastRadioFactory::descriptor)), + ::testing::Values("AM_FM", "SAT", "DT")), + android::hardware::PrintInstanceTupleNameToString<>); \ No newline at end of file diff --git a/broadcastradio/1.1/vts/functional/Android.bp b/broadcastradio/1.1/vts/functional/Android.bp index 0a02a694c5..661439aa67 100644 --- a/broadcastradio/1.1/vts/functional/Android.bp +++ b/broadcastradio/1.1/vts/functional/Android.bp @@ -25,5 +25,8 @@ cc_test { "android.hardware.broadcastradio@vts-utils-lib", "libgmock", ], - test_suites: ["general-tests"], + test_suites: [ + "general-tests", + "vts", + ], } diff --git a/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp b/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp index 6687731965..4833beb5bb 100644 --- a/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp +++ b/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp @@ -16,7 +16,6 @@ #define LOG_TAG "broadcastradio.vts" -#include #include #include #include @@ -25,13 +24,16 @@ #include #include #include -#include +#include #include #include #include #include #include +#include +#include #include +#include #include #include @@ -51,6 +53,7 @@ using testing::DoAll; using testing::Invoke; using testing::SaveArg; +using broadcastradio::V1_0::vts::RadioClassFromString; using broadcastradio::vts::CallBarrier; using V1_0::BandConfig; using V1_0::Class; @@ -59,7 +62,6 @@ using V1_0::MetadataKey; using V1_0::MetadataType; using broadcastradio::vts::clearAndWait; -using broadcastradio::vts::BroadcastRadioHidlEnvironment; static constexpr auto kConfigTimeout = 10s; static constexpr auto kConnectModuleTimeout = 1s; @@ -93,11 +95,9 @@ struct TunerCallbackMock : public ITunerCallback { MOCK_TIMEOUT_METHOD1(currentProgramInfoChanged, Return(const ProgramInfo&)); }; -static BroadcastRadioHidlEnvironment* gEnv = nullptr; - -class BroadcastRadioHalTest : public ::testing::VtsHalHidlTargetTestBase, - public ::testing::WithParamInterface { - protected: +class BroadcastRadioHalTest + : public ::testing::TestWithParam> { + protected: virtual void SetUp() override; virtual void TearDown() override; @@ -120,11 +120,10 @@ class BroadcastRadioHalTest : public ::testing::VtsHalHidlTargetTestBase, }; void BroadcastRadioHalTest::SetUp() { - radioClass = GetParam(); + radioClass = RadioClassFromString(std::get<1>(GetParam())); // lookup HIDL service - auto factory = - getService(gEnv->getServiceName()); + auto factory = IBroadcastRadioFactory::getService(std::get<0>(GetParam())); ASSERT_NE(nullptr, factory.get()); // connect radio module @@ -601,24 +600,15 @@ TEST_P(BroadcastRadioHalTest, VerifyIdentifiersFormat) { } while (nextBand()); } -INSTANTIATE_TEST_CASE_P(BroadcastRadioHalTestCases, BroadcastRadioHalTest, - ::testing::Values(Class::AM_FM, Class::SAT, Class::DT)); +INSTANTIATE_TEST_CASE_P( + PerInstance, BroadcastRadioHalTest, + testing::Combine(testing::ValuesIn(android::hardware::getAllHalInstanceNames( + IBroadcastRadioFactory::descriptor)), + ::testing::Values("AM_FM", "SAT", "DT")), + android::hardware::PrintInstanceTupleNameToString<>); } // namespace vts } // namespace V1_1 } // namespace broadcastradio } // namespace hardware } // namespace android - -int main(int argc, char** argv) { - using android::hardware::broadcastradio::V1_1::vts::gEnv; - using android::hardware::broadcastradio::V1_1::IBroadcastRadioFactory; - using android::hardware::broadcastradio::vts::BroadcastRadioHidlEnvironment; - gEnv = new BroadcastRadioHidlEnvironment; - ::testing::AddGlobalTestEnvironment(gEnv); - ::testing::InitGoogleTest(&argc, argv); - gEnv->init(&argc, argv); - int status = RUN_ALL_TESTS(); - ALOGI("Test result = %d", status); - return status; -} diff --git a/broadcastradio/common/vts/utils/Android.bp b/broadcastradio/common/vts/utils/Android.bp index d3edc76678..24fea0bb2d 100644 --- a/broadcastradio/common/vts/utils/Android.bp +++ b/broadcastradio/common/vts/utils/Android.bp @@ -25,8 +25,5 @@ cc_library_static { "-Wextra", "-Werror", ], - static_libs: [ - "VtsHalHidlTargetTestBase", - ], group_static_libs: true, } diff --git a/broadcastradio/common/vts/utils/include/broadcastradio-vts-utils/environment-utils.h b/broadcastradio/common/vts/utils/include/broadcastradio-vts-utils/environment-utils.h deleted file mode 100644 index 274e6322b8..0000000000 --- a/broadcastradio/common/vts/utils/include/broadcastradio-vts-utils/environment-utils.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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_BROADCASTRADIO_VTS_ENVIRONMENT_UTILS -#define ANDROID_HARDWARE_BROADCASTRADIO_VTS_ENVIRONMENT_UTILS - -#include - -namespace android { -namespace hardware { -namespace broadcastradio { -namespace vts { - -// Test environment for BroadcastRadio HIDL HAL. -template -class BroadcastRadioHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - virtual void registerTestServices() override { - using expander = int[]; - (void)expander{0, (registerTestService(), 0)...}; - } -}; - -} // namespace vts -} // namespace broadcastradio -} // namespace hardware -} // namespace android - -#endif // ANDROID_HARDWARE_BROADCASTRADIO_VTS_ENVIRONMENT_UTILS diff --git a/broadcastradio/common/vts/utils/include/broadcastradio-vts-utils/hal-1.x-enum-utils.h b/broadcastradio/common/vts/utils/include/broadcastradio-vts-utils/hal-1.x-enum-utils.h new file mode 100644 index 0000000000..6059ef8fdb --- /dev/null +++ b/broadcastradio/common/vts/utils/include/broadcastradio-vts-utils/hal-1.x-enum-utils.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2020 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 + +namespace android::hardware::broadcastradio::V1_0::vts { + +using android::hardware::broadcastradio::V1_0::Class; + +/** + * Convert a string of Class name to its enum value. Fail the test if the enum + * value is not found. + * + * @param className string value of a Class enum. + * @return Class enum that matches the string value. + */ +Class RadioClassFromString(std::string className) { + if (className == "AM_FM") return Class::AM_FM; + if (className == "SAT") return Class::SAT; + if (className == "DT") return Class::DT; + // Fail the test run. + CHECK(false) << "Class name not found: " << className; + // Return some arbitrary enum. + return Class::AM_FM; +} +} // namespace android::hardware::broadcastradio::V1_0::vts diff --git a/camera/common/1.0/default/Android.bp b/camera/common/1.0/default/Android.bp index 3e5c6d7812..3b8b23946e 100644 --- a/camera/common/1.0/default/Android.bp +++ b/camera/common/1.0/default/Android.bp @@ -8,7 +8,7 @@ cc_library_static { "CameraParameters.cpp", "VendorTagDescriptor.cpp", "HandleImporter.cpp", - "Exif.cpp" + "Exif.cpp", ], cflags: [ "-Werror", @@ -17,13 +17,14 @@ cc_library_static { ], shared_libs: [ "liblog", + "libgralloctypes", "libhardware", "libcamera_metadata", "android.hardware.graphics.mapper@2.0", "android.hardware.graphics.mapper@3.0", + "android.hardware.graphics.mapper@4.0", "libexif", ], include_dirs: ["system/media/private/camera/include"], - export_include_dirs : ["include"] + export_include_dirs: ["include"], } - diff --git a/camera/common/1.0/default/CameraModule.cpp b/camera/common/1.0/default/CameraModule.cpp index 467c121a94..86f26e480c 100644 --- a/camera/common/1.0/default/CameraModule.cpp +++ b/camera/common/1.0/default/CameraModule.cpp @@ -194,16 +194,6 @@ void CameraModule::deriveCameraCharacteristicsKeys( } } - // Always add a default for the pre-correction active array if the vendor chooses to omit this - camera_metadata_entry entry = chars.find(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE); - if (entry.count == 0) { - Vector preCorrectionArray; - entry = chars.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE); - preCorrectionArray.appendArray(entry.data.i32, entry.count); - chars.update(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, preCorrectionArray); - derivedCharKeys.push(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE); - } - // Add those newly added keys to AVAILABLE_CHARACTERISTICS_KEYS // This has to be done at this end of this function. if (derivedCharKeys.size() > 0) { diff --git a/camera/common/1.0/default/HandleImporter.cpp b/camera/common/1.0/default/HandleImporter.cpp index b8c40e95b8..05a552cd95 100644 --- a/camera/common/1.0/default/HandleImporter.cpp +++ b/camera/common/1.0/default/HandleImporter.cpp @@ -16,6 +16,8 @@ #define LOG_TAG "HandleImporter" #include "HandleImporter.h" + +#include #include namespace android { @@ -25,9 +27,14 @@ namespace common { namespace V1_0 { namespace helper { +using aidl::android::hardware::graphics::common::PlaneLayout; +using aidl::android::hardware::graphics::common::PlaneLayoutComponent; +using aidl::android::hardware::graphics::common::PlaneLayoutComponentType; using MapperErrorV2 = android::hardware::graphics::mapper::V2_0::Error; using MapperErrorV3 = android::hardware::graphics::mapper::V3_0::Error; +using MapperErrorV4 = android::hardware::graphics::mapper::V4_0::Error; using IMapperV3 = android::hardware::graphics::mapper::V3_0::IMapper; +using IMapperV4 = android::hardware::graphics::mapper::V4_0::IMapper; HandleImporter::HandleImporter() : mInitialized(false) {} @@ -36,6 +43,12 @@ void HandleImporter::initializeLocked() { return; } + mMapperV4 = IMapperV4::getService(); + if (mMapperV4 != nullptr) { + mInitialized = true; + return; + } + mMapperV3 = IMapperV3::getService(); if (mMapperV3 != nullptr) { mInitialized = true; @@ -53,6 +66,7 @@ void HandleImporter::initializeLocked() { } void HandleImporter::cleanup() { + mMapperV4.clear(); mMapperV3.clear(); mMapperV2.clear(); mInitialized = false; @@ -109,6 +123,79 @@ YCbCrLayout HandleImporter::lockYCbCrInternal(const sp mapper, buffer_handle_ return layout; } +template <> +YCbCrLayout HandleImporter::lockYCbCrInternal( + const sp mapper, buffer_handle_t& buf, uint64_t cpuUsage, + const IMapper::Rect& accessRegion) { + hidl_handle acquireFenceHandle; + auto buffer = const_cast(buf); + YCbCrLayout layout = {}; + void* mapped = nullptr; + + typename IMapperV4::Rect accessRegionV4 = {accessRegion.left, accessRegion.top, + accessRegion.width, accessRegion.height}; + mapper->lock(buffer, cpuUsage, accessRegionV4, acquireFenceHandle, + [&](const auto& tmpError, const auto& tmpPtr) { + if (tmpError == MapperErrorV4::NONE) { + mapped = tmpPtr; + } else { + ALOGE("%s: failed to lock error %d!", __FUNCTION__, tmpError); + } + }); + + if (mapped == nullptr) { + return layout; + } + + hidl_vec encodedPlaneLayouts; + mapper->get(buffer, gralloc4::MetadataType_PlaneLayouts, + [&](const auto& tmpError, const auto& tmpEncodedPlaneLayouts) { + if (tmpError == MapperErrorV4::NONE) { + encodedPlaneLayouts = tmpEncodedPlaneLayouts; + } else { + ALOGE("%s: failed to get plane layouts %d!", __FUNCTION__, tmpError); + } + }); + + std::vector planeLayouts; + gralloc4::decodePlaneLayouts(encodedPlaneLayouts, &planeLayouts); + + for (const auto& planeLayout : planeLayouts) { + for (const auto& planeLayoutComponent : planeLayout.components) { + const auto& type = planeLayoutComponent.type; + + if (!gralloc4::isStandardPlaneLayoutComponentType(type)) { + continue; + } + + uint8_t* data = reinterpret_cast(mapped); + data += planeLayout.offsetInBytes; + data += planeLayoutComponent.offsetInBits / 8; + + switch (static_cast(type.value)) { + case PlaneLayoutComponentType::Y: + layout.y = data; + layout.yStride = planeLayout.strideInBytes; + break; + case PlaneLayoutComponentType::CB: + layout.cb = data; + layout.cStride = planeLayout.strideInBytes; + layout.chromaStep = planeLayout.sampleIncrementInBits / 8; + break; + case PlaneLayoutComponentType::CR: + layout.cr = data; + layout.cStride = planeLayout.strideInBytes; + layout.chromaStep = planeLayout.sampleIncrementInBits / 8; + break; + default: + break; + } + } + } + + return layout; +} + template int HandleImporter::unlockInternal(const sp mapper, buffer_handle_t& buf) { int releaseFence = -1; @@ -151,6 +238,10 @@ bool HandleImporter::importBuffer(buffer_handle_t& handle) { initializeLocked(); } + if (mMapperV4 != nullptr) { + return importBufferInternal(mMapperV4, handle); + } + if (mMapperV3 != nullptr) { return importBufferInternal(mMapperV3, handle); } @@ -159,7 +250,7 @@ bool HandleImporter::importBuffer(buffer_handle_t& handle) { return importBufferInternal(mMapperV2, handle); } - ALOGE("%s: mMapperV3 and mMapperV2 are both null!", __FUNCTION__); + ALOGE("%s: mMapperV4, mMapperV3 and mMapperV2 are all null!", __FUNCTION__); return false; } @@ -169,12 +260,16 @@ void HandleImporter::freeBuffer(buffer_handle_t handle) { } Mutex::Autolock lock(mLock); - if (mMapperV3 == nullptr && mMapperV2 == nullptr) { - ALOGE("%s: mMapperV3 and mMapperV2 are both null!", __FUNCTION__); - return; + if (!mInitialized) { + initializeLocked(); } - if (mMapperV3 != nullptr) { + if (mMapperV4 != nullptr) { + auto ret = mMapperV4->freeBuffer(const_cast(handle)); + if (!ret.isOk()) { + ALOGE("%s: mapper freeBuffer failed: %s", __FUNCTION__, ret.description().c_str()); + } + } else if (mMapperV3 != nullptr) { auto ret = mMapperV3->freeBuffer(const_cast(handle)); if (!ret.isOk()) { ALOGE("%s: mapper freeBuffer failed: %s", @@ -215,48 +310,67 @@ void HandleImporter::closeFence(int fd) const { void* HandleImporter::lock( buffer_handle_t& buf, uint64_t cpuUsage, size_t size) { + IMapper::Rect accessRegion{0, 0, static_cast(size), 1}; + return lock(buf, cpuUsage, accessRegion); +} + +void* HandleImporter::lock(buffer_handle_t& buf, uint64_t cpuUsage, + const IMapper::Rect& accessRegion) { Mutex::Autolock lock(mLock); - void *ret = 0; if (!mInitialized) { initializeLocked(); } - if (mMapperV3 == nullptr && mMapperV2 == nullptr) { - ALOGE("%s: mMapperV3 and mMapperV2 are both null!", __FUNCTION__); + void* ret = nullptr; + + if (mMapperV4 == nullptr && mMapperV3 == nullptr && mMapperV2 == nullptr) { + ALOGE("%s: mMapperV4, mMapperV3 and mMapperV2 are all null!", __FUNCTION__); return ret; } hidl_handle acquireFenceHandle; auto buffer = const_cast(buf); - if (mMapperV3 != nullptr) { - IMapperV3::Rect accessRegion { 0, 0, static_cast(size), 1 }; - // No need to use bytesPerPixel and bytesPerStride because we are using - // an 1-D buffer and accressRegion. - mMapperV3->lock(buffer, cpuUsage, accessRegion, acquireFenceHandle, - [&](const auto& tmpError, const auto& tmpPtr, const auto& /*bytesPerPixel*/, - const auto& /*bytesPerStride*/) { - if (tmpError == MapperErrorV3::NONE) { - ret = tmpPtr; - } else { - ALOGE("%s: failed to lock error %d!", - __FUNCTION__, tmpError); - } - }); + if (mMapperV4 != nullptr) { + IMapperV4::Rect accessRegionV4{accessRegion.left, accessRegion.top, accessRegion.width, + accessRegion.height}; + + mMapperV4->lock(buffer, cpuUsage, accessRegionV4, acquireFenceHandle, + [&](const auto& tmpError, const auto& tmpPtr) { + if (tmpError == MapperErrorV4::NONE) { + ret = tmpPtr; + } else { + ALOGE("%s: failed to lock error %d!", __FUNCTION__, tmpError); + } + }); + } else if (mMapperV3 != nullptr) { + IMapperV3::Rect accessRegionV3{accessRegion.left, accessRegion.top, accessRegion.width, + accessRegion.height}; + + mMapperV3->lock(buffer, cpuUsage, accessRegionV3, acquireFenceHandle, + [&](const auto& tmpError, const auto& tmpPtr, const auto& /*bytesPerPixel*/, + const auto& /*bytesPerStride*/) { + if (tmpError == MapperErrorV3::NONE) { + ret = tmpPtr; + } else { + ALOGE("%s: failed to lock error %d!", __FUNCTION__, tmpError); + } + }); } else { - IMapper::Rect accessRegion { 0, 0, static_cast(size), 1 }; mMapperV2->lock(buffer, cpuUsage, accessRegion, acquireFenceHandle, [&](const auto& tmpError, const auto& tmpPtr) { if (tmpError == MapperErrorV2::NONE) { ret = tmpPtr; } else { - ALOGE("%s: failed to lock error %d!", - __FUNCTION__, tmpError); + ALOGE("%s: failed to lock error %d!", __FUNCTION__, tmpError); } }); } - ALOGV("%s: ptr %p size: %zu", __FUNCTION__, ret, size); + ALOGV("%s: ptr %p accessRegion.top: %d accessRegion.left: %d accessRegion.width: %d " + "accessRegion.height: %d", + __FUNCTION__, ret, accessRegion.top, accessRegion.left, accessRegion.width, + accessRegion.height); return ret; } @@ -269,6 +383,10 @@ YCbCrLayout HandleImporter::lockYCbCr( initializeLocked(); } + if (mMapperV4 != nullptr) { + return lockYCbCrInternal(mMapperV4, buf, cpuUsage, accessRegion); + } + if (mMapperV3 != nullptr) { return lockYCbCrInternal( mMapperV3, buf, cpuUsage, accessRegion); @@ -279,11 +397,14 @@ YCbCrLayout HandleImporter::lockYCbCr( mMapperV2, buf, cpuUsage, accessRegion); } - ALOGE("%s: mMapperV3 and mMapperV2 are both null!", __FUNCTION__); + ALOGE("%s: mMapperV4, mMapperV3 and mMapperV2 are all null!", __FUNCTION__); return {}; } int HandleImporter::unlock(buffer_handle_t& buf) { + if (mMapperV4 != nullptr) { + return unlockInternal(mMapperV4, buf); + } if (mMapperV3 != nullptr) { return unlockInternal(mMapperV3, buf); } @@ -291,7 +412,7 @@ int HandleImporter::unlock(buffer_handle_t& buf) { return unlockInternal(mMapperV2, buf); } - ALOGE("%s: mMapperV3 and mMapperV2 are both null!", __FUNCTION__); + ALOGE("%s: mMapperV4, mMapperV3 and mMapperV2 are all null!", __FUNCTION__); return -1; } diff --git a/camera/common/1.0/default/include/HandleImporter.h b/camera/common/1.0/default/include/HandleImporter.h index a93d4554ad..edc97ad9a7 100644 --- a/camera/common/1.0/default/include/HandleImporter.h +++ b/camera/common/1.0/default/include/HandleImporter.h @@ -17,10 +17,11 @@ #ifndef CAMERA_COMMON_1_0_HANDLEIMPORTED_H #define CAMERA_COMMON_1_0_HANDLEIMPORTED_H -#include #include #include +#include #include +#include using android::hardware::graphics::mapper::V2_0::IMapper; using android::hardware::graphics::mapper::V2_0::YCbCrLayout; @@ -45,10 +46,13 @@ public: bool importFence(const native_handle_t* handle, int& fd) const; void closeFence(int fd) const; - // Assume caller has done waiting for acquire fences + // Locks 1-D buffer. Assumes caller has waited for acquire fences. void* lock(buffer_handle_t& buf, uint64_t cpuUsage, size_t size); - // Assume caller has done waiting for acquire fences + // Locks 2-D buffer. Assumes caller has waited for acquire fences. + void* lock(buffer_handle_t& buf, uint64_t cpuUsage, const IMapper::Rect& accessRegion); + + // Assumes caller has waited for acquire fences. YCbCrLayout lockYCbCr(buffer_handle_t& buf, uint64_t cpuUsage, const IMapper::Rect& accessRegion); @@ -70,6 +74,7 @@ private: bool mInitialized; sp mMapperV2; sp mMapperV3; + sp mMapperV4; }; } // namespace helper diff --git a/camera/device/1.0/default/Android.bp b/camera/device/1.0/default/Android.bp index 97d0b5f1d5..da70577f93 100644 --- a/camera/device/1.0/default/Android.bp +++ b/camera/device/1.0/default/Android.bp @@ -14,20 +14,21 @@ cc_library_shared { "android.hardware.graphics.allocator@2.0", "android.hardware.graphics.mapper@2.0", "android.hardware.graphics.mapper@3.0", + "android.hardware.graphics.mapper@4.0", "android.hardware.graphics.common@1.0", "android.hidl.allocator@1.0", "android.hidl.memory@1.0", "libcutils", "liblog", + "libgralloctypes", "libhardware", "libcamera_metadata", ], static_libs: [ - "android.hardware.camera.common@1.0-helper" + "android.hardware.camera.common@1.0-helper", ], header_libs: [ "media_plugin_headers", ], - export_include_dirs: ["."] + export_include_dirs: ["."], } - diff --git a/camera/device/3.2/ICameraDeviceCallback.hal b/camera/device/3.2/ICameraDeviceCallback.hal index dec3bd88c6..206a649da3 100644 --- a/camera/device/3.2/ICameraDeviceCallback.hal +++ b/camera/device/3.2/ICameraDeviceCallback.hal @@ -87,15 +87,19 @@ interface ICameraDeviceCallback { * ERROR_RESULT message. * * If an output buffer cannot be filled, its status field must be set to - * STATUS_ERROR. In addition, notify() must be called with a ERROR_BUFFER - * message. + * STATUS_ERROR. In this case, notify() isn't required to be called with + * an ERROR_BUFFER message. The framework will simply treat the notify() + * call with ERROR_BUFFER as a no-op, and derive whether and when to notify + * the application of buffer loss based on the buffer status and whether or not + * the entire capture has failed. * * If the entire capture has failed, then this method still needs to be * called to return the output buffers to the framework. All the buffer * statuses must be STATUS_ERROR, and the result metadata must be an * empty buffer. In addition, notify() must be called with a ERROR_REQUEST * message. In this case, individual ERROR_RESULT/ERROR_BUFFER messages - * must not be sent. + * must not be sent. Note that valid partial results are still allowed + * as long as the final result metadata fails to be generated. * * Performance requirements: * diff --git a/camera/device/3.2/default/Android.bp b/camera/device/3.2/default/Android.bp index e4d9e85b44..be2de07c6b 100644 --- a/camera/device/3.2/default/Android.bp +++ b/camera/device/3.2/default/Android.bp @@ -2,9 +2,11 @@ cc_library_shared { name: "camera.device@3.2-impl", defaults: ["hidl_defaults"], proprietary: true, - srcs: ["CameraDevice.cpp", - "CameraDeviceSession.cpp", - "convert.cpp"], + srcs: [ + "CameraDevice.cpp", + "CameraDeviceSession.cpp", + "convert.cpp", + ], shared_libs: [ "libhidlbase", "libutils", @@ -13,16 +15,18 @@ cc_library_shared { "android.hardware.camera.provider@2.4", "android.hardware.graphics.mapper@2.0", "android.hardware.graphics.mapper@3.0", + "android.hardware.graphics.mapper@4.0", "liblog", + "libgralloctypes", "libhardware", "libcamera_metadata", - "libfmq" + "libfmq", ], static_libs: [ - "android.hardware.camera.common@1.0-helper" + "android.hardware.camera.common@1.0-helper", ], export_include_dirs: ["."], export_shared_lib_headers: [ "libfmq", - ] + ], } diff --git a/camera/device/3.2/default/convert.cpp b/camera/device/3.2/default/convert.cpp index d878deb03d..06ad7e963c 100644 --- a/camera/device/3.2/default/convert.cpp +++ b/camera/device/3.2/default/convert.cpp @@ -38,7 +38,7 @@ bool convertFromHidl(const CameraMetadata &src, const camera_metadata_t** dst) { } const uint8_t* data = src.data(); - // sanity check the size of CameraMetadata match underlying camera_metadata_t + // check that the size of CameraMetadata match underlying camera_metadata_t if (get_camera_metadata_size((camera_metadata_t*)data) != src.size()) { ALOGE("%s: input CameraMetadata is corrupt!", __FUNCTION__); return false; diff --git a/camera/device/3.3/default/Android.bp b/camera/device/3.3/default/Android.bp index d964f3df3e..0aa0dd7ca4 100644 --- a/camera/device/3.3/default/Android.bp +++ b/camera/device/3.3/default/Android.bp @@ -2,9 +2,11 @@ cc_library_shared { name: "camera.device@3.3-impl", defaults: ["hidl_defaults"], proprietary: true, - srcs: ["CameraDevice.cpp", - "CameraDeviceSession.cpp", - "convert.cpp"], + srcs: [ + "CameraDevice.cpp", + "CameraDeviceSession.cpp", + "convert.cpp", + ], shared_libs: [ "libhidlbase", "libutils", @@ -15,16 +17,18 @@ cc_library_shared { "android.hardware.camera.provider@2.4", "android.hardware.graphics.mapper@2.0", "android.hardware.graphics.mapper@3.0", + "android.hardware.graphics.mapper@4.0", "liblog", + "libgralloctypes", "libhardware", "libcamera_metadata", - "libfmq" + "libfmq", ], static_libs: [ - "android.hardware.camera.common@1.0-helper" + "android.hardware.camera.common@1.0-helper", ], export_include_dirs: ["."], export_shared_lib_headers: [ "libfmq", - ] + ], } diff --git a/camera/device/3.4/default/Android.bp b/camera/device/3.4/default/Android.bp index 71a9e77063..982dce1aae 100644 --- a/camera/device/3.4/default/Android.bp +++ b/camera/device/3.4/default/Android.bp @@ -17,13 +17,13 @@ cc_library_headers { name: "camera.device@3.4-impl_headers", vendor: true, - export_include_dirs: ["include/device_v3_4_impl"] + export_include_dirs: ["include/device_v3_4_impl"], } cc_library_headers { name: "camera.device@3.4-external-impl_headers", vendor: true, - export_include_dirs: ["include/ext_device_v3_4_impl"] + export_include_dirs: ["include/ext_device_v3_4_impl"], } cc_library_shared { @@ -34,7 +34,7 @@ cc_library_shared { srcs: [ "CameraDevice.cpp", "CameraDeviceSession.cpp", - "convert.cpp" + "convert.cpp", ], shared_libs: [ "libhidlbase", @@ -48,7 +48,9 @@ cc_library_shared { "android.hardware.camera.provider@2.4", "android.hardware.graphics.mapper@2.0", "android.hardware.graphics.mapper@3.0", + "android.hardware.graphics.mapper@4.0", "liblog", + "libgralloctypes", "libhardware", "libcamera_metadata", "libfmq", @@ -84,7 +86,9 @@ cc_library_shared { "android.hardware.camera.provider@2.4", "android.hardware.graphics.mapper@2.0", "android.hardware.graphics.mapper@3.0", + "android.hardware.graphics.mapper@4.0", "liblog", + "libgralloctypes", "libhardware", "libcamera_metadata", "libfmq", @@ -92,7 +96,7 @@ cc_library_shared { "libyuv", "libjpeg", "libexif", - "libtinyxml2" + "libtinyxml2", ], static_libs: [ "android.hardware.camera.common@1.0-helper", diff --git a/camera/device/3.4/default/ExternalCameraDevice.cpp b/camera/device/3.4/default/ExternalCameraDevice.cpp index f518a155a7..677b496324 100644 --- a/camera/device/3.4/default/ExternalCameraDevice.cpp +++ b/camera/device/3.4/default/ExternalCameraDevice.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include "android-base/macros.h" #include "CameraMetadata.h" @@ -46,10 +47,20 @@ constexpr int OPEN_RETRY_SLEEP_US = 100000; // 100ms * MAX_RETRY = 0.5 seconds } // anonymous namespace +const std::regex kDevicePathRE("/dev/video([0-9]+)"); + ExternalCameraDevice::ExternalCameraDevice( - const std::string& cameraId, const ExternalCameraConfig& cfg) : - mCameraId(cameraId), - mCfg(cfg) {} + const std::string& devicePath, const ExternalCameraConfig& cfg) : + mCameraId("-1"), + mDevicePath(devicePath), + mCfg(cfg) { + std::smatch sm; + if (std::regex_match(mDevicePath, sm, kDevicePathRE)) { + mCameraId = std::to_string(mCfg.cameraIdOffset + std::stoi(sm[1])); + } else { + ALOGE("%s: device path match failed for %s", __FUNCTION__, mDevicePath.c_str()); + } +} ExternalCameraDevice::~ExternalCameraDevice() {} @@ -129,20 +140,20 @@ Return ExternalCameraDevice::open( return Void(); } - unique_fd fd(::open(mCameraId.c_str(), O_RDWR)); + unique_fd fd(::open(mDevicePath.c_str(), O_RDWR)); if (fd.get() < 0) { int numAttempt = 0; do { ALOGW("%s: v4l2 device %s open failed, wait 33ms and try again", - __FUNCTION__, mCameraId.c_str()); + __FUNCTION__, mDevicePath.c_str()); usleep(OPEN_RETRY_SLEEP_US); // sleep and try again - fd.reset(::open(mCameraId.c_str(), O_RDWR)); + fd.reset(::open(mDevicePath.c_str(), O_RDWR)); numAttempt++; } while (fd.get() < 0 && numAttempt <= MAX_RETRY); if (fd.get() < 0) { ALOGE("%s: v4l2 device open %s failed: %s", - __FUNCTION__, mCameraId.c_str(), strerror(errno)); + __FUNCTION__, mDevicePath.c_str(), strerror(errno)); mLock.unlock(); _hidl_cb(Status::INTERNAL_ERROR, nullptr); return Void(); @@ -203,9 +214,9 @@ Return ExternalCameraDevice::dumpState(const ::android::hardware::hidl_han status_t ExternalCameraDevice::initCameraCharacteristics() { if (mCameraCharacteristics.isEmpty()) { // init camera characteristics - unique_fd fd(::open(mCameraId.c_str(), O_RDWR)); + unique_fd fd(::open(mDevicePath.c_str(), O_RDWR)); if (fd.get() < 0) { - ALOGE("%s: v4l2 device open %s failed", __FUNCTION__, mCameraId.c_str()); + ALOGE("%s: v4l2 device open %s failed", __FUNCTION__, mDevicePath.c_str()); return DEAD_OBJECT; } diff --git a/camera/device/3.4/default/ExternalCameraDeviceSession.cpp b/camera/device/3.4/default/ExternalCameraDeviceSession.cpp index 9ff0d74687..5f8674219c 100644 --- a/camera/device/3.4/default/ExternalCameraDeviceSession.cpp +++ b/camera/device/3.4/default/ExternalCameraDeviceSession.cpp @@ -81,8 +81,6 @@ bool tryLock(std::mutex& mutex) return locked; } -buffer_handle_t sEmptyBuffer = nullptr; - } // Anonymous namespace // Static instances @@ -119,8 +117,8 @@ bool ExternalCameraDeviceSession::initialize() { std::string make, model; if (ret < 0) { ALOGW("%s v4l2 QUERYCAP failed", __FUNCTION__); - make = "Generic UVC webcam"; - model = "Generic UVC webcam"; + mExifMake = "Generic UVC webcam"; + mExifModel = "Generic UVC webcam"; } else { // capability.card is UTF-8 encoded char card[32]; @@ -134,11 +132,11 @@ bool ExternalCameraDeviceSession::initialize() { } } if (j == 0 || card[j - 1] != '\0') { - make = "Generic UVC webcam"; - model = "Generic UVC webcam"; + mExifMake = "Generic UVC webcam"; + mExifModel = "Generic UVC webcam"; } else { - make = card; - model = card; + mExifMake = card; + mExifModel = card; } } @@ -147,7 +145,7 @@ bool ExternalCameraDeviceSession::initialize() { ALOGE("%s: init OutputThread failed!", __FUNCTION__); return true; } - mOutputThread->setExifMakeModel(make, model); + mOutputThread->setExifMakeModel(mExifMake, mExifModel); status_t status = initDefaultRequests(); if (status != OK) { @@ -161,7 +159,7 @@ bool ExternalCameraDeviceSession::initialize() { ALOGE("%s: invalid request fmq", __FUNCTION__); return true; } - mResultMetadataQueue = std::make_shared( + mResultMetadataQueue = std::make_shared( kMetadataMsgQueueSize, false /* non blocking */); if (!mResultMetadataQueue->isValid()) { ALOGE("%s: invalid result fmq", __FUNCTION__); @@ -183,7 +181,7 @@ bool ExternalCameraDeviceSession::isInitFailed() { } void ExternalCameraDeviceSession::initOutputThread() { - mOutputThread = new OutputThread(this, mCroppingType); + mOutputThread = new OutputThread(this, mCroppingType, mCameraCharacteristics); } void ExternalCameraDeviceSession::closeOutputThread() { @@ -518,35 +516,9 @@ Status ExternalCameraDeviceSession::importBufferLocked(int32_t streamId, uint64_t bufId, buffer_handle_t buf, /*out*/buffer_handle_t** outBufPtr, bool allowEmptyBuf) { - - if (buf == nullptr && bufId == BUFFER_ID_NO_BUFFER) { - if (allowEmptyBuf) { - *outBufPtr = &sEmptyBuffer; - return Status::OK; - } else { - ALOGE("%s: bufferId %" PRIu64 " has null buffer handle!", __FUNCTION__, bufId); - return Status::ILLEGAL_ARGUMENT; - } - } - - CirculatingBuffers& cbs = mCirculatingBuffers[streamId]; - if (cbs.count(bufId) == 0) { - if (buf == nullptr) { - ALOGE("%s: bufferId %" PRIu64 " has null buffer handle!", __FUNCTION__, bufId); - return Status::ILLEGAL_ARGUMENT; - } - // Register a newly seen buffer - buffer_handle_t importedBuf = buf; - sHandleImporter.importBuffer(importedBuf); - if (importedBuf == nullptr) { - ALOGE("%s: output buffer for stream %d is invalid!", __FUNCTION__, streamId); - return Status::INTERNAL_ERROR; - } else { - cbs[bufId] = importedBuf; - } - } - *outBufPtr = &cbs[bufId]; - return Status::OK; + return importBufferImpl( + mCirculatingBuffers, sHandleImporter, streamId, + bufId, buf, outBufPtr, allowEmptyBuf); } Status ExternalCameraDeviceSession::importRequestLockedImpl( @@ -791,15 +763,32 @@ void ExternalCameraDeviceSession::notifyError( //TODO: refactor with processCaptureResult Status ExternalCameraDeviceSession::processCaptureRequestError( - const std::shared_ptr& req) { + const std::shared_ptr& req, + /*out*/std::vector* outMsgs, + /*out*/std::vector* outResults) { ATRACE_CALL(); // Return V4L2 buffer to V4L2 buffer queue - enqueueV4l2Frame(req->frameIn); + sp v4l2Frame = + static_cast(req->frameIn.get()); + enqueueV4l2Frame(v4l2Frame); - // NotifyShutter - notifyShutter(req->frameNumber, req->shutterTs); + if (outMsgs == nullptr) { + notifyShutter(req->frameNumber, req->shutterTs); + notifyError(/*frameNum*/req->frameNumber, /*stream*/-1, ErrorCode::ERROR_REQUEST); + } else { + NotifyMsg shutter; + shutter.type = MsgType::SHUTTER; + shutter.msg.shutter.frameNumber = req->frameNumber; + shutter.msg.shutter.timestamp = req->shutterTs; - notifyError(/*frameNum*/req->frameNumber, /*stream*/-1, ErrorCode::ERROR_REQUEST); + NotifyMsg error; + error.type = MsgType::ERROR; + error.msg.error.frameNumber = req->frameNumber; + error.msg.error.errorStreamId = -1; + error.msg.error.errorCode = ErrorCode::ERROR_REQUEST; + outMsgs->push_back(shutter); + outMsgs->push_back(error); + } // Fill output buffers hidl_vec results; @@ -826,16 +815,22 @@ Status ExternalCameraDeviceSession::processCaptureRequestError( mInflightFrames.erase(req->frameNumber); } - // Callback into framework - invokeProcessCaptureResultCallback(results, /* tryWriteFmq */true); - freeReleaseFences(results); + if (outResults == nullptr) { + // Callback into framework + invokeProcessCaptureResultCallback(results, /* tryWriteFmq */true); + freeReleaseFences(results); + } else { + outResults->push_back(result); + } return Status::OK; } Status ExternalCameraDeviceSession::processCaptureResult(std::shared_ptr& req) { ATRACE_CALL(); // Return V4L2 buffer to V4L2 buffer queue - enqueueV4l2Frame(req->frameIn); + sp v4l2Frame = + static_cast(req->frameIn.get()); + enqueueV4l2Frame(v4l2Frame); // NotifyShutter notifyShutter(req->frameNumber, req->shutterTs); @@ -923,29 +918,10 @@ void ExternalCameraDeviceSession::invokeProcessCaptureResultCallback( mProcessCaptureResultLock.unlock(); } -void ExternalCameraDeviceSession::freeReleaseFences(hidl_vec& results) { - for (auto& result : results) { - if (result.inputBuffer.releaseFence.getNativeHandle() != nullptr) { - native_handle_t* handle = const_cast( - result.inputBuffer.releaseFence.getNativeHandle()); - native_handle_close(handle); - native_handle_delete(handle); - } - for (auto& buf : result.outputBuffers) { - if (buf.releaseFence.getNativeHandle() != nullptr) { - native_handle_t* handle = const_cast( - buf.releaseFence.getNativeHandle()); - native_handle_close(handle); - native_handle_delete(handle); - } - } - } - return; -} - ExternalCameraDeviceSession::OutputThread::OutputThread( - wp parent, - CroppingType ct) : mParent(parent), mCroppingType(ct) {} + wp parent, CroppingType ct, + const common::V1_0::helper::CameraMetadata& chars) : + mParent(parent), mCroppingType(ct), mCameraCharacteristics(chars) {} ExternalCameraDeviceSession::OutputThread::~OutputThread() {} @@ -955,88 +931,6 @@ void ExternalCameraDeviceSession::OutputThread::setExifMakeModel( mExifModel = model; } -uint32_t ExternalCameraDeviceSession::OutputThread::getFourCcFromLayout( - const YCbCrLayout& layout) { - intptr_t cb = reinterpret_cast(layout.cb); - intptr_t cr = reinterpret_cast(layout.cr); - if (std::abs(cb - cr) == 1 && layout.chromaStep == 2) { - // Interleaved format - if (layout.cb > layout.cr) { - return V4L2_PIX_FMT_NV21; - } else { - return V4L2_PIX_FMT_NV12; - } - } else if (layout.chromaStep == 1) { - // Planar format - if (layout.cb > layout.cr) { - return V4L2_PIX_FMT_YVU420; // YV12 - } else { - return V4L2_PIX_FMT_YUV420; // YU12 - } - } else { - return FLEX_YUV_GENERIC; - } -} - -int ExternalCameraDeviceSession::OutputThread::getCropRect( - CroppingType ct, const Size& inSize, const Size& outSize, IMapper::Rect* out) { - if (out == nullptr) { - ALOGE("%s: out is null", __FUNCTION__); - return -1; - } - - uint32_t inW = inSize.width; - uint32_t inH = inSize.height; - uint32_t outW = outSize.width; - uint32_t outH = outSize.height; - - // Handle special case where aspect ratio is close to input but scaled - // dimension is slightly larger than input - float arIn = ASPECT_RATIO(inSize); - float arOut = ASPECT_RATIO(outSize); - if (isAspectRatioClose(arIn, arOut)) { - out->left = 0; - out->top = 0; - out->width = inW; - out->height = inH; - return 0; - } - - if (ct == VERTICAL) { - uint64_t scaledOutH = static_cast(outH) * inW / outW; - if (scaledOutH > inH) { - ALOGE("%s: Output size %dx%d cannot be vertically cropped from input size %dx%d", - __FUNCTION__, outW, outH, inW, inH); - return -1; - } - scaledOutH = scaledOutH & ~0x1; // make it multiple of 2 - - out->left = 0; - out->top = ((inH - scaledOutH) / 2) & ~0x1; - out->width = inW; - out->height = static_cast(scaledOutH); - ALOGV("%s: crop %dx%d to %dx%d: top %d, scaledH %d", - __FUNCTION__, inW, inH, outW, outH, out->top, static_cast(scaledOutH)); - } else { - uint64_t scaledOutW = static_cast(outW) * inH / outH; - if (scaledOutW > inW) { - ALOGE("%s: Output size %dx%d cannot be horizontally cropped from input size %dx%d", - __FUNCTION__, outW, outH, inW, inH); - return -1; - } - scaledOutW = scaledOutW & ~0x1; // make it multiple of 2 - - out->left = ((inW - scaledOutW) / 2) & ~0x1; - out->top = 0; - out->width = static_cast(scaledOutW); - out->height = inH; - ALOGV("%s: crop %dx%d to %dx%d: top %d, scaledW %d", - __FUNCTION__, inW, inH, outW, outH, out->top, static_cast(scaledOutW)); - } - - return 0; -} - int ExternalCameraDeviceSession::OutputThread::cropAndScaleLocked( sp& in, const Size& outSz, YCbCrLayout* out) { Size inSz = {in->mWidth, in->mHeight}; @@ -1274,265 +1168,6 @@ int ExternalCameraDeviceSession::OutputThread::cropAndScaleThumbLocked( return 0; } -int ExternalCameraDeviceSession::OutputThread::formatConvertLocked( - const YCbCrLayout& in, const YCbCrLayout& out, Size sz, uint32_t format) { - int ret = 0; - switch (format) { - case V4L2_PIX_FMT_NV21: - ret = libyuv::I420ToNV21( - static_cast(in.y), - in.yStride, - static_cast(in.cb), - in.cStride, - static_cast(in.cr), - in.cStride, - static_cast(out.y), - out.yStride, - static_cast(out.cr), - out.cStride, - sz.width, - sz.height); - if (ret != 0) { - ALOGE("%s: convert to NV21 buffer failed! ret %d", - __FUNCTION__, ret); - return ret; - } - break; - case V4L2_PIX_FMT_NV12: - ret = libyuv::I420ToNV12( - static_cast(in.y), - in.yStride, - static_cast(in.cb), - in.cStride, - static_cast(in.cr), - in.cStride, - static_cast(out.y), - out.yStride, - static_cast(out.cb), - out.cStride, - sz.width, - sz.height); - if (ret != 0) { - ALOGE("%s: convert to NV12 buffer failed! ret %d", - __FUNCTION__, ret); - return ret; - } - break; - case V4L2_PIX_FMT_YVU420: // YV12 - case V4L2_PIX_FMT_YUV420: // YU12 - // TODO: maybe we can speed up here by somehow save this copy? - ret = libyuv::I420Copy( - static_cast(in.y), - in.yStride, - static_cast(in.cb), - in.cStride, - static_cast(in.cr), - in.cStride, - static_cast(out.y), - out.yStride, - static_cast(out.cb), - out.cStride, - static_cast(out.cr), - out.cStride, - sz.width, - sz.height); - if (ret != 0) { - ALOGE("%s: copy to YV12 or YU12 buffer failed! ret %d", - __FUNCTION__, ret); - return ret; - } - break; - case FLEX_YUV_GENERIC: - // TODO: b/72261744 write to arbitrary flexible YUV layout. Slow. - ALOGE("%s: unsupported flexible yuv layout" - " y %p cb %p cr %p y_str %d c_str %d c_step %d", - __FUNCTION__, out.y, out.cb, out.cr, - out.yStride, out.cStride, out.chromaStep); - return -1; - default: - ALOGE("%s: unknown YUV format 0x%x!", __FUNCTION__, format); - return -1; - } - return 0; -} - -int ExternalCameraDeviceSession::OutputThread::encodeJpegYU12( - const Size & inSz, const YCbCrLayout& inLayout, - int jpegQuality, const void *app1Buffer, size_t app1Size, - void *out, const size_t maxOutSize, size_t &actualCodeSize) -{ - /* libjpeg is a C library so we use C-style "inheritance" by - * putting libjpeg's jpeg_destination_mgr first in our custom - * struct. This allows us to cast jpeg_destination_mgr* to - * CustomJpegDestMgr* when we get it passed to us in a callback */ - struct CustomJpegDestMgr { - struct jpeg_destination_mgr mgr; - JOCTET *mBuffer; - size_t mBufferSize; - size_t mEncodedSize; - bool mSuccess; - } dmgr; - - jpeg_compress_struct cinfo = {}; - jpeg_error_mgr jerr; - - /* Initialize error handling with standard callbacks, but - * then override output_message (to print to ALOG) and - * error_exit to set a flag and print a message instead - * of killing the whole process */ - cinfo.err = jpeg_std_error(&jerr); - - cinfo.err->output_message = [](j_common_ptr cinfo) { - char buffer[JMSG_LENGTH_MAX]; - - /* Create the message */ - (*cinfo->err->format_message)(cinfo, buffer); - ALOGE("libjpeg error: %s", buffer); - }; - cinfo.err->error_exit = [](j_common_ptr cinfo) { - (*cinfo->err->output_message)(cinfo); - if(cinfo->client_data) { - auto & dmgr = - *reinterpret_cast(cinfo->client_data); - dmgr.mSuccess = false; - } - }; - /* Now that we initialized some callbacks, let's create our compressor */ - jpeg_create_compress(&cinfo); - - /* Initialize our destination manager */ - dmgr.mBuffer = static_cast(out); - dmgr.mBufferSize = maxOutSize; - dmgr.mEncodedSize = 0; - dmgr.mSuccess = true; - cinfo.client_data = static_cast(&dmgr); - - /* These lambdas become C-style function pointers and as per C++11 spec - * may not capture anything */ - dmgr.mgr.init_destination = [](j_compress_ptr cinfo) { - auto & dmgr = reinterpret_cast(*cinfo->dest); - dmgr.mgr.next_output_byte = dmgr.mBuffer; - dmgr.mgr.free_in_buffer = dmgr.mBufferSize; - ALOGV("%s:%d jpeg start: %p [%zu]", - __FUNCTION__, __LINE__, dmgr.mBuffer, dmgr.mBufferSize); - }; - - dmgr.mgr.empty_output_buffer = [](j_compress_ptr cinfo __unused) { - ALOGV("%s:%d Out of buffer", __FUNCTION__, __LINE__); - return 0; - }; - - dmgr.mgr.term_destination = [](j_compress_ptr cinfo) { - auto & dmgr = reinterpret_cast(*cinfo->dest); - dmgr.mEncodedSize = dmgr.mBufferSize - dmgr.mgr.free_in_buffer; - ALOGV("%s:%d Done with jpeg: %zu", __FUNCTION__, __LINE__, dmgr.mEncodedSize); - }; - cinfo.dest = reinterpret_cast(&dmgr); - - /* We are going to be using JPEG in raw data mode, so we are passing - * straight subsampled planar YCbCr and it will not touch our pixel - * data or do any scaling or anything */ - cinfo.image_width = inSz.width; - cinfo.image_height = inSz.height; - cinfo.input_components = 3; - cinfo.in_color_space = JCS_YCbCr; - - /* Initialize defaults and then override what we want */ - jpeg_set_defaults(&cinfo); - - jpeg_set_quality(&cinfo, jpegQuality, 1); - jpeg_set_colorspace(&cinfo, JCS_YCbCr); - cinfo.raw_data_in = 1; - cinfo.dct_method = JDCT_IFAST; - - /* Configure sampling factors. The sampling factor is JPEG subsampling 420 - * because the source format is YUV420. Note that libjpeg sampling factors - * are... a little weird. Sampling of Y=2,U=1,V=1 means there is 1 U and - * 1 V value for each 2 Y values */ - cinfo.comp_info[0].h_samp_factor = 2; - cinfo.comp_info[0].v_samp_factor = 2; - cinfo.comp_info[1].h_samp_factor = 1; - cinfo.comp_info[1].v_samp_factor = 1; - cinfo.comp_info[2].h_samp_factor = 1; - cinfo.comp_info[2].v_samp_factor = 1; - - /* Let's not hardcode YUV420 in 6 places... 5 was enough */ - int maxVSampFactor = std::max( { - cinfo.comp_info[0].v_samp_factor, - cinfo.comp_info[1].v_samp_factor, - cinfo.comp_info[2].v_samp_factor - }); - int cVSubSampling = cinfo.comp_info[0].v_samp_factor / - cinfo.comp_info[1].v_samp_factor; - - /* Start the compressor */ - jpeg_start_compress(&cinfo, TRUE); - - /* Compute our macroblock height, so we can pad our input to be vertically - * macroblock aligned. - * TODO: Does it need to be horizontally MCU aligned too? */ - - size_t mcuV = DCTSIZE*maxVSampFactor; - size_t paddedHeight = mcuV * ((inSz.height + mcuV - 1) / mcuV); - - /* libjpeg uses arrays of row pointers, which makes it really easy to pad - * data vertically (unfortunately doesn't help horizontally) */ - std::vector yLines (paddedHeight); - std::vector cbLines(paddedHeight/cVSubSampling); - std::vector crLines(paddedHeight/cVSubSampling); - - uint8_t *py = static_cast(inLayout.y); - uint8_t *pcr = static_cast(inLayout.cr); - uint8_t *pcb = static_cast(inLayout.cb); - - for(uint32_t i = 0; i < paddedHeight; i++) - { - /* Once we are in the padding territory we still point to the last line - * effectively replicating it several times ~ CLAMP_TO_EDGE */ - int li = std::min(i, inSz.height - 1); - yLines[i] = static_cast(py + li * inLayout.yStride); - if(i < paddedHeight / cVSubSampling) - { - crLines[i] = static_cast(pcr + li * inLayout.cStride); - cbLines[i] = static_cast(pcb + li * inLayout.cStride); - } - } - - /* If APP1 data was passed in, use it */ - if(app1Buffer && app1Size) - { - jpeg_write_marker(&cinfo, JPEG_APP0 + 1, - static_cast(app1Buffer), app1Size); - } - - /* While we still have padded height left to go, keep giving it one - * macroblock at a time. */ - while (cinfo.next_scanline < cinfo.image_height) { - const uint32_t batchSize = DCTSIZE * maxVSampFactor; - const uint32_t nl = cinfo.next_scanline; - JSAMPARRAY planes[3]{ &yLines[nl], - &cbLines[nl/cVSubSampling], - &crLines[nl/cVSubSampling] }; - - uint32_t done = jpeg_write_raw_data(&cinfo, planes, batchSize); - - if (done != batchSize) { - ALOGE("%s: compressed %u lines, expected %u (total %u/%u)", - __FUNCTION__, done, batchSize, cinfo.next_scanline, - cinfo.image_height); - return -1; - } - } - - /* This will flush everything */ - jpeg_finish_compress(&cinfo); - - /* Grab the actual code size and set it */ - actualCodeSize = dmgr.mEncodedSize; - - return 0; -} - /* * TODO: There needs to be a mechanism to discover allocated buffer size * in the HAL. @@ -1555,25 +1190,9 @@ Size ExternalCameraDeviceSession::getMaxJpegResolution() const { } Size ExternalCameraDeviceSession::getMaxThumbResolution() const { - Size thumbSize { 0, 0 }; - camera_metadata_ro_entry entry = - mCameraCharacteristics.find(ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES); - for(uint32_t i = 0; i < entry.count; i += 2) { - Size sz { static_cast(entry.data.i32[i]), - static_cast(entry.data.i32[i+1]) }; - if(sz.width * sz.height > thumbSize.width * thumbSize.height) { - thumbSize = sz; - } - } - - if (thumbSize.width * thumbSize.height == 0) { - ALOGW("%s: non-zero thumbnail size not available", __FUNCTION__); - } - - return thumbSize; + return getMaxThumbnailResolution(mCameraCharacteristics); } - ssize_t ExternalCameraDeviceSession::getJpegBufferSize( uint32_t width, uint32_t height) const { // Constant from camera3.h @@ -1616,7 +1235,7 @@ ssize_t ExternalCameraDeviceSession::getJpegBufferSize( int ExternalCameraDeviceSession::OutputThread::createJpegLocked( HalStreamBuffer &halBuf, - const std::shared_ptr& req) + const common::V1_0::helper::CameraMetadata& setting) { ATRACE_CALL(); int ret; @@ -1645,17 +1264,17 @@ int ExternalCameraDeviceSession::OutputThread::createJpegLocked( Size thumbSize; bool outputThumbnail = true; - if (req->setting.exists(ANDROID_JPEG_QUALITY)) { - camera_metadata_entry entry = - req->setting.find(ANDROID_JPEG_QUALITY); + if (setting.exists(ANDROID_JPEG_QUALITY)) { + camera_metadata_ro_entry entry = + setting.find(ANDROID_JPEG_QUALITY); jpegQuality = entry.data.u8[0]; } else { return lfail("%s: ANDROID_JPEG_QUALITY not set",__FUNCTION__); } - if (req->setting.exists(ANDROID_JPEG_THUMBNAIL_QUALITY)) { - camera_metadata_entry entry = - req->setting.find(ANDROID_JPEG_THUMBNAIL_QUALITY); + if (setting.exists(ANDROID_JPEG_THUMBNAIL_QUALITY)) { + camera_metadata_ro_entry entry = + setting.find(ANDROID_JPEG_THUMBNAIL_QUALITY); thumbQuality = entry.data.u8[0]; } else { return lfail( @@ -1663,9 +1282,9 @@ int ExternalCameraDeviceSession::OutputThread::createJpegLocked( __FUNCTION__); } - if (req->setting.exists(ANDROID_JPEG_THUMBNAIL_SIZE)) { - camera_metadata_entry entry = - req->setting.find(ANDROID_JPEG_THUMBNAIL_SIZE); + if (setting.exists(ANDROID_JPEG_THUMBNAIL_SIZE)) { + camera_metadata_ro_entry entry = + setting.find(ANDROID_JPEG_THUMBNAIL_SIZE); thumbSize = Size { static_cast(entry.data.i32[0]), static_cast(entry.data.i32[1]) }; @@ -1732,8 +1351,8 @@ int ExternalCameraDeviceSession::OutputThread::createJpegLocked( /* Combine camera characteristics with request settings to form EXIF * metadata */ - common::V1_0::helper::CameraMetadata meta(parent->mCameraCharacteristics); - meta.append(req->setting); + common::V1_0::helper::CameraMetadata meta(mCameraCharacteristics); + meta.append(setting); /* Generate EXIF object */ std::unique_ptr utils(ExifUtils::create()); @@ -1838,7 +1457,7 @@ bool ExternalCameraDeviceSession::OutputThread::threadLoop() { // TODO: see if we can save some computation by converting to YV12 here uint8_t* inData; size_t inDataSize; - if (req->frameIn->map(&inData, &inDataSize) != 0) { + if (req->frameIn->getData(&inData, &inDataSize) != 0) { lk.unlock(); return onDeviceError("%s: V4L2 buffer map failed", __FUNCTION__); } @@ -1899,7 +1518,7 @@ bool ExternalCameraDeviceSession::OutputThread::threadLoop() { // Gralloc lockYCbCr the buffer switch (halBuf.format) { case PixelFormat::BLOB: { - int ret = createJpegLocked(halBuf, req); + int ret = createJpegLocked(halBuf, req->setting); if(ret != 0) { lk.unlock(); @@ -1949,8 +1568,8 @@ bool ExternalCameraDeviceSession::OutputThread::threadLoop() { } Size sz {halBuf.width, halBuf.height}; - ATRACE_BEGIN("formatConvertLocked"); - ret = formatConvertLocked(cropAndScaled, outLayout, sz, outputFourcc); + ATRACE_BEGIN("formatConvert"); + ret = formatConvert(cropAndScaled, outLayout, sz, outputFourcc); ATRACE_END(); if (ret != 0) { lk.unlock(); @@ -2055,6 +1674,14 @@ Status ExternalCameraDeviceSession::OutputThread::allocateIntermediateBuffers( return Status::OK; } +void ExternalCameraDeviceSession::OutputThread::clearIntermediateBuffers() { + std::lock_guard lk(mBufferLock); + mYu12Frame.clear(); + mYu12ThumbFrame.clear(); + mIntermediateBuffers.clear(); + mBlobBufferSize = 0; +} + Status ExternalCameraDeviceSession::OutputThread::submitRequest( const std::shared_ptr& req) { std::unique_lock lk(mRequestListLock); @@ -2090,6 +1717,32 @@ void ExternalCameraDeviceSession::OutputThread::flush() { } } +std::list> +ExternalCameraDeviceSession::OutputThread::switchToOffline() { + ATRACE_CALL(); + std::list> emptyList; + auto parent = mParent.promote(); + if (parent == nullptr) { + ALOGE("%s: session has been disconnected!", __FUNCTION__); + return emptyList; + } + + std::unique_lock lk(mRequestListLock); + std::list> reqs = std::move(mRequestList); + mRequestList.clear(); + if (mProcessingRequest) { + std::chrono::seconds timeout = std::chrono::seconds(kFlushWaitTimeoutSec); + auto st = mRequestDoneCond.wait_for(lk, timeout); + if (st == std::cv_status::timeout) { + ALOGE("%s: wait for inflight request finish timeout!", __FUNCTION__); + } + } + lk.unlock(); + clearIntermediateBuffers(); + ALOGV("%s: returning %zu request for offline processing", __FUNCTION__, reqs.size()); + return reqs; +} + void ExternalCameraDeviceSession::OutputThread::waitForNextRequest( std::shared_ptr* out) { ATRACE_CALL(); @@ -2733,6 +2386,7 @@ Status ExternalCameraDeviceSession::configureStreams( return Status::INTERNAL_ERROR; } + mBlobBufferSize = blobBufferSize; status = mOutputThread->allocateIntermediateBuffers(v4lSize, mMaxThumbResolution, config.streams, blobBufferSize); if (status != Status::OK) { @@ -2916,16 +2570,6 @@ status_t ExternalCameraDeviceSession::initDefaultRequests() { status_t ExternalCameraDeviceSession::fillCaptureResult( common::V1_0::helper::CameraMetadata &md, nsecs_t timestamp) { - // android.control - // For USB camera, we don't know the AE state. Set the state to converged to - // indicate the frame should be good to use. Then apps don't have to wait the - // AE state. - const uint8_t aeState = ANDROID_CONTROL_AE_STATE_CONVERGED; - UPDATE(md, ANDROID_CONTROL_AE_STATE, &aeState, 1); - - const uint8_t ae_lock = ANDROID_CONTROL_AE_LOCK_OFF; - UPDATE(md, ANDROID_CONTROL_AE_LOCK, &ae_lock, 1); - bool afTrigger = false; { std::lock_guard lk(mAfTriggerLock); @@ -2951,46 +2595,10 @@ status_t ExternalCameraDeviceSession::fillCaptureResult( } UPDATE(md, ANDROID_CONTROL_AF_STATE, &afState, 1); - // Set AWB state to converged to indicate the frame should be good to use. - const uint8_t awbState = ANDROID_CONTROL_AWB_STATE_CONVERGED; - UPDATE(md, ANDROID_CONTROL_AWB_STATE, &awbState, 1); + camera_metadata_ro_entry activeArraySize = + mCameraCharacteristics.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE); - const uint8_t awbLock = ANDROID_CONTROL_AWB_LOCK_OFF; - UPDATE(md, ANDROID_CONTROL_AWB_LOCK, &awbLock, 1); - - camera_metadata_ro_entry active_array_size = - mCameraCharacteristics.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE); - - if (active_array_size.count == 0) { - ALOGE("%s: cannot find active array size!", __FUNCTION__); - return -EINVAL; - } - - const uint8_t flashState = ANDROID_FLASH_STATE_UNAVAILABLE; - UPDATE(md, ANDROID_FLASH_STATE, &flashState, 1); - - // This means pipeline latency of X frame intervals. The maximum number is 4. - const uint8_t requestPipelineMaxDepth = 4; - UPDATE(md, ANDROID_REQUEST_PIPELINE_DEPTH, &requestPipelineMaxDepth, 1); - - // android.scaler - const int32_t crop_region[] = { - active_array_size.data.i32[0], active_array_size.data.i32[1], - active_array_size.data.i32[2], active_array_size.data.i32[3], - }; - UPDATE(md, ANDROID_SCALER_CROP_REGION, crop_region, ARRAY_SIZE(crop_region)); - - // android.sensor - UPDATE(md, ANDROID_SENSOR_TIMESTAMP, ×tamp, 1); - - // android.statistics - const uint8_t lensShadingMapMode = ANDROID_STATISTICS_LENS_SHADING_MAP_MODE_OFF; - UPDATE(md, ANDROID_STATISTICS_LENS_SHADING_MAP_MODE, &lensShadingMapMode, 1); - - const uint8_t sceneFlicker = ANDROID_STATISTICS_SCENE_FLICKER_NONE; - UPDATE(md, ANDROID_STATISTICS_SCENE_FLICKER, &sceneFlicker, 1); - - return OK; + return fillCaptureResultCommon(md, timestamp, activeArraySize); } #undef ARRAY_SIZE diff --git a/camera/device/3.4/default/ExternalCameraUtils.cpp b/camera/device/3.4/default/ExternalCameraUtils.cpp index e25deff797..8f4626c56d 100644 --- a/camera/device/3.4/default/ExternalCameraUtils.cpp +++ b/camera/device/3.4/default/ExternalCameraUtils.cpp @@ -18,10 +18,23 @@ #include #include +#include #include #include + +#define HAVE_JPEG // required for libyuv.h to export MJPEG decode APIs +#include + +#include + #include "ExternalCameraUtils.h" +namespace { + +buffer_handle_t sEmptyBuffer = nullptr; + +} // Anonymous namespace + namespace android { namespace hardware { namespace camera { @@ -29,10 +42,13 @@ namespace device { namespace V3_4 { namespace implementation { +Frame::Frame(uint32_t width, uint32_t height, uint32_t fourcc) : + mWidth(width), mHeight(height), mFourcc(fourcc) {} + V4L2Frame::V4L2Frame( uint32_t w, uint32_t h, uint32_t fourcc, int bufIdx, int fd, uint32_t dataSize, uint64_t offset) : - mWidth(w), mHeight(h), mFourcc(fourcc), + Frame(w, h, fourcc), mBufferIndex(bufIdx), mFd(fd), mDataSize(dataSize), mOffset(offset) {} int V4L2Frame::map(uint8_t** data, size_t* dataSize) { @@ -75,9 +91,13 @@ V4L2Frame::~V4L2Frame() { unmap(); } +int V4L2Frame::getData(uint8_t** outData, size_t* dataSize) { + return map(outData, dataSize); +} + AllocatedFrame::AllocatedFrame( uint32_t w, uint32_t h) : - mWidth(w), mHeight(h), mFourcc(V4L2_PIX_FMT_YUV420) {}; + Frame(w, h, V4L2_PIX_FMT_YUV420) {}; AllocatedFrame::~AllocatedFrame() {} @@ -106,6 +126,17 @@ int AllocatedFrame::allocate(YCbCrLayout* out) { return 0; } +int AllocatedFrame::getData(uint8_t** outData, size_t* dataSize) { + YCbCrLayout layout; + int ret = allocate(&layout); + if (ret != 0) { + return ret; + } + *outData = mData.data(); + *dataSize = mData.size(); + return 0; +} + int AllocatedFrame::getLayout(YCbCrLayout* out) { IMapper::Rect noCrop = {0, 0, static_cast(mWidth), @@ -150,8 +181,521 @@ double SupportedV4L2Format::FrameRate::getDouble() const { return durationDenominator / static_cast(durationNumerator); } +::android::hardware::camera::common::V1_0::Status importBufferImpl( + /*inout*/std::map& circulatingBuffers, + /*inout*/HandleImporter& handleImporter, + int32_t streamId, + uint64_t bufId, buffer_handle_t buf, + /*out*/buffer_handle_t** outBufPtr, + bool allowEmptyBuf) { + using ::android::hardware::camera::common::V1_0::Status; + if (buf == nullptr && bufId == BUFFER_ID_NO_BUFFER) { + if (allowEmptyBuf) { + *outBufPtr = &sEmptyBuffer; + return Status::OK; + } else { + ALOGE("%s: bufferId %" PRIu64 " has null buffer handle!", __FUNCTION__, bufId); + return Status::ILLEGAL_ARGUMENT; + } + } + + CirculatingBuffers& cbs = circulatingBuffers[streamId]; + if (cbs.count(bufId) == 0) { + if (buf == nullptr) { + ALOGE("%s: bufferId %" PRIu64 " has null buffer handle!", __FUNCTION__, bufId); + return Status::ILLEGAL_ARGUMENT; + } + // Register a newly seen buffer + buffer_handle_t importedBuf = buf; + handleImporter.importBuffer(importedBuf); + if (importedBuf == nullptr) { + ALOGE("%s: output buffer for stream %d is invalid!", __FUNCTION__, streamId); + return Status::INTERNAL_ERROR; + } else { + cbs[bufId] = importedBuf; + } + } + *outBufPtr = &cbs[bufId]; + return Status::OK; +} + +uint32_t getFourCcFromLayout(const YCbCrLayout& layout) { + intptr_t cb = reinterpret_cast(layout.cb); + intptr_t cr = reinterpret_cast(layout.cr); + if (std::abs(cb - cr) == 1 && layout.chromaStep == 2) { + // Interleaved format + if (layout.cb > layout.cr) { + return V4L2_PIX_FMT_NV21; + } else { + return V4L2_PIX_FMT_NV12; + } + } else if (layout.chromaStep == 1) { + // Planar format + if (layout.cb > layout.cr) { + return V4L2_PIX_FMT_YVU420; // YV12 + } else { + return V4L2_PIX_FMT_YUV420; // YU12 + } + } else { + return FLEX_YUV_GENERIC; + } +} + +int getCropRect( + CroppingType ct, const Size& inSize, const Size& outSize, IMapper::Rect* out) { + if (out == nullptr) { + ALOGE("%s: out is null", __FUNCTION__); + return -1; + } + + uint32_t inW = inSize.width; + uint32_t inH = inSize.height; + uint32_t outW = outSize.width; + uint32_t outH = outSize.height; + + // Handle special case where aspect ratio is close to input but scaled + // dimension is slightly larger than input + float arIn = ASPECT_RATIO(inSize); + float arOut = ASPECT_RATIO(outSize); + if (isAspectRatioClose(arIn, arOut)) { + out->left = 0; + out->top = 0; + out->width = inW; + out->height = inH; + return 0; + } + + if (ct == VERTICAL) { + uint64_t scaledOutH = static_cast(outH) * inW / outW; + if (scaledOutH > inH) { + ALOGE("%s: Output size %dx%d cannot be vertically cropped from input size %dx%d", + __FUNCTION__, outW, outH, inW, inH); + return -1; + } + scaledOutH = scaledOutH & ~0x1; // make it multiple of 2 + + out->left = 0; + out->top = ((inH - scaledOutH) / 2) & ~0x1; + out->width = inW; + out->height = static_cast(scaledOutH); + ALOGV("%s: crop %dx%d to %dx%d: top %d, scaledH %d", + __FUNCTION__, inW, inH, outW, outH, out->top, static_cast(scaledOutH)); + } else { + uint64_t scaledOutW = static_cast(outW) * inH / outH; + if (scaledOutW > inW) { + ALOGE("%s: Output size %dx%d cannot be horizontally cropped from input size %dx%d", + __FUNCTION__, outW, outH, inW, inH); + return -1; + } + scaledOutW = scaledOutW & ~0x1; // make it multiple of 2 + + out->left = ((inW - scaledOutW) / 2) & ~0x1; + out->top = 0; + out->width = static_cast(scaledOutW); + out->height = inH; + ALOGV("%s: crop %dx%d to %dx%d: top %d, scaledW %d", + __FUNCTION__, inW, inH, outW, outH, out->top, static_cast(scaledOutW)); + } + + return 0; +} + +int formatConvert( + const YCbCrLayout& in, const YCbCrLayout& out, Size sz, uint32_t format) { + int ret = 0; + switch (format) { + case V4L2_PIX_FMT_NV21: + ret = libyuv::I420ToNV21( + static_cast(in.y), + in.yStride, + static_cast(in.cb), + in.cStride, + static_cast(in.cr), + in.cStride, + static_cast(out.y), + out.yStride, + static_cast(out.cr), + out.cStride, + sz.width, + sz.height); + if (ret != 0) { + ALOGE("%s: convert to NV21 buffer failed! ret %d", + __FUNCTION__, ret); + return ret; + } + break; + case V4L2_PIX_FMT_NV12: + ret = libyuv::I420ToNV12( + static_cast(in.y), + in.yStride, + static_cast(in.cb), + in.cStride, + static_cast(in.cr), + in.cStride, + static_cast(out.y), + out.yStride, + static_cast(out.cb), + out.cStride, + sz.width, + sz.height); + if (ret != 0) { + ALOGE("%s: convert to NV12 buffer failed! ret %d", + __FUNCTION__, ret); + return ret; + } + break; + case V4L2_PIX_FMT_YVU420: // YV12 + case V4L2_PIX_FMT_YUV420: // YU12 + // TODO: maybe we can speed up here by somehow save this copy? + ret = libyuv::I420Copy( + static_cast(in.y), + in.yStride, + static_cast(in.cb), + in.cStride, + static_cast(in.cr), + in.cStride, + static_cast(out.y), + out.yStride, + static_cast(out.cb), + out.cStride, + static_cast(out.cr), + out.cStride, + sz.width, + sz.height); + if (ret != 0) { + ALOGE("%s: copy to YV12 or YU12 buffer failed! ret %d", + __FUNCTION__, ret); + return ret; + } + break; + case FLEX_YUV_GENERIC: + // TODO: b/72261744 write to arbitrary flexible YUV layout. Slow. + ALOGE("%s: unsupported flexible yuv layout" + " y %p cb %p cr %p y_str %d c_str %d c_step %d", + __FUNCTION__, out.y, out.cb, out.cr, + out.yStride, out.cStride, out.chromaStep); + return -1; + default: + ALOGE("%s: unknown YUV format 0x%x!", __FUNCTION__, format); + return -1; + } + return 0; +} + +int encodeJpegYU12( + const Size & inSz, const YCbCrLayout& inLayout, + int jpegQuality, const void *app1Buffer, size_t app1Size, + void *out, const size_t maxOutSize, size_t &actualCodeSize) +{ + /* libjpeg is a C library so we use C-style "inheritance" by + * putting libjpeg's jpeg_destination_mgr first in our custom + * struct. This allows us to cast jpeg_destination_mgr* to + * CustomJpegDestMgr* when we get it passed to us in a callback */ + struct CustomJpegDestMgr { + struct jpeg_destination_mgr mgr; + JOCTET *mBuffer; + size_t mBufferSize; + size_t mEncodedSize; + bool mSuccess; + } dmgr; + + jpeg_compress_struct cinfo = {}; + jpeg_error_mgr jerr; + + /* Initialize error handling with standard callbacks, but + * then override output_message (to print to ALOG) and + * error_exit to set a flag and print a message instead + * of killing the whole process */ + cinfo.err = jpeg_std_error(&jerr); + + cinfo.err->output_message = [](j_common_ptr cinfo) { + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message)(cinfo, buffer); + ALOGE("libjpeg error: %s", buffer); + }; + cinfo.err->error_exit = [](j_common_ptr cinfo) { + (*cinfo->err->output_message)(cinfo); + if(cinfo->client_data) { + auto & dmgr = + *reinterpret_cast(cinfo->client_data); + dmgr.mSuccess = false; + } + }; + /* Now that we initialized some callbacks, let's create our compressor */ + jpeg_create_compress(&cinfo); + + /* Initialize our destination manager */ + dmgr.mBuffer = static_cast(out); + dmgr.mBufferSize = maxOutSize; + dmgr.mEncodedSize = 0; + dmgr.mSuccess = true; + cinfo.client_data = static_cast(&dmgr); + + /* These lambdas become C-style function pointers and as per C++11 spec + * may not capture anything */ + dmgr.mgr.init_destination = [](j_compress_ptr cinfo) { + auto & dmgr = reinterpret_cast(*cinfo->dest); + dmgr.mgr.next_output_byte = dmgr.mBuffer; + dmgr.mgr.free_in_buffer = dmgr.mBufferSize; + ALOGV("%s:%d jpeg start: %p [%zu]", + __FUNCTION__, __LINE__, dmgr.mBuffer, dmgr.mBufferSize); + }; + + dmgr.mgr.empty_output_buffer = [](j_compress_ptr cinfo __unused) { + ALOGV("%s:%d Out of buffer", __FUNCTION__, __LINE__); + return 0; + }; + + dmgr.mgr.term_destination = [](j_compress_ptr cinfo) { + auto & dmgr = reinterpret_cast(*cinfo->dest); + dmgr.mEncodedSize = dmgr.mBufferSize - dmgr.mgr.free_in_buffer; + ALOGV("%s:%d Done with jpeg: %zu", __FUNCTION__, __LINE__, dmgr.mEncodedSize); + }; + cinfo.dest = reinterpret_cast(&dmgr); + + /* We are going to be using JPEG in raw data mode, so we are passing + * straight subsampled planar YCbCr and it will not touch our pixel + * data or do any scaling or anything */ + cinfo.image_width = inSz.width; + cinfo.image_height = inSz.height; + cinfo.input_components = 3; + cinfo.in_color_space = JCS_YCbCr; + + /* Initialize defaults and then override what we want */ + jpeg_set_defaults(&cinfo); + + jpeg_set_quality(&cinfo, jpegQuality, 1); + jpeg_set_colorspace(&cinfo, JCS_YCbCr); + cinfo.raw_data_in = 1; + cinfo.dct_method = JDCT_IFAST; + + /* Configure sampling factors. The sampling factor is JPEG subsampling 420 + * because the source format is YUV420. Note that libjpeg sampling factors + * are... a little weird. Sampling of Y=2,U=1,V=1 means there is 1 U and + * 1 V value for each 2 Y values */ + cinfo.comp_info[0].h_samp_factor = 2; + cinfo.comp_info[0].v_samp_factor = 2; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + + /* Let's not hardcode YUV420 in 6 places... 5 was enough */ + int maxVSampFactor = std::max( { + cinfo.comp_info[0].v_samp_factor, + cinfo.comp_info[1].v_samp_factor, + cinfo.comp_info[2].v_samp_factor + }); + int cVSubSampling = cinfo.comp_info[0].v_samp_factor / + cinfo.comp_info[1].v_samp_factor; + + /* Start the compressor */ + jpeg_start_compress(&cinfo, TRUE); + + /* Compute our macroblock height, so we can pad our input to be vertically + * macroblock aligned. + * TODO: Does it need to be horizontally MCU aligned too? */ + + size_t mcuV = DCTSIZE*maxVSampFactor; + size_t paddedHeight = mcuV * ((inSz.height + mcuV - 1) / mcuV); + + /* libjpeg uses arrays of row pointers, which makes it really easy to pad + * data vertically (unfortunately doesn't help horizontally) */ + std::vector yLines (paddedHeight); + std::vector cbLines(paddedHeight/cVSubSampling); + std::vector crLines(paddedHeight/cVSubSampling); + + uint8_t *py = static_cast(inLayout.y); + uint8_t *pcr = static_cast(inLayout.cr); + uint8_t *pcb = static_cast(inLayout.cb); + + for(uint32_t i = 0; i < paddedHeight; i++) + { + /* Once we are in the padding territory we still point to the last line + * effectively replicating it several times ~ CLAMP_TO_EDGE */ + int li = std::min(i, inSz.height - 1); + yLines[i] = static_cast(py + li * inLayout.yStride); + if(i < paddedHeight / cVSubSampling) + { + li = std::min(i, (inSz.height - 1) / cVSubSampling); + crLines[i] = static_cast(pcr + li * inLayout.cStride); + cbLines[i] = static_cast(pcb + li * inLayout.cStride); + } + } + + /* If APP1 data was passed in, use it */ + if(app1Buffer && app1Size) + { + jpeg_write_marker(&cinfo, JPEG_APP0 + 1, + static_cast(app1Buffer), app1Size); + } + + /* While we still have padded height left to go, keep giving it one + * macroblock at a time. */ + while (cinfo.next_scanline < cinfo.image_height) { + const uint32_t batchSize = DCTSIZE * maxVSampFactor; + const uint32_t nl = cinfo.next_scanline; + JSAMPARRAY planes[3]{ &yLines[nl], + &cbLines[nl/cVSubSampling], + &crLines[nl/cVSubSampling] }; + + uint32_t done = jpeg_write_raw_data(&cinfo, planes, batchSize); + + if (done != batchSize) { + ALOGE("%s: compressed %u lines, expected %u (total %u/%u)", + __FUNCTION__, done, batchSize, cinfo.next_scanline, + cinfo.image_height); + return -1; + } + } + + /* This will flush everything */ + jpeg_finish_compress(&cinfo); + + /* Grab the actual code size and set it */ + actualCodeSize = dmgr.mEncodedSize; + + return 0; +} + +Size getMaxThumbnailResolution(const common::V1_0::helper::CameraMetadata& chars) { + Size thumbSize { 0, 0 }; + camera_metadata_ro_entry entry = + chars.find(ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES); + for(uint32_t i = 0; i < entry.count; i += 2) { + Size sz { static_cast(entry.data.i32[i]), + static_cast(entry.data.i32[i+1]) }; + if(sz.width * sz.height > thumbSize.width * thumbSize.height) { + thumbSize = sz; + } + } + + if (thumbSize.width * thumbSize.height == 0) { + ALOGW("%s: non-zero thumbnail size not available", __FUNCTION__); + } + + return thumbSize; +} + +void freeReleaseFences(hidl_vec& results) { + for (auto& result : results) { + if (result.inputBuffer.releaseFence.getNativeHandle() != nullptr) { + native_handle_t* handle = const_cast( + result.inputBuffer.releaseFence.getNativeHandle()); + native_handle_close(handle); + native_handle_delete(handle); + } + for (auto& buf : result.outputBuffers) { + if (buf.releaseFence.getNativeHandle() != nullptr) { + native_handle_t* handle = const_cast( + buf.releaseFence.getNativeHandle()); + native_handle_close(handle); + native_handle_delete(handle); + } + } + } + return; +} + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) +#define UPDATE(md, tag, data, size) \ +do { \ + if ((md).update((tag), (data), (size))) { \ + ALOGE("Update " #tag " failed!"); \ + return BAD_VALUE; \ + } \ +} while (0) + +status_t fillCaptureResultCommon( + common::V1_0::helper::CameraMetadata &md, nsecs_t timestamp, + camera_metadata_ro_entry& activeArraySize) { + if (activeArraySize.count < 4) { + ALOGE("%s: cannot find active array size!", __FUNCTION__); + return -EINVAL; + } + // android.control + // For USB camera, we don't know the AE state. Set the state to converged to + // indicate the frame should be good to use. Then apps don't have to wait the + // AE state. + const uint8_t aeState = ANDROID_CONTROL_AE_STATE_CONVERGED; + UPDATE(md, ANDROID_CONTROL_AE_STATE, &aeState, 1); + + const uint8_t ae_lock = ANDROID_CONTROL_AE_LOCK_OFF; + UPDATE(md, ANDROID_CONTROL_AE_LOCK, &ae_lock, 1); + + // Set AWB state to converged to indicate the frame should be good to use. + const uint8_t awbState = ANDROID_CONTROL_AWB_STATE_CONVERGED; + UPDATE(md, ANDROID_CONTROL_AWB_STATE, &awbState, 1); + + const uint8_t awbLock = ANDROID_CONTROL_AWB_LOCK_OFF; + UPDATE(md, ANDROID_CONTROL_AWB_LOCK, &awbLock, 1); + + const uint8_t flashState = ANDROID_FLASH_STATE_UNAVAILABLE; + UPDATE(md, ANDROID_FLASH_STATE, &flashState, 1); + + // This means pipeline latency of X frame intervals. The maximum number is 4. + const uint8_t requestPipelineMaxDepth = 4; + UPDATE(md, ANDROID_REQUEST_PIPELINE_DEPTH, &requestPipelineMaxDepth, 1); + + // android.scaler + const int32_t crop_region[] = { + activeArraySize.data.i32[0], activeArraySize.data.i32[1], + activeArraySize.data.i32[2], activeArraySize.data.i32[3], + }; + UPDATE(md, ANDROID_SCALER_CROP_REGION, crop_region, ARRAY_SIZE(crop_region)); + + // android.sensor + UPDATE(md, ANDROID_SENSOR_TIMESTAMP, ×tamp, 1); + + // android.statistics + const uint8_t lensShadingMapMode = ANDROID_STATISTICS_LENS_SHADING_MAP_MODE_OFF; + UPDATE(md, ANDROID_STATISTICS_LENS_SHADING_MAP_MODE, &lensShadingMapMode, 1); + + const uint8_t sceneFlicker = ANDROID_STATISTICS_SCENE_FLICKER_NONE; + UPDATE(md, ANDROID_STATISTICS_SCENE_FLICKER, &sceneFlicker, 1); + + return OK; +} + +#undef ARRAY_SIZE +#undef UPDATE + } // namespace implementation } // namespace V3_4 + +namespace V3_6 { +namespace implementation { + +AllocatedV4L2Frame::AllocatedV4L2Frame(sp frameIn) : + Frame(frameIn->mWidth, frameIn->mHeight, frameIn->mFourcc) { + uint8_t* dataIn; + size_t dataSize; + if (frameIn->getData(&dataIn, &dataSize) != 0) { + ALOGE("%s: map input V4L2 frame failed!", __FUNCTION__); + return; + } + + mData.resize(dataSize); + std::memcpy(mData.data(), dataIn, dataSize); +} + +int AllocatedV4L2Frame::getData(uint8_t** outData, size_t* dataSize) { + if (outData == nullptr || dataSize == nullptr) { + ALOGE("%s: outData(%p)/dataSize(%p) must not be null", __FUNCTION__, outData, dataSize); + return -1; + } + + *outData = mData.data(); + *dataSize = mData.size(); + return 0; +} + +AllocatedV4L2Frame::~AllocatedV4L2Frame() {} + +} // namespace implementation +} // namespace V3_6 } // namespace device @@ -159,6 +703,7 @@ namespace external { namespace common { namespace { + const int kDefaultCameraIdOffset = 100; const int kDefaultJpegBufSize = 5 << 20; // 5MB const int kDefaultNumVideoBuffer = 4; const int kDefaultNumStillBuffer = 2; @@ -194,6 +739,11 @@ ExternalCameraConfig ExternalCameraConfig::loadFromCfg(const char* cfgPath) { return ret; } + XMLElement *cameraIdOffset = providerCfg->FirstChildElement("CameraIdOffset"); + if (cameraIdOffset != nullptr) { + ret.cameraIdOffset = std::atoi(cameraIdOffset->GetText()); + } + XMLElement *ignore = providerCfg->FirstChildElement("ignore"); if (ignore == nullptr) { ALOGI("%s: no internal ignored device specified", __FUNCTION__); @@ -330,6 +880,7 @@ bool ExternalCameraConfig::updateFpsList(tinyxml2::XMLElement* fpsList, } ExternalCameraConfig::ExternalCameraConfig() : + cameraIdOffset(kDefaultCameraIdOffset), maxJpegBufSize(kDefaultJpegBufSize), numVideoBuffers(kDefaultNumVideoBuffer), numStillBuffers(kDefaultNumStillBuffer), diff --git a/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraDeviceSession.h b/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraDeviceSession.h index 71b7c17dd6..180f0c155a 100644 --- a/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraDeviceSession.h +++ b/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraDeviceSession.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICE3SESSION_H -#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICE3SESSION_H +#ifndef ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICESESSION_H +#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICESESSION_H #include #include @@ -84,7 +84,8 @@ using ::android::sp; using ::android::Mutex; using ::android::base::unique_fd; -struct ExternalCameraDeviceSession : public virtual RefBase { +struct ExternalCameraDeviceSession : public virtual RefBase, + public virtual OutputThreadInterface { ExternalCameraDeviceSession(const sp&, const ExternalCameraConfig& cfg, @@ -110,6 +111,82 @@ struct ExternalCameraDeviceSession : public virtual RefBase { static const int kMaxStallStream = 1; static const uint32_t kMaxBytesPerPixel = 2; + class OutputThread : public android::Thread { + public: + OutputThread(wp parent, CroppingType, + const common::V1_0::helper::CameraMetadata&); + virtual ~OutputThread(); + + Status allocateIntermediateBuffers( + const Size& v4lSize, const Size& thumbSize, + const hidl_vec& streams, + uint32_t blobBufferSize); + Status submitRequest(const std::shared_ptr&); + void flush(); + void dump(int fd); + virtual bool threadLoop() override; + + void setExifMakeModel(const std::string& make, const std::string& model); + + // The remaining request list is returned for offline processing + std::list> switchToOffline(); + + protected: + // Methods to request output buffer in parallel + // No-op for device@3.4. Implemented in device@3.5 + virtual int requestBufferStart(const std::vector&) { return 0; } + virtual int waitForBufferRequestDone( + /*out*/std::vector*) { return 0; } + + static const int kFlushWaitTimeoutSec = 3; // 3 sec + static const int kReqWaitTimeoutMs = 33; // 33ms + static const int kReqWaitTimesMax = 90; // 33ms * 90 ~= 3 sec + + void waitForNextRequest(std::shared_ptr* out); + void signalRequestDone(); + + int cropAndScaleLocked( + sp& in, const Size& outSize, + YCbCrLayout* out); + + int cropAndScaleThumbLocked( + sp& in, const Size& outSize, + YCbCrLayout* out); + + int createJpegLocked(HalStreamBuffer &halBuf, + const common::V1_0::helper::CameraMetadata& settings); + + void clearIntermediateBuffers(); + + const wp mParent; + const CroppingType mCroppingType; + const common::V1_0::helper::CameraMetadata mCameraCharacteristics; + + mutable std::mutex mRequestListLock; // Protect acccess to mRequestList, + // mProcessingRequest and mProcessingFrameNumer + std::condition_variable mRequestCond; // signaled when a new request is submitted + std::condition_variable mRequestDoneCond; // signaled when a request is done processing + std::list> mRequestList; + bool mProcessingRequest = false; + uint32_t mProcessingFrameNumer = 0; + + // V4L2 frameIn + // (MJPG decode)-> mYu12Frame + // (Scale)-> mScaledYu12Frames + // (Format convert) -> output gralloc frames + mutable std::mutex mBufferLock; // Protect access to intermediate buffers + sp mYu12Frame; + sp mYu12ThumbFrame; + std::unordered_map, SizeHasher> mIntermediateBuffers; + std::unordered_map, SizeHasher> mScaledYu12Frames; + YCbCrLayout mYu12FrameLayout; + YCbCrLayout mYu12ThumbFrameLayout; + uint32_t mBlobBufferSize = 0; // 0 -> HAL derive buffer size, else: use given size + + std::string mExifMake; + std::string mExifModel; + }; + protected: // Methods from ::android::hardware::camera::device::V3_2::ICameraDeviceSession follow @@ -150,27 +227,22 @@ protected: ICameraDeviceSession::processCaptureRequest_3_4_cb _hidl_cb); protected: - struct HalStreamBuffer { - int32_t streamId; - uint64_t bufferId; - uint32_t width; - uint32_t height; - PixelFormat format; - V3_2::BufferUsageFlags usage; - buffer_handle_t* bufPtr; - int acquireFence; - bool fenceTimeout; - }; + // Methods from OutputThreadInterface + virtual Status importBuffer(int32_t streamId, + uint64_t bufId, buffer_handle_t buf, + /*out*/buffer_handle_t** outBufPtr, + bool allowEmptyBuf) override; - struct HalRequest { - uint32_t frameNumber; - common::V1_0::helper::CameraMetadata setting; - sp frameIn; - nsecs_t shutterTs; - std::vector buffers; - }; + virtual Status processCaptureResult(std::shared_ptr&) override; - static const uint64_t BUFFER_ID_NO_BUFFER = 0; + virtual Status processCaptureRequestError(const std::shared_ptr&, + /*out*/std::vector* msgs = nullptr, + /*out*/std::vector* results = nullptr) override; + + virtual ssize_t getJpegBufferSize(uint32_t width, uint32_t height) const override; + + virtual void notifyError(uint32_t frameNumber, int32_t streamId, ErrorCode ec) override; + // End of OutputThreadInterface methods Status constructDefaultRequestSettingsRaw(RequestTemplate type, V3_2::CameraMetadata *outMetadata); @@ -219,11 +291,6 @@ protected: // Optional argument for ICameraDeviceSession@3.5 impl bool allowEmptyBuf = false); - Status importBuffer(int32_t streamId, - uint64_t bufId, buffer_handle_t buf, - /*out*/buffer_handle_t** outBufPtr, - bool allowEmptyBuf); - Status importBufferLocked(int32_t streamId, uint64_t bufId, buffer_handle_t buf, /*out*/buffer_handle_t** outBufPtr, @@ -236,106 +303,15 @@ protected: Status processOneCaptureRequest(const CaptureRequest& request); - Status processCaptureResult(std::shared_ptr&); - Status processCaptureRequestError(const std::shared_ptr&); void notifyShutter(uint32_t frameNumber, nsecs_t shutterTs); - void notifyError(uint32_t frameNumber, int32_t streamId, ErrorCode ec); void invokeProcessCaptureResultCallback( hidl_vec &results, bool tryWriteFmq); - static void freeReleaseFences(hidl_vec&); Size getMaxJpegResolution() const; Size getMaxThumbResolution() const; - ssize_t getJpegBufferSize(uint32_t width, uint32_t height) const; - int waitForV4L2BufferReturnLocked(std::unique_lock& lk); - class OutputThread : public android::Thread { - public: - OutputThread(wp parent, CroppingType); - virtual ~OutputThread(); - - Status allocateIntermediateBuffers( - const Size& v4lSize, const Size& thumbSize, - const hidl_vec& streams, - uint32_t blobBufferSize); - Status submitRequest(const std::shared_ptr&); - void flush(); - void dump(int fd); - virtual bool threadLoop() override; - - void setExifMakeModel(const std::string& make, const std::string& model); - - protected: - // Methods to request output buffer in parallel - // No-op for device@3.4. Implemented in device@3.5 - virtual int requestBufferStart(const std::vector&) { return 0; } - virtual int waitForBufferRequestDone( - /*out*/std::vector*) { return 0; } - - static const uint32_t FLEX_YUV_GENERIC = static_cast('F') | - static_cast('L') << 8 | static_cast('E') << 16 | - static_cast('X') << 24; - // returns FLEX_YUV_GENERIC for formats other than YV12/YU12/NV12/NV21 - static uint32_t getFourCcFromLayout(const YCbCrLayout&); - static int getCropRect( - CroppingType ct, const Size& inSize, const Size& outSize, IMapper::Rect* out); - - static const int kFlushWaitTimeoutSec = 3; // 3 sec - static const int kReqWaitTimeoutMs = 33; // 33ms - static const int kReqWaitTimesMax = 90; // 33ms * 90 ~= 3 sec - - void waitForNextRequest(std::shared_ptr* out); - void signalRequestDone(); - - int cropAndScaleLocked( - sp& in, const Size& outSize, - YCbCrLayout* out); - - int cropAndScaleThumbLocked( - sp& in, const Size& outSize, - YCbCrLayout* out); - - int formatConvertLocked(const YCbCrLayout& in, const YCbCrLayout& out, - Size sz, uint32_t format); - - static int encodeJpegYU12(const Size &inSz, - const YCbCrLayout& inLayout, int jpegQuality, - const void *app1Buffer, size_t app1Size, - void *out, size_t maxOutSize, - size_t &actualCodeSize); - - int createJpegLocked(HalStreamBuffer &halBuf, const std::shared_ptr& req); - - const wp mParent; - const CroppingType mCroppingType; - - mutable std::mutex mRequestListLock; // Protect acccess to mRequestList, - // mProcessingRequest and mProcessingFrameNumer - std::condition_variable mRequestCond; // signaled when a new request is submitted - std::condition_variable mRequestDoneCond; // signaled when a request is done processing - std::list> mRequestList; - bool mProcessingRequest = false; - uint32_t mProcessingFrameNumer = 0; - - // V4L2 frameIn - // (MJPG decode)-> mYu12Frame - // (Scale)-> mScaledYu12Frames - // (Format convert) -> output gralloc frames - mutable std::mutex mBufferLock; // Protect access to intermediate buffers - sp mYu12Frame; - sp mYu12ThumbFrame; - std::unordered_map, SizeHasher> mIntermediateBuffers; - std::unordered_map, SizeHasher> mScaledYu12Frames; - YCbCrLayout mYu12FrameLayout; - YCbCrLayout mYu12ThumbFrameLayout; - uint32_t mBlobBufferSize = 0; // 0 -> HAL derive buffer size, else: use given size - - std::string mExifMake; - std::string mExifModel; - }; - // Protect (most of) HIDL interface methods from synchronized-entering mutable Mutex mInterfaceLock; @@ -345,7 +321,7 @@ protected: const common::V1_0::helper::CameraMetadata mCameraCharacteristics; const std::vector mSupportedFormats; const CroppingType mCroppingType; - const std::string& mCameraId; + const std::string mCameraId; // Not protected by mLock, this is almost a const. // Setup in constructor, reset in close() after OutputThread is joined @@ -381,12 +357,6 @@ protected: std::mutex mInflightFramesLock; // protect mInflightFrames std::unordered_set mInflightFrames; - // buffers currently circulating between HAL and camera service - // key: bufferId sent via HIDL interface - // value: imported buffer_handle_t - // Buffer will be imported during processCaptureRequest and will be freed - // when the its stream is deleted or camera device session is closed - typedef std::unordered_map CirculatingBuffers; // Stream ID -> circulating buffers map std::map mCirculatingBuffers; // Protect mCirculatingBuffers, must not lock mLock after acquiring this lock @@ -395,6 +365,8 @@ protected: std::mutex mAfTriggerLock; // protect mAfTrigger bool mAfTrigger = false; + uint32_t mBlobBufferSize = 0; + static HandleImporter sHandleImporter; /* Beginning of members not changed after initialize() */ @@ -410,6 +382,9 @@ protected: const Size mMaxThumbResolution; const Size mMaxJpegResolution; + + std::string mExifMake; + std::string mExifModel; /* End of members not changed after initialize() */ private: @@ -484,4 +459,4 @@ private: } // namespace hardware } // namespace android -#endif // ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICE3SESSION_H +#endif // ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMERADEVICESESSION_H diff --git a/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraDevice_3_4.h b/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraDevice_3_4.h index bd7980780b..88726f4969 100644 --- a/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraDevice_3_4.h +++ b/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraDevice_3_4.h @@ -105,7 +105,7 @@ protected: // Calls into virtual member function. Do not use it in constructor status_t initCameraCharacteristics(); // Init available capabilities keys - status_t initAvailableCapabilities( + virtual status_t initAvailableCapabilities( ::android::hardware::camera::common::V1_0::helper::CameraMetadata*); // Init non-device dependent keys virtual status_t initDefaultCharsKeys( @@ -149,6 +149,7 @@ protected: bool mInitialized = false; bool mInitFailed = false; std::string mCameraId; + std::string mDevicePath; const ExternalCameraConfig& mCfg; std::vector mSupportedFormats; CroppingType mCroppingType; diff --git a/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraUtils.h b/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraUtils.h index 341c62218d..b354406a5b 100644 --- a/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraUtils.h +++ b/camera/device/3.4/default/include/ext_device_v3_4_impl/ExternalCameraUtils.h @@ -17,16 +17,27 @@ #ifndef ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMUTIL_H #define ANDROID_HARDWARE_CAMERA_DEVICE_V3_4_EXTCAMUTIL_H +#include +#include +#include #include #include #include +#include #include #include #include "tinyxml2.h" // XML parsing #include "utils/LightRefBase.h" +#include "utils/Timers.h" +#include +#include -using android::hardware::graphics::mapper::V2_0::IMapper; -using android::hardware::graphics::mapper::V2_0::YCbCrLayout; + +using ::android::hardware::graphics::mapper::V2_0::IMapper; +using ::android::hardware::graphics::mapper::V2_0::YCbCrLayout; +using ::android::hardware::camera::common::V1_0::helper::HandleImporter; +using ::android::hardware::camera::common::V1_0::Status; +using ::android::hardware::camera::device::V3_2::ErrorCode; namespace android { namespace hardware { @@ -57,6 +68,9 @@ struct ExternalCameraConfig { static const char* kDefaultCfgPath; static ExternalCameraConfig loadFromCfg(const char* cfgPath = kDefaultCfgPath); + // CameraId base offset for numerical representation + uint32_t cameraIdOffset; + // List of internal V4L2 video nodes external camera HAL must ignore. std::unordered_set mInternalDevices; @@ -113,16 +127,28 @@ struct SupportedV4L2Format { std::vector frameRates; }; +// A Base class with basic information about a frame +struct Frame : public VirtualLightRefBase { +public: + Frame(uint32_t width, uint32_t height, uint32_t fourcc); + const uint32_t mWidth; + const uint32_t mHeight; + const uint32_t mFourcc; + + // getData might involve map/allocation + virtual int getData(uint8_t** outData, size_t* dataSize) = 0; +}; + // A class provide access to a dequeued V4L2 frame buffer (mostly in MJPG format) // Also contains necessary information to enqueue the buffer back to V4L2 buffer queue -class V4L2Frame : public virtual VirtualLightRefBase { +class V4L2Frame : public Frame { public: V4L2Frame(uint32_t w, uint32_t h, uint32_t fourcc, int bufIdx, int fd, uint32_t dataSize, uint64_t offset); ~V4L2Frame() override; - const uint32_t mWidth; - const uint32_t mHeight; - const uint32_t mFourcc; + + virtual int getData(uint8_t** outData, size_t* dataSize) override; + const int mBufferIndex; // for later enqueue int map(uint8_t** data, size_t* dataSize); int unmap(); @@ -137,13 +163,13 @@ private: // A RAII class representing a CPU allocated YUV frame used as intermeidate buffers // when generating output images. -class AllocatedFrame : public virtual VirtualLightRefBase { +class AllocatedFrame : public Frame { public: - AllocatedFrame(uint32_t w, uint32_t h); // TODO: use Size? + AllocatedFrame(uint32_t w, uint32_t h); // only support V4L2_PIX_FMT_YUV420 for now ~AllocatedFrame() override; - const uint32_t mWidth; - const uint32_t mHeight; - const uint32_t mFourcc; // Only support YU12 format for now + + virtual int getData(uint8_t** outData, size_t* dataSize) override; + int allocate(YCbCrLayout* out = nullptr); int getLayout(YCbCrLayout* out); int getCroppedLayout(const IMapper::Rect&, YCbCrLayout* out); // return non-zero for bad input @@ -165,8 +191,110 @@ const float kMinAspectRatio = 1.f; bool isAspectRatioClose(float ar1, float ar2); +struct HalStreamBuffer { + int32_t streamId; + uint64_t bufferId; + uint32_t width; + uint32_t height; + ::android::hardware::graphics::common::V1_0::PixelFormat format; + ::android::hardware::camera::device::V3_2::BufferUsageFlags usage; + buffer_handle_t* bufPtr; + int acquireFence; + bool fenceTimeout; +}; + +struct HalRequest { + uint32_t frameNumber; + common::V1_0::helper::CameraMetadata setting; + sp frameIn; + nsecs_t shutterTs; + std::vector buffers; +}; + +static const uint64_t BUFFER_ID_NO_BUFFER = 0; + +// buffers currently circulating between HAL and camera service +// key: bufferId sent via HIDL interface +// value: imported buffer_handle_t +// Buffer will be imported during processCaptureRequest (or requestStreamBuffer +// in the case of HAL buffer manager is enabled) and will be freed +// when the stream is deleted or camera device session is closed +typedef std::unordered_map CirculatingBuffers; + +::android::hardware::camera::common::V1_0::Status importBufferImpl( + /*inout*/std::map& circulatingBuffers, + /*inout*/HandleImporter& handleImporter, + int32_t streamId, + uint64_t bufId, buffer_handle_t buf, + /*out*/buffer_handle_t** outBufPtr, + bool allowEmptyBuf); + +static const uint32_t FLEX_YUV_GENERIC = static_cast('F') | + static_cast('L') << 8 | static_cast('E') << 16 | + static_cast('X') << 24; + +// returns FLEX_YUV_GENERIC for formats other than YV12/YU12/NV12/NV21 +uint32_t getFourCcFromLayout(const YCbCrLayout&); + +using ::android::hardware::camera::external::common::Size; +int getCropRect(CroppingType ct, const Size& inSize, + const Size& outSize, IMapper::Rect* out); + +int formatConvert(const YCbCrLayout& in, const YCbCrLayout& out, Size sz, uint32_t format); + +int encodeJpegYU12(const Size &inSz, + const YCbCrLayout& inLayout, int jpegQuality, + const void *app1Buffer, size_t app1Size, + void *out, size_t maxOutSize, + size_t &actualCodeSize); + +Size getMaxThumbnailResolution(const common::V1_0::helper::CameraMetadata&); + +void freeReleaseFences(hidl_vec&); + +status_t fillCaptureResultCommon(common::V1_0::helper::CameraMetadata& md, nsecs_t timestamp, + camera_metadata_ro_entry& activeArraySize); + +// Interface for OutputThread calling back to parent +struct OutputThreadInterface : public virtual RefBase { + virtual ::android::hardware::camera::common::V1_0::Status importBuffer( + int32_t streamId, uint64_t bufId, buffer_handle_t buf, + /*out*/buffer_handle_t** outBufPtr, bool allowEmptyBuf) = 0; + + virtual void notifyError(uint32_t frameNumber, int32_t streamId, ErrorCode ec) = 0; + + // Callbacks are fired within the method if msgs/results are nullptr. + // Otherwise the callbacks will be returned and caller is responsible to + // fire the callback later + virtual ::android::hardware::camera::common::V1_0::Status processCaptureRequestError( + const std::shared_ptr&, + /*out*/std::vector* msgs = nullptr, + /*out*/std::vector* results = nullptr) = 0; + + virtual ::android::hardware::camera::common::V1_0::Status processCaptureResult( + std::shared_ptr&) = 0; + + virtual ssize_t getJpegBufferSize(uint32_t width, uint32_t height) const = 0; +}; + } // namespace implementation } // namespace V3_4 + +namespace V3_6 { +namespace implementation { + +// A CPU copy of a mapped V4L2Frame. Will map the input V4L2 frame. +class AllocatedV4L2Frame : public V3_4::implementation::Frame { +public: + AllocatedV4L2Frame(sp frameIn); + ~AllocatedV4L2Frame() override; + virtual int getData(uint8_t** outData, size_t* dataSize) override; +private: + std::vector mData; +}; + +} // namespace implementation +} // namespace V3_6 } // namespace device } // namespace camera } // namespace hardware diff --git a/camera/device/3.5/default/Android.bp b/camera/device/3.5/default/Android.bp index 43362fd1a9..d106b4b992 100644 --- a/camera/device/3.5/default/Android.bp +++ b/camera/device/3.5/default/Android.bp @@ -17,13 +17,13 @@ cc_library_headers { name: "camera.device@3.5-impl_headers", vendor: true, - export_include_dirs: ["include/device_v3_5_impl"] + export_include_dirs: ["include/device_v3_5_impl"], } cc_library_headers { name: "camera.device@3.5-external-impl_headers", vendor: true, - export_include_dirs: ["include/ext_device_v3_5_impl"] + export_include_dirs: ["include/ext_device_v3_5_impl"], } cc_library_shared { @@ -49,7 +49,9 @@ cc_library_shared { "android.hardware.camera.provider@2.4", "android.hardware.graphics.mapper@2.0", "android.hardware.graphics.mapper@3.0", + "android.hardware.graphics.mapper@4.0", "liblog", + "libgralloctypes", "libhardware", "libcamera_metadata", ], @@ -81,8 +83,10 @@ cc_library_shared { "android.hardware.camera.device@3.5", "android.hardware.camera.provider@2.4", "android.hardware.graphics.mapper@2.0", - "android.hardware.graphics.mapper@3.0", + "android.hardware.graphics.mapper@3.0", + "android.hardware.graphics.mapper@4.0", "liblog", + "libgralloctypes", "libhardware", "libcamera_metadata", "libfmq", @@ -90,7 +94,7 @@ cc_library_shared { "libyuv", "libjpeg", "libexif", - "libtinyxml2" + "libtinyxml2", ], static_libs: [ "android.hardware.camera.common@1.0-helper", diff --git a/camera/device/3.5/default/ExternalCameraDeviceSession.cpp b/camera/device/3.5/default/ExternalCameraDeviceSession.cpp index 00c1d0de39..287ac324ec 100644 --- a/camera/device/3.5/default/ExternalCameraDeviceSession.cpp +++ b/camera/device/3.5/default/ExternalCameraDeviceSession.cpp @@ -80,7 +80,7 @@ Status ExternalCameraDeviceSession::importRequestLocked( ExternalCameraDeviceSession::BufferRequestThread::BufferRequestThread( - wp parent, + wp parent, sp callbacks) : mParent(parent), mCallbacks(callbacks) {} @@ -254,7 +254,8 @@ void ExternalCameraDeviceSession::initOutputThread() { mBufferRequestThread = new BufferRequestThread(this, mCallback_3_5); mBufferRequestThread->run("ExtCamBufReq", PRIORITY_DISPLAY); } - mOutputThread = new OutputThread(this, mCroppingType, mBufferRequestThread); + mOutputThread = new OutputThread( + this, mCroppingType, mCameraCharacteristics, mBufferRequestThread); } void ExternalCameraDeviceSession::closeOutputThreadImpl() { @@ -271,10 +272,11 @@ void ExternalCameraDeviceSession::closeOutputThread() { } ExternalCameraDeviceSession::OutputThread::OutputThread( - wp parent, + wp parent, CroppingType ct, + const common::V1_0::helper::CameraMetadata& chars, sp bufReqThread) : - V3_4::implementation::ExternalCameraDeviceSession::OutputThread(parent, ct), + V3_4::implementation::ExternalCameraDeviceSession::OutputThread(parent, ct, chars), mBufferRequestThread(bufReqThread) {} ExternalCameraDeviceSession::OutputThread::~OutputThread() {} diff --git a/camera/device/3.5/default/include/ext_device_v3_5_impl/ExternalCameraDeviceSession.h b/camera/device/3.5/default/include/ext_device_v3_5_impl/ExternalCameraDeviceSession.h index 281f93a13b..e89ef45f52 100644 --- a/camera/device/3.5/default/include/ext_device_v3_5_impl/ExternalCameraDeviceSession.h +++ b/camera/device/3.5/default/include/ext_device_v3_5_impl/ExternalCameraDeviceSession.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef ANDROID_HARDWARE_CAMERA_DEVICE_V3_5_EXTCAMERADEVICE3SESSION_H -#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_5_EXTCAMERADEVICE3SESSION_H +#ifndef ANDROID_HARDWARE_CAMERA_DEVICE_V3_5_EXTCAMERADEVICESESSION_H +#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_5_EXTCAMERADEVICESESSION_H #include #include @@ -72,6 +72,7 @@ using ::android::base::unique_fd; using ::android::hardware::camera::device::V3_4::implementation::SupportedV4L2Format; using ::android::hardware::camera::device::V3_4::implementation::CroppingType; +using ::android::hardware::camera::device::V3_4::implementation::HalStreamBuffer; struct ExternalCameraDeviceSession : public V3_4::implementation::ExternalCameraDeviceSession { @@ -97,6 +98,62 @@ struct ExternalCameraDeviceSession : public V3_4::implementation::ExternalCamera config, supportedFormats, devCfg); } + class BufferRequestThread : public android::Thread { + public: + BufferRequestThread( + wp parent, + sp callbacks); + + int requestBufferStart(const std::vector&); + int waitForBufferRequestDone( + /*out*/std::vector*); + + virtual bool threadLoop() override; + + private: + void waitForNextRequest(); + + const wp mParent; + const sp mCallbacks; + + std::mutex mLock; + bool mRequestingBuffer = false; + + std::vector mBufferReqs; + std::vector mPendingReturnBufferReqs; + // mHalBufferReqs is not under mLock protection during the HIDL transaction + hidl_vec mHalBufferReqs; + + // request buffers takes much less time in steady state, but can take much longer + // when requesting 1st buffer from a stream. + // TODO: consider a separate timeout for new vs. steady state? + // TODO: or make sure framework is warming up the pipeline during configure new stream? + static const int kReqProcTimeoutMs = 66; + + static const int kReqWaitTimeoutMs = 33; + static const int kReqWaitTimesWarn = 90; // 33ms * 90 ~= 3 sec + std::condition_variable mRequestCond; // signaled when a new buffer request incoming + std::condition_variable mRequestDoneCond; // signaled when a request is done + }; + + class OutputThread : + public V3_4::implementation::ExternalCameraDeviceSession::OutputThread { + public: + // TODO: pass buffer request thread to OutputThread ctor + OutputThread(wp parent, CroppingType, + const common::V1_0::helper::CameraMetadata&, + sp bufReqThread); + virtual ~OutputThread(); + + protected: + // Methods to request output buffer in parallel + virtual int requestBufferStart(const std::vector&) override; + virtual int waitForBufferRequestDone( + /*out*/std::vector*) override; + + const sp mBufferRequestThread; + }; + protected: // Methods from v3.4 and earlier will trampoline to inherited implementation Return configureStreams_3_5( @@ -120,63 +177,8 @@ protected: hidl_vec& allBufPtrs, hidl_vec& allFences) override; - class BufferRequestThread : public android::Thread { - public: - BufferRequestThread( - wp parent, - sp callbacks); - - int requestBufferStart(const std::vector&); - int waitForBufferRequestDone( - /*out*/std::vector*); - - virtual bool threadLoop() override; - - private: - void waitForNextRequest(); - - const wp mParent; - const sp mCallbacks; - - std::mutex mLock; - bool mRequestingBuffer = false; - - std::vector mBufferReqs; - std::vector mPendingReturnBufferReqs; - // mHalBufferReqs is not under mLock protection during the HIDL transaction - hidl_vec mHalBufferReqs; - - // request buffers takes much less time in steady state, but can take much longer - // when requesting 1st buffer from a stream. - // TODO: consider a separate timeout for new vs. steady state? - // TODO: or make sure framework is warming up the pipeline during configure new stream? - static const int kReqProcTimeoutMs = 66; - - static const int kReqWaitTimeoutMs = 33; - static const int kReqWaitTimesWarn = 90; // 33ms * 90 ~= 3 sec - std::condition_variable mRequestCond; // signaled when a new buffer request incoming - std::condition_variable mRequestDoneCond; // signaled when a request is done - }; - sp mBufferRequestThread; - class OutputThread : - public V3_4::implementation::ExternalCameraDeviceSession::OutputThread { - public: - // TODO: pass buffer request thread to OutputThread ctor - OutputThread(wp parent, CroppingType, - sp bufReqThread); - virtual ~OutputThread(); - - protected: - // Methods to request output buffer in parallel - virtual int requestBufferStart(const std::vector&) override; - virtual int waitForBufferRequestDone( - /*out*/std::vector*) override; - - const sp mBufferRequestThread; - }; - sp mCallback_3_5; bool mSupportBufMgr; @@ -270,4 +272,4 @@ private: } // namespace hardware } // namespace android -#endif // ANDROID_HARDWARE_CAMERA_DEVICE_V3_5_EXTCAMERADEVICE3SESSION_H +#endif // ANDROID_HARDWARE_CAMERA_DEVICE_V3_5_EXTCAMERADEVICESESSION_H diff --git a/camera/device/3.5/types.hal b/camera/device/3.5/types.hal index 6d861e2e72..38493b4f72 100644 --- a/camera/device/3.5/types.hal +++ b/camera/device/3.5/types.hal @@ -23,7 +23,8 @@ import @3.2::CameraBlobId; /** * If the result metadata cannot be produced for a physical camera device part of a logical * multi-camera, then HAL must invoke the notification callback and pass a message with ERROR_RESULT - * code and errorStreamId that contains the stream id associated with that physical device. + * code and errorStreamId that contains the stream id associated with that physical device. Such + * callback must be made before the final processCaptureResult() call for the corresponding request. * The behavior during absent result metadata remains unchanged for a logical or a non-logical * camera device and the errorStreamId must be set to -1. */ diff --git a/camera/device/3.6/Android.bp b/camera/device/3.6/Android.bp new file mode 100644 index 0000000000..e6f458ca5f --- /dev/null +++ b/camera/device/3.6/Android.bp @@ -0,0 +1,22 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.camera.device@3.6", + root: "android.hardware", + srcs: [ + "types.hal", + "ICameraDevice.hal", + "ICameraDeviceSession.hal", + "ICameraOfflineSession.hal", + ], + interfaces: [ + "android.hardware.camera.common@1.0", + "android.hardware.camera.device@3.2", + "android.hardware.camera.device@3.3", + "android.hardware.camera.device@3.4", + "android.hardware.camera.device@3.5", + "android.hardware.graphics.common@1.0", + "android.hidl.base@1.0", + ], + gen_java: false, +} diff --git a/camera/device/3.6/ICameraDevice.hal b/camera/device/3.6/ICameraDevice.hal new file mode 100644 index 0000000000..e859606376 --- /dev/null +++ b/camera/device/3.6/ICameraDevice.hal @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 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.camera.device@3.6; + +import @3.5::ICameraDevice; + +/** + * Camera device interface + * + * Supports the android.hardware.Camera API, and the android.hardware.camera2 + * API at LIMITED or better hardware level. + * + * ICameraDevice.open() must return @3.2::ICameraDeviceSession or + * @3.5::ICameraDeviceSession or @3.6::ICameraDeviceSession. + */ +interface ICameraDevice extends @3.5::ICameraDevice { +}; diff --git a/camera/device/3.6/ICameraDeviceSession.hal b/camera/device/3.6/ICameraDeviceSession.hal new file mode 100644 index 0000000000..00ebcc3382 --- /dev/null +++ b/camera/device/3.6/ICameraDeviceSession.hal @@ -0,0 +1,132 @@ +/* + * 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.camera.device@3.6; + +import android.hardware.camera.common@1.0::Status; +import @3.5::ICameraDeviceSession; +import @3.5::StreamConfiguration; +import ICameraOfflineSession; + +/** + * Camera device active session interface. + * + * Obtained via ICameraDevice::open(), this interface contains the methods to + * configure and request captures from an active camera device. + */ +interface ICameraDeviceSession extends @3.5::ICameraDeviceSession { + /** + * configureStreams_3_6: + * + * Identical to @3.5::ICameraDeviceSession.configureStreams, except that: + * + * - a boolean supportOffline is added to HalStreamConfiguration to indicate + * if this stream can be switched to offline mode later. + * + * @return status Status code for the operation, one of: + * OK: + * On successful stream configuration. + * INTERNAL_ERROR: + * If there has been a fatal error and the device is no longer + * operational. Only close() can be called successfully by the + * framework after this error is returned. + * ILLEGAL_ARGUMENT: + * If the requested stream configuration is invalid. Some examples + * of invalid stream configurations include: + * - Including more than 1 INPUT stream + * - Not including any OUTPUT streams + * - Including streams with unsupported formats, or an unsupported + * size for that format. + * - Including too many output streams of a certain format. + * - Unsupported rotation configuration + * - Stream sizes/formats don't satisfy the + * StreamConfigurationMode requirements + * for non-NORMAL mode, or the requested operation_mode is not + * supported by the HAL. + * - Unsupported usage flag + * The camera service cannot filter out all possible illegal stream + * configurations, since some devices may support more simultaneous + * streams or larger stream resolutions than the minimum required + * for a given camera device hardware level. The HAL must return an + * ILLEGAL_ARGUMENT for any unsupported stream set, and then be + * ready to accept a future valid stream configuration in a later + * configureStreams call. + * @return halConfiguration The stream parameters desired by the HAL for + * each stream, including maximum buffers, the usage flags, and the + * override format. + */ + configureStreams_3_6(@3.5::StreamConfiguration requestedConfiguration) + generates (Status status, HalStreamConfiguration halConfiguration); + + /** + * switchToOffline: + * + * Switch the current running session from actively streaming mode to the + * offline mode. See ICameraOfflineSession for more details. + * + * The streamsToKeep argument contains list of streams IDs where application + * still needs its output. For all streams application does not need anymore, + * camera HAL can send ERROR_BUFFER to speed up the transition, or even send + * ERROR_REQUEST if all output targets of a request is not needed. By the + * time this call returns, camera HAL must have returned all buffers coming + * from streams no longer needed and have erased buffer caches of such streams. + * + * For all requests that are going to be transferred to offline session, + * the ICameraDeviceSession is responsible to capture all input buffers from + * the image sensor before the switchToOffline call returns. Before + * switchToOffline returns, camera HAL must have completed all requests not + * switching to offline mode, and collected information on what streams and + * requests are going to continue in the offline session, in the + * offlineSessionInfo output argument. + * + * If there are no requests qualified to be transferred to offline session, + * the camera HAL must return a null ICameraOfflineSession object with OK + * status. In this scenario, the camera HAL still must flush all inflight + * requests and unconfigure all streams before returning this call. + * + * After switchToOffline returns, the ICameraDeviceSession must be back to + * unconfigured state as if it is just created and no streams are configured. + * Also, camera HAL must not call any methods in ICameraDeviceCallback since + * all unfinished requests are now transferred to the offline session. + * After the call returns, camera service may then call close to close + * the camera device, or call configureStream* again to reconfigure the + * camera and then send new capture requests with processCaptureRequest. In + * the latter case, it is legitimate for camera HAL to call methods in + * ICameraDeviceCallback again in response to the newly submitted capture + * requests. + * + * @return status Status code for the operation, one of: + * OK: + * On switching to offline session and unconfiguring streams + * successfully. + * ILLEGAL_ARGUMENT: + * If camera does not support offline mode in any one of streams + * in streamsToKeep argument. Note that the camera HAL must report + * if a stream supports offline mode in HalStreamConfiguration + * output of configureStreams_3_6 method. If all streams in + * streamsToKeep argument support offline mode, then the camera HAL + * must not return this error. + * + * + * @return offlineSessionInfo Information on what streams and requests will + * be transferred to offline session to continue processing. + * + * @return offlineSession The offline session object camera service will use + * to interact with. + */ + switchToOffline(vec streamsToKeep) generates (Status status, + CameraOfflineSessionInfo offlineSessionInfo, ICameraOfflineSession offlineSession); +}; diff --git a/camera/device/3.6/ICameraOfflineSession.hal b/camera/device/3.6/ICameraOfflineSession.hal new file mode 100644 index 0000000000..03cea64a0c --- /dev/null +++ b/camera/device/3.6/ICameraOfflineSession.hal @@ -0,0 +1,80 @@ +/* + * 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.camera.device@3.6; + +import @3.5::ICameraDeviceCallback; + +/** + * Camera device offline session interface. + * + * Obtained via ICameraDeviceSession::switchToOffline(), this interface contains + * the methods and callback interfaces that define how camera service interacts + * with an offline session. + * + * An offline session contains some unfinished capture requests that were submitted + * to the parent ICameraDeviceSession before calling switchToOffline, and is + * responsible for delivering these capture results back to camera service regardless + * of whether the parent camera device is still opened or not. An offline session must + * not have access to the camera device's image sensor. During switchToOffline + * call, camera HAL must capture all necessary frames from the image sensor that + * is needed for completing the requests offline later. + */ +interface ICameraOfflineSession { + /** + * Set the callbacks for offline session to communicate with camera service. + * + * Offline session is responsible to store all callbacks the camera HAL + * generated after the return of ICameraDeviceSession::switchToOffline, and + * send them to camera service once this method is called. + * + * Camera service must not call this method more than once, so these + * callbacks can be assumed to be constant after the first setCallback call. + */ + setCallback(ICameraDeviceCallback cb); + + /** + * getCaptureResultMetadataQueue: + * + * Retrieves the queue used along with + * ICameraDeviceCallback#processCaptureResult. + * + * Clients to ICameraOfflineSession must: + * - Call getCaptureRequestMetadataQueue to retrieve the fast message queue; + * - In implementation of ICameraDeviceCallback, test whether + * .fmqResultSize field is zero. + * - If .fmqResultSize != 0, read result metadata from the fast message + * queue; + * - otherwise, read result metadata in CaptureResult.result. + * + * @return queue the queue that implementation writes result metadata to. + */ + getCaptureResultMetadataQueue() generates (fmq_sync queue); + + /** + * Close the offline session and release all resources. + * + * Camera service may call this method before or after the offline session + * has finished all requests it needs to handle. If there are still unfinished + * requests when close is called, camera HAL must send ERROR_REQUEST for + * all unfinished requests and return all buffers via + * ICameraDeviceCallback#processCaptureResult or + * ICameraDeviceCallback#returnStreamBuffers. + * Also, all buffer caches maintained by the offline session must be erased + * before the close call returns. + */ + close(); +}; diff --git a/camera/device/3.6/default/Android.bp b/camera/device/3.6/default/Android.bp new file mode 100644 index 0000000000..2871e2a1ad --- /dev/null +++ b/camera/device/3.6/default/Android.bp @@ -0,0 +1,68 @@ +// +// Copyright (C) 2020 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_headers { + name: "camera.device@3.6-external-impl_headers", + vendor: true, + export_include_dirs: ["include/ext_device_v3_6_impl"], +} + +cc_library_shared { + name: "camera.device@3.6-external-impl", + defaults: ["hidl_defaults"], + proprietary: true, + vendor: true, + srcs: [ + "ExternalCameraDevice.cpp", + "ExternalCameraDeviceSession.cpp", + "ExternalCameraOfflineSession.cpp", + ], + shared_libs: [ + "libhidlbase", + "libutils", + "libcutils", + "camera.device@3.2-impl", + "camera.device@3.3-impl", + "camera.device@3.4-external-impl", + "camera.device@3.5-external-impl", + "android.hardware.camera.device@3.2", + "android.hardware.camera.device@3.3", + "android.hardware.camera.device@3.4", + "android.hardware.camera.device@3.5", + "android.hardware.camera.device@3.6", + "android.hardware.camera.provider@2.4", + "android.hardware.graphics.mapper@2.0", + "android.hardware.graphics.mapper@3.0", + "android.hardware.graphics.mapper@4.0", + "liblog", + "libgralloctypes", + "libhardware", + "libcamera_metadata", + "libfmq", + "libsync", + "libyuv", + "libjpeg", + "libexif", + "libtinyxml2", + ], + static_libs: [ + "android.hardware.camera.common@1.0-helper", + ], + local_include_dirs: ["include/ext_device_v3_6_impl"], + export_shared_lib_headers: [ + "libfmq", + ], +} diff --git a/camera/device/3.6/default/ExternalCameraDevice.cpp b/camera/device/3.6/default/ExternalCameraDevice.cpp new file mode 100644 index 0000000000..244c7dd460 --- /dev/null +++ b/camera/device/3.6/default/ExternalCameraDevice.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2020 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 "ExtCamDev@3.6" +//#define LOG_NDEBUG 0 +#include + +#include "ExternalCameraDevice_3_6.h" + +namespace android { +namespace hardware { +namespace camera { +namespace device { +namespace V3_6 { +namespace implementation { + +ExternalCameraDevice::ExternalCameraDevice( + const std::string& cameraId, const ExternalCameraConfig& cfg) : + V3_5::implementation::ExternalCameraDevice(cameraId, cfg) {} + +ExternalCameraDevice::~ExternalCameraDevice() {} + +sp ExternalCameraDevice::createSession( + const sp& cb, + const ExternalCameraConfig& cfg, + const std::vector& sortedFormats, + const CroppingType& croppingType, + const common::V1_0::helper::CameraMetadata& chars, + const std::string& cameraId, + unique_fd v4l2Fd) { + return new ExternalCameraDeviceSession( + cb, cfg, sortedFormats, croppingType, chars, cameraId, std::move(v4l2Fd)); +} + +#define UPDATE(tag, data, size) \ +do { \ + if (metadata->update((tag), (data), (size))) { \ + ALOGE("Update " #tag " failed!"); \ + return -EINVAL; \ + } \ +} while (0) + +status_t ExternalCameraDevice::initAvailableCapabilities( + ::android::hardware::camera::common::V1_0::helper::CameraMetadata* metadata) { + status_t res = + V3_4::implementation::ExternalCameraDevice::initAvailableCapabilities(metadata); + + if (res != OK) { + return res; + } + + camera_metadata_entry caps = metadata->find(ANDROID_REQUEST_AVAILABLE_CAPABILITIES); + std::vector availableCapabilities; + + for (size_t i = 0; i < caps.count; i++) { + uint8_t capability = caps.data.u8[i]; + availableCapabilities.push_back(capability); + } + + // Add OFFLINE_PROCESSING capability to device 3.6 + availableCapabilities.push_back(ANDROID_REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING); + + UPDATE(ANDROID_REQUEST_AVAILABLE_CAPABILITIES, + availableCapabilities.data(), + availableCapabilities.size()); + + return OK; +} + +#undef UPDATE + +} // namespace implementation +} // namespace V3_6 +} // namespace device +} // namespace camera +} // namespace hardware +} // namespace android + diff --git a/camera/device/3.6/default/ExternalCameraDeviceSession.cpp b/camera/device/3.6/default/ExternalCameraDeviceSession.cpp new file mode 100644 index 0000000000..8fd8e5897b --- /dev/null +++ b/camera/device/3.6/default/ExternalCameraDeviceSession.cpp @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2020 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 "ExtCamDevSsn@3.6" +#include + +#include +#include "ExternalCameraDeviceSession.h" + +namespace android { +namespace hardware { +namespace camera { +namespace device { +namespace V3_6 { +namespace implementation { + +ExternalCameraDeviceSession::ExternalCameraDeviceSession( + const sp& callback, + const ExternalCameraConfig& cfg, + const std::vector& sortedFormats, + const CroppingType& croppingType, + const common::V1_0::helper::CameraMetadata& chars, + const std::string& cameraId, + unique_fd v4l2Fd) : + V3_5::implementation::ExternalCameraDeviceSession( + callback, cfg, sortedFormats, croppingType, chars, cameraId, std::move(v4l2Fd)) { +} + +ExternalCameraDeviceSession::~ExternalCameraDeviceSession() {} + + +Return ExternalCameraDeviceSession::configureStreams_3_6( + const StreamConfiguration& requestedConfiguration, + ICameraDeviceSession::configureStreams_3_6_cb _hidl_cb) { + V3_2::StreamConfiguration config_v32; + V3_3::HalStreamConfiguration outStreams_v33; + V3_6::HalStreamConfiguration outStreams; + const V3_4::StreamConfiguration& requestedConfiguration_3_4 = requestedConfiguration.v3_4; + Mutex::Autolock _il(mInterfaceLock); + + config_v32.operationMode = requestedConfiguration_3_4.operationMode; + config_v32.streams.resize(requestedConfiguration_3_4.streams.size()); + uint32_t blobBufferSize = 0; + int numStallStream = 0; + for (size_t i = 0; i < config_v32.streams.size(); i++) { + config_v32.streams[i] = requestedConfiguration_3_4.streams[i].v3_2; + if (config_v32.streams[i].format == PixelFormat::BLOB) { + blobBufferSize = requestedConfiguration_3_4.streams[i].bufferSize; + numStallStream++; + } + } + + // Fail early if there are multiple BLOB streams + if (numStallStream > kMaxStallStream) { + ALOGE("%s: too many stall streams (expect <= %d, got %d)", __FUNCTION__, + kMaxStallStream, numStallStream); + _hidl_cb(Status::ILLEGAL_ARGUMENT, outStreams); + return Void(); + } + + Status status = configureStreams(config_v32, &outStreams_v33, blobBufferSize); + + fillOutputStream3_6(outStreams_v33, &outStreams); + + _hidl_cb(status, outStreams); + return Void(); +} + +Return ExternalCameraDeviceSession::switchToOffline( + const hidl_vec& streamsToKeep, + ICameraDeviceSession::switchToOffline_cb _hidl_cb) { + std::vector msgs; + std::vector results; + CameraOfflineSessionInfo info; + sp session; + + Status st = switchToOffline(streamsToKeep, &msgs, &results, &info, &session); + + mCallback->notify(msgs); + hidl_vec hidlResults(std::move(results)); + invokeProcessCaptureResultCallback(hidlResults, /* tryWriteFmq */true); + V3_4::implementation::freeReleaseFences(hidlResults); + + _hidl_cb(st, info, session); + return Void(); +} + +void ExternalCameraDeviceSession::fillOutputStream3_6( + const V3_3::HalStreamConfiguration& outStreams_v33, + /*out*/V3_6::HalStreamConfiguration* outStreams_v36) { + if (outStreams_v36 == nullptr) { + ALOGE("%s: outStreams_v36 must not be null!", __FUNCTION__); + return; + } + Mutex::Autolock _l(mLock); + outStreams_v36->streams.resize(outStreams_v33.streams.size()); + for (size_t i = 0; i < outStreams_v36->streams.size(); i++) { + outStreams_v36->streams[i].v3_4.v3_3 = outStreams_v33.streams[i]; + outStreams_v36->streams[i].supportOffline = + supportOfflineLocked(outStreams_v33.streams[i].v3_2.id); + } +} + +bool ExternalCameraDeviceSession::supportOfflineLocked(int32_t streamId) { + const Stream& stream = mStreamMap[streamId]; + if (stream.format == PixelFormat::BLOB && + stream.dataSpace == static_cast(Dataspace::V0_JFIF)) { + return true; + } + // TODO: support YUV output stream? + return false; +} + +bool ExternalCameraDeviceSession::canDropRequest(const hidl_vec& offlineStreams, + std::shared_ptr halReq) { + for (const auto& buffer : halReq->buffers) { + for (auto offlineStreamId : offlineStreams) { + if (buffer.streamId == offlineStreamId) { + return false; + } + } + } + // Only drop a request completely if it has no offline output + return true; +} + +void ExternalCameraDeviceSession::fillOfflineSessionInfo(const hidl_vec& offlineStreams, + std::deque>& offlineReqs, + const std::map& circulatingBuffers, + /*out*/CameraOfflineSessionInfo* info) { + if (info == nullptr) { + ALOGE("%s: output info must not be null!", __FUNCTION__); + return; + } + + info->offlineStreams.resize(offlineStreams.size()); + info->offlineRequests.resize(offlineReqs.size()); + + // Fill in offline reqs and count outstanding buffers + for (size_t i = 0; i < offlineReqs.size(); i++) { + info->offlineRequests[i].frameNumber = offlineReqs[i]->frameNumber; + info->offlineRequests[i].pendingStreams.resize(offlineReqs[i]->buffers.size()); + for (size_t bIdx = 0; bIdx < offlineReqs[i]->buffers.size(); bIdx++) { + int32_t streamId = offlineReqs[i]->buffers[bIdx].streamId; + info->offlineRequests[i].pendingStreams[bIdx] = streamId; + } + } + + for (size_t i = 0; i < offlineStreams.size(); i++) { + int32_t streamId = offlineStreams[i]; + info->offlineStreams[i].id = streamId; + // outstanding buffers are 0 since we are doing hal buffer management and + // offline session will ask for those buffers later + info->offlineStreams[i].numOutstandingBuffers = 0; + const CirculatingBuffers& bufIdMap = circulatingBuffers.at(streamId); + info->offlineStreams[i].circulatingBufferIds.resize(bufIdMap.size()); + size_t bIdx = 0; + for (const auto& pair : bufIdMap) { + // Fill in bufferId + info->offlineStreams[i].circulatingBufferIds[bIdx++] = pair.first; + } + + } +} + +Status ExternalCameraDeviceSession::switchToOffline(const hidl_vec& offlineStreams, + /*out*/std::vector* msgs, + /*out*/std::vector* results, + /*out*/CameraOfflineSessionInfo* info, + /*out*/sp* session) { + ATRACE_CALL(); + if (offlineStreams.size() > 1) { + ALOGE("%s: more than one offline stream is not supported", __FUNCTION__); + return Status::ILLEGAL_ARGUMENT; + } + + if (msgs == nullptr || results == nullptr || info == nullptr || session == nullptr) { + ALOGE("%s: output arguments (%p, %p, %p, %p) must not be null", __FUNCTION__, + msgs, results, info, session); + return Status::ILLEGAL_ARGUMENT; + } + + msgs->clear(); + results->clear(); + + Mutex::Autolock _il(mInterfaceLock); + Status status = initStatus(); + if (status != Status::OK) { + return status; + } + + Mutex::Autolock _l(mLock); + for (auto streamId : offlineStreams) { + if (!supportOfflineLocked(streamId)) { + return Status::ILLEGAL_ARGUMENT; + } + } + + // pause output thread and get all remaining inflight requests + auto remainingReqs = mOutputThread->switchToOffline(); + std::vector> halReqs; + + // Send out buffer/request error for remaining requests and filter requests + // to be handled in offline mode + for (auto& halReq : remainingReqs) { + bool dropReq = canDropRequest(offlineStreams, halReq); + if (dropReq) { + // Request is dropped completely. Just send request error and + // there is no need to send the request to offline session + processCaptureRequestError(halReq, msgs, results); + continue; + } + + // All requests reach here must have at least one offline stream output + NotifyMsg shutter; + shutter.type = MsgType::SHUTTER; + shutter.msg.shutter.frameNumber = halReq->frameNumber; + shutter.msg.shutter.timestamp = halReq->shutterTs; + msgs->push_back(shutter); + + std::vector offlineBuffers; + for (const auto& buffer : halReq->buffers) { + bool dropBuffer = true; + for (auto offlineStreamId : offlineStreams) { + if (buffer.streamId == offlineStreamId) { + dropBuffer = false; + break; + } + } + if (dropBuffer) { + NotifyMsg error; + error.type = MsgType::ERROR; + error.msg.error.frameNumber = halReq->frameNumber; + error.msg.error.errorStreamId = buffer.streamId; + error.msg.error.errorCode = ErrorCode::ERROR_BUFFER; + msgs->push_back(error); + + CaptureResult result; + result.frameNumber = halReq->frameNumber; + result.partialResult = 0; // buffer only result + result.inputBuffer.streamId = -1; + result.outputBuffers.resize(1); + result.outputBuffers[0].streamId = buffer.streamId; + result.outputBuffers[0].bufferId = buffer.bufferId; + result.outputBuffers[0].status = BufferStatus::ERROR; + if (buffer.acquireFence >= 0) { + native_handle_t* handle = native_handle_create(/*numFds*/1, /*numInts*/0); + handle->data[0] = buffer.acquireFence; + result.outputBuffers[0].releaseFence.setTo(handle, /*shouldOwn*/false); + } + results->push_back(result); + } else { + offlineBuffers.push_back(buffer); + } + } + halReq->buffers = offlineBuffers; + halReqs.push_back(halReq); + } + + // convert hal requests to offline request + std::deque> offlineReqs(halReqs.size()); + size_t i = 0; + for (auto& v4lReq : halReqs) { + offlineReqs[i] = std::make_shared(); + offlineReqs[i]->frameNumber = v4lReq->frameNumber; + offlineReqs[i]->setting = v4lReq->setting; + offlineReqs[i]->shutterTs = v4lReq->shutterTs; + offlineReqs[i]->buffers = v4lReq->buffers; + sp v4l2Frame = + static_cast(v4lReq->frameIn.get()); + offlineReqs[i]->frameIn = new AllocatedV4L2Frame(v4l2Frame); + i++; + // enqueue V4L2 frame + enqueueV4l2Frame(v4l2Frame); + } + + // Collect buffer caches/streams + hidl_vec streamInfos; + streamInfos.resize(offlineStreams.size()); + std::map circulatingBuffers; + { + Mutex::Autolock _l(mCbsLock); + size_t idx = 0; + for(auto streamId : offlineStreams) { + circulatingBuffers[streamId] = mCirculatingBuffers.at(streamId); + mCirculatingBuffers.erase(streamId); + streamInfos[idx++] = mStreamMap.at(streamId); + mStreamMap.erase(streamId); + } + } + + fillOfflineSessionInfo(offlineStreams, offlineReqs, circulatingBuffers, info); + + // create the offline session object + bool afTrigger; + { + std::lock_guard lk(mAfTriggerLock); + afTrigger = mAfTrigger; + } + sp sessionImpl = new ExternalCameraOfflineSession( + mCroppingType, mCameraCharacteristics, mCameraId, + mExifMake, mExifModel, mBlobBufferSize, afTrigger, + streamInfos, offlineReqs, circulatingBuffers); + + bool initFailed = sessionImpl->initialize(); + if (initFailed) { + ALOGE("%s: offline session initialize failed!", __FUNCTION__); + return Status::INTERNAL_ERROR; + } + + // cleanup stream and buffer caches + { + Mutex::Autolock _l(mCbsLock); + for(auto pair : mStreamMap) { + cleanupBuffersLocked(/*Stream ID*/pair.first); + } + mCirculatingBuffers.clear(); + } + mStreamMap.clear(); + + // update inflight records + { + std::lock_guard lk(mInflightFramesLock); + mInflightFrames.clear(); + } + + // stop v4l2 streaming + if (v4l2StreamOffLocked() !=0) { + ALOGE("%s: stop V4L2 streaming failed!", __FUNCTION__); + return Status::INTERNAL_ERROR; + } + + // No need to return session if there is no offline requests left + if (offlineReqs.size() != 0) { + *session = sessionImpl->getInterface(); + } else { + *session = nullptr; + } + return Status::OK; +} + +} // namespace implementation +} // namespace V3_6 +} // namespace device +} // namespace camera +} // namespace hardware +} // namespace android diff --git a/camera/device/3.6/default/ExternalCameraOfflineSession.cpp b/camera/device/3.6/default/ExternalCameraOfflineSession.cpp new file mode 100644 index 0000000000..e606fda832 --- /dev/null +++ b/camera/device/3.6/default/ExternalCameraOfflineSession.cpp @@ -0,0 +1,554 @@ +/* + * Copyright (C) 2020 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 "ExtCamOfflnSsn@3.6" +#define ATRACE_TAG ATRACE_TAG_CAMERA +#include + +#include +#include + +#define HAVE_JPEG // required for libyuv.h to export MJPEG decode APIs +#include + +#include +#include "ExternalCameraOfflineSession.h" + +namespace { + +// Size of request/result metadata fast message queue. Change to 0 to always use hwbinder buffer. +static constexpr size_t kMetadataMsgQueueSize = 1 << 18 /* 256kB */; + +} // anonymous namespace + +namespace android { +namespace hardware { +namespace camera { +namespace device { +namespace V3_6 { +namespace implementation { + +// static instance +HandleImporter ExternalCameraOfflineSession::sHandleImporter; + +using V3_5::implementation::ExternalCameraDeviceSession; + +ExternalCameraOfflineSession::ExternalCameraOfflineSession( + const CroppingType& croppingType, + const common::V1_0::helper::CameraMetadata& chars, + const std::string& cameraId, + const std::string& exifMake, + const std::string& exifModel, + const uint32_t blobBufferSize, + const bool afTrigger, + const hidl_vec& offlineStreams, + std::deque>& offlineReqs, + const std::map& circulatingBuffers) : + mCroppingType(croppingType), mChars(chars), mCameraId(cameraId), + mExifMake(exifMake), mExifModel(exifModel), mBlobBufferSize(blobBufferSize), + mAfTrigger(afTrigger), mOfflineStreams(offlineStreams), mOfflineReqs(offlineReqs), + mCirculatingBuffers(circulatingBuffers) {} + +ExternalCameraOfflineSession::~ExternalCameraOfflineSession() { + close(); +} + +bool ExternalCameraOfflineSession::initialize() { + mResultMetadataQueue = std::make_shared( + kMetadataMsgQueueSize, false /* non blocking */); + if (!mResultMetadataQueue->isValid()) { + ALOGE("%s: invalid result fmq", __FUNCTION__); + return true; + } + return false; +} + +void ExternalCameraOfflineSession::initOutputThread() { + if (mOutputThread != nullptr) { + ALOGE("%s: OutputThread already exist!", __FUNCTION__); + return; + } + + mBufferRequestThread = new ExternalCameraDeviceSession::BufferRequestThread( + this, mCallback); + mBufferRequestThread->run("ExtCamBufReq", PRIORITY_DISPLAY); + + mOutputThread = new OutputThread(this, mCroppingType, mChars, + mBufferRequestThread, mOfflineReqs); + + mOutputThread->setExifMakeModel(mExifMake, mExifModel); + + Size inputSize = { mOfflineReqs[0]->frameIn->mWidth, mOfflineReqs[0]->frameIn->mHeight}; + Size maxThumbSize = V3_4::implementation::getMaxThumbnailResolution(mChars); + mOutputThread->allocateIntermediateBuffers( + inputSize, maxThumbSize, mOfflineStreams, mBlobBufferSize); + + mOutputThread->run("ExtCamOfflnOut", PRIORITY_DISPLAY); +} + +bool ExternalCameraOfflineSession::OutputThread::threadLoop() { + auto parent = mParent.promote(); + if (parent == nullptr) { + ALOGE("%s: session has been disconnected!", __FUNCTION__); + return false; + } + + if (mOfflineReqs.empty()) { + ALOGI("%s: all offline requests are processed. Stopping.", __FUNCTION__); + return false; + } + + std::shared_ptr req = mOfflineReqs.front(); + mOfflineReqs.pop_front(); + + auto onDeviceError = [&](auto... args) { + ALOGE(args...); + parent->notifyError( + req->frameNumber, /*stream*/-1, ErrorCode::ERROR_DEVICE); + signalRequestDone(); + return false; + }; + + if (req->frameIn->mFourcc != V4L2_PIX_FMT_MJPEG && req->frameIn->mFourcc != V4L2_PIX_FMT_Z16) { + return onDeviceError("%s: do not support V4L2 format %c%c%c%c", __FUNCTION__, + req->frameIn->mFourcc & 0xFF, + (req->frameIn->mFourcc >> 8) & 0xFF, + (req->frameIn->mFourcc >> 16) & 0xFF, + (req->frameIn->mFourcc >> 24) & 0xFF); + } + + int res = requestBufferStart(req->buffers); + if (res != 0) { + ALOGE("%s: send BufferRequest failed! res %d", __FUNCTION__, res); + return onDeviceError("%s: failed to send buffer request!", __FUNCTION__); + } + + std::unique_lock lk(mBufferLock); + // Convert input V4L2 frame to YU12 of the same size + // TODO: see if we can save some computation by converting to YV12 here + uint8_t* inData; + size_t inDataSize; + if (req->frameIn->getData(&inData, &inDataSize) != 0) { + lk.unlock(); + return onDeviceError("%s: V4L2 buffer map failed", __FUNCTION__); + } + + // TODO: in some special case maybe we can decode jpg directly to gralloc output? + if (req->frameIn->mFourcc == V4L2_PIX_FMT_MJPEG) { + ATRACE_BEGIN("MJPGtoI420"); + int res = libyuv::MJPGToI420( + inData, inDataSize, static_cast(mYu12FrameLayout.y), mYu12FrameLayout.yStride, + static_cast(mYu12FrameLayout.cb), mYu12FrameLayout.cStride, + static_cast(mYu12FrameLayout.cr), mYu12FrameLayout.cStride, + mYu12Frame->mWidth, mYu12Frame->mHeight, mYu12Frame->mWidth, mYu12Frame->mHeight); + ATRACE_END(); + + if (res != 0) { + // For some webcam, the first few V4L2 frames might be malformed... + ALOGE("%s: Convert V4L2 frame to YU12 failed! res %d", __FUNCTION__, res); + lk.unlock(); + Status st = parent->processCaptureRequestError(req); + if (st != Status::OK) { + return onDeviceError("%s: failed to process capture request error!", __FUNCTION__); + } + signalRequestDone(); + return true; + } + } + + ATRACE_BEGIN("Wait for BufferRequest done"); + res = waitForBufferRequestDone(&req->buffers); + ATRACE_END(); + + if (res != 0) { + ALOGE("%s: wait for BufferRequest done failed! res %d", __FUNCTION__, res); + lk.unlock(); + return onDeviceError("%s: failed to process buffer request error!", __FUNCTION__); + } + + ALOGV("%s processing new request", __FUNCTION__); + const int kSyncWaitTimeoutMs = 500; + for (auto& halBuf : req->buffers) { + if (*(halBuf.bufPtr) == nullptr) { + ALOGW("%s: buffer for stream %d missing", __FUNCTION__, halBuf.streamId); + halBuf.fenceTimeout = true; + } else if (halBuf.acquireFence >= 0) { + int ret = sync_wait(halBuf.acquireFence, kSyncWaitTimeoutMs); + if (ret) { + halBuf.fenceTimeout = true; + } else { + ::close(halBuf.acquireFence); + halBuf.acquireFence = -1; + } + } + + if (halBuf.fenceTimeout) { + continue; + } + + // Gralloc lockYCbCr the buffer + switch (halBuf.format) { + case PixelFormat::BLOB: { + int ret = createJpegLocked(halBuf, req->setting); + + if(ret != 0) { + lk.unlock(); + return onDeviceError("%s: createJpegLocked failed with %d", + __FUNCTION__, ret); + } + } break; + case PixelFormat::Y16: { + void* outLayout = sHandleImporter.lock(*(halBuf.bufPtr), halBuf.usage, inDataSize); + + std::memcpy(outLayout, inData, inDataSize); + + int relFence = sHandleImporter.unlock(*(halBuf.bufPtr)); + if (relFence >= 0) { + halBuf.acquireFence = relFence; + } + } break; + case PixelFormat::YCBCR_420_888: + case PixelFormat::YV12: { + IMapper::Rect outRect {0, 0, + static_cast(halBuf.width), + static_cast(halBuf.height)}; + YCbCrLayout outLayout = sHandleImporter.lockYCbCr( + *(halBuf.bufPtr), halBuf.usage, outRect); + ALOGV("%s: outLayout y %p cb %p cr %p y_str %d c_str %d c_step %d", + __FUNCTION__, outLayout.y, outLayout.cb, outLayout.cr, + outLayout.yStride, outLayout.cStride, outLayout.chromaStep); + + // Convert to output buffer size/format + uint32_t outputFourcc = V3_4::implementation::getFourCcFromLayout(outLayout); + ALOGV("%s: converting to format %c%c%c%c", __FUNCTION__, + outputFourcc & 0xFF, + (outputFourcc >> 8) & 0xFF, + (outputFourcc >> 16) & 0xFF, + (outputFourcc >> 24) & 0xFF); + + YCbCrLayout cropAndScaled; + ATRACE_BEGIN("cropAndScaleLocked"); + int ret = cropAndScaleLocked( + mYu12Frame, + Size { halBuf.width, halBuf.height }, + &cropAndScaled); + ATRACE_END(); + if (ret != 0) { + lk.unlock(); + return onDeviceError("%s: crop and scale failed!", __FUNCTION__); + } + + Size sz {halBuf.width, halBuf.height}; + ATRACE_BEGIN("formatConvert"); + ret = V3_4::implementation::formatConvert(cropAndScaled, outLayout, sz, outputFourcc); + ATRACE_END(); + if (ret != 0) { + lk.unlock(); + return onDeviceError("%s: format coversion failed!", __FUNCTION__); + } + int relFence = sHandleImporter.unlock(*(halBuf.bufPtr)); + if (relFence >= 0) { + halBuf.acquireFence = relFence; + } + } break; + default: + lk.unlock(); + return onDeviceError("%s: unknown output format %x", __FUNCTION__, halBuf.format); + } + } // for each buffer + mScaledYu12Frames.clear(); + + // Don't hold the lock while calling back to parent + lk.unlock(); + Status st = parent->processCaptureResult(req); + if (st != Status::OK) { + return onDeviceError("%s: failed to process capture result!", __FUNCTION__); + } + signalRequestDone(); + return true; +} + +Status ExternalCameraOfflineSession::importBuffer(int32_t streamId, + uint64_t bufId, buffer_handle_t buf, + /*out*/buffer_handle_t** outBufPtr, + bool allowEmptyBuf) { + Mutex::Autolock _l(mCbsLock); + return V3_4::implementation::importBufferImpl( + mCirculatingBuffers, sHandleImporter, streamId, + bufId, buf, outBufPtr, allowEmptyBuf); + return Status::OK; +}; + +#define UPDATE(md, tag, data, size) \ +do { \ + if ((md).update((tag), (data), (size))) { \ + ALOGE("Update " #tag " failed!"); \ + return BAD_VALUE; \ + } \ +} while (0) + +status_t ExternalCameraOfflineSession::fillCaptureResult( + common::V1_0::helper::CameraMetadata &md, nsecs_t timestamp) { + bool afTrigger = false; + { + std::lock_guard lk(mAfTriggerLock); + afTrigger = mAfTrigger; + if (md.exists(ANDROID_CONTROL_AF_TRIGGER)) { + camera_metadata_entry entry = md.find(ANDROID_CONTROL_AF_TRIGGER); + if (entry.data.u8[0] == ANDROID_CONTROL_AF_TRIGGER_START) { + mAfTrigger = afTrigger = true; + } else if (entry.data.u8[0] == ANDROID_CONTROL_AF_TRIGGER_CANCEL) { + mAfTrigger = afTrigger = false; + } + } + } + + // For USB camera, the USB camera handles everything and we don't have control + // over AF. We only simply fake the AF metadata based on the request + // received here. + uint8_t afState; + if (afTrigger) { + afState = ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED; + } else { + afState = ANDROID_CONTROL_AF_STATE_INACTIVE; + } + UPDATE(md, ANDROID_CONTROL_AF_STATE, &afState, 1); + + camera_metadata_ro_entry activeArraySize = + mChars.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE); + + return V3_4::implementation::fillCaptureResultCommon(md, timestamp, activeArraySize); +} + +#undef UPDATE + +Status ExternalCameraOfflineSession::processCaptureResult(std::shared_ptr& req) { + ATRACE_CALL(); + // Fill output buffers + hidl_vec results; + results.resize(1); + CaptureResult& result = results[0]; + result.frameNumber = req->frameNumber; + result.partialResult = 1; + result.inputBuffer.streamId = -1; + result.outputBuffers.resize(req->buffers.size()); + for (size_t i = 0; i < req->buffers.size(); i++) { + result.outputBuffers[i].streamId = req->buffers[i].streamId; + result.outputBuffers[i].bufferId = req->buffers[i].bufferId; + if (req->buffers[i].fenceTimeout) { + result.outputBuffers[i].status = BufferStatus::ERROR; + if (req->buffers[i].acquireFence >= 0) { + native_handle_t* handle = native_handle_create(/*numFds*/1, /*numInts*/0); + handle->data[0] = req->buffers[i].acquireFence; + result.outputBuffers[i].releaseFence.setTo(handle, /*shouldOwn*/false); + } + notifyError(req->frameNumber, req->buffers[i].streamId, ErrorCode::ERROR_BUFFER); + } else { + result.outputBuffers[i].status = BufferStatus::OK; + // TODO: refactor + if (req->buffers[i].acquireFence >= 0) { + native_handle_t* handle = native_handle_create(/*numFds*/1, /*numInts*/0); + handle->data[0] = req->buffers[i].acquireFence; + result.outputBuffers[i].releaseFence.setTo(handle, /*shouldOwn*/false); + } + } + } + + // Fill capture result metadata + fillCaptureResult(req->setting, req->shutterTs); + const camera_metadata_t *rawResult = req->setting.getAndLock(); + V3_2::implementation::convertToHidl(rawResult, &result.result); + req->setting.unlock(rawResult); + + // Callback into framework + invokeProcessCaptureResultCallback(results, /* tryWriteFmq */true); + V3_4::implementation::freeReleaseFences(results); + return Status::OK; +}; + +void ExternalCameraOfflineSession::invokeProcessCaptureResultCallback( + hidl_vec &results, bool tryWriteFmq) { + if (mProcessCaptureResultLock.tryLock() != OK) { + const nsecs_t NS_TO_SECOND = 1000000000; + ALOGV("%s: previous call is not finished! waiting 1s...", __FUNCTION__); + if (mProcessCaptureResultLock.timedLock(/* 1s */NS_TO_SECOND) != OK) { + ALOGE("%s: cannot acquire lock in 1s, cannot proceed", + __FUNCTION__); + return; + } + } + if (tryWriteFmq && mResultMetadataQueue->availableToWrite() > 0) { + for (CaptureResult &result : results) { + if (result.result.size() > 0) { + if (mResultMetadataQueue->write(result.result.data(), result.result.size())) { + result.fmqResultSize = result.result.size(); + result.result.resize(0); + } else { + ALOGW("%s: couldn't utilize fmq, fall back to hwbinder", __FUNCTION__); + result.fmqResultSize = 0; + } + } else { + result.fmqResultSize = 0; + } + } + } + auto status = mCallback->processCaptureResult(results); + if (!status.isOk()) { + ALOGE("%s: processCaptureResult ERROR : %s", __FUNCTION__, + status.description().c_str()); + } + + mProcessCaptureResultLock.unlock(); +} + +Status ExternalCameraOfflineSession::processCaptureRequestError( + const std::shared_ptr& req, + /*out*/std::vector* outMsgs, + /*out*/std::vector* outResults) { + ATRACE_CALL(); + + if (outMsgs == nullptr) { + notifyError(/*frameNum*/req->frameNumber, /*stream*/-1, ErrorCode::ERROR_REQUEST); + } else { + NotifyMsg shutter; + shutter.type = MsgType::SHUTTER; + shutter.msg.shutter.frameNumber = req->frameNumber; + shutter.msg.shutter.timestamp = req->shutterTs; + + NotifyMsg error; + error.type = MsgType::ERROR; + error.msg.error.frameNumber = req->frameNumber; + error.msg.error.errorStreamId = -1; + error.msg.error.errorCode = ErrorCode::ERROR_REQUEST; + outMsgs->push_back(shutter); + outMsgs->push_back(error); + } + + // Fill output buffers + hidl_vec results; + results.resize(1); + CaptureResult& result = results[0]; + result.frameNumber = req->frameNumber; + result.partialResult = 1; + result.inputBuffer.streamId = -1; + result.outputBuffers.resize(req->buffers.size()); + for (size_t i = 0; i < req->buffers.size(); i++) { + result.outputBuffers[i].streamId = req->buffers[i].streamId; + result.outputBuffers[i].bufferId = req->buffers[i].bufferId; + result.outputBuffers[i].status = BufferStatus::ERROR; + if (req->buffers[i].acquireFence >= 0) { + native_handle_t* handle = native_handle_create(/*numFds*/1, /*numInts*/0); + handle->data[0] = req->buffers[i].acquireFence; + result.outputBuffers[i].releaseFence.setTo(handle, /*shouldOwn*/false); + } + } + + if (outResults == nullptr) { + // Callback into framework + invokeProcessCaptureResultCallback(results, /* tryWriteFmq */true); + V3_4::implementation::freeReleaseFences(results); + } else { + outResults->push_back(result); + } + return Status::OK; +}; + +ssize_t ExternalCameraOfflineSession::getJpegBufferSize( + uint32_t /*width*/, uint32_t /*height*/) const { + // Empty implementation here as the jpeg buffer size is passed in by ctor + return 0; +}; + +void ExternalCameraOfflineSession::notifyError(uint32_t frameNumber, int32_t streamId, ErrorCode ec) { + NotifyMsg msg; + msg.type = MsgType::ERROR; + msg.msg.error.frameNumber = frameNumber; + msg.msg.error.errorStreamId = streamId; + msg.msg.error.errorCode = ec; + mCallback->notify({msg}); +}; + +Return ExternalCameraOfflineSession::setCallback(const sp& cb) { + Mutex::Autolock _il(mInterfaceLock); + if (mCallback != nullptr && cb != nullptr) { + ALOGE("%s: callback must not be set twice!", __FUNCTION__); + return Void(); + } + mCallback = cb; + + initOutputThread(); + + if (mOutputThread == nullptr) { + ALOGE("%s: init OutputThread failed!", __FUNCTION__); + } + return Void(); +} + +Return ExternalCameraOfflineSession::getCaptureResultMetadataQueue( + V3_3::ICameraDeviceSession::getCaptureResultMetadataQueue_cb _hidl_cb) { + Mutex::Autolock _il(mInterfaceLock); + _hidl_cb(*mResultMetadataQueue->getDesc()); + return Void(); +} + +void ExternalCameraOfflineSession::cleanupBuffersLocked(int id) { + for (auto& pair : mCirculatingBuffers.at(id)) { + sHandleImporter.freeBuffer(pair.second); + } + mCirculatingBuffers[id].clear(); + mCirculatingBuffers.erase(id); +} + +Return ExternalCameraOfflineSession::close() { + Mutex::Autolock _il(mInterfaceLock); + { + Mutex::Autolock _l(mLock); + if (mClosed) { + ALOGW("%s: offline session already closed!", __FUNCTION__); + return Void(); + } + } + if (mBufferRequestThread) { + mBufferRequestThread->requestExit(); + mBufferRequestThread->join(); + mBufferRequestThread.clear(); + } + if (mOutputThread) { + mOutputThread->flush(); + mOutputThread->requestExit(); + mOutputThread->join(); + mOutputThread.clear(); + } + + Mutex::Autolock _l(mLock); + // free all buffers + { + Mutex::Autolock _cbl(mCbsLock); + for(auto stream : mOfflineStreams) { + cleanupBuffersLocked(stream.id); + } + } + mCallback.clear(); + mClosed = true; + return Void(); +} + +} // namespace implementation +} // namespace V3_6 +} // namespace device +} // namespace camera +} // namespace hardware +} // namespace android diff --git a/camera/device/3.6/default/OWNERS b/camera/device/3.6/default/OWNERS new file mode 100644 index 0000000000..f48a95c5b3 --- /dev/null +++ b/camera/device/3.6/default/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/av:/camera/OWNERS diff --git a/camera/device/3.6/default/include/ext_device_v3_6_impl/ExternalCameraDeviceSession.h b/camera/device/3.6/default/include/ext_device_v3_6_impl/ExternalCameraDeviceSession.h new file mode 100644 index 0000000000..db0d9a548b --- /dev/null +++ b/camera/device/3.6/default/include/ext_device_v3_6_impl/ExternalCameraDeviceSession.h @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2020 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_CAMERA_DEVICE_V3_6_EXTCAMERADEVICESESSION_H +#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_6_EXTCAMERADEVICESESSION_H + +#include +#include +#include <../../3.5/default/include/ext_device_v3_5_impl/ExternalCameraDeviceSession.h> +#include "ExternalCameraOfflineSession.h" + +namespace android { +namespace hardware { +namespace camera { +namespace device { +namespace V3_6 { +namespace implementation { + +using ::android::hardware::camera::device::V3_2::BufferCache; +using ::android::hardware::camera::device::V3_2::CameraMetadata; +using ::android::hardware::camera::device::V3_2::CaptureRequest; +using ::android::hardware::camera::device::V3_2::CaptureResult; +using ::android::hardware::camera::device::V3_5::ICameraDeviceCallback; +using ::android::hardware::camera::device::V3_2::RequestTemplate; +using ::android::hardware::camera::device::V3_2::Stream; +using ::android::hardware::camera::device::V3_5::StreamConfiguration; +using ::android::hardware::camera::device::V3_6::ICameraDeviceSession; +using ::android::hardware::camera::device::V3_6::ICameraOfflineSession; +using ::android::hardware::camera::common::V1_0::Status; +using ::android::hardware::camera::external::common::ExternalCameraConfig; +using ::android::hardware::graphics::common::V1_0::PixelFormat; +using ::android::hardware::MQDescriptorSync; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::hidl_vec; +using ::android::hardware::hidl_string; +using ::android::sp; +using ::android::Mutex; +using ::android::base::unique_fd; + +using ::android::hardware::camera::device::V3_4::implementation::SupportedV4L2Format; +using ::android::hardware::camera::device::V3_4::implementation::CroppingType; + +struct ExternalCameraDeviceSession : public V3_5::implementation::ExternalCameraDeviceSession { + + ExternalCameraDeviceSession(const sp&, + const ExternalCameraConfig& cfg, + const std::vector& sortedFormats, + const CroppingType& croppingType, + const common::V1_0::helper::CameraMetadata& chars, + const std::string& cameraId, + unique_fd v4l2Fd); + virtual ~ExternalCameraDeviceSession(); + + // Retrieve the HIDL interface, split into its own class to avoid inheritance issues when + // dealing with minor version revs and simultaneous implementation and interface inheritance + virtual sp getInterface() override { + return new TrampolineSessionInterface_3_6(this); + } + +protected: + // Methods from v3.5 and earlier will trampoline to inherited implementation + Return configureStreams_3_6( + const StreamConfiguration& requestedConfiguration, + ICameraDeviceSession::configureStreams_3_6_cb _hidl_cb); + + Return switchToOffline( + const hidl_vec& streamsToKeep, + ICameraDeviceSession::switchToOffline_cb _hidl_cb); + + void fillOutputStream3_6(const V3_3::HalStreamConfiguration& outStreams_v33, + /*out*/V3_6::HalStreamConfiguration* outStreams_v36); + bool supportOfflineLocked(int32_t streamId); + + // Main body of switchToOffline. This method does not invoke any callbacks + // but instead returns the necessary callbacks in output arguments so callers + // can callback later without holding any locks + Status switchToOffline(const hidl_vec& offlineStreams, + /*out*/std::vector* msgs, + /*out*/std::vector* results, + /*out*/CameraOfflineSessionInfo* info, + /*out*/sp* session); + + // Whether a request can be completely dropped when switching to offline + bool canDropRequest(const hidl_vec& offlineStreams, + std::shared_ptr halReq); + + void fillOfflineSessionInfo(const hidl_vec& offlineStreams, + std::deque>& offlineReqs, + const std::map& circulatingBuffers, + /*out*/CameraOfflineSessionInfo* info); + +private: + + struct TrampolineSessionInterface_3_6 : public ICameraDeviceSession { + TrampolineSessionInterface_3_6(sp parent) : + mParent(parent) {} + + virtual Return constructDefaultRequestSettings( + RequestTemplate type, + V3_3::ICameraDeviceSession::constructDefaultRequestSettings_cb _hidl_cb) override { + return mParent->constructDefaultRequestSettings(type, _hidl_cb); + } + + virtual Return configureStreams( + const V3_2::StreamConfiguration& requestedConfiguration, + V3_3::ICameraDeviceSession::configureStreams_cb _hidl_cb) override { + return mParent->configureStreams(requestedConfiguration, _hidl_cb); + } + + virtual Return processCaptureRequest(const hidl_vec& requests, + const hidl_vec& cachesToRemove, + V3_3::ICameraDeviceSession::processCaptureRequest_cb _hidl_cb) override { + return mParent->processCaptureRequest(requests, cachesToRemove, _hidl_cb); + } + + virtual Return getCaptureRequestMetadataQueue( + V3_3::ICameraDeviceSession::getCaptureRequestMetadataQueue_cb _hidl_cb) override { + return mParent->getCaptureRequestMetadataQueue(_hidl_cb); + } + + virtual Return getCaptureResultMetadataQueue( + V3_3::ICameraDeviceSession::getCaptureResultMetadataQueue_cb _hidl_cb) override { + return mParent->getCaptureResultMetadataQueue(_hidl_cb); + } + + virtual Return flush() override { + return mParent->flush(); + } + + virtual Return close() override { + return mParent->close(); + } + + virtual Return configureStreams_3_3( + const V3_2::StreamConfiguration& requestedConfiguration, + configureStreams_3_3_cb _hidl_cb) override { + return mParent->configureStreams_3_3(requestedConfiguration, _hidl_cb); + } + + virtual Return configureStreams_3_4( + const V3_4::StreamConfiguration& requestedConfiguration, + configureStreams_3_4_cb _hidl_cb) override { + return mParent->configureStreams_3_4(requestedConfiguration, _hidl_cb); + } + + virtual Return processCaptureRequest_3_4(const hidl_vec& requests, + const hidl_vec& cachesToRemove, + ICameraDeviceSession::processCaptureRequest_3_4_cb _hidl_cb) override { + return mParent->processCaptureRequest_3_4(requests, cachesToRemove, _hidl_cb); + } + + virtual Return configureStreams_3_5( + const StreamConfiguration& requestedConfiguration, + configureStreams_3_5_cb _hidl_cb) override { + return mParent->configureStreams_3_5(requestedConfiguration, _hidl_cb); + } + + virtual Return signalStreamFlush( + const hidl_vec& requests, + uint32_t streamConfigCounter) override { + return mParent->signalStreamFlush(requests, streamConfigCounter); + } + + virtual Return isReconfigurationRequired(const V3_2::CameraMetadata& oldSessionParams, + const V3_2::CameraMetadata& newSessionParams, + ICameraDeviceSession::isReconfigurationRequired_cb _hidl_cb) override { + return mParent->isReconfigurationRequired(oldSessionParams, newSessionParams, _hidl_cb); + } + + virtual Return configureStreams_3_6( + const StreamConfiguration& requestedConfiguration, + configureStreams_3_6_cb _hidl_cb) override { + return mParent->configureStreams_3_6(requestedConfiguration, _hidl_cb); + } + + virtual Return switchToOffline( + const hidl_vec& streamsToKeep, + switchToOffline_cb _hidl_cb) override { + return mParent->switchToOffline(streamsToKeep, _hidl_cb); + } + + private: + sp mParent; + }; +}; + +} // namespace implementation +} // namespace V3_6 +} // namespace device +} // namespace camera +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_CAMERA_DEVICE_V3_6_EXTCAMERADEVICESESSION_H diff --git a/camera/device/3.6/default/include/ext_device_v3_6_impl/ExternalCameraDevice_3_6.h b/camera/device/3.6/default/include/ext_device_v3_6_impl/ExternalCameraDevice_3_6.h new file mode 100644 index 0000000000..020bec4a92 --- /dev/null +++ b/camera/device/3.6/default/include/ext_device_v3_6_impl/ExternalCameraDevice_3_6.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2020 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_CAMERA_DEVICE_V3_6_EXTCAMERADEVICE_H +#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_6_EXTCAMERADEVICE_H + +#include + +#include "ExternalCameraDeviceSession.h" +#include <../../../../3.5/default/include/ext_device_v3_5_impl/ExternalCameraDevice_3_5.h> + +namespace android { +namespace hardware { +namespace camera { +namespace device { +namespace V3_6 { +namespace implementation { + +using namespace ::android::hardware::camera::device; +using ::android::hardware::camera::device::V3_6::ICameraDevice; +using ::android::hardware::camera::common::V1_0::CameraResourceCost; +using ::android::hardware::camera::common::V1_0::TorchMode; +using ::android::hardware::camera::common::V1_0::Status; +using ::android::hardware::camera::external::common::ExternalCameraConfig; +using ::android::hardware::camera::external::common::Size; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::hidl_vec; +using ::android::hardware::hidl_string; +using ::android::sp; + +/* + * The camera device HAL implementation is opened lazily (via the open call) + */ +struct ExternalCameraDevice : public V3_5::implementation::ExternalCameraDevice { + + // Called by external camera provider HAL. + // Provider HAL must ensure the uniqueness of CameraDevice object per cameraId, or there could + // be multiple CameraDevice trying to access the same physical camera. Also, provider will have + // to keep track of all CameraDevice objects in order to notify CameraDevice when the underlying + // camera is detached. + ExternalCameraDevice(const std::string& cameraId, const ExternalCameraConfig& cfg); + virtual ~ExternalCameraDevice(); + + virtual sp getInterface() override { + return new TrampolineDeviceInterface_3_6(this); + } + +protected: + virtual sp createSession( + const sp&, + const ExternalCameraConfig& cfg, + const std::vector& sortedFormats, + const CroppingType& croppingType, + const common::V1_0::helper::CameraMetadata& chars, + const std::string& cameraId, + unique_fd v4l2Fd) override; + + virtual status_t initAvailableCapabilities( + ::android::hardware::camera::common::V1_0::helper::CameraMetadata*) override; + +private: + struct TrampolineDeviceInterface_3_6 : public ICameraDevice { + TrampolineDeviceInterface_3_6(sp parent) : + mParent(parent) {} + + virtual Return getResourceCost(V3_2::ICameraDevice::getResourceCost_cb _hidl_cb) + override { + return mParent->getResourceCost(_hidl_cb); + } + + virtual Return getCameraCharacteristics( + V3_2::ICameraDevice::getCameraCharacteristics_cb _hidl_cb) override { + return mParent->getCameraCharacteristics(_hidl_cb); + } + + virtual Return setTorchMode(TorchMode mode) override { + return mParent->setTorchMode(mode); + } + + virtual Return open(const sp& callback, + V3_2::ICameraDevice::open_cb _hidl_cb) override { + return mParent->open(callback, _hidl_cb); + } + + virtual Return dumpState(const hidl_handle& fd) override { + return mParent->dumpState(fd); + } + + virtual Return getPhysicalCameraCharacteristics(const hidl_string& physicalCameraId, + V3_5::ICameraDevice::getPhysicalCameraCharacteristics_cb _hidl_cb) override { + return mParent->getPhysicalCameraCharacteristics(physicalCameraId, _hidl_cb); + } + + virtual Return isStreamCombinationSupported( + const V3_4::StreamConfiguration& streams, + V3_5::ICameraDevice::isStreamCombinationSupported_cb _hidl_cb) override { + return mParent->isStreamCombinationSupported(streams, _hidl_cb); + } + + private: + sp mParent; + }; +}; + +} // namespace implementation +} // namespace V3_6 +} // namespace device +} // namespace camera +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_CAMERA_DEVICE_V3_6_EXTCAMERADEVICE_H diff --git a/camera/device/3.6/default/include/ext_device_v3_6_impl/ExternalCameraOfflineSession.h b/camera/device/3.6/default/include/ext_device_v3_6_impl/ExternalCameraOfflineSession.h new file mode 100644 index 0000000000..230b67c43c --- /dev/null +++ b/camera/device/3.6/default/include/ext_device_v3_6_impl/ExternalCameraOfflineSession.h @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2020 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_CAMERA_DEVICE_V3_6_EXTCAMERAOFFLINESESSION_H +#define ANDROID_HARDWARE_CAMERA_DEVICE_V3_6_EXTCAMERAOFFLINESESSION_H + +#include +#include +#include +#include +#include +#include +#include +#include <../../3.4/default/include/ext_device_v3_4_impl/ExternalCameraUtils.h> +#include <../../3.5/default/include/ext_device_v3_5_impl/ExternalCameraDeviceSession.h> +#include +#include +#include + +namespace android { +namespace hardware { +namespace camera { +namespace device { +namespace V3_6 { +namespace implementation { + +using ::android::hardware::camera::device::V3_2::BufferCache; +using ::android::hardware::camera::device::V3_5::BufferRequest; +using ::android::hardware::camera::device::V3_5::BufferRequestStatus; +using ::android::hardware::camera::device::V3_2::BufferStatus; +using ::android::hardware::camera::device::V3_2::CameraMetadata; +using ::android::hardware::camera::device::V3_2::CaptureRequest; +using ::android::hardware::camera::device::V3_2::CaptureResult; +using ::android::hardware::camera::device::V3_2::ErrorCode; +using ::android::hardware::camera::device::V3_5::ICameraDeviceCallback; +using ::android::hardware::camera::device::V3_2::MsgType; +using ::android::hardware::camera::device::V3_2::NotifyMsg; +using ::android::hardware::camera::device::V3_2::RequestTemplate; +using ::android::hardware::camera::device::V3_2::Stream; +using ::android::hardware::camera::device::V3_5::StreamConfiguration; +using ::android::hardware::camera::device::V3_2::StreamConfigurationMode; +using ::android::hardware::camera::device::V3_2::StreamRotation; +using ::android::hardware::camera::device::V3_2::StreamType; +using ::android::hardware::camera::device::V3_2::DataspaceFlags; +using ::android::hardware::camera::device::V3_2::CameraBlob; +using ::android::hardware::camera::device::V3_2::CameraBlobId; +using ::android::hardware::camera::device::V3_4::HalStreamConfiguration; +using ::android::hardware::camera::device::V3_6::ICameraOfflineSession; +using ::android::hardware::camera::common::V1_0::Status; +using ::android::hardware::camera::common::V1_0::helper::HandleImporter; +using ::android::hardware::camera::common::V1_0::helper::ExifUtils; +using ::android::hardware::camera::external::common::ExternalCameraConfig; +using ::android::hardware::camera::external::common::Size; +using ::android::hardware::camera::external::common::SizeHasher; +using ::android::hardware::graphics::common::V1_0::BufferUsage; +using ::android::hardware::graphics::common::V1_0::Dataspace; +using ::android::hardware::graphics::common::V1_0::PixelFormat; +using ::android::hardware::kSynchronizedReadWrite; +using ::android::hardware::MessageQueue; +using ::android::hardware::MQDescriptorSync; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::hidl_vec; +using ::android::hardware::hidl_string; +using ::android::sp; +using ::android::Mutex; +using ::android::base::unique_fd; + +using ::android::hardware::camera::device::V3_4::implementation::SupportedV4L2Format; +using ::android::hardware::camera::device::V3_4::implementation::CroppingType; +using ::android::hardware::camera::device::V3_4::implementation::CirculatingBuffers; +using ::android::hardware::camera::device::V3_4::implementation::HalRequest; +using ::android::hardware::camera::device::V3_4::implementation::OutputThreadInterface; + +struct ExternalCameraOfflineSession : public virtual RefBase, + public virtual OutputThreadInterface { + + ExternalCameraOfflineSession( + const CroppingType& croppingType, + const common::V1_0::helper::CameraMetadata& chars, + const std::string& cameraId, + const std::string& exifMake, + const std::string& exifModel, + uint32_t blobBufferSize, + bool afTrigger, + const hidl_vec& offlineStreams, + std::deque>& offlineReqs, + const std::map& circulatingBuffers); + + bool initialize(); + + virtual ~ExternalCameraOfflineSession(); + + // Retrieve the HIDL interface, split into its own class to avoid inheritance issues when + // dealing with minor version revs and simultaneous implementation and interface inheritance + virtual sp getInterface() { + return new TrampolineSessionInterface_3_6(this); + } + +protected: + + // Methods from OutputThreadInterface + virtual Status importBuffer(int32_t streamId, + uint64_t bufId, buffer_handle_t buf, + /*out*/buffer_handle_t** outBufPtr, + bool allowEmptyBuf) override; + + virtual Status processCaptureResult(std::shared_ptr&) override; + + virtual Status processCaptureRequestError(const std::shared_ptr&, + /*out*/std::vector* msgs = nullptr, + /*out*/std::vector* results = nullptr) override; + + virtual ssize_t getJpegBufferSize(uint32_t width, uint32_t height) const override; + + virtual void notifyError(uint32_t frameNumber, int32_t streamId, ErrorCode ec) override; + // End of OutputThreadInterface methods + + class OutputThread : public V3_5::implementation::ExternalCameraDeviceSession::OutputThread { + public: + OutputThread( + wp parent, CroppingType ct, + const common::V1_0::helper::CameraMetadata& chars, + sp bufReqThread, + std::deque>& offlineReqs) : + V3_5::implementation::ExternalCameraDeviceSession::OutputThread( + parent, ct, chars, bufReqThread), + mOfflineReqs(offlineReqs) {} + + virtual bool threadLoop() override; + + protected: + std::deque> mOfflineReqs; + }; // OutputThread + + + Return setCallback(const sp& cb); + + Return getCaptureResultMetadataQueue( + V3_3::ICameraDeviceSession::getCaptureResultMetadataQueue_cb _hidl_cb); + + Return close(); + + void initOutputThread(); + + void invokeProcessCaptureResultCallback( + hidl_vec &results, bool tryWriteFmq); + + status_t fillCaptureResult(common::V1_0::helper::CameraMetadata& md, nsecs_t timestamp); + + void cleanupBuffersLocked(int id); + + // Protect (most of) HIDL interface methods from synchronized-entering + mutable Mutex mInterfaceLock; + + mutable Mutex mLock; // Protect all data members except otherwise noted + + bool mClosed = false; + const CroppingType mCroppingType; + const common::V1_0::helper::CameraMetadata mChars; + const std::string mCameraId; + const std::string mExifMake; + const std::string mExifModel; + const uint32_t mBlobBufferSize; + + std::mutex mAfTriggerLock; // protect mAfTrigger + bool mAfTrigger; + + const hidl_vec mOfflineStreams; + std::deque> mOfflineReqs; + + // Protect mCirculatingBuffers, must not lock mLock after acquiring this lock + mutable Mutex mCbsLock; + std::map mCirculatingBuffers; + + static HandleImporter sHandleImporter; + + using ResultMetadataQueue = MessageQueue; + std::shared_ptr mResultMetadataQueue; + + // Protect against invokeProcessCaptureResultCallback() + Mutex mProcessCaptureResultLock; + + sp mCallback; + + sp mBufferRequestThread; + sp mOutputThread; +private: + + struct TrampolineSessionInterface_3_6 : public ICameraOfflineSession { + TrampolineSessionInterface_3_6(sp parent) : + mParent(parent) {} + + virtual Return setCallback(const sp& cb) override { + return mParent->setCallback(cb); + } + + virtual Return getCaptureResultMetadataQueue( + V3_3::ICameraDeviceSession::getCaptureResultMetadataQueue_cb _hidl_cb) override { + return mParent->getCaptureResultMetadataQueue(_hidl_cb); + } + + virtual Return close() override { + return mParent->close(); + } + + private: + sp mParent; + }; +}; + +} // namespace implementation +} // namespace V3_6 +} // namespace device +} // namespace camera +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_CAMERA_DEVICE_V3_6_EXTCAMERAOFFLINESESSION_H diff --git a/camera/device/3.6/types.hal b/camera/device/3.6/types.hal new file mode 100644 index 0000000000..f4c50ed1d0 --- /dev/null +++ b/camera/device/3.6/types.hal @@ -0,0 +1,149 @@ +/* + * 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.camera.device@3.6; + +import @3.2::BufferCache; +import @3.4::HalStream; + +/** + * OfflineRequest: + * + * Information about a capture request being switched to offline mode via the + * ICameraDeviceSession#switchToOffline method. + * + */ +struct OfflineRequest { + /** + * Must match a inflight CaptureRequest sent by camera service + */ + uint32_t frameNumber; + + /** + * Stream IDs for outputs that will be returned via ICameraDeviceCallback. + * The stream ID must be within one of offline stream listed in + * CameraOfflineSessionInfo. + * Camera service will validate these pending buffers are matching camera + * service's record to make sure no buffers are leaked during the + * switchToOffline call. + */ + vec pendingStreams; +}; + +/** + * OfflineStream: + * + * Information about a stream being switched to offline mode via the + * ICameraDeviceSession#switchToOffline method. + * + */ +struct OfflineStream { + /** + * IDs of a stream to be transferred to offline session. + * + * For devices that do not support HAL buffer management, this must be + * one of stream ID listed in streamsToKeep argument of the + * switchToOffline call. + * For devices that support HAL buffer management, this could be any stream + * that was configured right before calling switchToOffline. + */ + int32_t id; + + /** + * Number of outstanding buffers that will be returned via offline session + */ + uint32_t numOutstandingBuffers; + + /** + * Buffer ID of buffers currently cached between camera service and this + * stream, which may or may not be owned by the camera HAL right now. + * See StreamBuffer#bufferId for more details. + */ + vec circulatingBufferIds; +}; + +/** + * CameraOfflineSessionInfo: + * + * Information about pending outputs that's being transferred to an offline + * session from an active session using the + * ICameraDeviceSession#switchToOffline method. + * + */ +struct CameraOfflineSessionInfo { + /** + * Information on what streams will be preserved in offline session. + * Streams not listed here will be removed by camera service after + * switchToOffline call returns. + */ + vec offlineStreams; + + /** + * Information for requests that will be handled by offline session + * Camera service will validate this matches what camera service has on + * record. + */ + vec offlineRequests; +}; + +/** + * HalStream: + * + * The camera HAL's response to each requested stream configuration. + * + * This version extends the @3.4 HalStream with the physicalCameraId + * field + */ +struct HalStream { + /** + * The definition of HalStream from the prior version. + */ + @3.4::HalStream v3_4; + + /** + * Whether this stream can be switch to offline mode. + * + * For devices that does not support the OFFLINE_PROCESSING capability, this + * fields will always be false. + * + * For backward compatible camera devices that support the + * OFFLINE_PROCESSING capability: any input stream and any output stream + * that can be output of the input stream must set this field to true. Also + * any stream of YUV420_888 format or JPEG format, with CPU_READ usage flag, + * must set this field to true. + * + * For depth only camera devices that support the OFFLINE_PROCESSING + * capability: any DEPTH16 output stream must set this field to true. + * + * All other streams are up to camera HAL to advertise support or not, + * though it is not recommended to list support for streams with + * hardware composer or video encoder usage flags as these streams tend + * to be targeted continuously and can lead to long latency when trying to + * switch to offline. + * + */ + bool supportOffline; +}; + +/** + * HalStreamConfiguration: + * + * Identical to @3.4::HalStreamConfiguration, except that it contains @3.6::HalStream entries. + * + */ +struct HalStreamConfiguration { + vec streams; +}; diff --git a/camera/metadata/3.2/types.hal b/camera/metadata/3.2/types.hal index cef0397931..ad671d9db4 100644 --- a/camera/metadata/3.2/types.hal +++ b/camera/metadata/3.2/types.hal @@ -410,7 +410,7 @@ enum CameraMetadataTag : uint32_t { * *

List of the maximum number of regions that can be used for metering in * auto-exposure (AE), auto-white balance (AWB), and auto-focus (AF); - * this corresponds to the the maximum number of elements in + * this corresponds to the maximum number of elements in * ANDROID_CONTROL_AE_REGIONS, ANDROID_CONTROL_AWB_REGIONS, * and ANDROID_CONTROL_AF_REGIONS.

* @@ -1343,8 +1343,8 @@ enum CameraMetadataTag : uint32_t { /** android.sensor.rollingShutterSkew [dynamic, int64, public] * - *

Duration between the start of first row exposure - * and the start of last row exposure.

+ *

Duration between the start of exposure for the first row of the image sensor, + * and the start of exposure for one past the last row of the image sensor.

*/ ANDROID_SENSOR_ROLLING_SHUTTER_SKEW, diff --git a/camera/metadata/3.3/types.hal b/camera/metadata/3.3/types.hal index ca0c9d619b..0d896818bc 100644 --- a/camera/metadata/3.3/types.hal +++ b/camera/metadata/3.3/types.hal @@ -71,8 +71,10 @@ enum CameraMetadataTag : @3.2::CameraMetadataTag { /** android.lens.poseReference [static, enum, public] * - *

The origin for ANDROID_LENS_POSE_TRANSLATION.

+ *

The origin for ANDROID_LENS_POSE_TRANSLATION, and the accuracy of + * ANDROID_LENS_POSE_TRANSLATION and ANDROID_LENS_POSE_ROTATION.

* + * @see ANDROID_LENS_POSE_ROTATION * @see ANDROID_LENS_POSE_TRANSLATION */ ANDROID_LENS_POSE_REFERENCE = android.hardware.camera.metadata@3.2::CameraMetadataTag:ANDROID_LENS_END, diff --git a/camera/metadata/3.5/Android.bp b/camera/metadata/3.5/Android.bp new file mode 100644 index 0000000000..a28ba430a5 --- /dev/null +++ b/camera/metadata/3.5/Android.bp @@ -0,0 +1,15 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.camera.metadata@3.5", + root: "android.hardware", + srcs: [ + "types.hal", + ], + interfaces: [ + "android.hardware.camera.metadata@3.2", + "android.hardware.camera.metadata@3.3", + "android.hardware.camera.metadata@3.4", + ], + gen_java: true, +} diff --git a/camera/metadata/3.5/types.hal b/camera/metadata/3.5/types.hal new file mode 100644 index 0000000000..99d61152ac --- /dev/null +++ b/camera/metadata/3.5/types.hal @@ -0,0 +1,143 @@ +/* + * 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. + */ + +/* + * Autogenerated from camera metadata definitions in + * /system/media/camera/docs/metadata_definitions.xml + * *** DO NOT EDIT BY HAND *** + */ + +package android.hardware.camera.metadata@3.5; + +import android.hardware.camera.metadata@3.2; +import android.hardware.camera.metadata@3.3; +import android.hardware.camera.metadata@3.4; + +// No new metadata sections added in this revision + +/** + * Main enumeration for defining camera metadata tags added in this revision + * + *

Partial documentation is included for each tag; for complete documentation, reference + * '/system/media/camera/docs/docs.html' in the corresponding Android source tree.

+ */ +enum CameraMetadataTag : @3.4::CameraMetadataTag { + /** android.control.availableExtendedSceneModeMaxSizes [static, int32[], ndk_public] + * + *

The list of extended scene modes for ANDROID_CONTROL_EXTENDED_SCENE_MODE that are supported + * by this camera device, and each extended scene mode's maximum streaming (non-stall) size + * with effect.

+ * + * @see ANDROID_CONTROL_EXTENDED_SCENE_MODE + */ + ANDROID_CONTROL_AVAILABLE_EXTENDED_SCENE_MODE_MAX_SIZES = android.hardware.camera.metadata@3.3::CameraMetadataTag:ANDROID_CONTROL_END_3_3, + + /** android.control.availableExtendedSceneModeZoomRatioRanges [static, float[], ndk_public] + * + *

The ranges of supported zoom ratio for non-DISABLED ANDROID_CONTROL_EXTENDED_SCENE_MODE.

+ * + * @see ANDROID_CONTROL_EXTENDED_SCENE_MODE + */ + ANDROID_CONTROL_AVAILABLE_EXTENDED_SCENE_MODE_ZOOM_RATIO_RANGES, + + /** android.control.extendedSceneMode [dynamic, enum, public] + * + *

Whether extended scene mode is enabled for a particular capture request.

+ */ + ANDROID_CONTROL_EXTENDED_SCENE_MODE, + + /** android.control.zoomRatioRange [static, float[], public] + * + *

Minimum and maximum zoom ratios supported by this camera device.

+ */ + ANDROID_CONTROL_ZOOM_RATIO_RANGE, + + /** android.control.zoomRatio [dynamic, float, public] + * + *

The desired zoom ratio

+ */ + ANDROID_CONTROL_ZOOM_RATIO, + + ANDROID_CONTROL_END_3_5, + + /** android.scaler.availableRotateAndCropModes [static, byte[], hidden] + * + *

List of rotate-and-crop modes for ANDROID_SCALER_ROTATE_AND_CROP that are supported by this camera device.

+ * + * @see ANDROID_SCALER_ROTATE_AND_CROP + */ + ANDROID_SCALER_AVAILABLE_ROTATE_AND_CROP_MODES = android.hardware.camera.metadata@3.4::CameraMetadataTag:ANDROID_SCALER_END_3_4, + + /** android.scaler.rotateAndCrop [dynamic, enum, hidden] + * + *

Whether a rotation-and-crop operation is applied to processed + * outputs from the camera.

+ */ + ANDROID_SCALER_ROTATE_AND_CROP, + + ANDROID_SCALER_END_3_5, + +}; + +/* + * Enumeration definitions for the various entries that need them + */ + +/** android.control.mode enumeration values added since v3.2 + * @see ANDROID_CONTROL_MODE + */ +enum CameraMetadataEnumAndroidControlMode : + @3.2::CameraMetadataEnumAndroidControlMode { + ANDROID_CONTROL_MODE_USE_EXTENDED_SCENE_MODE, +}; + +/** android.control.extendedSceneMode enumeration values + * @see ANDROID_CONTROL_EXTENDED_SCENE_MODE + */ +enum CameraMetadataEnumAndroidControlExtendedSceneMode : uint32_t { + ANDROID_CONTROL_EXTENDED_SCENE_MODE_DISABLED = 0, + ANDROID_CONTROL_EXTENDED_SCENE_MODE_BOKEH_STILL_CAPTURE, + ANDROID_CONTROL_EXTENDED_SCENE_MODE_BOKEH_CONTINUOUS, + ANDROID_CONTROL_EXTENDED_SCENE_MODE_VENDOR_START = 0x40, +}; + +/** android.lens.poseReference enumeration values added since v3.3 + * @see ANDROID_LENS_POSE_REFERENCE + */ +enum CameraMetadataEnumAndroidLensPoseReference : + @3.3::CameraMetadataEnumAndroidLensPoseReference { + ANDROID_LENS_POSE_REFERENCE_UNDEFINED, +}; + +/** android.request.availableCapabilities enumeration values added since v3.4 + * @see ANDROID_REQUEST_AVAILABLE_CAPABILITIES + */ +enum CameraMetadataEnumAndroidRequestAvailableCapabilities : + @3.4::CameraMetadataEnumAndroidRequestAvailableCapabilities { + ANDROID_REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA, + ANDROID_REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING, +}; + +/** android.scaler.rotateAndCrop enumeration values + * @see ANDROID_SCALER_ROTATE_AND_CROP + */ +enum CameraMetadataEnumAndroidScalerRotateAndCrop : uint32_t { + ANDROID_SCALER_ROTATE_AND_CROP_NONE, + ANDROID_SCALER_ROTATE_AND_CROP_90, + ANDROID_SCALER_ROTATE_AND_CROP_180, + ANDROID_SCALER_ROTATE_AND_CROP_270, + ANDROID_SCALER_ROTATE_AND_CROP_AUTO, +}; diff --git a/camera/provider/2.4/ICameraProvider.hal b/camera/provider/2.4/ICameraProvider.hal index 74c3ff1693..105629dec3 100644 --- a/camera/provider/2.4/ICameraProvider.hal +++ b/camera/provider/2.4/ICameraProvider.hal @@ -115,7 +115,7 @@ interface ICameraProvider { * INTERNAL_ERROR: * A camera ID list cannot be created. This may be due to * a failure to initialize the camera subsystem, for example. - * @return cameraDeviceServiceNames The vector of internal camera device + * @return cameraDeviceNames The vector of internal camera device * names known to this provider. */ getCameraIdList() diff --git a/camera/provider/2.4/ICameraProviderCallback.hal b/camera/provider/2.4/ICameraProviderCallback.hal index 63dd3c516f..8822305c3a 100644 --- a/camera/provider/2.4/ICameraProviderCallback.hal +++ b/camera/provider/2.4/ICameraProviderCallback.hal @@ -39,7 +39,7 @@ interface ICameraProviderCallback { * are already present, as soon as the callbacks are available through * setCallback. * - * @param cameraDeviceServiceName The name of the camera device that has a + * @param cameraDeviceName The name of the camera device that has a * new status. * @param newStatus The new status that device is in. * @@ -57,7 +57,7 @@ interface ICameraProviderCallback { * android.flash.info.available is reported as true via the * ICameraDevice::getCameraCharacteristics call. * - * @param cameraDeviceServiceName The name of the camera device that has a + * @param cameraDeviceName The name of the camera device that has a * new status. * @param newStatus The new status that device is in. * diff --git a/camera/provider/2.4/default/Android.bp b/camera/provider/2.4/default/Android.bp index 95e27fd8c1..627ddf40bc 100644 --- a/camera/provider/2.4/default/Android.bp +++ b/camera/provider/2.4/default/Android.bp @@ -13,6 +13,7 @@ cc_library_shared { "android.hardware.camera.provider@2.4", "android.hardware.graphics.mapper@2.0", "android.hardware.graphics.mapper@3.0", + "android.hardware.graphics.mapper@4.0", "android.hidl.allocator@1.0", "android.hidl.memory@1.0", "camera.device@1.0-impl", @@ -48,9 +49,11 @@ cc_library_shared { "android.hardware.camera.device@3.3", "android.hardware.camera.device@3.4", "android.hardware.camera.device@3.5", + "android.hardware.camera.device@3.6", "android.hardware.camera.provider@2.4", "android.hardware.graphics.mapper@2.0", "android.hardware.graphics.mapper@3.0", + "android.hardware.graphics.mapper@4.0", "android.hidl.allocator@1.0", "android.hidl.memory@1.0", "camera.device@3.3-impl", @@ -58,6 +61,7 @@ cc_library_shared { "camera.device@3.4-impl", "camera.device@3.5-external-impl", "camera.device@3.5-impl", + "camera.device@3.6-external-impl", "libcamera_metadata", "libcutils", "libhardware", @@ -71,7 +75,8 @@ cc_library_shared { ], header_libs: [ "camera.device@3.4-external-impl_headers", - "camera.device@3.5-external-impl_headers" + "camera.device@3.5-external-impl_headers", + "camera.device@3.6-external-impl_headers" ], export_include_dirs: ["."], } @@ -93,6 +98,8 @@ cc_library_shared { "android.hardware.camera.provider@2.4-external", "android.hardware.camera.provider@2.4-legacy", "android.hardware.graphics.mapper@2.0", + "android.hardware.graphics.mapper@3.0", + "android.hardware.graphics.mapper@4.0", "android.hidl.allocator@1.0", "android.hidl.memory@1.0", "camera.device@1.0-impl", @@ -137,6 +144,8 @@ cc_defaults { "android.hardware.camera.device@3.5", "android.hardware.camera.provider@2.4", "android.hardware.graphics.mapper@2.0", + "android.hardware.graphics.mapper@3.0", + "android.hardware.graphics.mapper@4.0", "android.hidl.allocator@1.0", "android.hidl.memory@1.0", "libbinder", diff --git a/camera/provider/2.4/default/ExternalCameraProviderImpl_2_4.cpp b/camera/provider/2.4/default/ExternalCameraProviderImpl_2_4.cpp index a6fd288125..64a51f6141 100644 --- a/camera/provider/2.4/default/ExternalCameraProviderImpl_2_4.cpp +++ b/camera/provider/2.4/default/ExternalCameraProviderImpl_2_4.cpp @@ -26,6 +26,7 @@ #include "ExternalCameraProviderImpl_2_4.h" #include "ExternalCameraDevice_3_4.h" #include "ExternalCameraDevice_3_5.h" +#include "ExternalCameraDevice_3_6.h" namespace android { namespace hardware { @@ -43,17 +44,19 @@ const int kMaxDevicePathLen = 256; const char* kDevicePath = "/dev/"; constexpr char kPrefix[] = "video"; constexpr int kPrefixLen = sizeof(kPrefix) - 1; +constexpr int kDevicePrefixLen = sizeof(kDevicePath) + kPrefixLen + 1; -bool matchDeviceName(const hidl_string& deviceName, std::string* deviceVersion, - std::string* cameraId) { +bool matchDeviceName(int cameraIdOffset, + const hidl_string& deviceName, std::string* deviceVersion, + std::string* cameraDevicePath) { std::string deviceNameStd(deviceName.c_str()); std::smatch sm; if (std::regex_match(deviceNameStd, sm, kDeviceNameRE)) { if (deviceVersion != nullptr) { *deviceVersion = sm[1]; } - if (cameraId != nullptr) { - *cameraId = sm[2]; + if (cameraDevicePath != nullptr) { + *cameraDevicePath = "/dev/video" + std::to_string(std::stoi(sm[2]) - cameraIdOffset); } return true; } @@ -73,6 +76,7 @@ ExternalCameraProviderImpl_2_4::ExternalCameraProviderImpl_2_4() : switch(mPreferredHal3MinorVersion) { case 4: case 5: + case 6: // OK break; default: @@ -144,8 +148,9 @@ Return ExternalCameraProviderImpl_2_4::getCameraDeviceInterface_V3_x( const hidl_string& cameraDeviceName, ICameraProvider::getCameraDeviceInterface_V3_x_cb _hidl_cb) { - std::string cameraId, deviceVersion; - bool match = matchDeviceName(cameraDeviceName, &deviceVersion, &cameraId); + std::string cameraDevicePath, deviceVersion; + bool match = matchDeviceName(mCfg.cameraIdOffset, cameraDeviceName, + &deviceVersion, &cameraDevicePath); if (!match) { _hidl_cb(Status::ILLEGAL_ARGUMENT, nullptr); return Void(); @@ -162,13 +167,19 @@ Return ExternalCameraProviderImpl_2_4::getCameraDeviceInterface_V3_x( case 4: { ALOGV("Constructing v3.4 external camera device"); deviceImpl = new device::V3_4::implementation::ExternalCameraDevice( - cameraId, mCfg); + cameraDevicePath, mCfg); break; } case 5: { ALOGV("Constructing v3.5 external camera device"); deviceImpl = new device::V3_5::implementation::ExternalCameraDevice( - cameraId, mCfg); + cameraDevicePath, mCfg); + break; + } + case 6: { + ALOGV("Constructing v3.6 external camera device"); + deviceImpl = new device::V3_6::implementation::ExternalCameraDevice( + cameraDevicePath, mCfg); break; } default: @@ -178,7 +189,7 @@ Return ExternalCameraProviderImpl_2_4::getCameraDeviceInterface_V3_x( } if (deviceImpl == nullptr || deviceImpl->isInitFailed()) { - ALOGE("%s: camera device %s init failed!", __FUNCTION__, cameraId.c_str()); + ALOGE("%s: camera device %s init failed!", __FUNCTION__, cameraDevicePath.c_str()); _hidl_cb(Status::INTERNAL_ERROR, nullptr); return Void(); } @@ -202,10 +213,14 @@ void ExternalCameraProviderImpl_2_4::addExternalCamera(const char* devName) { ALOGI("ExtCam: adding %s to External Camera HAL!", devName); Mutex::Autolock _l(mLock); std::string deviceName; - if (mPreferredHal3MinorVersion == 5) { - deviceName = std::string("device@3.5/external/") + devName; + std::string cameraId = std::to_string(mCfg.cameraIdOffset + + std::atoi(devName + kDevicePrefixLen)); + if (mPreferredHal3MinorVersion == 6) { + deviceName = std::string("device@3.6/external/") + cameraId; + } else if (mPreferredHal3MinorVersion == 5) { + deviceName = std::string("device@3.5/external/") + cameraId; } else { - deviceName = std::string("device@3.4/external/") + devName; + deviceName = std::string("device@3.4/external/") + cameraId; } mCameraStatusMap[deviceName] = CameraDeviceStatus::PRESENT; if (mCallbacks != nullptr) { @@ -249,10 +264,14 @@ void ExternalCameraProviderImpl_2_4::deviceAdded(const char* devName) { void ExternalCameraProviderImpl_2_4::deviceRemoved(const char* devName) { Mutex::Autolock _l(mLock); std::string deviceName; - if (mPreferredHal3MinorVersion == 5) { - deviceName = std::string("device@3.5/external/") + devName; + std::string cameraId = std::to_string(mCfg.cameraIdOffset + + std::atoi(devName + kDevicePrefixLen)); + if (mPreferredHal3MinorVersion == 6) { + deviceName = std::string("device@3.6/external/") + cameraId; + } else if (mPreferredHal3MinorVersion == 5) { + deviceName = std::string("device@3.5/external/") + cameraId; } else { - deviceName = std::string("device@3.4/external/") + devName; + deviceName = std::string("device@3.4/external/") + cameraId; } if (mCameraStatusMap.find(deviceName) != mCameraStatusMap.end()) { mCameraStatusMap.erase(deviceName); diff --git a/camera/provider/2.4/vts/functional/Android.bp b/camera/provider/2.4/vts/functional/Android.bp index 2c3ed37a6a..4b9d6f125b 100644 --- a/camera/provider/2.4/vts/functional/Android.bp +++ b/camera/provider/2.4/vts/functional/Android.bp @@ -38,17 +38,16 @@ cc_test { "android.hardware.camera.device@3.3", "android.hardware.camera.device@3.4", "android.hardware.camera.device@3.5", - "android.hardware.camera.metadata@3.4", + "android.hardware.camera.device@3.6", + "android.hardware.camera.metadata@3.4", "android.hardware.camera.provider@2.4", "android.hardware.camera.provider@2.5", - "android.hardware.graphics.allocator@2.0", - "android.hardware.graphics.allocator@3.0", + "android.hardware.camera.provider@2.6", "android.hardware.graphics.common@1.0", - "android.hardware.graphics.mapper@2.0", - "android.hardware.graphics.mapper@3.0", "android.hidl.allocator@1.0", "libgrallocusage", "libhidlmemory", + "libgralloctypes", ], - test_suites: ["general-tests"], + test_suites: ["general-tests", "vts-core"], } diff --git a/camera/provider/2.4/vts/functional/AndroidTest.xml b/camera/provider/2.4/vts/functional/AndroidTest.xml new file mode 100644 index 0000000000..3000c0e3a6 --- /dev/null +++ b/camera/provider/2.4/vts/functional/AndroidTest.xml @@ -0,0 +1,33 @@ + + + + diff --git a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp index a5369e7b8d..f235235ab7 100644 --- a/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp +++ b/camera/provider/2.4/vts/functional/VtsHalCameraProviderV2_4TargetTest.cpp @@ -18,113 +18,119 @@ #include #include +#include +#include #include #include +#include #include #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include +#include #include #include #include #include #include +#include +#include +#include #include #include #include +#include +#include -#include -#include -#include -#include -#include #include #include #include -#include -#include - using namespace ::android::hardware::camera::device; -using ::android::hardware::Return; -using ::android::hardware::Void; +using ::android::BufferItemConsumer; +using ::android::BufferQueue; +using ::android::GraphicBuffer; +using ::android::IGraphicBufferConsumer; +using ::android::IGraphicBufferProducer; +using ::android::sp; +using ::android::Surface; +using ::android::wp; using ::android::hardware::hidl_bitfield; using ::android::hardware::hidl_handle; using ::android::hardware::hidl_string; using ::android::hardware::hidl_vec; -using ::android::sp; -using ::android::wp; -using ::android::GraphicBuffer; -using ::android::IGraphicBufferProducer; -using ::android::IGraphicBufferConsumer; -using ::android::BufferQueue; -using ::android::BufferItemConsumer; -using ::android::Surface; -using ::android::hardware::graphics::common::V1_0::BufferUsage; -using ::android::hardware::graphics::common::V1_0::Dataspace; -using ::android::hardware::graphics::common::V1_0::PixelFormat; -using ::android::hardware::camera::common::V1_0::Status; +using ::android::hardware::kSynchronizedReadWrite; +using ::android::hardware::MessageQueue; +using ::android::hardware::Return; +using ::android::hardware::Void; using ::android::hardware::camera::common::V1_0::CameraDeviceStatus; +using ::android::hardware::camera::common::V1_0::Status; using ::android::hardware::camera::common::V1_0::TorchMode; using ::android::hardware::camera::common::V1_0::TorchModeStatus; using ::android::hardware::camera::common::V1_0::helper::CameraParameters; using ::android::hardware::camera::common::V1_0::helper::Size; -using ::android::hardware::camera::provider::V2_4::ICameraProvider; -using ::android::hardware::camera::provider::V2_4::ICameraProviderCallback; -using ::android::hardware::camera::device::V3_2::ICameraDevice; -using ::android::hardware::camera::device::V3_2::BufferCache; -using ::android::hardware::camera::device::V3_2::CaptureRequest; -using ::android::hardware::camera::device::V3_2::CaptureResult; -using ::android::hardware::camera::device::V3_2::ICameraDeviceSession; -using ::android::hardware::camera::device::V3_2::NotifyMsg; -using ::android::hardware::camera::device::V3_2::RequestTemplate; -using ::android::hardware::camera::device::V3_2::StreamType; -using ::android::hardware::camera::device::V3_2::StreamRotation; -using ::android::hardware::camera::device::V3_2::StreamConfiguration; -using ::android::hardware::camera::device::V3_2::StreamConfigurationMode; -using ::android::hardware::camera::device::V3_2::CameraMetadata; -using ::android::hardware::camera::device::V3_2::HalStreamConfiguration; -using ::android::hardware::camera::device::V3_2::BufferStatus; -using ::android::hardware::camera::device::V3_2::StreamBuffer; -using ::android::hardware::camera::device::V3_2::MsgType; -using ::android::hardware::camera::device::V3_2::ErrorMsg; -using ::android::hardware::camera::device::V3_2::ErrorCode; using ::android::hardware::camera::device::V1_0::CameraFacing; -using ::android::hardware::camera::device::V1_0::NotifyCallbackMsg; +using ::android::hardware::camera::device::V1_0::CameraFrameMetadata; using ::android::hardware::camera::device::V1_0::CommandType; using ::android::hardware::camera::device::V1_0::DataCallbackMsg; -using ::android::hardware::camera::device::V1_0::CameraFrameMetadata; -using ::android::hardware::camera::device::V1_0::ICameraDevicePreviewCallback; using ::android::hardware::camera::device::V1_0::FrameCallbackFlag; using ::android::hardware::camera::device::V1_0::HandleTimestampMessage; -using ::android::hardware::camera::metadata::V3_4::CameraMetadataEnumAndroidSensorInfoColorFilterArrangement; -using ::android::hardware::camera::metadata::V3_4::CameraMetadataTag; +using ::android::hardware::camera::device::V1_0::ICameraDevicePreviewCallback; +using ::android::hardware::camera::device::V1_0::NotifyCallbackMsg; +using ::android::hardware::camera::device::V3_2::BufferCache; +using ::android::hardware::camera::device::V3_2::BufferStatus; +using ::android::hardware::camera::device::V3_2::CameraMetadata; +using ::android::hardware::camera::device::V3_2::CaptureRequest; +using ::android::hardware::camera::device::V3_2::CaptureResult; +using ::android::hardware::camera::device::V3_2::ErrorCode; +using ::android::hardware::camera::device::V3_2::ErrorMsg; +using ::android::hardware::camera::device::V3_2::HalStreamConfiguration; +using ::android::hardware::camera::device::V3_2::ICameraDevice; +using ::android::hardware::camera::device::V3_2::ICameraDeviceSession; +using ::android::hardware::camera::device::V3_2::MsgType; +using ::android::hardware::camera::device::V3_2::NotifyMsg; +using ::android::hardware::camera::device::V3_2::RequestTemplate; +using ::android::hardware::camera::device::V3_2::StreamBuffer; +using ::android::hardware::camera::device::V3_2::StreamConfiguration; +using ::android::hardware::camera::device::V3_2::StreamConfigurationMode; +using ::android::hardware::camera::device::V3_2::StreamRotation; +using ::android::hardware::camera::device::V3_2::StreamType; using ::android::hardware::camera::device::V3_4::PhysicalCameraMetadata; -using ::android::hardware::MessageQueue; -using ::android::hardware::kSynchronizedReadWrite; +using ::android::hardware::camera::metadata::V3_4:: + CameraMetadataEnumAndroidSensorInfoColorFilterArrangement; +using ::android::hardware::camera::metadata::V3_4::CameraMetadataTag; +using ::android::hardware::camera::provider::V2_4::ICameraProvider; +using ::android::hardware::camera::provider::V2_4::ICameraProviderCallback; +using ::android::hardware::camera::provider::V2_6::CameraIdAndStreamCombination; +using ::android::hardware::graphics::common::V1_0::BufferUsage; +using ::android::hardware::graphics::common::V1_0::Dataspace; +using ::android::hardware::graphics::common::V1_0::PixelFormat; using ::android::hidl::allocator::V1_0::IAllocator; -using ::android::hidl::memory::V1_0::IMemory; using ::android::hidl::memory::V1_0::IMapper; +using ::android::hidl::memory::V1_0::IMemory; using ResultMetadataQueue = MessageQueue; using ::android::hidl::manager::V1_0::IServiceManager; @@ -132,6 +138,8 @@ using namespace ::android::hardware::camera; const uint32_t kMaxPreviewWidth = 1920; const uint32_t kMaxPreviewHeight = 1080; +const uint32_t kMaxStillWidth = 2048; +const uint32_t kMaxStillHeight = 1536; const uint32_t kMaxVideoWidth = 4096; const uint32_t kMaxVideoHeight = 2160; const int64_t kStreamBufferTimeoutSec = 3; @@ -158,14 +166,36 @@ enum ReprocessType { YUV_REPROCESS, }; +enum SystemCameraKind { + /** + * These camera devices are visible to all apps and system components alike + */ + PUBLIC = 0, + + /** + * These camera devices are visible only to processes having the + * android.permission.SYSTEM_CAMERA permission. They are not exposed to 3P + * apps. + */ + SYSTEM_ONLY_CAMERA, + + /** + * These camera devices are visible only to HAL clients (that try to connect + * on a hwbinder thread). + */ + HIDDEN_SECURE_CAMERA +}; + namespace { // "device@/legacy/" const char *kDeviceNameRE = "device@([0-9]+\\.[0-9]+)/%s/(.+)"; + const int CAMERA_DEVICE_API_VERSION_3_6 = 0x306; const int CAMERA_DEVICE_API_VERSION_3_5 = 0x305; const int CAMERA_DEVICE_API_VERSION_3_4 = 0x304; const int CAMERA_DEVICE_API_VERSION_3_3 = 0x303; const int CAMERA_DEVICE_API_VERSION_3_2 = 0x302; const int CAMERA_DEVICE_API_VERSION_1_0 = 0x100; + const char *kHAL3_6 = "3.6"; const char *kHAL3_5 = "3.5"; const char *kHAL3_4 = "3.4"; const char *kHAL3_3 = "3.3"; @@ -201,7 +231,9 @@ namespace { return -1; } - if (version.compare(kHAL3_5) == 0) { + if (version.compare(kHAL3_6) == 0) { + return CAMERA_DEVICE_API_VERSION_3_6; + } else if (version.compare(kHAL3_5) == 0) { return CAMERA_DEVICE_API_VERSION_3_5; } else if (version.compare(kHAL3_4) == 0) { return CAMERA_DEVICE_API_VERSION_3_4; @@ -286,27 +318,6 @@ namespace { } } -// Test environment for camera -class CameraHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { - public: - // get the test environment singleton - static CameraHidlEnvironment* Instance() { - static CameraHidlEnvironment* instance = new CameraHidlEnvironment; - return instance; - } - - virtual void HidlSetUp() override { ALOGI("SetUp CameraHidlEnvironment"); } - - virtual void HidlTearDown() override { ALOGI("TearDown CameraHidlEnvironment"); } - - virtual void registerTestServices() override { registerTestService(); } - - private: - CameraHidlEnvironment() {} - - GTEST_DISALLOW_COPY_AND_ASSIGN_(CameraHidlEnvironment); -}; - struct BufferItemHander: public BufferItemConsumer::FrameAvailableListener { BufferItemHander(wp consumer) : mConsumer(consumer) {} @@ -547,25 +558,34 @@ Return PreviewWindowCb::setTimestamp(int64_t timestamp) { } // The main test class for camera HIDL HAL. -class CameraHidlTest : public ::testing::VtsHalHidlTargetTestBase { +class CameraHidlTest : public ::testing::TestWithParam { public: virtual void SetUp() override { - string service_name = CameraHidlEnvironment::Instance()->getServiceName(); + std::string service_name = GetParam(); ALOGI("get service with name: %s", service_name.c_str()); - mProvider = ::testing::VtsHalHidlTargetTestBase::getService(service_name); + mProvider = ICameraProvider::getService(service_name); + ASSERT_NE(mProvider, nullptr); uint32_t id; ASSERT_TRUE(parseProviderName(service_name, &mProviderType, &id)); - castProvider(mProvider, &mProvider2_5); + castProvider(mProvider, &mProvider2_5, &mProvider2_6); notifyDeviceState(provider::V2_5::DeviceState::NORMAL); } virtual void TearDown() override {} - hidl_vec getCameraDeviceNames(sp provider); + hidl_vec getCameraDeviceNames(sp provider, + bool addSecureOnly = false); - struct EmptyDeviceCb : public V3_5::ICameraDeviceCallback { + bool isSecureOnly(sp provider, const hidl_string& name); + + std::map getCameraDeviceIdToNameMap(sp provider); + + hidl_vec> getConcurrentDeviceCombinations( + sp<::android::hardware::camera::provider::V2_6::ICameraProvider>&); + + struct EmptyDeviceCb : public V3_5::ICameraDeviceCallback { virtual Return processCaptureResult( const hidl_vec& /*results*/) override { ALOGI("processCaptureResult callback"); @@ -602,8 +622,7 @@ public: ADD_FAILURE(); // Empty callback should not reach here return Void(); } - - }; + }; struct DeviceCb : public V3_5::ICameraDeviceCallback { DeviceCb(CameraHidlTest *parent, int deviceVersion, const camera_metadata_t *staticMeta) : @@ -622,7 +641,7 @@ public: Return returnStreamBuffers(const hidl_vec& buffers) override; - void setCurrentStreamConfig(const hidl_vec& streams, + void setCurrentStreamConfig(const hidl_vec& streams, const hidl_vec& halStreams); void waitForBuffersReturned(); @@ -639,7 +658,7 @@ public: /* members for requestStreamBuffers() and returnStreamBuffers()*/ std::mutex mLock; // protecting members below bool mUseHalBufManager = false; - hidl_vec mStreams; + hidl_vec mStreams; hidl_vec mHalStreams; uint64_t mNextBufferId = 1; using OutstandingBuffers = std::unordered_map; @@ -730,12 +749,14 @@ public: sp *session /*out*/, camera_metadata_t **staticMeta /*out*/, ::android::sp *device = nullptr/*out*/); - void castProvider(const sp &provider, - sp *provider2_5 /*out*/); + void castProvider(const sp& provider, + sp* provider2_5 /*out*/, + sp* provider2_6 /*out*/); void castSession(const sp &session, int32_t deviceVersion, sp *session3_3 /*out*/, sp *session3_4 /*out*/, - sp *session3_5 /*out*/); + sp *session3_5 /*out*/, + sp *session3_6 /*out*/); void castDevice(const sp &device, int32_t deviceVersion, sp *device3_5/*out*/); void createStreamConfiguration(const ::android::hardware::hidl_vec& streams3_2, @@ -745,6 +766,17 @@ public: ::android::hardware::camera::device::V3_5::StreamConfiguration *config3_5, uint32_t jpegBufferSize = 0); + void configureOfflineStillStream(const std::string &name, int32_t deviceVersion, + sp provider, + const AvailableStream *threshold, + sp *session/*out*/, + V3_2::Stream *stream /*out*/, + device::V3_6::HalStreamConfiguration *halStreamConfig /*out*/, + bool *supportsPartialResults /*out*/, + uint32_t *partialResultCount /*out*/, + sp *outCb /*out*/, + uint32_t *jpegBufferSize /*out*/, + bool *useHalBufManager /*out*/); void configurePreviewStreams3_4(const std::string &name, int32_t deviceVersion, sp provider, const AvailableStream *previewThreshold, @@ -770,12 +802,24 @@ public: bool *useHalBufManager /*out*/, sp *cb /*out*/, uint32_t streamConfigCounter = 0); + void configureSingleStream(const std::string& name, int32_t deviceVersion, + sp provider, + const AvailableStream* previewThreshold, uint64_t bufferUsage, + RequestTemplate reqTemplate, + sp* session /*out*/, + V3_2::Stream* previewStream /*out*/, + HalStreamConfiguration* halStreamConfig /*out*/, + bool* supportsPartialResults /*out*/, + uint32_t* partialResultCount /*out*/, bool* useHalBufManager /*out*/, + sp* cb /*out*/, uint32_t streamConfigCounter = 0); void verifyLogicalCameraMetadata(const std::string& cameraName, const ::android::sp<::android::hardware::camera::device::V3_2::ICameraDevice>& device, const CameraMetadata& chars, int deviceVersion, const hidl_vec& deviceNames); void verifyCameraCharacteristics(Status status, const CameraMetadata& chars); + void verifyExtendedSceneModeCharacteristics(const camera_metadata_t* metadata); + void verifyZoomCharacteristics(const camera_metadata_t* metadata); void verifyRecommendedConfigs(const CameraMetadata& metadata); void verifyMonochromeCharacteristics(const CameraMetadata& chars, int deviceVersion); void verifyMonochromeCameraResult( @@ -797,15 +841,25 @@ public: void verifySessionReconfigurationQuery(sp session3_5, camera_metadata* oldSessionParams, camera_metadata* newSessionParams); - bool isDepthOnly(camera_metadata_t* staticMeta); + void verifyRequestTemplate(const camera_metadata_t* metadata, RequestTemplate requestTemplate); - static Status getAvailableOutputStreams(camera_metadata_t *staticMeta, + static bool isDepthOnly(const camera_metadata_t* staticMeta); + + static Status getAvailableOutputStreams(const camera_metadata_t *staticMeta, std::vector &outputStreams, const AvailableStream *threshold = nullptr); + + static Status getMaxOutputSizeForFormat(const camera_metadata_t* staticMeta, PixelFormat format, + Size* size); + + static Status getMandatoryConcurrentStreams(const camera_metadata_t* staticMeta, + std::vector* outputStreams); + static Status getJpegBufferSize(camera_metadata_t *staticMeta, uint32_t* outBufSize); static Status isConstrainedModeAvailable(camera_metadata_t *staticMeta); static Status isLogicalMultiCamera(const camera_metadata_t *staticMeta); + static Status isOfflineSessionSupported(const camera_metadata_t *staticMeta); static Status getPhysicalCameraIds(const camera_metadata_t *staticMeta, std::unordered_set *physicalIds/*out*/); static Status getSupportedKeys(camera_metadata_t *staticMeta, @@ -831,6 +885,14 @@ public: static Status isAutoFocusModeAvailable( CameraParameters &cameraParams, const char *mode) ; static Status isMonochromeCamera(const camera_metadata_t *staticMeta); + static Status getSystemCameraKind(const camera_metadata_t* staticMeta, + SystemCameraKind* systemCameraKind); + + void processCaptureRequestInternal(uint64_t bufferusage, RequestTemplate reqTemplate, + bool useSecureOnlyCameras); + + // Used by switchToOffline where a new result queue is created for offline reqs + void updateInflightResultQueue(std::shared_ptr resultQueue); protected: @@ -865,6 +927,8 @@ protected: int32_t partialResultCount; // For buffer drop errors, the stream ID for the stream that lost a buffer. + // For physical sub-camera result errors, the Id of the physical stream + // for the physical sub-camera. // Otherwise -1. int32_t errorStreamId; @@ -878,6 +942,8 @@ protected: // return from HAL but framework. ::android::Vector resultOutputBuffers; + std::unordered_set expectedPhysicalResults; + InFlightRequest() : shutterTimestamp(0), errorCodeValid(false), @@ -907,6 +973,24 @@ protected: partialResultCount(0), errorStreamId(-1), hasInputBuffer(hasInput) {} + + InFlightRequest(ssize_t numBuffers, bool hasInput, + bool partialResults, uint32_t partialCount, + const std::unordered_set& extraPhysicalResult, + std::shared_ptr queue = nullptr) : + shutterTimestamp(0), + errorCodeValid(false), + errorCode(ErrorCode::ERROR_BUFFER), + usePartialResult(partialResults), + numPartialResults(partialCount), + resultQueue(queue), + haveResultMetadata(false), + numBuffersLeft(numBuffers), + frameNumber(0), + partialResultCount(0), + errorStreamId(-1), + hasInputBuffer(hasInput), + expectedPhysicalResults(extraPhysicalResult) {} }; // Map from frame number to the in-flight request state @@ -932,6 +1016,7 @@ protected: // Camera provider service sp mProvider; sp<::android::hardware::camera::provider::V2_5::ICameraProvider> mProvider2_5; + sp<::android::hardware::camera::provider::V2_6::ICameraProvider> mProvider2_6; // Camera provider type. std::string mProviderType; @@ -1124,6 +1209,13 @@ bool CameraHidlTest::DeviceCb::processCaptureResultLocked(const CaptureResult& r return notify; } + if (physicalCameraMetadata.size() != request->expectedPhysicalResults.size()) { + ALOGE("%s: Frame %d: Returned physical metadata count %zu " + "must be equal to expected count %zu", __func__, frameNumber, + physicalCameraMetadata.size(), request->expectedPhysicalResults.size()); + ADD_FAILURE(); + return notify; + } std::vector<::android::hardware::camera::device::V3_2::CameraMetadata> physResultMetadata; physResultMetadata.resize(physicalCameraMetadata.size()); for (size_t i = 0; i < physicalCameraMetadata.size(); i++) { @@ -1251,11 +1343,11 @@ bool CameraHidlTest::DeviceCb::processCaptureResultLocked(const CaptureResult& r } void CameraHidlTest::DeviceCb::setCurrentStreamConfig( - const hidl_vec& streams, const hidl_vec& halStreams) { + const hidl_vec& streams, const hidl_vec& halStreams) { ASSERT_EQ(streams.size(), halStreams.size()); ASSERT_NE(streams.size(), 0); for (size_t i = 0; i < streams.size(); i++) { - ASSERT_EQ(streams[i].id, halStreams[i].id); + ASSERT_EQ(streams[i].v3_2.id, halStreams[i].id); } std::lock_guard l(mLock); mUseHalBufManager = true; @@ -1293,16 +1385,6 @@ Return CameraHidlTest::DeviceCb::notify( std::lock_guard l(mParent->mLock); for (size_t i = 0; i < messages.size(); i++) { - ssize_t idx = mParent->mInflightMap.indexOfKey( - messages[i].msg.shutter.frameNumber); - if (::android::NAME_NOT_FOUND == idx) { - ALOGE("%s: Unexpected frame number! received: %u", - __func__, messages[i].msg.shutter.frameNumber); - ADD_FAILURE(); - break; - } - InFlightRequest *r = mParent->mInflightMap.editValueAt(idx); - switch(messages[i].type) { case MsgType::ERROR: if (ErrorCode::ERROR_DEVICE == messages[i].msg.error.errorCode) { @@ -1310,13 +1392,59 @@ Return CameraHidlTest::DeviceCb::notify( __func__); ADD_FAILURE(); } else { - r->errorCodeValid = true; - r->errorCode = messages[i].msg.error.errorCode; - r->errorStreamId = messages[i].msg.error.errorStreamId; + ssize_t idx = mParent->mInflightMap.indexOfKey( + messages[i].msg.error.frameNumber); + if (::android::NAME_NOT_FOUND == idx) { + ALOGE("%s: Unexpected error frame number! received: %u", + __func__, messages[i].msg.error.frameNumber); + ADD_FAILURE(); + break; + } + InFlightRequest *r = mParent->mInflightMap.editValueAt(idx); + + if (ErrorCode::ERROR_RESULT == messages[i].msg.error.errorCode && + messages[i].msg.error.errorStreamId != -1) { + if (r->haveResultMetadata) { + ALOGE("%s: Camera must report physical camera result error before " + "the final capture result!", __func__); + ADD_FAILURE(); + } else { + for (size_t j = 0; j < mStreams.size(); j++) { + if (mStreams[j].v3_2.id == messages[i].msg.error.errorStreamId) { + hidl_string physicalCameraId = mStreams[j].physicalCameraId; + bool idExpected = r->expectedPhysicalResults.find( + physicalCameraId) != r->expectedPhysicalResults.end(); + if (!idExpected) { + ALOGE("%s: ERROR_RESULT's error stream's physicalCameraId " + "%s must be expected", __func__, + physicalCameraId.c_str()); + ADD_FAILURE(); + } else { + r->expectedPhysicalResults.erase(physicalCameraId); + } + break; + } + } + } + } else { + r->errorCodeValid = true; + r->errorCode = messages[i].msg.error.errorCode; + r->errorStreamId = messages[i].msg.error.errorStreamId; + } } break; case MsgType::SHUTTER: + { + ssize_t idx = mParent->mInflightMap.indexOfKey(messages[i].msg.shutter.frameNumber); + if (::android::NAME_NOT_FOUND == idx) { + ALOGE("%s: Unexpected shutter frame number! received: %u", + __func__, messages[i].msg.shutter.frameNumber); + ADD_FAILURE(); + break; + } + InFlightRequest *r = mParent->mInflightMap.editValueAt(idx); r->shutterTimestamp = messages[i].msg.shutter.timestamp; + } break; default: ALOGE("%s: Unsupported notify message %d", __func__, @@ -1357,7 +1485,7 @@ Return CameraHidlTest::DeviceCb::requestStreamBuffers( for (size_t i = 0; i < bufReqs.size(); i++) { bool found = false; for (size_t idx = 0; idx < mStreams.size(); idx++) { - if (bufReqs[i].streamId == mStreams[idx].id) { + if (bufReqs[i].streamId == mStreams[idx].v3_2.id) { found = true; indexes[i] = idx; break; @@ -1381,7 +1509,7 @@ Return CameraHidlTest::DeviceCb::requestStreamBuffers( const auto& halStream = mHalStreams[idx]; const V3_5::BufferRequest& bufReq = bufReqs[i]; if (mOutstandingBufferIds[idx].size() + bufReq.numBuffersRequested > halStream.maxBuffers) { - bufRets[i].streamId = stream.id; + bufRets[i].streamId = stream.v3_2.id; bufRets[i].val.error(StreamBufferRequestError::MAX_BUFFER_EXCEEDED); allStreamOk = false; continue; @@ -1390,17 +1518,23 @@ Return CameraHidlTest::DeviceCb::requestStreamBuffers( hidl_vec tmpRetBuffers(bufReq.numBuffersRequested); for (size_t j = 0; j < bufReq.numBuffersRequested; j++) { hidl_handle buffer_handle; - mParent->allocateGraphicBuffer(stream.width, stream.height, + uint32_t w = stream.v3_2.width; + uint32_t h = stream.v3_2.height; + if (stream.v3_2.format == PixelFormat::BLOB) { + w = stream.bufferSize; + h = 1; + } + mParent->allocateGraphicBuffer(w, h, android_convertGralloc1To0Usage( halStream.producerUsage, halStream.consumerUsage), halStream.overrideFormat, &buffer_handle); - tmpRetBuffers[j] = {stream.id, mNextBufferId, buffer_handle, BufferStatus::OK, + tmpRetBuffers[j] = {stream.v3_2.id, mNextBufferId, buffer_handle, BufferStatus::OK, nullptr, nullptr}; mOutstandingBufferIds[idx].insert(std::make_pair(mNextBufferId++, buffer_handle)); } atLeastOneStreamOk = true; - bufRets[i].streamId = stream.id; + bufRets[i].streamId = stream.v3_2.id; bufRets[i].val.buffers(std::move(tmpRetBuffers)); } @@ -1426,11 +1560,11 @@ Return CameraHidlTest::DeviceCb::returnStreamBuffers( ADD_FAILURE(); } - std::lock_guard l(mLock); + std::unique_lock l(mLock); for (const auto& buf : buffers) { bool found = false; for (size_t idx = 0; idx < mOutstandingBufferIds.size(); idx++) { - if (mStreams[idx].id == buf.streamId && + if (mStreams[idx].v3_2.id == buf.streamId && mOutstandingBufferIds[idx].count(buf.bufferId) == 1) { mOutstandingBufferIds[idx].erase(buf.bufferId); // TODO: check do we need to close/delete native handle or assume we have enough @@ -1446,10 +1580,29 @@ Return CameraHidlTest::DeviceCb::returnStreamBuffers( ALOGE("%s: unknown buffer ID %" PRIu64, __FUNCTION__, buf.bufferId); ADD_FAILURE(); } + if (!hasOutstandingBuffersLocked()) { + l.unlock(); + mFlushedCondition.notify_one(); + } return Void(); } -hidl_vec CameraHidlTest::getCameraDeviceNames(sp provider) { +std::map CameraHidlTest::getCameraDeviceIdToNameMap( + sp provider) { + hidl_vec cameraDeviceNames = getCameraDeviceNames(provider); + std::map idToNameMap; + for (auto& name : cameraDeviceNames) { + std::string version, cameraId; + if (!matchDeviceName(name, mProviderType, &version, &cameraId)) { + ADD_FAILURE(); + } + idToNameMap.insert(std::make_pair(hidl_string(cameraId), name)); + } + return idToNameMap; +} + +hidl_vec CameraHidlTest::getCameraDeviceNames(sp provider, + bool addSecureOnly) { std::vector cameraDeviceNames; Return ret; ret = provider->getCameraIdList( @@ -1498,15 +1651,70 @@ hidl_vec CameraHidlTest::getCameraDeviceNames(sp p } } - hidl_vec retList(cameraDeviceNames.size()); + std::vector retList; for (size_t i = 0; i < cameraDeviceNames.size(); i++) { - retList[i] = cameraDeviceNames[i]; + bool isSecureOnlyCamera = isSecureOnly(mProvider, cameraDeviceNames[i]); + if (addSecureOnly) { + if (isSecureOnlyCamera) { + retList.emplace_back(cameraDeviceNames[i]); + } + } else if (!isSecureOnlyCamera) { + retList.emplace_back(cameraDeviceNames[i]); + } } - return retList; + hidl_vec finalRetList = std::move(retList); + return finalRetList; +} + +bool CameraHidlTest::isSecureOnly(sp provider, const hidl_string& name) { + Return ret; + ::android::sp device3_x; + bool retVal = false; + if (getCameraDeviceVersion(mProviderType, name) == CAMERA_DEVICE_API_VERSION_1_0) { + return false; + } + ret = provider->getCameraDeviceInterface_V3_x(name, [&](auto status, const auto& device) { + ALOGI("getCameraDeviceInterface_V3_x returns status:%d", (int)status); + ASSERT_EQ(Status::OK, status); + ASSERT_NE(device, nullptr); + device3_x = device; + }); + if (!ret.isOk()) { + ADD_FAILURE() << "Failed to get camera device interface for " << name; + } + ret = device3_x->getCameraCharacteristics([&](Status s, CameraMetadata metadata) { + ASSERT_EQ(Status::OK, s); + camera_metadata_t* chars = (camera_metadata_t*)metadata.data(); + SystemCameraKind systemCameraKind = SystemCameraKind::PUBLIC; + Status status = getSystemCameraKind(chars, &systemCameraKind); + ASSERT_EQ(status, Status::OK); + if (systemCameraKind == SystemCameraKind::HIDDEN_SECURE_CAMERA) { + retVal = true; + } + }); + if (!ret.isOk()) { + ADD_FAILURE() << "Failed to get camera characteristics for device " << name; + } + return retVal; +} + +hidl_vec> CameraHidlTest::getConcurrentDeviceCombinations( + sp<::android::hardware::camera::provider::V2_6::ICameraProvider>& provider2_6) { + hidl_vec> combinations; + Return ret = provider2_6->getConcurrentStreamingCameraIds( + [&combinations](Status concurrentIdStatus, + const hidl_vec>& cameraDeviceIdCombinations) { + ASSERT_EQ(concurrentIdStatus, Status::OK); + combinations = cameraDeviceIdCombinations; + }); + if (!ret.isOk()) { + ADD_FAILURE(); + } + return combinations; } // Test devices with first_api_level >= P does not advertise device@1.0 -TEST_F(CameraHidlTest, noHal1AfterP) { +TEST_P(CameraHidlTest, noHal1AfterP) { constexpr int32_t HAL1_PHASE_OUT_API_LEVEL = 28; int32_t firstApiLevel = 0; getFirstApiLevel(&firstApiLevel); @@ -1531,7 +1739,7 @@ TEST_F(CameraHidlTest, noHal1AfterP) { // Test if ICameraProvider::isTorchModeSupported returns Status::OK // Also if first_api_level >= Q torch API must be supported. -TEST_F(CameraHidlTest, isTorchModeSupported) { +TEST_P(CameraHidlTest, isTorchModeSupported) { constexpr int32_t API_LEVEL_Q = 29; int32_t firstApiLevel = 0; getFirstApiLevel(&firstApiLevel); @@ -1548,7 +1756,7 @@ TEST_F(CameraHidlTest, isTorchModeSupported) { } // TODO: consider removing this test if getCameraDeviceNames() has the same coverage -TEST_F(CameraHidlTest, getCameraIdList) { +TEST_P(CameraHidlTest, getCameraIdList) { Return ret; ret = mProvider->getCameraIdList([&](auto status, const auto& idList) { ALOGI("getCameraIdList returns status:%d", (int)status); @@ -1561,7 +1769,7 @@ TEST_F(CameraHidlTest, getCameraIdList) { } // Test if ICameraProvider::getVendorTags returns Status::OK -TEST_F(CameraHidlTest, getVendorTags) { +TEST_P(CameraHidlTest, getVendorTags) { Return ret; ret = mProvider->getVendorTags([&](auto status, const auto& vendorTagSecs) { ALOGI("getVendorTags returns status:%d numSections %zu", (int)status, vendorTagSecs.size()); @@ -1579,7 +1787,7 @@ TEST_F(CameraHidlTest, getVendorTags) { } // Test if ICameraProvider::setCallback returns Status::OK -TEST_F(CameraHidlTest, setCallback) { +TEST_P(CameraHidlTest, setCallback) { struct ProviderCb : public ICameraProviderCallback { virtual Return cameraDeviceStatusChange( const hidl_string& cameraDeviceName, @@ -1597,6 +1805,33 @@ TEST_F(CameraHidlTest, setCallback) { return Void(); } }; + + struct ProviderCb2_6 + : public ::android::hardware::camera::provider::V2_6::ICameraProviderCallback { + virtual Return cameraDeviceStatusChange(const hidl_string& cameraDeviceName, + CameraDeviceStatus newStatus) override { + ALOGI("camera device status callback name %s, status %d", cameraDeviceName.c_str(), + (int)newStatus); + return Void(); + } + + virtual Return torchModeStatusChange(const hidl_string& cameraDeviceName, + TorchModeStatus newStatus) override { + ALOGI("Torch mode status callback name %s, status %d", cameraDeviceName.c_str(), + (int)newStatus); + return Void(); + } + + virtual Return physicalCameraDeviceStatusChange( + const hidl_string& cameraDeviceName, const hidl_string& physicalCameraDeviceName, + CameraDeviceStatus newStatus) override { + ALOGI("physical camera device status callback name %s, physical camera name %s," + " status %d", + cameraDeviceName.c_str(), physicalCameraDeviceName.c_str(), (int)newStatus); + return Void(); + } + }; + sp cb = new ProviderCb; auto status = mProvider->setCallback(cb); ASSERT_TRUE(status.isOk()); @@ -1604,15 +1839,26 @@ TEST_F(CameraHidlTest, setCallback) { status = mProvider->setCallback(nullptr); ASSERT_TRUE(status.isOk()); ASSERT_EQ(Status::OK, status); + + if (mProvider2_6.get() != nullptr) { + sp cb = new ProviderCb2_6; + auto status = mProvider2_6->setCallback(cb); + ASSERT_TRUE(status.isOk()); + ASSERT_EQ(Status::OK, status); + status = mProvider2_6->setCallback(nullptr); + ASSERT_TRUE(status.isOk()); + ASSERT_EQ(Status::OK, status); + } } // Test if ICameraProvider::getCameraDeviceInterface returns Status::OK and non-null device -TEST_F(CameraHidlTest, getCameraDeviceInterface) { +TEST_P(CameraHidlTest, getCameraDeviceInterface) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { int deviceVersion = getCameraDeviceVersion(name, mProviderType); switch (deviceVersion) { + case CAMERA_DEVICE_API_VERSION_3_6: case CAMERA_DEVICE_API_VERSION_3_5: case CAMERA_DEVICE_API_VERSION_3_4: case CAMERA_DEVICE_API_VERSION_3_3: @@ -1648,13 +1894,14 @@ TEST_F(CameraHidlTest, getCameraDeviceInterface) { } // Verify that the device resource cost can be retrieved and the values are -// sane. -TEST_F(CameraHidlTest, getResourceCost) { +// correct. +TEST_P(CameraHidlTest, getResourceCost) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { int deviceVersion = getCameraDeviceVersion(name, mProviderType); switch (deviceVersion) { + case CAMERA_DEVICE_API_VERSION_3_6: case CAMERA_DEVICE_API_VERSION_3_5: case CAMERA_DEVICE_API_VERSION_3_4: case CAMERA_DEVICE_API_VERSION_3_3: @@ -1719,7 +1966,7 @@ TEST_F(CameraHidlTest, getResourceCost) { // Verify that the static camera info can be retrieved // successfully. -TEST_F(CameraHidlTest, getCameraInfo) { +TEST_P(CameraHidlTest, getCameraInfo) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { @@ -1767,7 +2014,7 @@ TEST_F(CameraHidlTest, getCameraInfo) { } // Check whether preview window can be configured -TEST_F(CameraHidlTest, setPreviewWindow) { +TEST_P(CameraHidlTest, setPreviewWindow) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { @@ -1787,7 +2034,7 @@ TEST_F(CameraHidlTest, setPreviewWindow) { } // Verify that setting preview window fails in case device is not open -TEST_F(CameraHidlTest, setPreviewWindowInvalid) { +TEST_P(CameraHidlTest, setPreviewWindowInvalid) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { @@ -1812,7 +2059,7 @@ TEST_F(CameraHidlTest, setPreviewWindowInvalid) { } // Start and stop preview checking whether it gets enabled in between. -TEST_F(CameraHidlTest, startStopPreview) { +TEST_P(CameraHidlTest, startStopPreview) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { @@ -1837,7 +2084,7 @@ TEST_F(CameraHidlTest, startStopPreview) { // Start preview without active preview window. Preview should start as soon // as a valid active window gets configured. -TEST_F(CameraHidlTest, startStopPreviewDelayed) { +TEST_P(CameraHidlTest, startStopPreviewDelayed) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { @@ -1867,7 +2114,7 @@ TEST_F(CameraHidlTest, startStopPreviewDelayed) { } // Verify that image capture behaves as expected along with preview callbacks. -TEST_F(CameraHidlTest, takePicture) { +TEST_P(CameraHidlTest, takePicture) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { @@ -1916,7 +2163,7 @@ TEST_F(CameraHidlTest, takePicture) { } // Image capture should fail in case preview didn't get enabled first. -TEST_F(CameraHidlTest, takePictureFail) { +TEST_P(CameraHidlTest, takePictureFail) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { @@ -1936,7 +2183,7 @@ TEST_F(CameraHidlTest, takePictureFail) { } // Verify that image capture can be cancelled. -TEST_F(CameraHidlTest, cancelPicture) { +TEST_P(CameraHidlTest, cancelPicture) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { @@ -1963,7 +2210,7 @@ TEST_F(CameraHidlTest, cancelPicture) { } // Image capture cancel is a no-op when image capture is not running. -TEST_F(CameraHidlTest, cancelPictureNOP) { +TEST_P(CameraHidlTest, cancelPictureNOP) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { @@ -1986,7 +2233,7 @@ TEST_F(CameraHidlTest, cancelPictureNOP) { } // Test basic video recording. -TEST_F(CameraHidlTest, startStopRecording) { +TEST_P(CameraHidlTest, startStopRecording) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { @@ -2064,7 +2311,7 @@ TEST_F(CameraHidlTest, startStopRecording) { } // It shouldn't be possible to start recording without enabling preview first. -TEST_F(CameraHidlTest, startRecordingFail) { +TEST_P(CameraHidlTest, startRecordingFail) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { @@ -2088,7 +2335,7 @@ TEST_F(CameraHidlTest, startRecordingFail) { } // Check autofocus support if available. -TEST_F(CameraHidlTest, autoFocus) { +TEST_P(CameraHidlTest, autoFocus) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); std::vector focusModes = {CameraParameters::FOCUS_MODE_AUTO, CameraParameters::FOCUS_MODE_CONTINUOUS_PICTURE, @@ -2149,7 +2396,7 @@ TEST_F(CameraHidlTest, autoFocus) { } // In case autofocus is supported verify that it can be cancelled. -TEST_F(CameraHidlTest, cancelAutoFocus) { +TEST_P(CameraHidlTest, cancelAutoFocus) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { @@ -2195,7 +2442,7 @@ TEST_F(CameraHidlTest, cancelAutoFocus) { } // Check whether face detection is available and try to enable&disable. -TEST_F(CameraHidlTest, sendCommandFaceDetection) { +TEST_P(CameraHidlTest, sendCommandFaceDetection) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { @@ -2250,7 +2497,7 @@ TEST_F(CameraHidlTest, sendCommandFaceDetection) { } // Check whether smooth zoom is available and try to enable&disable. -TEST_F(CameraHidlTest, sendCommandSmoothZoom) { +TEST_P(CameraHidlTest, sendCommandSmoothZoom) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { @@ -2297,8 +2544,8 @@ TEST_F(CameraHidlTest, sendCommandSmoothZoom) { } } -// Basic sanity tests related to camera parameters. -TEST_F(CameraHidlTest, getSetParameters) { +// Basic correctness tests related to camera parameters. +TEST_P(CameraHidlTest, getSetParameters) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { @@ -2388,14 +2635,99 @@ TEST_F(CameraHidlTest, getSetParameters) { } } +TEST_P(CameraHidlTest, systemCameraTest) { + hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); + std::map> hiddenPhysicalIdToLogicalMap; + for (const auto& name : cameraDeviceNames) { + int deviceVersion = getCameraDeviceVersion(name, mProviderType); + switch (deviceVersion) { + case CAMERA_DEVICE_API_VERSION_3_6: + case CAMERA_DEVICE_API_VERSION_3_5: + case CAMERA_DEVICE_API_VERSION_3_4: + case CAMERA_DEVICE_API_VERSION_3_3: + case CAMERA_DEVICE_API_VERSION_3_2: { + ::android::sp<::android::hardware::camera::device::V3_2::ICameraDevice> device3_x; + ALOGI("getCameraCharacteristics: Testing camera device %s", name.c_str()); + Return ret; + ret = mProvider->getCameraDeviceInterface_V3_x( + name, [&](auto status, const auto& device) { + ALOGI("getCameraDeviceInterface_V3_x returns status:%d", (int)status); + ASSERT_EQ(Status::OK, status); + ASSERT_NE(device, nullptr); + device3_x = device; + }); + ASSERT_TRUE(ret.isOk()); + + ret = device3_x->getCameraCharacteristics([&](auto status, const auto& chars) { + ASSERT_EQ(status, Status::OK); + const camera_metadata_t* staticMeta = + reinterpret_cast(chars.data()); + ASSERT_NE(staticMeta, nullptr); + Status rc = isLogicalMultiCamera(staticMeta); + ASSERT_TRUE(Status::OK == rc || Status::METHOD_NOT_SUPPORTED == rc); + if (Status::METHOD_NOT_SUPPORTED == rc) { + return; + } + std::unordered_set physicalIds; + ASSERT_EQ(Status::OK, getPhysicalCameraIds(staticMeta, &physicalIds)); + SystemCameraKind systemCameraKind = SystemCameraKind::PUBLIC; + rc = getSystemCameraKind(staticMeta, &systemCameraKind); + ASSERT_EQ(rc, Status::OK); + for (auto physicalId : physicalIds) { + bool isPublicId = false; + for (auto& deviceName : cameraDeviceNames) { + std::string publicVersion, publicId; + ASSERT_TRUE(::matchDeviceName(deviceName, mProviderType, &publicVersion, + &publicId)); + if (physicalId == publicId) { + isPublicId = true; + break; + } + } + // For hidden physical cameras, collect their associated logical cameras + // and store the system camera kind. + if (!isPublicId) { + auto it = hiddenPhysicalIdToLogicalMap.find(physicalId); + if (it == hiddenPhysicalIdToLogicalMap.end()) { + hiddenPhysicalIdToLogicalMap.insert(std::make_pair( + physicalId, std::list(systemCameraKind))); + } else { + it->second.push_back(systemCameraKind); + } + } + } + }); + ASSERT_TRUE(ret.isOk()); + } break; + case CAMERA_DEVICE_API_VERSION_1_0: { + // Not applicable + } break; + default: { + ALOGE("%s: Unsupported device version %d", __func__, deviceVersion); + ADD_FAILURE(); + } break; + } + } + + // Check that the system camera kind of the logical cameras associated with + // each hidden physical camera is the same. + for (const auto& it : hiddenPhysicalIdToLogicalMap) { + SystemCameraKind neededSystemCameraKind = it.second.front(); + for (auto foundSystemCamera : it.second) { + ASSERT_EQ(neededSystemCameraKind, foundSystemCamera); + } + } +} + // Verify that the static camera characteristics can be retrieved // successfully. -TEST_F(CameraHidlTest, getCameraCharacteristics) { +TEST_P(CameraHidlTest, getCameraCharacteristics) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { int deviceVersion = getCameraDeviceVersion(name, mProviderType); switch (deviceVersion) { + case CAMERA_DEVICE_API_VERSION_3_6: case CAMERA_DEVICE_API_VERSION_3_5: case CAMERA_DEVICE_API_VERSION_3_4: case CAMERA_DEVICE_API_VERSION_3_3: @@ -2456,7 +2788,7 @@ TEST_F(CameraHidlTest, getCameraCharacteristics) { //In case it is supported verify that torch can be enabled. //Check for corresponding toch callbacks as well. -TEST_F(CameraHidlTest, setTorchMode) { +TEST_P(CameraHidlTest, setTorchMode) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); bool torchControlSupported = false; Return ret; @@ -2475,6 +2807,7 @@ TEST_F(CameraHidlTest, setTorchMode) { for (const auto& name : cameraDeviceNames) { int deviceVersion = getCameraDeviceVersion(name, mProviderType); switch (deviceVersion) { + case CAMERA_DEVICE_API_VERSION_3_6: case CAMERA_DEVICE_API_VERSION_3_5: case CAMERA_DEVICE_API_VERSION_3_4: case CAMERA_DEVICE_API_VERSION_3_3: @@ -2594,13 +2927,14 @@ TEST_F(CameraHidlTest, setTorchMode) { } // Check dump functionality. -TEST_F(CameraHidlTest, dumpState) { +TEST_P(CameraHidlTest, dumpState) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); Return ret; for (const auto& name : cameraDeviceNames) { int deviceVersion = getCameraDeviceVersion(name, mProviderType); switch (deviceVersion) { + case CAMERA_DEVICE_API_VERSION_3_6: case CAMERA_DEVICE_API_VERSION_3_5: case CAMERA_DEVICE_API_VERSION_3_4: case CAMERA_DEVICE_API_VERSION_3_3: @@ -2659,13 +2993,14 @@ TEST_F(CameraHidlTest, dumpState) { } // Open, dumpStates, then close -TEST_F(CameraHidlTest, openClose) { +TEST_P(CameraHidlTest, openClose) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); Return ret; for (const auto& name : cameraDeviceNames) { int deviceVersion = getCameraDeviceVersion(name, mProviderType); switch (deviceVersion) { + case CAMERA_DEVICE_API_VERSION_3_6: case CAMERA_DEVICE_API_VERSION_3_5: case CAMERA_DEVICE_API_VERSION_3_4: case CAMERA_DEVICE_API_VERSION_3_3: @@ -2695,8 +3030,12 @@ TEST_F(CameraHidlTest, openClose) { sp sessionV3_3; sp sessionV3_4; sp sessionV3_5; - castSession(session, deviceVersion, &sessionV3_3, &sessionV3_4, &sessionV3_5); - if (deviceVersion == CAMERA_DEVICE_API_VERSION_3_5) { + sp sessionV3_6; + castSession(session, deviceVersion, &sessionV3_3, + &sessionV3_4, &sessionV3_5, &sessionV3_6); + if (deviceVersion == CAMERA_DEVICE_API_VERSION_3_6) { + ASSERT_TRUE(sessionV3_6.get() != nullptr); + } else if (deviceVersion == CAMERA_DEVICE_API_VERSION_3_5) { ASSERT_TRUE(sessionV3_5.get() != nullptr); } else if (deviceVersion == CAMERA_DEVICE_API_VERSION_3_4) { ASSERT_TRUE(sessionV3_4.get() != nullptr); @@ -2752,12 +3091,13 @@ TEST_F(CameraHidlTest, openClose) { // Check whether all common default request settings can be sucessfully // constructed. -TEST_F(CameraHidlTest, constructDefaultRequestSettings) { +TEST_P(CameraHidlTest, constructDefaultRequestSettings) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { int deviceVersion = getCameraDeviceVersion(name, mProviderType); switch (deviceVersion) { + case CAMERA_DEVICE_API_VERSION_3_6: case CAMERA_DEVICE_API_VERSION_3_5: case CAMERA_DEVICE_API_VERSION_3_4: case CAMERA_DEVICE_API_VERSION_3_3: @@ -2809,13 +3149,7 @@ TEST_F(CameraHidlTest, constructDefaultRequestSettings) { metadata, &expectedSize); ASSERT_TRUE((result == 0) || (result == CAMERA_METADATA_VALIDATION_SHIFTED)); - size_t entryCount = - get_camera_metadata_entry_count(metadata); - // TODO: we can do better than 0 here. Need to check how many required - // request keys we've defined for each template - ASSERT_GT(entryCount, 0u); - ALOGI("template %u metadata entry count is %zu", - t, entryCount); + verifyRequestTemplate(metadata, reqTemplate); } else { ASSERT_EQ(0u, req.size()); } @@ -2842,7 +3176,7 @@ TEST_F(CameraHidlTest, constructDefaultRequestSettings) { // Verify that all supported stream formats and sizes can be configured // successfully. -TEST_F(CameraHidlTest, configureStreamsAvailableOutputs) { +TEST_P(CameraHidlTest, configureStreamsAvailableOutputs) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); std::vector outputStreams; @@ -2862,11 +3196,12 @@ TEST_F(CameraHidlTest, configureStreamsAvailableOutputs) { sp session3_3; sp session3_4; sp session3_5; + sp session3_6; sp cameraDevice; sp cameraDevice3_5; openEmptyDeviceSession(name, mProvider, &session /*out*/, &staticMeta /*out*/, &cameraDevice /*out*/); - castSession(session, deviceVersion, &session3_3, &session3_4, &session3_5); + castSession(session, deviceVersion, &session3_3, &session3_4, &session3_5, &session3_6); castDevice(cameraDevice, deviceVersion, &cameraDevice3_5); outputStreams.clear(); @@ -2949,8 +3284,159 @@ TEST_F(CameraHidlTest, configureStreamsAvailableOutputs) { } } +// Verify that mandatory concurrent streams and outputs are supported. +TEST_P(CameraHidlTest, configureConcurrentStreamsAvailableOutputs) { + struct CameraTestInfo { + camera_metadata_t* staticMeta = nullptr; + sp session; + sp session3_3; + sp session3_4; + sp session3_5; + sp session3_6; + sp cameraDevice; + sp cameraDevice3_5; + ::android::hardware::camera::device::V3_5::StreamConfiguration config3_5; + ::android::hardware::camera::device::V3_4::StreamConfiguration config3_4; + ::android::hardware::camera::device::V3_2::StreamConfiguration config3_2; + }; + if (mProvider2_6 == nullptr) { + // This test is provider@2.6 specific + ALOGW("%s provider not 2_6, skipping", __func__); + return; + } + + std::map idToNameMap = getCameraDeviceIdToNameMap(mProvider2_6); + hidl_vec> concurrentDeviceCombinations = + getConcurrentDeviceCombinations(mProvider2_6); + std::vector outputStreams; + for (const auto& cameraDeviceIds : concurrentDeviceCombinations) { + std::vector cameraIdsAndStreamCombinations; + std::vector cameraTestInfos; + size_t i = 0; + for (const auto& id : cameraDeviceIds) { + CameraTestInfo cti; + Return ret; + auto it = idToNameMap.find(id); + ASSERT_TRUE(idToNameMap.end() != it); + hidl_string name = it->second; + int deviceVersion = getCameraDeviceVersion(name, mProviderType); + if (deviceVersion == CAMERA_DEVICE_API_VERSION_1_0) { + continue; + } else if (deviceVersion <= 0) { + ALOGE("%s: Unsupported device version %d", __func__, deviceVersion); + ADD_FAILURE(); + return; + } + openEmptyDeviceSession(name, mProvider2_6, &cti.session /*out*/, + &cti.staticMeta /*out*/, &cti.cameraDevice /*out*/); + castSession(cti.session, deviceVersion, &cti.session3_3, &cti.session3_4, + &cti.session3_5, &cti.session3_6); + castDevice(cti.cameraDevice, deviceVersion, &cti.cameraDevice3_5); + + outputStreams.clear(); + ASSERT_EQ(Status::OK, getMandatoryConcurrentStreams(cti.staticMeta, &outputStreams)); + ASSERT_NE(0u, outputStreams.size()); + + uint32_t jpegBufferSize = 0; + ASSERT_EQ(Status::OK, getJpegBufferSize(cti.staticMeta, &jpegBufferSize)); + ASSERT_NE(0u, jpegBufferSize); + + int32_t streamId = 0; + ::android::hardware::hidl_vec streams3_2(outputStreams.size()); + size_t j = 0; + for (const auto& it : outputStreams) { + V3_2::Stream stream3_2; + V3_2::DataspaceFlags dataspaceFlag = 0; + switch (static_cast(it.format)) { + case PixelFormat::BLOB: + dataspaceFlag = static_cast(Dataspace::V0_JFIF); + break; + case PixelFormat::Y16: + dataspaceFlag = static_cast(Dataspace::DEPTH); + break; + default: + dataspaceFlag = static_cast(Dataspace::UNKNOWN); + } + stream3_2 = {streamId++, + StreamType::OUTPUT, + static_cast(it.width), + static_cast(it.height), + static_cast(it.format), + GRALLOC1_CONSUMER_USAGE_HWCOMPOSER, + dataspaceFlag, + StreamRotation::ROTATION_0}; + streams3_2[j] = stream3_2; + j++; + } + + // Add the created stream configs to cameraIdsAndStreamCombinations + createStreamConfiguration(streams3_2, StreamConfigurationMode::NORMAL_MODE, + &cti.config3_2, &cti.config3_4, &cti.config3_5, + jpegBufferSize); + + cti.config3_5.streamConfigCounter = outputStreams.size(); + CameraIdAndStreamCombination cameraIdAndStreamCombination; + cameraIdAndStreamCombination.cameraId = id; + cameraIdAndStreamCombination.streamConfiguration = cti.config3_4; + cameraIdsAndStreamCombinations.push_back(cameraIdAndStreamCombination); + i++; + cameraTestInfos.push_back(cti); + } + // Now verify that concurrent streams are supported + auto cb = [](Status s, bool supported) { + ASSERT_EQ(Status::OK, s); + ASSERT_EQ(supported, true); + }; + + auto ret = mProvider2_6->isConcurrentStreamCombinationSupported( + cameraIdsAndStreamCombinations, cb); + + // Test the stream can actually be configured + for (const auto& cti : cameraTestInfos) { + if (cti.session3_5 != nullptr) { + bool expectStreamCombQuery = (isLogicalMultiCamera(cti.staticMeta) == Status::OK); + verifyStreamCombination(cti.cameraDevice3_5, cti.config3_4, + /*expectedStatus*/ true, expectStreamCombQuery); + ret = cti.session3_5->configureStreams_3_5( + cti.config3_5, + [&cti](Status s, device::V3_4::HalStreamConfiguration halConfig) { + ASSERT_EQ(Status::OK, s); + ASSERT_EQ(cti.config3_5.v3_4.streams.size(), halConfig.streams.size()); + }); + } else if (cti.session3_4 != nullptr) { + ret = cti.session3_4->configureStreams_3_4( + cti.config3_4, + [&cti](Status s, device::V3_4::HalStreamConfiguration halConfig) { + ASSERT_EQ(Status::OK, s); + ASSERT_EQ(cti.config3_4.streams.size(), halConfig.streams.size()); + }); + } else if (cti.session3_3 != nullptr) { + ret = cti.session3_3->configureStreams_3_3( + cti.config3_2, + [&cti](Status s, device::V3_3::HalStreamConfiguration halConfig) { + ASSERT_EQ(Status::OK, s); + ASSERT_EQ(cti.config3_2.streams.size(), halConfig.streams.size()); + }); + } else { + ret = cti.session->configureStreams( + cti.config3_2, [&cti](Status s, HalStreamConfiguration halConfig) { + ASSERT_EQ(Status::OK, s); + ASSERT_EQ(cti.config3_2.streams.size(), halConfig.streams.size()); + }); + } + ASSERT_TRUE(ret.isOk()); + } + + for (const auto& cti : cameraTestInfos) { + free_camera_metadata(cti.staticMeta); + ret = cti.session->close(); + ASSERT_TRUE(ret.isOk()); + } + } +} + // Check for correct handling of invalid/incorrect configuration parameters. -TEST_F(CameraHidlTest, configureStreamsInvalidOutputs) { +TEST_P(CameraHidlTest, configureStreamsInvalidOutputs) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); std::vector outputStreams; @@ -2970,11 +3456,12 @@ TEST_F(CameraHidlTest, configureStreamsInvalidOutputs) { sp session3_3; sp session3_4; sp session3_5; + sp session3_6; sp cameraDevice; sp cameraDevice3_5; openEmptyDeviceSession(name, mProvider, &session /*out*/, &staticMeta /*out*/, &cameraDevice /*out*/); - castSession(session, deviceVersion, &session3_3, &session3_4, &session3_5); + castSession(session, deviceVersion, &session3_3, &session3_4, &session3_5, &session3_6); castDevice(cameraDevice, deviceVersion, &cameraDevice3_5); outputStreams.clear(); @@ -3146,7 +3633,7 @@ TEST_F(CameraHidlTest, configureStreamsInvalidOutputs) { // Check whether all supported ZSL output stream combinations can be // configured successfully. -TEST_F(CameraHidlTest, configureStreamsZSLInputOutputs) { +TEST_P(CameraHidlTest, configureStreamsZSLInputOutputs) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); std::vector inputStreams; std::vector inputOutputMap; @@ -3167,11 +3654,12 @@ TEST_F(CameraHidlTest, configureStreamsZSLInputOutputs) { sp session3_3; sp session3_4; sp session3_5; + sp session3_6; sp cameraDevice; sp cameraDevice3_5; openEmptyDeviceSession(name, mProvider, &session /*out*/, &staticMeta /*out*/, &cameraDevice /*out*/); - castSession(session, deviceVersion, &session3_3, &session3_4, &session3_5); + castSession(session, deviceVersion, &session3_3, &session3_4, &session3_5, &session3_6); castDevice(cameraDevice, deviceVersion, &cameraDevice3_5); Status rc = isZSLModeAvailable(staticMeta); @@ -3312,7 +3800,7 @@ TEST_F(CameraHidlTest, configureStreamsZSLInputOutputs) { // Check whether session parameters are supported. If Hal support for them // exist, then try to configure a preview stream using them. -TEST_F(CameraHidlTest, configureStreamsWithSessionParameters) { +TEST_P(CameraHidlTest, configureStreamsWithSessionParameters) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); std::vector outputPreviewStreams; AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight, @@ -3334,8 +3822,9 @@ TEST_F(CameraHidlTest, configureStreamsWithSessionParameters) { sp session3_3; sp session3_4; sp session3_5; + sp session3_6; openEmptyDeviceSession(name, mProvider, &session /*out*/, &staticMetaBuffer /*out*/); - castSession(session, deviceVersion, &session3_3, &session3_4, &session3_5); + castSession(session, deviceVersion, &session3_3, &session3_4, &session3_5, &session3_6); if (deviceVersion == CAMERA_DEVICE_API_VERSION_3_4) { ASSERT_NE(session3_4, nullptr); } else { @@ -3431,7 +3920,7 @@ TEST_F(CameraHidlTest, configureStreamsWithSessionParameters) { // Verify that all supported preview + still capture stream combinations // can be configured successfully. -TEST_F(CameraHidlTest, configureStreamsPreviewStillOutputs) { +TEST_P(CameraHidlTest, configureStreamsPreviewStillOutputs) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); std::vector outputBlobStreams; std::vector outputPreviewStreams; @@ -3456,11 +3945,12 @@ TEST_F(CameraHidlTest, configureStreamsPreviewStillOutputs) { sp session3_3; sp session3_4; sp session3_5; + sp session3_6; sp cameraDevice; sp cameraDevice3_5; openEmptyDeviceSession(name, mProvider, &session /*out*/, &staticMeta /*out*/, &cameraDevice /*out*/); - castSession(session, deviceVersion, &session3_3, &session3_4, &session3_5); + castSession(session, deviceVersion, &session3_3, &session3_4, &session3_5, &session3_6); castDevice(cameraDevice, deviceVersion, &cameraDevice3_5); // Check if camera support depth only @@ -3554,7 +4044,7 @@ TEST_F(CameraHidlTest, configureStreamsPreviewStillOutputs) { // In case constrained mode is supported, test whether it can be // configured. Additionally check for common invalid inputs when // using this mode. -TEST_F(CameraHidlTest, configureStreamsConstrainedOutputs) { +TEST_P(CameraHidlTest, configureStreamsConstrainedOutputs) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); for (const auto& name : cameraDeviceNames) { @@ -3573,11 +4063,12 @@ TEST_F(CameraHidlTest, configureStreamsConstrainedOutputs) { sp session3_3; sp session3_4; sp session3_5; + sp session3_6; sp cameraDevice; sp cameraDevice3_5; openEmptyDeviceSession(name, mProvider, &session /*out*/, &staticMeta /*out*/, &cameraDevice /*out*/); - castSession(session, deviceVersion, &session3_3, &session3_4, &session3_5); + castSession(session, deviceVersion, &session3_3, &session3_4, &session3_5, &session3_6); castDevice(cameraDevice, deviceVersion, &cameraDevice3_5); Status rc = isConstrainedModeAvailable(staticMeta); @@ -3759,7 +4250,7 @@ TEST_F(CameraHidlTest, configureStreamsConstrainedOutputs) { // Verify that all supported video + snapshot stream combinations can // be configured successfully. -TEST_F(CameraHidlTest, configureStreamsVideoStillOutputs) { +TEST_P(CameraHidlTest, configureStreamsVideoStillOutputs) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); std::vector outputBlobStreams; std::vector outputVideoStreams; @@ -3784,11 +4275,12 @@ TEST_F(CameraHidlTest, configureStreamsVideoStillOutputs) { sp session3_3; sp session3_4; sp session3_5; + sp session3_6; sp cameraDevice; sp cameraDevice3_5; openEmptyDeviceSession(name, mProvider, &session /*out*/, &staticMeta /*out*/, &cameraDevice /*out*/); - castSession(session, deviceVersion, &session3_3, &session3_4, &session3_5); + castSession(session, deviceVersion, &session3_3, &session3_4, &session3_5, &session3_6); castDevice(cameraDevice, deviceVersion, &cameraDevice3_5); // Check if camera support depth only @@ -3880,9 +4372,22 @@ TEST_F(CameraHidlTest, configureStreamsVideoStillOutputs) { } // Generate and verify a camera capture request -TEST_F(CameraHidlTest, processCaptureRequestPreview) { - hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); - AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight, +TEST_P(CameraHidlTest, processCaptureRequestPreview) { + processCaptureRequestInternal(GRALLOC1_CONSUMER_USAGE_HWCOMPOSER, RequestTemplate::PREVIEW, + false /*secureOnlyCameras*/); +} + +// Generate and verify a secure camera capture request +TEST_P(CameraHidlTest, processSecureCaptureRequest) { + processCaptureRequestInternal(GRALLOC1_PRODUCER_USAGE_PROTECTED, RequestTemplate::STILL_CAPTURE, + true /*secureOnlyCameras*/); +} + +void CameraHidlTest::processCaptureRequestInternal(uint64_t bufferUsage, + RequestTemplate reqTemplate, + bool useSecureOnlyCameras) { + hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider, useSecureOnlyCameras); + AvailableStream streamThreshold = {kMaxPreviewWidth, kMaxPreviewHeight, static_cast(PixelFormat::IMPLEMENTATION_DEFINED)}; uint64_t bufferId = 1; uint32_t frameNumber = 1; @@ -3898,17 +4403,17 @@ TEST_F(CameraHidlTest, processCaptureRequestPreview) { return; } - V3_2::Stream previewStream; + V3_2::Stream testStream; HalStreamConfiguration halStreamConfig; sp session; sp cb; bool supportsPartialResults = false; bool useHalBufManager = false; uint32_t partialResultCount = 0; - configurePreviewStream(name, deviceVersion, mProvider, &previewThreshold, &session /*out*/, - &previewStream /*out*/, &halStreamConfig /*out*/, - &supportsPartialResults /*out*/, - &partialResultCount /*out*/, &useHalBufManager /*out*/, &cb /*out*/); + configureSingleStream(name, deviceVersion, mProvider, &streamThreshold, bufferUsage, + reqTemplate, &session /*out*/, &testStream /*out*/, + &halStreamConfig /*out*/, &supportsPartialResults /*out*/, + &partialResultCount /*out*/, &useHalBufManager /*out*/, &cb /*out*/); std::shared_ptr resultQueue; auto resultQueueRet = @@ -3929,7 +4434,6 @@ TEST_F(CameraHidlTest, processCaptureRequestPreview) { InFlightRequest inflightReq = {1, false, supportsPartialResults, partialResultCount, resultQueue}; - RequestTemplate reqTemplate = RequestTemplate::PREVIEW; Return ret; ret = session->constructDefaultRequestSettings(reqTemplate, [&](auto status, const auto& req) { @@ -3948,7 +4452,7 @@ TEST_F(CameraHidlTest, processCaptureRequestPreview) { nullptr, nullptr}; } else { - allocateGraphicBuffer(previewStream.width, previewStream.height, + allocateGraphicBuffer(testStream.width, testStream.height, android_convertGralloc1To0Usage(halStreamConfig.streams[0].producerUsage, halStreamConfig.streams[0].consumerUsage), halStreamConfig.streams[0].overrideFormat, &buffer_handle); @@ -3997,7 +4501,7 @@ TEST_F(CameraHidlTest, processCaptureRequestPreview) { ASSERT_FALSE(inflightReq.errorCodeValid); ASSERT_NE(inflightReq.resultOutputBuffers.size(), 0u); - ASSERT_EQ(previewStream.id, inflightReq.resultOutputBuffers[0].streamId); + ASSERT_EQ(testStream.id, inflightReq.resultOutputBuffers[0].streamId); request.frameNumber++; // Empty settings should be supported after the first call @@ -4035,11 +4539,11 @@ TEST_F(CameraHidlTest, processCaptureRequestPreview) { ASSERT_FALSE(inflightReq.errorCodeValid); ASSERT_NE(inflightReq.resultOutputBuffers.size(), 0u); - ASSERT_EQ(previewStream.id, inflightReq.resultOutputBuffers[0].streamId); + ASSERT_EQ(testStream.id, inflightReq.resultOutputBuffers[0].streamId); } if (useHalBufManager) { - verifyBuffersReturned(session, deviceVersion, previewStream.id, cb); + verifyBuffersReturned(session, deviceVersion, testStream.id, cb); } ret = session->close(); @@ -4048,7 +4552,7 @@ TEST_F(CameraHidlTest, processCaptureRequestPreview) { } // Generate and verify a multi-camera capture request -TEST_F(CameraHidlTest, processMultiCaptureRequestPreview) { +TEST_P(CameraHidlTest, processMultiCaptureRequestPreview) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight, static_cast(PixelFormat::YCBCR_420_888)}; @@ -4117,7 +4621,7 @@ TEST_F(CameraHidlTest, processMultiCaptureRequestPreview) { // Leave only 2 physical devices in the id set. auto it = physicalIds.begin(); - string physicalDeviceId = *it; it++; + std::string physicalDeviceId = *it; it++; physicalIds.erase(++it, physicalIds.end()); ASSERT_EQ(physicalIds.size(), 2u); @@ -4157,7 +4661,7 @@ TEST_F(CameraHidlTest, processMultiCaptureRequestPreview) { ASSERT_TRUE(resultQueueRet.isOk()); InFlightRequest inflightReq = {static_cast (halStreamConfig.streams.size()), false, - supportsPartialResults, partialResultCount, resultQueue}; + supportsPartialResults, partialResultCount, physicalIds, resultQueue}; std::vector graphicBuffers; graphicBuffers.reserve(halStreamConfig.streams.size()); @@ -4236,7 +4740,7 @@ TEST_F(CameraHidlTest, processMultiCaptureRequestPreview) { request.v3_2.outputBuffers[0].buffer = nullptr; mInflightMap.clear(); inflightReq = {static_cast (physicalIds.size()), false, - supportsPartialResults, partialResultCount, resultQueue}; + supportsPartialResults, partialResultCount, physicalIds, resultQueue}; mInflightMap.add(request.v3_2.frameNumber, &inflightReq); } @@ -4295,7 +4799,7 @@ TEST_F(CameraHidlTest, processMultiCaptureRequestPreview) { } // Generate and verify a burst containing alternating sensor sensitivity values -TEST_F(CameraHidlTest, processCaptureRequestBurstISO) { +TEST_P(CameraHidlTest, processCaptureRequestBurstISO) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight, static_cast(PixelFormat::IMPLEMENTATION_DEFINED)}; @@ -4453,7 +4957,7 @@ TEST_F(CameraHidlTest, processCaptureRequestBurstISO) { // Test whether an incorrect capture request with missing settings will // be reported correctly. -TEST_F(CameraHidlTest, processCaptureRequestInvalidSinglePreview) { +TEST_P(CameraHidlTest, processCaptureRequestInvalidSinglePreview) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); std::vector outputPreviewStreams; AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight, @@ -4526,9 +5030,210 @@ TEST_F(CameraHidlTest, processCaptureRequestInvalidSinglePreview) { } } +// Verify camera offline session behavior +TEST_P(CameraHidlTest, switchToOffline) { + hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); + AvailableStream threshold = {kMaxStillWidth, kMaxStillHeight, + static_cast(PixelFormat::BLOB)}; + uint64_t bufferId = 1; + uint32_t frameNumber = 1; + ::android::hardware::hidl_vec settings; + + for (const auto& name : cameraDeviceNames) { + int deviceVersion = getCameraDeviceVersion(name, mProviderType); + if (deviceVersion == CAMERA_DEVICE_API_VERSION_1_0) { + continue; + } else if (deviceVersion <= 0) { + ALOGE("%s: Unsupported device version %d", __func__, deviceVersion); + ADD_FAILURE(); + return; + } + + camera_metadata_t* staticMetaBuffer; + { + Return ret; + sp session; + openEmptyDeviceSession(name, mProvider, &session /*out*/, &staticMetaBuffer /*out*/); + ::android::hardware::camera::common::V1_0::helper::CameraMetadata staticMeta( + staticMetaBuffer); + + if (isOfflineSessionSupported(staticMetaBuffer) != Status::OK) { + ret = session->close(); + ASSERT_TRUE(ret.isOk()); + continue; + } + ret = session->close(); + ASSERT_TRUE(ret.isOk()); + } + + bool supportsPartialResults = false; + uint32_t partialResultCount = 0; + V3_2::Stream stream; + V3_6::HalStreamConfiguration halStreamConfig; + sp session; + sp cb; + uint32_t jpegBufferSize; + bool useHalBufManager; + configureOfflineStillStream(name, deviceVersion, mProvider, &threshold, + &session /*out*/, &stream /*out*/, &halStreamConfig /*out*/, + &supportsPartialResults /*out*/, &partialResultCount /*out*/, &cb /*out*/, + &jpegBufferSize /*out*/, &useHalBufManager /*out*/); + + auto ret = session->constructDefaultRequestSettings(RequestTemplate::STILL_CAPTURE, + [&](auto status, const auto& req) { + ASSERT_EQ(Status::OK, status); + settings = req; }); + ASSERT_TRUE(ret.isOk()); + + std::shared_ptr resultQueue; + auto resultQueueRet = + session->getCaptureResultMetadataQueue( + [&resultQueue](const auto& descriptor) { + resultQueue = std::make_shared( + descriptor); + if (!resultQueue->isValid() || + resultQueue->availableToWrite() <= 0) { + ALOGE("%s: HAL returns empty result metadata fmq," + " not use it", __func__); + resultQueue = nullptr; + // Don't use the queue onwards. + } + }); + ASSERT_TRUE(resultQueueRet.isOk()); + + ::android::hardware::camera::common::V1_0::helper::CameraMetadata requestMeta; + StreamBuffer emptyInputBuffer = {-1, 0, nullptr, BufferStatus::ERROR, nullptr, nullptr}; + hidl_handle buffers[kBurstFrameCount]; + StreamBuffer outputBuffers[kBurstFrameCount]; + CaptureRequest requests[kBurstFrameCount]; + InFlightRequest inflightReqs[kBurstFrameCount]; + hidl_vec requestSettings[kBurstFrameCount]; + auto halStreamConfig3_2 = halStreamConfig.streams[0].v3_4.v3_3.v3_2; + for (uint32_t i = 0; i < kBurstFrameCount; i++) { + std::unique_lock l(mLock); + + if (useHalBufManager) { + outputBuffers[i] = {halStreamConfig3_2.id, /*bufferId*/ 0, + buffers[i], BufferStatus::OK, nullptr, nullptr}; + } else { + // jpeg buffer (w,h) = (blobLen, 1) + allocateGraphicBuffer(jpegBufferSize, /*height*/1, + android_convertGralloc1To0Usage(halStreamConfig3_2.producerUsage, + halStreamConfig3_2.consumerUsage), + halStreamConfig3_2.overrideFormat, &buffers[i]); + outputBuffers[i] = {halStreamConfig3_2.id, bufferId + i, + buffers[i], BufferStatus::OK, nullptr, nullptr}; + } + + requestMeta.clear(); + requestMeta.append(reinterpret_cast (settings.data())); + + camera_metadata_t *metaBuffer = requestMeta.release(); + requestSettings[i].setToExternal(reinterpret_cast (metaBuffer), + get_camera_metadata_size(metaBuffer), true); + + requests[i] = {frameNumber + i, 0 /* fmqSettingsSize */, requestSettings[i], + emptyInputBuffer, {outputBuffers[i]}}; + + inflightReqs[i] = {1, false, supportsPartialResults, partialResultCount, + resultQueue}; + mInflightMap.add(frameNumber + i, &inflightReqs[i]); + } + + Status status = Status::INTERNAL_ERROR; + uint32_t numRequestProcessed = 0; + hidl_vec cachesToRemove; + hidl_vec burstRequest; + burstRequest.setToExternal(requests, kBurstFrameCount); + Return returnStatus = session->processCaptureRequest(burstRequest, cachesToRemove, + [&status, &numRequestProcessed] (auto s, uint32_t n) { + status = s; + numRequestProcessed = n; + }); + ASSERT_TRUE(returnStatus.isOk()); + ASSERT_EQ(Status::OK, status); + ASSERT_EQ(numRequestProcessed, kBurstFrameCount); + + hidl_vec offlineStreamIds = {halStreamConfig3_2.id}; + V3_6::CameraOfflineSessionInfo offlineSessionInfo; + sp offlineSession; + returnStatus = session->switchToOffline(offlineStreamIds, + [&status, &offlineSessionInfo, &offlineSession] (auto stat, auto info, + auto offSession) { + status = stat; + offlineSessionInfo = info; + offlineSession = offSession; + }); + ASSERT_TRUE(returnStatus.isOk()); + + if (!halStreamConfig.streams[0].supportOffline) { + ASSERT_EQ(status, Status::ILLEGAL_ARGUMENT); + ret = session->close(); + ASSERT_TRUE(ret.isOk()); + continue; + } + + ASSERT_EQ(status, Status::OK); + // Hal might be unable to find any requests qualified for offline mode. + if (offlineSession == nullptr) { + ret = session->close(); + ASSERT_TRUE(ret.isOk()); + continue; + } + + ASSERT_EQ(offlineSessionInfo.offlineStreams.size(), 1u); + ASSERT_EQ(offlineSessionInfo.offlineStreams[0].id, halStreamConfig3_2.id); + ASSERT_NE(offlineSessionInfo.offlineRequests.size(), 0u); + + // close device session to make sure offline session does not rely on it + ret = session->close(); + ASSERT_TRUE(ret.isOk()); + + std::shared_ptr offlineResultQueue; + auto offlineResultQueueRet = + offlineSession->getCaptureResultMetadataQueue( + [&offlineResultQueue](const auto& descriptor) { + offlineResultQueue = std::make_shared( + descriptor); + if (!offlineResultQueue->isValid() || + offlineResultQueue->availableToWrite() <= 0) { + ALOGE("%s: offline session returns empty result metadata fmq," + " not use it", __func__); + offlineResultQueue = nullptr; + // Don't use the queue onwards. + } + }); + ASSERT_TRUE(offlineResultQueueRet.isOk()); + + updateInflightResultQueue(offlineResultQueue); + + ret = offlineSession->setCallback(cb); + ASSERT_TRUE(ret.isOk()); + + for (size_t i = 0; i < kBurstFrameCount; i++) { + std::unique_lock l(mLock); + while (!inflightReqs[i].errorCodeValid && ((0 < inflightReqs[i].numBuffersLeft) || + (!inflightReqs[i].haveResultMetadata))) { + auto timeout = std::chrono::system_clock::now() + + std::chrono::seconds(kStreamBufferTimeoutSec); + ASSERT_NE(std::cv_status::timeout, mResultCondition.wait_until(l, timeout)); + } + + ASSERT_FALSE(inflightReqs[i].errorCodeValid); + ASSERT_NE(inflightReqs[i].resultOutputBuffers.size(), 0u); + ASSERT_EQ(stream.id, inflightReqs[i].resultOutputBuffers[0].streamId); + ASSERT_FALSE(inflightReqs[i].collectedResult.isEmpty()); + } + + + ret = offlineSession->close(); + ASSERT_TRUE(ret.isOk()); + } +} + // Check whether an invalid capture request with missing output buffers // will be reported correctly. -TEST_F(CameraHidlTest, processCaptureRequestInvalidBuffer) { +TEST_P(CameraHidlTest, processCaptureRequestInvalidBuffer) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); std::vector outputBlobStreams; AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight, @@ -4593,7 +5298,7 @@ TEST_F(CameraHidlTest, processCaptureRequestInvalidBuffer) { } // Generate, trigger and flush a preview request -TEST_F(CameraHidlTest, flushPreviewRequest) { +TEST_P(CameraHidlTest, flushPreviewRequest) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); std::vector outputPreviewStreams; AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight, @@ -4736,7 +5441,7 @@ TEST_F(CameraHidlTest, flushPreviewRequest) { } // Verify that camera flushes correctly without any pending requests. -TEST_F(CameraHidlTest, flushEmpty) { +TEST_P(CameraHidlTest, flushEmpty) { hidl_vec cameraDeviceNames = getCameraDeviceNames(mProvider); std::vector outputPreviewStreams; AvailableStream previewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight, @@ -4781,7 +5486,7 @@ TEST_F(CameraHidlTest, flushEmpty) { } // Test camera provider@2.5 notify method -TEST_F(CameraHidlTest, providerDeviceStateNotification) { +TEST_P(CameraHidlTest, providerDeviceStateNotification) { notifyDeviceState(provider::V2_5::DeviceState::BACK_COVERED); notifyDeviceState(provider::V2_5::DeviceState::NORMAL); @@ -4789,7 +5494,7 @@ TEST_F(CameraHidlTest, providerDeviceStateNotification) { // Retrieve all valid output stream resolutions from the camera // static characteristics. -Status CameraHidlTest::getAvailableOutputStreams(camera_metadata_t *staticMeta, +Status CameraHidlTest::getAvailableOutputStreams(const camera_metadata_t *staticMeta, std::vector &outputStreams, const AvailableStream *threshold) { AvailableStream depthPreviewThreshold = {kMaxPreviewWidth, kMaxPreviewHeight, @@ -4822,6 +5527,78 @@ Status CameraHidlTest::getAvailableOutputStreams(camera_metadata_t *staticMeta, return Status::OK; } +static Size getMinSize(Size a, Size b) { + if (a.width * a.height < b.width * b.height) { + return a; + } + return b; +} + +// TODO: Add more combinations +Status CameraHidlTest::getMandatoryConcurrentStreams(const camera_metadata_t* staticMeta, + std::vector* outputStreams) { + if (nullptr == staticMeta || nullptr == outputStreams) { + return Status::ILLEGAL_ARGUMENT; + } + + if (isDepthOnly(staticMeta)) { + Size y16MaxSize(640, 480); + Size maxAvailableY16Size; + getMaxOutputSizeForFormat(staticMeta, PixelFormat::Y16, &maxAvailableY16Size); + Size y16ChosenSize = getMinSize(y16MaxSize, maxAvailableY16Size); + AvailableStream y16Stream = {.width = y16ChosenSize.width, + .height = y16ChosenSize.height, + .format = static_cast(PixelFormat::Y16)}; + outputStreams->push_back(y16Stream); + return Status::OK; + } + + Size yuvMaxSize(1280, 720); + Size jpegMaxSize(1920, 1440); + Size maxAvailableYuvSize; + Size maxAvailableJpegSize; + getMaxOutputSizeForFormat(staticMeta, PixelFormat::YCBCR_420_888, &maxAvailableYuvSize); + getMaxOutputSizeForFormat(staticMeta, PixelFormat::BLOB, &maxAvailableJpegSize); + Size yuvChosenSize = getMinSize(yuvMaxSize, maxAvailableYuvSize); + Size jpegChosenSize = getMinSize(jpegMaxSize, maxAvailableJpegSize); + + AvailableStream yuvStream = {.width = yuvChosenSize.width, + .height = yuvChosenSize.height, + .format = static_cast(PixelFormat::YCBCR_420_888)}; + + AvailableStream jpegStream = {.width = jpegChosenSize.width, + .height = jpegChosenSize.height, + .format = static_cast(PixelFormat::BLOB)}; + outputStreams->push_back(yuvStream); + outputStreams->push_back(jpegStream); + + return Status::OK; +} + +Status CameraHidlTest::getMaxOutputSizeForFormat(const camera_metadata_t* staticMeta, + PixelFormat format, Size* size) { + std::vector outputStreams; + if (size == nullptr || getAvailableOutputStreams(staticMeta, outputStreams) != Status::OK) { + return Status::ILLEGAL_ARGUMENT; + } + Size maxSize; + bool found = false; + for (auto& outputStream : outputStreams) { + if (static_cast(format) == outputStream.format && + (outputStream.width * outputStream.height > maxSize.width * maxSize.height)) { + maxSize.width = outputStream.width; + maxSize.height = outputStream.height; + found = true; + } + } + if (!found) { + ALOGE("%s :chosen format %d not found", __FUNCTION__, static_cast(format)); + return Status::ILLEGAL_ARGUMENT; + } + *size = maxSize; + return Status::OK; +} + void CameraHidlTest::fillOutputStreams(camera_metadata_ro_entry_t* entry, std::vector& outputStreams, const AvailableStream* threshold, const int32_t availableConfigOutputTag) { @@ -4885,6 +5662,30 @@ Status CameraHidlTest::isLogicalMultiCamera(const camera_metadata_t *staticMeta) return ret; } +// Check if the camera device has logical multi-camera capability. +Status CameraHidlTest::isOfflineSessionSupported(const camera_metadata_t *staticMeta) { + Status ret = Status::METHOD_NOT_SUPPORTED; + if (nullptr == staticMeta) { + return Status::ILLEGAL_ARGUMENT; + } + + camera_metadata_ro_entry entry; + int rc = find_camera_metadata_ro_entry(staticMeta, + ANDROID_REQUEST_AVAILABLE_CAPABILITIES, &entry); + if (0 != rc) { + return Status::ILLEGAL_ARGUMENT; + } + + for (size_t i = 0; i < entry.count; i++) { + if (ANDROID_REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING == entry.data.u8[i]) { + ret = Status::OK; + break; + } + } + + return ret; +} + // Generate a list of physical camera ids backing a logical multi-camera. Status CameraHidlTest::getPhysicalCameraIds(const camera_metadata_t *staticMeta, std::unordered_set *physicalIds) { @@ -5059,6 +5860,39 @@ Status CameraHidlTest::isZSLModeAvailable(const camera_metadata_t *staticMeta, return ret; } +Status CameraHidlTest::getSystemCameraKind(const camera_metadata_t* staticMeta, + SystemCameraKind* systemCameraKind) { + Status ret = Status::OK; + if (nullptr == staticMeta || nullptr == systemCameraKind) { + return Status::ILLEGAL_ARGUMENT; + } + + camera_metadata_ro_entry entry; + int rc = find_camera_metadata_ro_entry(staticMeta, ANDROID_REQUEST_AVAILABLE_CAPABILITIES, + &entry); + if (0 != rc) { + return Status::ILLEGAL_ARGUMENT; + } + + if (entry.count == 1 && + entry.data.u8[0] == ANDROID_REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA) { + *systemCameraKind = SystemCameraKind::HIDDEN_SECURE_CAMERA; + return ret; + } + + // Go through the capabilities and check if it has + // ANDROID_REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA + for (size_t i = 0; i < entry.count; ++i) { + uint8_t capability = entry.data.u8[i]; + if (capability == ANDROID_REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA) { + *systemCameraKind = SystemCameraKind::SYSTEM_ONLY_CAMERA; + return ret; + } + } + *systemCameraKind = SystemCameraKind::PUBLIC; + return ret; +} + // Check whether this is a monochrome camera using the static camera characteristics. Status CameraHidlTest::isMonochromeCamera(const camera_metadata_t *staticMeta) { Status ret = Status::METHOD_NOT_SUPPORTED; @@ -5243,7 +6077,8 @@ void CameraHidlTest::configurePreviewStreams3_4(const std::string &name, int32_t *outCb = cb; sp session3_3; - castSession(session, deviceVersion, &session3_3, session3_4, session3_5); + sp session3_6; + castSession(session, deviceVersion, &session3_3, session3_4, session3_5, &session3_6); ASSERT_NE(nullptr, (*session3_4).get()); *useHalBufManager = false; @@ -5315,10 +6150,10 @@ void CameraHidlTest::configurePreviewStreams3_4(const std::string &name, int32_t ASSERT_EQ(physicalIds.size(), halConfig.streams.size()); *halStreamConfig = halConfig; if (*useHalBufManager) { - hidl_vec streams(physicalIds.size()); + hidl_vec streams(physicalIds.size()); hidl_vec halStreams(physicalIds.size()); for (size_t i = 0; i < physicalIds.size(); i++) { - streams[i] = streams3_4[i].v3_2; + streams[i] = streams3_4[i]; halStreams[i] = halConfig.streams[i].v3_3.v3_2; } cb->setCurrentStreamConfig(streams, halStreams); @@ -5336,7 +6171,145 @@ void CameraHidlTest::configurePreviewStreams3_4(const std::string &name, int32_t ASSERT_TRUE(ret.isOk()); } -bool CameraHidlTest::isDepthOnly(camera_metadata_t* staticMeta) { +// Configure preview stream with possible offline session support +void CameraHidlTest::configureOfflineStillStream(const std::string &name, + int32_t deviceVersion, + sp provider, + const AvailableStream *threshold, + sp *session/*out*/, + V3_2::Stream *stream /*out*/, + device::V3_6::HalStreamConfiguration *halStreamConfig /*out*/, + bool *supportsPartialResults /*out*/, + uint32_t *partialResultCount /*out*/, + sp *outCb /*out*/, + uint32_t *jpegBufferSize /*out*/, + bool *useHalBufManager /*out*/) { + ASSERT_NE(nullptr, session); + ASSERT_NE(nullptr, halStreamConfig); + ASSERT_NE(nullptr, stream); + ASSERT_NE(nullptr, supportsPartialResults); + ASSERT_NE(nullptr, partialResultCount); + ASSERT_NE(nullptr, outCb); + ASSERT_NE(nullptr, jpegBufferSize); + ASSERT_NE(nullptr, useHalBufManager); + + std::vector outputStreams; + ::android::sp cameraDevice; + ALOGI("configureStreams: Testing camera device %s", name.c_str()); + Return ret; + ret = provider->getCameraDeviceInterface_V3_x( + name, + [&cameraDevice](auto status, const auto& device) { + ALOGI("getCameraDeviceInterface_V3_x returns status:%d", + (int)status); + ASSERT_EQ(Status::OK, status); + ASSERT_NE(device, nullptr); + auto castResult = device::V3_6::ICameraDevice::castFrom(device); + ASSERT_TRUE(castResult.isOk()); + cameraDevice = castResult; + }); + ASSERT_TRUE(ret.isOk()); + + camera_metadata_t *staticMeta; + ret = cameraDevice->getCameraCharacteristics([&] (Status s, + CameraMetadata metadata) { + ASSERT_EQ(Status::OK, s); + staticMeta = clone_camera_metadata( + reinterpret_cast(metadata.data())); + ASSERT_NE(nullptr, staticMeta); + }); + ASSERT_TRUE(ret.isOk()); + + camera_metadata_ro_entry entry; + auto status = find_camera_metadata_ro_entry(staticMeta, + ANDROID_REQUEST_PARTIAL_RESULT_COUNT, &entry); + if ((0 == status) && (entry.count > 0)) { + *partialResultCount = entry.data.i32[0]; + *supportsPartialResults = (*partialResultCount > 1); + } + + *useHalBufManager = false; + status = find_camera_metadata_ro_entry(staticMeta, + ANDROID_INFO_SUPPORTED_BUFFER_MANAGEMENT_VERSION, &entry); + if ((0 == status) && (entry.count == 1)) { + *useHalBufManager = (entry.data.u8[0] == + ANDROID_INFO_SUPPORTED_BUFFER_MANAGEMENT_VERSION_HIDL_DEVICE_3_5); + } + + auto st = getJpegBufferSize(staticMeta, jpegBufferSize); + ASSERT_EQ(st, Status::OK); + + sp cb = new DeviceCb(this, deviceVersion, staticMeta); + ret = cameraDevice->open(cb, [&session](auto status, const auto& newSession) { + ALOGI("device::open returns status:%d", (int)status); + ASSERT_EQ(Status::OK, status); + ASSERT_NE(newSession, nullptr); + auto castResult = device::V3_6::ICameraDeviceSession::castFrom(newSession); + ASSERT_TRUE(castResult.isOk()); + *session = castResult; + }); + ASSERT_TRUE(ret.isOk()); + *outCb = cb; + + outputStreams.clear(); + auto rc = getAvailableOutputStreams(staticMeta, + outputStreams, threshold); + size_t idx = 0; + int currLargest = outputStreams[0].width * outputStreams[0].height; + for (size_t i = 0; i < outputStreams.size(); i++) { + int area = outputStreams[i].width * outputStreams[i].height; + if (area > currLargest) { + idx = i; + currLargest = area; + } + } + free_camera_metadata(staticMeta); + ASSERT_EQ(Status::OK, rc); + ASSERT_FALSE(outputStreams.empty()); + + V3_2::DataspaceFlags dataspaceFlag = 0; + switch (static_cast(outputStreams[idx].format)) { + case PixelFormat::BLOB: + dataspaceFlag = static_cast(Dataspace::V0_JFIF); + break; + case PixelFormat::Y16: + dataspaceFlag = static_cast(Dataspace::DEPTH); + break; + default: + dataspaceFlag = static_cast(Dataspace::UNKNOWN); + } + + ::android::hardware::hidl_vec streams3_4(/*size*/1); + V3_4::Stream stream3_4 = {{ 0 /*streamId*/, StreamType::OUTPUT, + static_cast (outputStreams[idx].width), + static_cast (outputStreams[idx].height), + static_cast (outputStreams[idx].format), + GRALLOC1_CONSUMER_USAGE_CPU_READ, dataspaceFlag, StreamRotation::ROTATION_0}, + nullptr /*physicalId*/, /*bufferSize*/ *jpegBufferSize}; + streams3_4[0] = stream3_4; + + ::android::hardware::camera::device::V3_4::StreamConfiguration config3_4; + ::android::hardware::camera::device::V3_5::StreamConfiguration config3_5; + config3_4 = {streams3_4, StreamConfigurationMode::NORMAL_MODE, {}}; + + config3_5.v3_4 = config3_4; + config3_5.streamConfigCounter = 0; + ret = (*session)->configureStreams_3_6(config3_5, + [&] (Status s, device::V3_6::HalStreamConfiguration halConfig) { + ASSERT_EQ(Status::OK, s); + *halStreamConfig = halConfig; + + if (*useHalBufManager) { + hidl_vec halStreams3_2(1); + halStreams3_2[0] = halConfig.streams[0].v3_4.v3_3.v3_2; + cb->setCurrentStreamConfig(streams3_4, halStreams3_2); + } + }); + *stream = streams3_4[0].v3_2; + ASSERT_TRUE(ret.isOk()); +} + +bool CameraHidlTest::isDepthOnly(const camera_metadata_t* staticMeta) { camera_metadata_ro_entry scalarEntry; camera_metadata_ro_entry depthEntry; @@ -5367,6 +6340,14 @@ bool CameraHidlTest::isDepthOnly(camera_metadata_t* staticMeta) { return false; } +void CameraHidlTest::updateInflightResultQueue(std::shared_ptr resultQueue) { + std::unique_lock l(mLock); + for (size_t i = 0; i < mInflightMap.size(); i++) { + auto& req = mInflightMap.editValueAt(i); + req->resultQueue = resultQueue; + } +} + // Open a device session and configure a preview stream. void CameraHidlTest::configurePreviewStream(const std::string &name, int32_t deviceVersion, sp provider, @@ -5379,6 +6360,19 @@ void CameraHidlTest::configurePreviewStream(const std::string &name, int32_t dev bool *useHalBufManager /*out*/, sp *outCb /*out*/, uint32_t streamConfigCounter) { + configureSingleStream(name, deviceVersion, provider, previewThreshold, + GRALLOC1_CONSUMER_USAGE_HWCOMPOSER, RequestTemplate::PREVIEW, session, + previewStream, halStreamConfig, supportsPartialResults, + partialResultCount, useHalBufManager, outCb, streamConfigCounter); +} +// Open a device session and configure a preview stream. +void CameraHidlTest::configureSingleStream( + const std::string& name, int32_t deviceVersion, sp provider, + const AvailableStream* previewThreshold, uint64_t bufferUsage, RequestTemplate reqTemplate, + sp* session /*out*/, V3_2::Stream* previewStream /*out*/, + HalStreamConfiguration* halStreamConfig /*out*/, bool* supportsPartialResults /*out*/, + uint32_t* partialResultCount /*out*/, bool* useHalBufManager /*out*/, + sp* outCb /*out*/, uint32_t streamConfigCounter) { ASSERT_NE(nullptr, session); ASSERT_NE(nullptr, previewStream); ASSERT_NE(nullptr, halStreamConfig); @@ -5435,7 +6429,8 @@ void CameraHidlTest::configurePreviewStream(const std::string &name, int32_t dev sp session3_3; sp session3_4; sp session3_5; - castSession(*session, deviceVersion, &session3_3, &session3_4, &session3_5); + sp session3_6; + castSession(*session, deviceVersion, &session3_3, &session3_4, &session3_5, &session3_6); *useHalBufManager = false; status = find_camera_metadata_ro_entry(staticMeta, @@ -5466,11 +6461,14 @@ void CameraHidlTest::configurePreviewStream(const std::string &name, int32_t dev dataspaceFlag = static_cast(Dataspace::UNKNOWN); } - V3_2::Stream stream3_2 = {0, StreamType::OUTPUT, - static_cast (outputPreviewStreams[0].width), - static_cast (outputPreviewStreams[0].height), - static_cast (outputPreviewStreams[0].format), - GRALLOC1_CONSUMER_USAGE_HWCOMPOSER, dataspaceFlag, StreamRotation::ROTATION_0}; + V3_2::Stream stream3_2 = {0, + StreamType::OUTPUT, + static_cast(outputPreviewStreams[0].width), + static_cast(outputPreviewStreams[0].height), + static_cast(outputPreviewStreams[0].format), + bufferUsage, + dataspaceFlag, + StreamRotation::ROTATION_0}; ::android::hardware::hidl_vec streams3_2 = {stream3_2}; ::android::hardware::camera::device::V3_2::StreamConfiguration config3_2; ::android::hardware::camera::device::V3_4::StreamConfiguration config3_4; @@ -5478,7 +6476,6 @@ void CameraHidlTest::configurePreviewStream(const std::string &name, int32_t dev createStreamConfiguration(streams3_2, StreamConfigurationMode::NORMAL_MODE, &config3_2, &config3_4, &config3_5, jpegBufferSize); if (session3_5 != nullptr) { - RequestTemplate reqTemplate = RequestTemplate::PREVIEW; ret = session3_5->constructDefaultRequestSettings(reqTemplate, [&config3_5](auto status, const auto& req) { ASSERT_EQ(Status::OK, status); @@ -5493,15 +6490,14 @@ void CameraHidlTest::configurePreviewStream(const std::string &name, int32_t dev halStreamConfig->streams.resize(1); halStreamConfig->streams[0] = halConfig.streams[0].v3_3.v3_2; if (*useHalBufManager) { - hidl_vec streams(1); + hidl_vec streams(1); hidl_vec halStreams(1); - streams[0] = stream3_2; + streams[0] = config3_4.streams[0]; halStreams[0] = halConfig.streams[0].v3_3.v3_2; cb->setCurrentStreamConfig(streams, halStreams); } }); } else if (session3_4 != nullptr) { - RequestTemplate reqTemplate = RequestTemplate::PREVIEW; ret = session3_4->constructDefaultRequestSettings(reqTemplate, [&config3_4](auto status, const auto& req) { ASSERT_EQ(Status::OK, status); @@ -5550,12 +6546,19 @@ void CameraHidlTest::castDevice(const sp &device, } //Cast camera provider to corresponding version if available -void CameraHidlTest::castProvider(const sp &provider, - sp *provider2_5 /*out*/) { +void CameraHidlTest::castProvider(const sp& provider, + sp* provider2_5 /*out*/, + sp* provider2_6 /*out*/) { ASSERT_NE(nullptr, provider2_5); - auto castResult = provider::V2_5::ICameraProvider::castFrom(provider); - if (castResult.isOk()) { - *provider2_5 = castResult; + auto castResult2_5 = provider::V2_5::ICameraProvider::castFrom(provider); + if (castResult2_5.isOk()) { + *provider2_5 = castResult2_5; + } + + ASSERT_NE(nullptr, provider2_6); + auto castResult2_6 = provider::V2_6::ICameraProvider::castFrom(provider); + if (castResult2_6.isOk()) { + *provider2_6 = castResult2_6; } } @@ -5563,12 +6566,20 @@ void CameraHidlTest::castProvider(const sp &provider, void CameraHidlTest::castSession(const sp &session, int32_t deviceVersion, sp *session3_3 /*out*/, sp *session3_4 /*out*/, - sp *session3_5 /*out*/) { + sp *session3_5 /*out*/, + sp *session3_6 /*out*/) { ASSERT_NE(nullptr, session3_3); ASSERT_NE(nullptr, session3_4); ASSERT_NE(nullptr, session3_5); + ASSERT_NE(nullptr, session3_6); switch (deviceVersion) { + case CAMERA_DEVICE_API_VERSION_3_6: { + auto castResult = device::V3_6::ICameraDeviceSession::castFrom(session); + ASSERT_TRUE(castResult.isOk()); + *session3_6 = castResult; + } + [[fallthrough]]; case CAMERA_DEVICE_API_VERSION_3_5: { auto castResult = device::V3_5::ICameraDeviceSession::castFrom(session); ASSERT_TRUE(castResult.isOk()); @@ -5616,13 +6627,20 @@ void CameraHidlTest::verifyLogicalCameraMetadata(const std::string& cameraName, const hidl_vec& deviceNames) { const camera_metadata_t* metadata = (camera_metadata_t*)chars.data(); ASSERT_NE(nullptr, metadata); - - Status rc = isLogicalMultiCamera(metadata); + SystemCameraKind systemCameraKind = SystemCameraKind::PUBLIC; + Status rc = getSystemCameraKind(metadata, &systemCameraKind); + ASSERT_EQ(rc, Status::OK); + rc = isLogicalMultiCamera(metadata); ASSERT_TRUE(Status::OK == rc || Status::METHOD_NOT_SUPPORTED == rc); if (Status::METHOD_NOT_SUPPORTED == rc) { return; } + camera_metadata_ro_entry entry; + int retcode = find_camera_metadata_ro_entry(metadata, + ANDROID_CONTROL_ZOOM_RATIO_RANGE, &entry); + bool hasZoomRatioRange = (0 == retcode && entry.count == 2); + std::string version, cameraId; ASSERT_TRUE(::matchDeviceName(cameraName, mProviderType, &version, &cameraId)); std::unordered_set physicalIds; @@ -5630,15 +6648,45 @@ void CameraHidlTest::verifyLogicalCameraMetadata(const std::string& cameraName, for (auto physicalId : physicalIds) { ASSERT_NE(physicalId, cameraId); bool isPublicId = false; + std::string fullPublicId; + SystemCameraKind physSystemCameraKind = SystemCameraKind::PUBLIC; for (auto& deviceName : deviceNames) { std::string publicVersion, publicId; ASSERT_TRUE(::matchDeviceName(deviceName, mProviderType, &publicVersion, &publicId)); if (physicalId == publicId) { isPublicId = true; + fullPublicId = deviceName; break; } } if (isPublicId) { + ::android::sp<::android::hardware::camera::device::V3_2::ICameraDevice> subDevice; + Return ret; + ret = mProvider->getCameraDeviceInterface_V3_x( + fullPublicId, [&](auto status, const auto& device) { + ASSERT_EQ(Status::OK, status); + ASSERT_NE(device, nullptr); + subDevice = device; + }); + ASSERT_TRUE(ret.isOk()); + + ret = subDevice->getCameraCharacteristics( + [&](auto status, const auto& chars) { + ASSERT_EQ(Status::OK, status); + const camera_metadata_t* staticMeta = + reinterpret_cast(chars.data()); + rc = getSystemCameraKind(staticMeta, &physSystemCameraKind); + ASSERT_EQ(rc, Status::OK); + // Make sure that the system camera kind of a non-hidden + // physical cameras is the same as the logical camera associated + // with it. + ASSERT_EQ(physSystemCameraKind, systemCameraKind); + retcode = find_camera_metadata_ro_entry(staticMeta, + ANDROID_CONTROL_ZOOM_RATIO_RANGE, &entry); + bool subCameraHasZoomRatioRange = (0 == retcode && entry.count == 2); + ASSERT_EQ(hasZoomRatioRange, subCameraHasZoomRatioRange); + }); + ASSERT_TRUE(ret.isOk()); continue; } @@ -5650,11 +6698,16 @@ void CameraHidlTest::verifyLogicalCameraMetadata(const std::string& cameraName, ASSERT_NE(device3_5, nullptr); // Check camera characteristics for hidden camera id - Return ret = device3_5->getPhysicalCameraCharacteristics(physicalId, - [&](auto status, const auto& chars) { - verifyCameraCharacteristics(status, chars); - verifyMonochromeCharacteristics(chars, deviceVersion); - }); + Return ret = device3_5->getPhysicalCameraCharacteristics( + physicalId, [&](auto status, const auto& chars) { + verifyCameraCharacteristics(status, chars); + verifyMonochromeCharacteristics(chars, deviceVersion); + retcode = + find_camera_metadata_ro_entry((const camera_metadata_t*)chars.data(), + ANDROID_CONTROL_ZOOM_RATIO_RANGE, &entry); + bool subCameraHasZoomRatioRange = (0 == retcode && entry.count == 2); + ASSERT_EQ(hasZoomRatioRange, subCameraHasZoomRatioRange); + }); ASSERT_TRUE(ret.isOk()); // Check calling getCameraDeviceInterface_V3_x() on hidden camera id returns @@ -5673,8 +6726,7 @@ void CameraHidlTest::verifyLogicalCameraMetadata(const std::string& cameraName, // Make sure ANDROID_LOGICAL_MULTI_CAMERA_ACTIVE_PHYSICAL_ID is available in // result keys. if (deviceVersion >= CAMERA_DEVICE_API_VERSION_3_5) { - camera_metadata_ro_entry entry; - int retcode = find_camera_metadata_ro_entry(metadata, + retcode = find_camera_metadata_ro_entry(metadata, ANDROID_REQUEST_AVAILABLE_RESULT_KEYS, &entry); if ((0 == retcode) && (entry.count > 0)) { ASSERT_NE(std::find(entry.data.i32, entry.data.i32 + entry.count, @@ -5772,6 +6824,245 @@ void CameraHidlTest::verifyCameraCharacteristics(Status status, const CameraMeta ADD_FAILURE() << "Get Heic maxJpegAppSegmentsCount failed!"; } } + + retcode = find_camera_metadata_ro_entry(metadata, + ANDROID_LENS_POSE_REFERENCE, &entry); + if (0 == retcode && entry.count > 0) { + uint8_t poseReference = entry.data.u8[0]; + ASSERT_TRUE(poseReference <= ANDROID_LENS_POSE_REFERENCE_UNDEFINED && + poseReference >= ANDROID_LENS_POSE_REFERENCE_PRIMARY_CAMERA); + } + + verifyExtendedSceneModeCharacteristics(metadata); + verifyZoomCharacteristics(metadata); +} + +void CameraHidlTest::verifyExtendedSceneModeCharacteristics(const camera_metadata_t* metadata) { + camera_metadata_ro_entry entry; + int retcode = 0; + + retcode = find_camera_metadata_ro_entry(metadata, ANDROID_CONTROL_AVAILABLE_MODES, &entry); + if ((0 == retcode) && (entry.count > 0)) { + for (auto i = 0; i < entry.count; i++) { + ASSERT_TRUE(entry.data.u8[i] >= ANDROID_CONTROL_MODE_OFF && + entry.data.u8[i] <= ANDROID_CONTROL_MODE_USE_EXTENDED_SCENE_MODE); + } + } else { + ADD_FAILURE() << "Get camera controlAvailableModes failed!"; + } + + // Check key availability in capabilities, request and result. + + retcode = find_camera_metadata_ro_entry(metadata, + ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS, &entry); + bool hasExtendedSceneModeRequestKey = false; + if ((0 == retcode) && (entry.count > 0)) { + hasExtendedSceneModeRequestKey = + std::find(entry.data.i32, entry.data.i32 + entry.count, + ANDROID_CONTROL_EXTENDED_SCENE_MODE) != entry.data.i32 + entry.count; + } else { + ADD_FAILURE() << "Get camera availableRequestKeys failed!"; + } + + retcode = find_camera_metadata_ro_entry(metadata, + ANDROID_REQUEST_AVAILABLE_RESULT_KEYS, &entry); + bool hasExtendedSceneModeResultKey = false; + if ((0 == retcode) && (entry.count > 0)) { + hasExtendedSceneModeResultKey = + std::find(entry.data.i32, entry.data.i32 + entry.count, + ANDROID_CONTROL_EXTENDED_SCENE_MODE) != entry.data.i32 + entry.count; + } else { + ADD_FAILURE() << "Get camera availableResultKeys failed!"; + } + + retcode = find_camera_metadata_ro_entry(metadata, + ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS, &entry); + bool hasExtendedSceneModeMaxSizesKey = false; + bool hasExtendedSceneModeZoomRatioRangesKey = false; + if ((0 == retcode) && (entry.count > 0)) { + hasExtendedSceneModeMaxSizesKey = + std::find(entry.data.i32, entry.data.i32 + entry.count, + ANDROID_CONTROL_AVAILABLE_EXTENDED_SCENE_MODE_MAX_SIZES) != + entry.data.i32 + entry.count; + hasExtendedSceneModeZoomRatioRangesKey = + std::find(entry.data.i32, entry.data.i32 + entry.count, + ANDROID_CONTROL_AVAILABLE_EXTENDED_SCENE_MODE_ZOOM_RATIO_RANGES) != + entry.data.i32 + entry.count; + } else { + ADD_FAILURE() << "Get camera availableCharacteristicsKeys failed!"; + } + + camera_metadata_ro_entry maxSizesEntry; + retcode = find_camera_metadata_ro_entry( + metadata, ANDROID_CONTROL_AVAILABLE_EXTENDED_SCENE_MODE_MAX_SIZES, &maxSizesEntry); + bool hasExtendedSceneModeMaxSizes = (0 == retcode && maxSizesEntry.count > 0); + + camera_metadata_ro_entry zoomRatioRangesEntry; + retcode = find_camera_metadata_ro_entry( + metadata, ANDROID_CONTROL_AVAILABLE_EXTENDED_SCENE_MODE_ZOOM_RATIO_RANGES, + &zoomRatioRangesEntry); + bool hasExtendedSceneModeZoomRatioRanges = (0 == retcode && zoomRatioRangesEntry.count > 0); + + // Extended scene mode keys must all be available, or all be unavailable. + bool noExtendedSceneMode = + !hasExtendedSceneModeRequestKey && !hasExtendedSceneModeResultKey && + !hasExtendedSceneModeMaxSizesKey && !hasExtendedSceneModeZoomRatioRangesKey && + !hasExtendedSceneModeMaxSizes && !hasExtendedSceneModeZoomRatioRanges; + if (noExtendedSceneMode) { + return; + } + bool hasExtendedSceneMode = hasExtendedSceneModeRequestKey && hasExtendedSceneModeResultKey && + hasExtendedSceneModeMaxSizesKey && + hasExtendedSceneModeZoomRatioRangesKey && + hasExtendedSceneModeMaxSizes && hasExtendedSceneModeZoomRatioRanges; + ASSERT_TRUE(hasExtendedSceneMode); + + // Must have DISABLED, and must have one of BOKEH_STILL_CAPTURE, BOKEH_CONTINUOUS, or a VENDOR + // mode. + ASSERT_TRUE((maxSizesEntry.count == 6 && zoomRatioRangesEntry.count == 2) || + (maxSizesEntry.count == 9 && zoomRatioRangesEntry.count == 4)); + bool hasDisabledMode = false; + bool hasBokehStillCaptureMode = false; + bool hasBokehContinuousMode = false; + bool hasVendorMode = false; + std::vector outputStreams; + ASSERT_EQ(Status::OK, getAvailableOutputStreams(metadata, outputStreams)); + for (int i = 0, j = 0; i < maxSizesEntry.count && j < zoomRatioRangesEntry.count; i += 3) { + int32_t mode = maxSizesEntry.data.i32[i]; + int32_t maxWidth = maxSizesEntry.data.i32[i+1]; + int32_t maxHeight = maxSizesEntry.data.i32[i+2]; + switch (mode) { + case ANDROID_CONTROL_EXTENDED_SCENE_MODE_DISABLED: + hasDisabledMode = true; + ASSERT_TRUE(maxWidth == 0 && maxHeight == 0); + break; + case ANDROID_CONTROL_EXTENDED_SCENE_MODE_BOKEH_STILL_CAPTURE: + hasBokehStillCaptureMode = true; + j += 2; + break; + case ANDROID_CONTROL_EXTENDED_SCENE_MODE_BOKEH_CONTINUOUS: + hasBokehContinuousMode = true; + j += 2; + break; + default: + if (mode < ANDROID_CONTROL_EXTENDED_SCENE_MODE_VENDOR_START) { + ADD_FAILURE() << "Invalid extended scene mode advertised: " << mode; + } else { + hasVendorMode = true; + j += 2; + } + break; + } + + if (mode != ANDROID_CONTROL_EXTENDED_SCENE_MODE_DISABLED) { + // Make sure size is supported. + bool sizeSupported = false; + for (const auto& stream : outputStreams) { + if ((stream.format == static_cast(PixelFormat::YCBCR_420_888) || + stream.format == static_cast(PixelFormat::IMPLEMENTATION_DEFINED)) + && stream.width == maxWidth && stream.height == maxHeight) { + sizeSupported = true; + break; + } + } + ASSERT_TRUE(sizeSupported); + + // Make sure zoom range is valid + float minZoomRatio = zoomRatioRangesEntry.data.f[0]; + float maxZoomRatio = zoomRatioRangesEntry.data.f[1]; + ASSERT_GT(minZoomRatio, 0.0f); + ASSERT_LE(minZoomRatio, maxZoomRatio); + } + } + ASSERT_TRUE(hasDisabledMode); + ASSERT_TRUE(hasBokehStillCaptureMode || hasBokehContinuousMode || hasVendorMode); +} + +void CameraHidlTest::verifyZoomCharacteristics(const camera_metadata_t* metadata) { + camera_metadata_ro_entry entry; + int retcode = 0; + + // Check key availability in capabilities, request and result. + retcode = find_camera_metadata_ro_entry(metadata, + ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM, &entry); + float maxDigitalZoom = 1.0; + if ((0 == retcode) && (entry.count == 1)) { + maxDigitalZoom = entry.data.f[0]; + } else { + ADD_FAILURE() << "Get camera scalerAvailableMaxDigitalZoom failed!"; + } + + retcode = find_camera_metadata_ro_entry(metadata, + ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS, &entry); + bool hasZoomRequestKey = false; + if ((0 == retcode) && (entry.count > 0)) { + hasZoomRequestKey = std::find(entry.data.i32, entry.data.i32+entry.count, + ANDROID_CONTROL_ZOOM_RATIO) != entry.data.i32+entry.count; + } else { + ADD_FAILURE() << "Get camera availableRequestKeys failed!"; + } + + retcode = find_camera_metadata_ro_entry(metadata, + ANDROID_REQUEST_AVAILABLE_RESULT_KEYS, &entry); + bool hasZoomResultKey = false; + if ((0 == retcode) && (entry.count > 0)) { + hasZoomResultKey = std::find(entry.data.i32, entry.data.i32+entry.count, + ANDROID_CONTROL_ZOOM_RATIO) != entry.data.i32+entry.count; + } else { + ADD_FAILURE() << "Get camera availableResultKeys failed!"; + } + + retcode = find_camera_metadata_ro_entry(metadata, + ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS, &entry); + bool hasZoomCharacteristicsKey = false; + if ((0 == retcode) && (entry.count > 0)) { + hasZoomCharacteristicsKey = std::find(entry.data.i32, entry.data.i32+entry.count, + ANDROID_CONTROL_ZOOM_RATIO_RANGE) != entry.data.i32+entry.count; + } else { + ADD_FAILURE() << "Get camera availableCharacteristicsKeys failed!"; + } + + retcode = find_camera_metadata_ro_entry(metadata, + ANDROID_CONTROL_ZOOM_RATIO_RANGE, &entry); + bool hasZoomRatioRange = (0 == retcode && entry.count == 2); + + // Zoom keys must all be available, or all be unavailable. + bool noZoomRatio = !hasZoomRequestKey && !hasZoomResultKey && !hasZoomCharacteristicsKey && + !hasZoomRatioRange; + if (noZoomRatio) { + return; + } + bool hasZoomRatio = hasZoomRequestKey && hasZoomResultKey && hasZoomCharacteristicsKey && + hasZoomRatioRange; + ASSERT_TRUE(hasZoomRatio); + + float minZoomRatio = entry.data.f[0]; + float maxZoomRatio = entry.data.f[1]; + constexpr float FLOATING_POINT_THRESHOLD = 0.00001f; + if (maxDigitalZoom > maxZoomRatio + FLOATING_POINT_THRESHOLD) { + ADD_FAILURE() << "Maximum digital zoom " << maxDigitalZoom + << " is larger than maximum zoom ratio " << maxZoomRatio << " + threshold " + << FLOATING_POINT_THRESHOLD << "!"; + } + if (minZoomRatio > maxZoomRatio) { + ADD_FAILURE() << "Maximum zoom ratio is less than minimum zoom ratio!"; + } + if (minZoomRatio > 1.0f) { + ADD_FAILURE() << "Minimum zoom ratio is more than 1.0!"; + } + if (maxZoomRatio < 1.0f) { + ADD_FAILURE() << "Maximum zoom ratio is less than 1.0!"; + } + + // Make sure CROPPING_TYPE is CENTER_ONLY + retcode = find_camera_metadata_ro_entry(metadata, + ANDROID_SCALER_CROPPING_TYPE, &entry); + if ((0 == retcode) && (entry.count == 1)) { + int8_t croppingType = entry.data.u8[0]; + ASSERT_EQ(croppingType, ANDROID_SCALER_CROPPING_TYPE_CENTER_ONLY); + } else { + ADD_FAILURE() << "Get camera scalerCroppingType failed!"; + } } void CameraHidlTest::verifyMonochromeCharacteristics(const CameraMetadata& chars, @@ -5925,7 +7216,8 @@ void CameraHidlTest::verifyBuffersReturned( sp session3_3; sp session3_4; sp session3_5; - castSession(session, deviceVersion, &session3_3, &session3_4, &session3_5); + sp session3_6; + castSession(session, deviceVersion, &session3_3, &session3_4, &session3_5, &session3_6); ASSERT_NE(nullptr, session3_5.get()); hidl_vec streamIds(1); @@ -6144,68 +7436,15 @@ void CameraHidlTest::allocateGraphicBuffer(uint32_t width, uint32_t height, uint PixelFormat format, hidl_handle *buffer_handle /*out*/) { ASSERT_NE(buffer_handle, nullptr); - sp allocator = - android::hardware::graphics::allocator::V2_0::IAllocator::getService(); - sp allocatorV3 = - android::hardware::graphics::allocator::V3_0::IAllocator::getService(); + buffer_handle_t buffer; + uint32_t stride; - sp mapperV3 = - android::hardware::graphics::mapper::V3_0::IMapper::getService(); - sp mapper = - android::hardware::graphics::mapper::V2_0::IMapper::getService(); - ::android::hardware::hidl_vec descriptor; - if (mapperV3 != nullptr && allocatorV3 != nullptr) { - android::hardware::graphics::mapper::V3_0::IMapper::BufferDescriptorInfo descriptorInfo {}; - descriptorInfo.width = width; - descriptorInfo.height = height; - descriptorInfo.layerCount = 1; - descriptorInfo.format = - static_cast(format); - descriptorInfo.usage = usage; + android::status_t err = android::GraphicBufferAllocator::get().allocateRawHandle( + width, height, static_cast(format), 1u /*layerCount*/, usage, &buffer, &stride, + "VtsHalCameraProviderV2_4"); + ASSERT_EQ(err, android::NO_ERROR); - auto ret = mapperV3->createDescriptor( - descriptorInfo, [&descriptor](android::hardware::graphics::mapper::V3_0::Error err, - ::android::hardware::hidl_vec desc) { - ASSERT_EQ(err, android::hardware::graphics::mapper::V3_0::Error::NONE); - descriptor = desc; - }); - ASSERT_TRUE(ret.isOk()); - - ret = allocatorV3->allocate(descriptor, 1u, - [&](android::hardware::graphics::mapper::V3_0::Error err, uint32_t /*stride*/, - const ::android::hardware::hidl_vec<::android::hardware::hidl_handle>& buffers) { - ASSERT_EQ(android::hardware::graphics::mapper::V3_0::Error::NONE, err); - ASSERT_EQ(buffers.size(), 1u); - *buffer_handle = buffers[0]; - }); - ASSERT_TRUE(ret.isOk()); - } else { - ASSERT_NE(mapper.get(), nullptr); - ASSERT_NE(allocator.get(), nullptr); - android::hardware::graphics::mapper::V2_0::IMapper::BufferDescriptorInfo descriptorInfo {}; - descriptorInfo.width = width; - descriptorInfo.height = height; - descriptorInfo.layerCount = 1; - descriptorInfo.format = format; - descriptorInfo.usage = usage; - - auto ret = mapper->createDescriptor( - descriptorInfo, [&descriptor](android::hardware::graphics::mapper::V2_0::Error err, - ::android::hardware::hidl_vec desc) { - ASSERT_EQ(err, android::hardware::graphics::mapper::V2_0::Error::NONE); - descriptor = desc; - }); - ASSERT_TRUE(ret.isOk()); - - ret = allocator->allocate(descriptor, 1u, - [&](android::hardware::graphics::mapper::V2_0::Error err, uint32_t /*stride*/, - const ::android::hardware::hidl_vec<::android::hardware::hidl_handle>& buffers) { - ASSERT_EQ(android::hardware::graphics::mapper::V2_0::Error::NONE, err); - ASSERT_EQ(buffers.size(), 1u); - *buffer_handle = buffers[0]; - }); - ASSERT_TRUE(ret.isOk()); - } + buffer_handle->setTo(const_cast(buffer), true /*shouldOwn*/); } void CameraHidlTest::verifyRecommendedConfigs(const CameraMetadata& chars) { @@ -6325,11 +7564,27 @@ void CameraHidlTest::verifySessionReconfigurationQuery( } } -int main(int argc, char **argv) { - ::testing::AddGlobalTestEnvironment(CameraHidlEnvironment::Instance()); - ::testing::InitGoogleTest(&argc, argv); - CameraHidlEnvironment::Instance()->init(&argc, argv); - int status = RUN_ALL_TESTS(); - ALOGI("Test result = %d", status); - return status; +void CameraHidlTest::verifyRequestTemplate(const camera_metadata_t* metadata, + RequestTemplate requestTemplate) { + ASSERT_NE(nullptr, metadata); + size_t entryCount = + get_camera_metadata_entry_count(metadata); + ALOGI("template %u metadata entry count is %zu", (int32_t)requestTemplate, entryCount); + // TODO: we can do better than 0 here. Need to check how many required + // request keys we've defined for each template + ASSERT_GT(entryCount, 0u); + + // Check zoomRatio + camera_metadata_ro_entry zoomRatioEntry; + int foundZoomRatio = find_camera_metadata_ro_entry(metadata, + ANDROID_CONTROL_ZOOM_RATIO, &zoomRatioEntry); + if (foundZoomRatio == 0) { + ASSERT_EQ(zoomRatioEntry.count, 1); + ASSERT_EQ(zoomRatioEntry.data.f[0], 1.0f); + } } + +INSTANTIATE_TEST_SUITE_P( + PerInstance, CameraHidlTest, + testing::ValuesIn(android::hardware::getAllHalInstanceNames(ICameraProvider::descriptor)), + android::hardware::PrintInstanceNameToString); diff --git a/camera/provider/2.5/default/Android.bp b/camera/provider/2.5/default/Android.bp index 4563362ddb..9ddf651440 100644 --- a/camera/provider/2.5/default/Android.bp +++ b/camera/provider/2.5/default/Android.bp @@ -52,6 +52,8 @@ cc_library_shared { "android.hardware.camera.provider@2.4-external", "android.hardware.camera.provider@2.5", "android.hardware.graphics.mapper@2.0", + "android.hardware.graphics.mapper@3.0", + "android.hardware.graphics.mapper@4.0", "android.hidl.allocator@1.0", "android.hidl.memory@1.0", "camera.device@3.3-impl", @@ -72,7 +74,8 @@ cc_library_shared { ], header_libs: [ "camera.device@3.4-external-impl_headers", - "camera.device@3.5-external-impl_headers" + "camera.device@3.5-external-impl_headers", + "camera.device@3.6-external-impl_headers" ], export_include_dirs: ["."], } @@ -165,7 +168,10 @@ cc_binary { "android.hardware.camera.provider@2.5", "android.hardware.camera.provider@2.5-external", "android.hardware.graphics.mapper@2.0", + "android.hardware.graphics.mapper@3.0", + "android.hardware.graphics.mapper@4.0", "libbinder", + "libcamera_metadata", "libhidlbase", "liblog", "libtinyxml2", @@ -179,5 +185,6 @@ cc_binary { "camera.device@3.4-impl_headers", "camera.device@3.5-external-impl_headers", "camera.device@3.5-impl_headers", + "camera.device@3.6-external-impl_headers", ], } diff --git a/camera/provider/2.6/Android.bp b/camera/provider/2.6/Android.bp new file mode 100644 index 0000000000..6934c175e0 --- /dev/null +++ b/camera/provider/2.6/Android.bp @@ -0,0 +1,23 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.camera.provider@2.6", + root: "android.hardware", + srcs: [ + "types.hal", + "ICameraProvider.hal", + "ICameraProviderCallback.hal", + ], + interfaces: [ + "android.hardware.camera.common@1.0", + "android.hardware.camera.device@1.0", + "android.hardware.camera.device@3.2", + "android.hardware.camera.device@3.3", + "android.hardware.camera.device@3.4", + "android.hardware.camera.provider@2.4", + "android.hardware.camera.provider@2.5", + "android.hardware.graphics.common@1.0", + "android.hidl.base@1.0", + ], + gen_java: false, +} diff --git a/camera/provider/2.6/ICameraProvider.hal b/camera/provider/2.6/ICameraProvider.hal new file mode 100644 index 0000000000..d720b26e5c --- /dev/null +++ b/camera/provider/2.6/ICameraProvider.hal @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2020 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.camera.provider@2.6; + +import @2.5::ICameraProvider; +import android.hardware.camera.common@1.0::Status; +import android.hardware.camera.device@3.4::StreamConfiguration; + +/** + * Camera provider HAL + * + * @2.6::adds support for the getConcurrentStreamingCameraIds() and + * isConcurrentStreamCombinationSupported() + * @2.6::ICameraProviderCallback to receive physical camera availability + * callbacks for logical multi-cameras. + */ +interface ICameraProvider extends @2.5::ICameraProvider { + /** + * getConcurrentStreamingCameraIds + * + * Get a vector of combinations of camera device ids that are able to + * configure streams concurrently. Each camera device advertised in a + * combination MUST at the very least support the following streams while + * streaming concurrently with the other camera ids in the combination. + * + * Target 1 Target 2 + * ----------------------------------------------------- + * | Type | Size | Type | Size | + * ----------------------------------------------------- + * | YUV | s1440p | | + * ----------------------------------------------------- + * | JPEG | s1440p | | + * ----------------------------------------------------- + * | PRIV | s1440p | | + * ----------------------------------------------------- + * | YUV / PRIV | s720p | YUV / PRIV | s1440p | + * ----------------------------------------------------- + * | YUV / PRIV | s720p | JPEG | s1440p | + * ----------------------------------------------------- + * + * where: + * s720p - min (max output resolution for the given format, 1280 X 720) + * s1440p - min (max output resolution for the given format, 1920 X 1440) + * + * If a device has MONOCHROME capability (device's capabilities include + * ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME) and therefore supports Y8 + * outputs, stream combinations mentioned above, where YUV is substituted by + * Y8 must be also supported. + * + * Devices whose capabilities do not include + * ANDROID_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE, must support + * at least a single Y16 stream, Dataspace::DEPTH with sVGA resolution, + * during concurrent operation. + * Where sVGA - min (max output resolution for the given format, 640 X 480) + * + * The camera framework must call this method whenever it gets a + * cameraDeviceStatusChange callback adding a new camera device or removing + * a camera device known to it. This is so that the camera framework can get new combinations + * of camera ids that can stream concurrently, that might have potentially appeared. + * + * For each combination (and their subsets) of camera device ids returned by + * getConcurrentStreamingCameraIds(): If only the mandatory combinations can + * be supported concurrently by each device, then the resource costs must + * sum up to > 100 for the concurrent set, to ensure arbitration between + * camera applications work as expected. Only if resources are sufficient + * to run a set of cameras at full capability (maximally + * resource-consuming framerate and stream size settings available in the + * configuration settings exposed through camera metadata), should the sum + * of resource costs for the combination be <= 100. + * + * For guaranteed concurrent camera operation, the camera framework must call + * ICameraDevice.open() on all devices (intended for concurrent operation), before configuring + * any streams on them. This gives the camera HAL process an opportunity to potentially + * distribute hardware resources better before stream configuration. + * + * Due to potential hardware constraints around internal switching of physical camera devices, + * a device's complete ZOOM_RATIO_RANGE(if supported), may not apply during concurrent + * operation. If ZOOM_RATIO is supported, camera HALs must ensure ZOOM_RATIO_RANGE of + * [1.0, ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM] is supported by that device, during + * concurrent operation. + * + * @return status Status code for the operation + * @return cameraIds a list of camera id combinations that support + * concurrent stream configurations with the minimum guarantees + * specified. + */ + getConcurrentStreamingCameraIds() generates (Status status, vec> cameraIds); + + /** + * isConcurrentStreamCombinationSupported: + * + * Check for device support of specific camera stream combinations while + * streaming concurrently with other devices. + * + * The per device streamList must contain at least one output-capable stream, and may + * not contain more than one input-capable stream. + * In contrast to regular stream configuration the framework does not create + * or initialize any actual streams. This means that Hal must not use or + * consider the stream "id" value. + * + * ------------------------------------------------------------------------ + * + * Preconditions: + * + * The framework can call this method at any time before, during and + * after active session configuration per device. This means that calls must not + * impact the performance of pending camera requests in any way. In + * particular there must not be any glitches or delays during normal + * camera streaming. + * + * The framework must not call this method with any combination of camera + * ids that is not a subset of the camera ids advertised by getConcurrentStreamingCameraIds of + * the same provider. + * + * Performance requirements: + * This call is expected to be significantly faster than stream + * configuration. In general HW and SW camera settings must not be + * changed and there must not be a user-visible impact on camera performance. + * + * @param configs a vector of camera ids and their corresponding stream + * configurations that need to be queried for support. + * + * @return status Status code for the operation, one of: + * OK: + * On successful stream combination query. + * METHOD_NOT_SUPPORTED: + * The camera provider does not support stream combination query. + * INTERNAL_ERROR: + * The stream combination query cannot complete due to internal + * error. + * @return true in case the stream combination is supported, false otherwise. + * + * + */ + isConcurrentStreamCombinationSupported(vec configs) + generates (Status status, bool queryStatus); +}; diff --git a/camera/provider/2.6/ICameraProviderCallback.hal b/camera/provider/2.6/ICameraProviderCallback.hal new file mode 100644 index 0000000000..42c1092676 --- /dev/null +++ b/camera/provider/2.6/ICameraProviderCallback.hal @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 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.camera.provider@2.6; + +import android.hardware.camera.common@1.0::types; +import android.hardware.camera.provider@2.4::ICameraProviderCallback; + +/** + * Callback functions for a camera provider HAL to use to inform the camera + * service of changes to the camera subsystem. + * + * Version 2.6 adds support for physical camera device status callback for + * multi-camera. + */ +interface ICameraProviderCallback extends @2.4::ICameraProviderCallback { + + /** + * cameraPhysicalDeviceStatusChange: + * + * Callback to the camera service to indicate that the state of a physical + * camera device of a logical multi-camera has changed. + * + * On camera service startup, when ICameraProvider::setCallback is invoked, + * the camera service must assume that all physical devices backing internal + * multi-camera devices are in the CAMERA_DEVICE_STATUS_PRESENT state. + * + * The provider must call this method to inform the camera service of any + * initially NOT_PRESENT physical devices, as soon as the callbacks are available + * through setCallback. + * + * @param cameraDeviceName The name of the logical multi-camera whose + * physical camera has a new status. + * @param physicalCameraDeviceName The name of the physical camera device + * that has a new status. + * @param newStatus The new status that device is in. + * + */ + physicalCameraDeviceStatusChange(string cameraDeviceName, + string physicalCameraDeviceName, CameraDeviceStatus newStatus); +}; diff --git a/camera/provider/2.6/types.hal b/camera/provider/2.6/types.hal new file mode 100644 index 0000000000..24c62aa24d --- /dev/null +++ b/camera/provider/2.6/types.hal @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 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.camera.provider@2.6; + +import android.hardware.camera.device@3.4::StreamConfiguration; + +/** + * CameraIdAndStreamCombination: + * Pairs the cameraId and the StreamConfiguration to be + * tested with other concurrent camera id and StreamConfigurations + */ +struct CameraIdAndStreamCombination { + string cameraId; + + @3.4::StreamConfiguration streamConfiguration; +}; 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 14b8bbdd5a..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 -#include #include #include #include @@ -27,8 +25,11 @@ #include #include #include +#include +#include #include #include +#include #include #include #include @@ -208,29 +209,16 @@ void MediaCasListener::testEventEcho(sp& 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(); } -}; - -class MediaCasHidlTest : public ::testing::VtsHalHidlTargetTestBase { - public: +class MediaCasHidlTest : public testing::TestWithParam { + public: virtual void SetUp() override { - mService = ::testing::VtsHalHidlTargetTestBase::getService( - CasHidlEnvironment::Instance()->getServiceName()); + mService = IMediaCasService::getService(GetParam()); ASSERT_NE(mService, nullptr); } - sp mService; + sp mService = nullptr; - protected: + protected: static void description(const std::string& description) { RecordProperty("description", description); } @@ -325,7 +313,7 @@ class MediaCasHidlTest : public ::testing::VtsHalHidlTargetTestBase { return ::testing::AssertionFailure(); } - uint8_t* ipBuffer = static_cast(static_cast(mem->pointer())); + uint8_t* ipBuffer = static_cast(static_cast(mem->unsecurePointer())); memcpy(ipBuffer, kInBinaryBuffer, sizeof(kInBinaryBuffer)); // hidlMemory is not to be passed out of scope! @@ -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 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 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 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)); @@ -568,7 +556,7 @@ TEST_F(MediaCasHidlTest, TestClearKeyApis) { EXPECT_EQ(Status::OK, descrambleStatus); ASSERT_NE(nullptr, dataMemory.get()); - uint8_t* opBuffer = static_cast(static_cast(dataMemory->pointer())); + uint8_t* opBuffer = static_cast(static_cast(dataMemory->unsecurePointer())); int compareResult = memcmp(static_cast(opBuffer), static_cast(kOutRefBinaryBuffer), @@ -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 88f1fb01d6..1b5797b845 100644 --- a/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp +++ b/cas/1.1/vts/functional/VtsHalCasV1_1TargetTest.cpp @@ -16,8 +16,6 @@ #define LOG_TAG "mediacas_hidl_hal_test" -#include -#include #include #include #include @@ -27,8 +25,11 @@ #include #include #include +#include +#include #include #include +#include #include #include #include @@ -251,27 +252,14 @@ void MediaCasListener::testSessionEventEcho(sp& mediaCas, const hidl_vec(); } -}; - -class MediaCasHidlTest : public ::testing::VtsHalHidlTargetTestBase { +class MediaCasHidlTest : public testing::TestWithParam { public: virtual void SetUp() override { - mService = ::testing::VtsHalHidlTargetTestBase::getService( - CasHidlEnvironment::Instance()->getServiceName()); + mService = IMediaCasService::getService(GetParam()); ASSERT_NE(mService, nullptr); } - sp mService; + sp mService = nullptr; protected: static void description(const std::string& description) { @@ -366,7 +354,7 @@ class MediaCasHidlTest : public ::testing::VtsHalHidlTargetTestBase { return ::testing::AssertionFailure(); } - uint8_t* ipBuffer = static_cast(static_cast(mem->pointer())); + uint8_t* ipBuffer = static_cast(static_cast(mem->unsecurePointer())); memcpy(ipBuffer, kInBinaryBuffer, sizeof(kInBinaryBuffer)); // hidlMemory is not to be passed out of scope! @@ -453,7 +441,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)); @@ -543,7 +531,7 @@ TEST_F(MediaCasHidlTest, TestClearKeyApisWithSession) { EXPECT_EQ(Status::OK, descrambleStatus); ASSERT_NE(nullptr, dataMemory.get()); - uint8_t* opBuffer = static_cast(static_cast(dataMemory->pointer())); + uint8_t* opBuffer = static_cast(static_cast(dataMemory->unsecurePointer())); int compareResult = memcmp(static_cast(opBuffer), @@ -561,11 +549,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/cas/1.2/types.hal b/cas/1.2/types.hal index 40c06cf1c7..06199cda16 100644 --- a/cas/1.2/types.hal +++ b/cas/1.2/types.hal @@ -45,6 +45,10 @@ enum Status : @1.0::Status { * ERROR_CAS_BLACKOUT is used to report geographical blackout. */ ERROR_CAS_BLACKOUT, + /** + * ERROR_CAS_REBOOTING is used to report CAS is during rebooting. + */ + ERROR_CAS_REBOOTING, }; /** diff --git a/cas/1.2/vts/functional/Android.bp b/cas/1.2/vts/functional/Android.bp index 9bc372c3a7..2d6517f0b0 100644 --- a/cas/1.2/vts/functional/Android.bp +++ b/cas/1.2/vts/functional/Android.bp @@ -31,6 +31,8 @@ cc_test { shared_libs: [ "libbinder", ], - test_suites: ["general-tests"], + test_suites: [ + "general-tests", + "vts-core", + ], } - diff --git a/cas/1.2/vts/functional/VtsHalCasV1_2TargetTest.cpp b/cas/1.2/vts/functional/VtsHalCasV1_2TargetTest.cpp index 8439ceb6fa..58e0f2e0a8 100644 --- a/cas/1.2/vts/functional/VtsHalCasV1_2TargetTest.cpp +++ b/cas/1.2/vts/functional/VtsHalCasV1_2TargetTest.cpp @@ -16,8 +16,6 @@ #define LOG_TAG "mediacas_hidl_hal_test" -#include -#include #include #include #include @@ -28,8 +26,11 @@ #include #include #include +#include +#include #include #include +#include #include #include #include @@ -293,27 +294,14 @@ void MediaCasListener::testStatusUpdate(sp& mediaCas, std::vector EXPECT_EQ(mEventArg, static_cast(mode)); } -// 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(); } -}; - -class MediaCasHidlTest : public ::testing::VtsHalHidlTargetTestBase { +class MediaCasHidlTest : public testing::TestWithParam { public: virtual void SetUp() override { - mService = ::testing::VtsHalHidlTargetTestBase::getService( - CasHidlEnvironment::Instance()->getServiceName()); + mService = IMediaCasService::getService(GetParam()); ASSERT_NE(mService, nullptr); } - sp mService; + sp mService = nullptr; protected: static void description(const std::string& description) { @@ -497,7 +485,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)); @@ -609,11 +597,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/common/aidl/Android.bp b/common/aidl/Android.bp index f55e799d0d..9ea4cdf5c1 100644 --- a/common/aidl/Android.bp +++ b/common/aidl/Android.bp @@ -17,5 +17,13 @@ aidl_interface { cpp: { enabled: false, }, + ndk: { + apex_available: [ + "//apex_available:platform", + "com.android.media.swcodec", + ], + min_sdk_version: "29", + }, }, + versions: ["1"], } diff --git a/common/aidl/aidl_api/android.hardware.common/1/.hash b/common/aidl/aidl_api/android.hardware.common/1/.hash new file mode 100644 index 0000000000..ad5102ae35 --- /dev/null +++ b/common/aidl/aidl_api/android.hardware.common/1/.hash @@ -0,0 +1 @@ +59e782d6ed4c2aed3744d37fb751ee23797835dd diff --git a/common/aidl/aidl_api/android.hardware.common/1/android/hardware/common/NativeHandle.aidl b/common/aidl/aidl_api/android.hardware.common/1/android/hardware/common/NativeHandle.aidl new file mode 100644 index 0000000000..f37b7d506f --- /dev/null +++ b/common/aidl/aidl_api/android.hardware.common/1/android/hardware/common/NativeHandle.aidl @@ -0,0 +1,23 @@ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL interface (or parcelable). Do not try to +// edit this file. It looks like you are doing that because you have modified +// an AIDL interface in a backward-incompatible way, e.g., deleting a function +// from an interface or a field from a parcelable and it broke the build. That +// breakage is intended. +// +// You must not make a backward incompatible changes to the AIDL files built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.common; +@VintfStability +parcelable NativeHandle { + ParcelFileDescriptor[] fds; + int[] ints; +} diff --git a/compatibility_matrices/compatibility_matrix.5.xml b/compatibility_matrices/compatibility_matrix.5.xml index cc4f0cd29c..e772b6fe1d 100644 --- a/compatibility_matrices/compatibility_matrix.5.xml +++ b/compatibility_matrices/compatibility_matrix.5.xml @@ -34,17 +34,46 @@ android.hardware.automotive.audiocontrol 1.0 + 2.0 IAudioControl default - android.hardware.automotive.evs + android.hardware.automotive.can 1.0 + + ICanBus + .* + + + ICanController + .* + + + + android.hardware.automotive.evs + 1.0-1 IEvsEnumerator default + [a-z]+/[0-9]+ + + + + android.hardware.automotive.occupant_awareness + + IOccupantAwareness + default + + + + android.hardware.automotive.sv + 1.0 + + ISurroundViewService + default @@ -57,7 +86,7 @@ android.hardware.biometrics.face - 1.0 + 1.0-1 IBiometricsFace default @@ -65,7 +94,7 @@ android.hardware.biometrics.fingerprint - 2.1 + 2.1-2 IBiometricsFingerprint default @@ -113,7 +142,7 @@ android.hardware.camera.provider - 2.4-5 + 2.4-6 ICameraProvider [^/]+/[0-9]+ @@ -121,20 +150,12 @@ android.hardware.cas - 1.1 + 1.1-2 IMediaCasService default - - android.hardware.configstore - 1.1 - - ISurfaceFlingerConfigs - default - - android.hardware.confirmationui 1.0 @@ -145,7 +166,7 @@ android.hardware.contexthub - 1.0 + 1.0-1 IContexthub default @@ -153,7 +174,7 @@ android.hardware.drm - 1.0-2 + 1.3 ICryptoFactory .* @@ -181,7 +202,7 @@ android.hardware.gnss - 2.0 + 2.0-1 IGnss default @@ -189,8 +210,10 @@ android.hardware.graphics.allocator + 2.0 3.0 + 4.0 IAllocator default @@ -198,7 +221,7 @@ android.hardware.graphics.composer - 2.1-3 + 2.1-4 IComposer default @@ -206,8 +229,10 @@ android.hardware.graphics.mapper + 2.1 3.0 + 4.0 IMapper default @@ -269,14 +294,6 @@ strongbox - - android.hardware.light - 2.0 - - ILight - default - - android.hardware.light @@ -286,7 +303,7 @@ android.hardware.media.c2 - 1.0 + 1.0-1 IComponentStore default[0-9]* @@ -354,6 +371,7 @@ android.hardware.radio + 1.4 1.5 IRadio @@ -389,6 +407,13 @@ default + + android.hardware.rebootescrow + + IRebootEscrow + default + + android.hardware.secure_element 1.0-2 @@ -401,7 +426,7 @@ android.hardware.sensors 1.0 - 2.0 + 2.0-1 ISensors default @@ -409,7 +434,7 @@ android.hardware.soundtrigger - 2.0-2 + 2.0-3 ISoundTriggerHw default @@ -455,6 +480,14 @@ default + + android.hardware.tv.tuner + 1.0 + + ITuner + default + + android.hardware.usb 1.0-2 @@ -465,7 +498,7 @@ android.hardware.usb.gadget - 1.0 + 1.0-1 IUsbGadget default @@ -496,7 +529,7 @@ android.hardware.wifi - 1.0-3 + 1.0-4 IWifi default @@ -504,7 +537,7 @@ android.hardware.wifi.hostapd - 1.0-1 + 1.0-2 IHostapd default @@ -512,7 +545,7 @@ android.hardware.wifi.supplicant - 1.0-2 + 1.0-3 ISupplicant default diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml index e86b8ac612..46cdf990f9 100644 --- a/compatibility_matrices/compatibility_matrix.current.xml +++ b/compatibility_matrices/compatibility_matrix.current.xml @@ -34,17 +34,46 @@ android.hardware.automotive.audiocontrol 1.0 + 2.0 IAudioControl default - android.hardware.automotive.evs + android.hardware.automotive.can 1.0 + + ICanBus + .* + + + ICanController + .* + + + + android.hardware.automotive.evs + 1.0-1 IEvsEnumerator default + [a-z]+/[0-9]+ + + + + android.hardware.automotive.occupant_awareness + + IOccupantAwareness + default + + + + android.hardware.automotive.sv + 1.0 + + ISurroundView + default @@ -57,7 +86,7 @@ android.hardware.biometrics.face - 1.0 + 1.0-1 IBiometricsFace default @@ -65,7 +94,7 @@ android.hardware.biometrics.fingerprint - 2.1 + 2.1-2 IBiometricsFingerprint default @@ -113,7 +142,7 @@ android.hardware.camera.provider - 2.4-5 + 2.4-6 ICameraProvider [^/]+/[0-9]+ @@ -121,7 +150,7 @@ android.hardware.cas - 1.1 + 1.1-2 IMediaCasService default @@ -137,7 +166,7 @@ android.hardware.contexthub - 1.0 + 1.0-1 IContexthub default @@ -145,7 +174,7 @@ android.hardware.drm - 1.0-2 + 1.3 ICryptoFactory .* @@ -173,7 +202,7 @@ android.hardware.gnss - 2.0 + 2.0-1 IGnss default @@ -181,8 +210,10 @@ android.hardware.graphics.allocator + 2.0 3.0 + 4.0 IAllocator default @@ -190,7 +221,7 @@ android.hardware.graphics.composer - 2.1-3 + 2.1-4 IComposer default @@ -198,8 +229,10 @@ android.hardware.graphics.mapper + 2.1 3.0 + 4.0 IMapper default @@ -261,14 +294,6 @@ strongbox - - android.hardware.light - 2.0 - - ILight - default - - android.hardware.light @@ -278,7 +303,7 @@ android.hardware.media.c2 - 1.0 + 1.0-1 IComponentStore default[0-9]* @@ -381,6 +406,13 @@ default + + android.hardware.rebootescrow + + IRebootEscrow + default + + android.hardware.secure_element 1.0-2 @@ -393,7 +425,7 @@ android.hardware.sensors 1.0 - 2.0 + 2.0-1 ISensors default @@ -401,7 +433,7 @@ android.hardware.soundtrigger - 2.0-2 + 2.0-3 ISoundTriggerHw default @@ -447,6 +479,14 @@ default + + android.hardware.tv.tuner + 1.0 + + ITuner + default + + android.hardware.usb 1.0-2 @@ -457,7 +497,7 @@ android.hardware.usb.gadget - 1.0 + 1.0-1 IUsbGadget default @@ -488,7 +528,7 @@ android.hardware.wifi - 1.0-3 + 1.0-4 IWifi default @@ -496,7 +536,7 @@ android.hardware.wifi.hostapd - 1.0-1 + 1.0-2 IHostapd default @@ -504,7 +544,7 @@ android.hardware.wifi.supplicant - 1.0-2 + 1.0-3 ISupplicant default diff --git a/contexthub/1.0/default/android.hardware.contexthub@1.0-service.rc b/contexthub/1.0/default/android.hardware.contexthub@1.0-service.rc index b659be8098..fc2893f071 100644 --- a/contexthub/1.0/default/android.hardware.contexthub@1.0-service.rc +++ b/contexthub/1.0/default/android.hardware.contexthub@1.0-service.rc @@ -1,5 +1,5 @@ service vendor.contexthub-hal-1-0 /vendor/bin/hw/android.hardware.contexthub@1.0-service interface android.hardware.contexthub@1.0::IContexthub default class hal - user system - group system + user context_hub + group context_hub diff --git a/contexthub/1.0/vts/functional/Android.bp b/contexthub/1.0/vts/functional/Android.bp index 8fa4fa574e..091f2dc201 100644 --- a/contexthub/1.0/vts/functional/Android.bp +++ b/contexthub/1.0/vts/functional/Android.bp @@ -18,7 +18,10 @@ cc_test { name: "VtsHalContexthubV1_0TargetTest", defaults: ["VtsHalTargetTestDefaults"], srcs: ["VtsHalContexthubV1_0TargetTest.cpp"], - static_libs: ["android.hardware.contexthub@1.0"], + static_libs: [ + "android.hardware.contexthub@1.0", + "VtsHalContexthubUtils", + ], test_suites: [ "general-tests", "vts", diff --git a/contexthub/1.0/vts/functional/VtsHalContexthubV1_0TargetTest.cpp b/contexthub/1.0/vts/functional/VtsHalContexthubV1_0TargetTest.cpp index a1d173bc32..ada232b9fc 100644 --- a/contexthub/1.0/vts/functional/VtsHalContexthubV1_0TargetTest.cpp +++ b/contexthub/1.0/vts/functional/VtsHalContexthubV1_0TargetTest.cpp @@ -16,6 +16,10 @@ #define LOG_TAG "contexthub_hidl_hal_test" +#include "ContexthubCallbackBase.h" +#include "ContexthubHidlTestBase.h" +#include "VtsHalContexthubUtils.h" + #include #include #include @@ -23,17 +27,17 @@ #include #include #include -#include #include #include #include #include -using ::android::hardware::Return; -using ::android::hardware::Void; +using ::android::sp; using ::android::hardware::hidl_string; using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; using ::android::hardware::contexthub::V1_0::AsyncEventType; using ::android::hardware::contexthub::V1_0::ContextHub; using ::android::hardware::contexthub::V1_0::ContextHubMsg; @@ -43,10 +47,11 @@ using ::android::hardware::contexthub::V1_0::IContexthubCallback; using ::android::hardware::contexthub::V1_0::NanoAppBinary; using ::android::hardware::contexthub::V1_0::Result; using ::android::hardware::contexthub::V1_0::TransactionResult; -using ::android::sp; - -#define ASSERT_OK(result) ASSERT_EQ(result, Result::OK) -#define EXPECT_OK(result) EXPECT_EQ(result, Result::OK) +using ::android::hardware::contexthub::vts_utils::asBaseType; +using ::android::hardware::contexthub::vts_utils::ContexthubCallbackBase; +using ::android::hardware::contexthub::vts_utils::ContexthubHidlTestBase; +using ::android::hardware::contexthub::vts_utils::getHalAndHubIdList; +using ::android::hardware::contexthub::vts_utils::getHubsSync; namespace { @@ -54,132 +59,37 @@ namespace { // app ID is reserved and must never appear in the list of loaded apps. constexpr uint64_t kNonExistentAppId = 0x476f6f6754555555; -// Helper that does explicit conversion of an enum class to its underlying/base -// type. Useful for stream output of enum values. -template -constexpr typename std::underlying_type::type asBaseType( - EnumType value) { - return static_cast::type>(value); -} +const std::vector> kTestParameters = + getHalAndHubIdList(); -// Synchronously queries IContexthub::getHubs() and returns the result -hidl_vec getHubsSync(sp hubApi) { - hidl_vec hubList; - std::promise barrier; - - hubApi->getHubs([&hubList, &barrier](const hidl_vec& hubs) { - hubList = hubs; - barrier.set_value(); - }); - barrier.get_future().wait_for(std::chrono::seconds(1)); - - return hubList; -} - -// Gets a list of valid hub IDs in the system -std::vector getHubIds(const std::string& service_name) { - std::vector hubIds; - - sp hubApi = IContexthub::getService(service_name); - - if (hubApi != nullptr) { - for (const ContextHub& hub : getHubsSync(hubApi)) { - hubIds.push_back(std::to_string(hub.hubId)); - } - } - - ALOGD("Running tests against all %zu reported hubs for service %s", hubIds.size(), - service_name.c_str()); - return hubIds; -} - -// Test fixture parameterized by hub ID, initializes the HAL and makes the context hub API handle -// available. -class ContexthubHidlTest : public ::testing::TestWithParam> { - public: - virtual void SetUp() override { - hubApi = IContexthub::getService(std::get<0>(GetParam())); - ASSERT_NE(hubApi, nullptr); - - // getHubs() must be called at least once for proper initialization of the - // HAL implementation - getHubsSync(hubApi); - } - - uint32_t getHubId() { return std::stoi(std::get<1>(GetParam())); } - - Result registerCallback(sp cb) { - Result result = hubApi->registerCallback(getHubId(), cb); - ALOGD("Registered callback, result %" PRIu32, result); - return result; - } - - sp hubApi; -}; - -// Base callback implementation that just logs all callbacks by default -class ContexthubCallbackBase : public IContexthubCallback { - public: - virtual Return handleClientMsg(const ContextHubMsg& /*msg*/) override { - ALOGD("Got client message callback"); - return Void(); - } - - virtual Return handleTxnResult( - uint32_t txnId, TransactionResult result) override { - ALOGD("Got transaction result callback for txnId %" PRIu32 " with result %" - PRId32, txnId, result); - return Void(); - } - - virtual Return handleHubEvent(AsyncEventType evt) override { - ALOGD("Got hub event callback for event type %" PRIu32, evt); - return Void(); - } - - virtual Return handleAppAbort(uint64_t appId, uint32_t abortCode) - override { - ALOGD("Got app abort notification for appId 0x%" PRIx64 " with abort code " - "0x%" PRIx32, appId, abortCode); - return Void(); - } - - virtual Return handleAppsInfo(const hidl_vec& /*appInfo*/) - override { - ALOGD("Got app info callback"); - return Void(); - } -}; +class ContexthubHidlTest : public ContexthubHidlTestBase {}; // Wait for a callback to occur (signaled by the given future) up to the // provided timeout. If the future is invalid or the callback does not come // within the given time, returns false. -template -bool waitForCallback( - std::future future, - ReturnType *result, - std::chrono::milliseconds timeout = std::chrono::seconds(5)) { - auto expiration = std::chrono::system_clock::now() + timeout; +template +bool waitForCallback(std::future future, ReturnType* result, + std::chrono::milliseconds timeout = std::chrono::seconds(5)) { + auto expiration = std::chrono::system_clock::now() + timeout; - EXPECT_NE(result, nullptr); - EXPECT_TRUE(future.valid()); - if (result != nullptr && future.valid()) { - std::future_status status = future.wait_until(expiration); - EXPECT_NE(status, std::future_status::timeout) - << "Timed out waiting for callback"; + EXPECT_NE(result, nullptr); + EXPECT_TRUE(future.valid()); + if (result != nullptr && future.valid()) { + std::future_status status = future.wait_until(expiration); + EXPECT_NE(status, std::future_status::timeout) << "Timed out waiting for callback"; - if (status == std::future_status::ready) { - *result = future.get(); - return true; + if (status == std::future_status::ready) { + *result = future.get(); + return true; + } } - } - return false; + return false; } // Ensures that the metadata reported in getHubs() is sane TEST_P(ContexthubHidlTest, TestGetHubs) { - hidl_vec hubs = getHubsSync(hubApi); + hidl_vec hubs = getHubsSync(hubApi.get()); ALOGD("System reports %zu hubs", hubs.size()); for (const ContextHub& hub : hubs) { @@ -199,189 +109,158 @@ TEST_P(ContexthubHidlTest, TestGetHubs) { } TEST_P(ContexthubHidlTest, TestRegisterCallback) { - ALOGD("TestRegisterCallback called, hubId %" PRIu32, getHubId()); - ASSERT_OK(registerCallback(new ContexthubCallbackBase())); + ALOGD("TestRegisterCallback called, hubId %" PRIu32, getHubId()); + ASSERT_OK(registerCallback(new ContexthubCallbackBase())); } TEST_P(ContexthubHidlTest, TestRegisterNullCallback) { - ALOGD("TestRegisterNullCallback called, hubId %" PRIu32, getHubId()); - ASSERT_OK(registerCallback(nullptr)); + ALOGD("TestRegisterNullCallback called, hubId %" PRIu32, getHubId()); + ASSERT_OK(registerCallback(nullptr)); } // Helper callback that puts the async appInfo callback data into a promise class QueryAppsCallback : public ContexthubCallbackBase { - public: - virtual Return handleAppsInfo(const hidl_vec& appInfo) - override { - ALOGD("Got app info callback with %zu apps", appInfo.size()); - promise.set_value(appInfo); - return Void(); - } + public: + virtual Return handleAppsInfo(const hidl_vec& appInfo) override { + ALOGD("Got app info callback with %zu apps", appInfo.size()); + promise.set_value(appInfo); + return Void(); + } - std::promise> promise; + std::promise> promise; }; // Calls queryApps() and checks the returned metadata TEST_P(ContexthubHidlTest, TestQueryApps) { - ALOGD("TestQueryApps called, hubId %u", getHubId()); - sp cb = new QueryAppsCallback(); - ASSERT_OK(registerCallback(cb)); + ALOGD("TestQueryApps called, hubId %u", getHubId()); + sp cb = new QueryAppsCallback(); + ASSERT_OK(registerCallback(cb)); - Result result = hubApi->queryApps(getHubId()); - ASSERT_OK(result); + Result result = hubApi->queryApps(getHubId()); + ASSERT_OK(result); - ALOGD("Waiting for app info callback"); - hidl_vec appList; - ASSERT_TRUE(waitForCallback(cb->promise.get_future(), &appList)); - for (const HubAppInfo &appInfo : appList) { - EXPECT_NE(appInfo.appId, UINT64_C(0)); - EXPECT_NE(appInfo.appId, kNonExistentAppId); - } + ALOGD("Waiting for app info callback"); + hidl_vec appList; + ASSERT_TRUE(waitForCallback(cb->promise.get_future(), &appList)); + for (const HubAppInfo& appInfo : appList) { + EXPECT_NE(appInfo.appId, UINT64_C(0)); + EXPECT_NE(appInfo.appId, kNonExistentAppId); + } } // Helper callback that puts the TransactionResult for the expectedTxnId into a // promise class TxnResultCallback : public ContexthubCallbackBase { - public: - virtual Return handleTxnResult( - uint32_t txnId, TransactionResult result) override { - ALOGD("Got transaction result callback for txnId %" PRIu32 " (expecting %" - PRIu32 ") with result %" PRId32, txnId, expectedTxnId, result); - if (txnId == expectedTxnId) { - promise.set_value(result); + public: + virtual Return handleTxnResult(uint32_t txnId, TransactionResult result) override { + ALOGD("Got transaction result callback for txnId %" PRIu32 " (expecting %" PRIu32 + ") with result %" PRId32, + txnId, expectedTxnId, result); + if (txnId == expectedTxnId) { + promise.set_value(result); + } + return Void(); } - return Void(); - } - uint32_t expectedTxnId = 0; - std::promise promise; + uint32_t expectedTxnId = 0; + std::promise promise; }; // Parameterized fixture that sets the callback to TxnResultCallback class ContexthubTxnTest : public ContexthubHidlTest { - public: - virtual void SetUp() override { - ContexthubHidlTest::SetUp(); - ASSERT_OK(registerCallback(cb)); - } + public: + virtual void SetUp() override { + ContexthubHidlTest::SetUp(); + ASSERT_OK(registerCallback(cb)); + } - sp cb = new TxnResultCallback(); + sp cb = new TxnResultCallback(); }; - // Checks cases where the hub implementation is expected to return an error, but // that error can be returned either synchronously or in the asynchronous // transaction callback. Returns an AssertionResult that can be used in // ASSERT/EXPECT_TRUE. Allows checking the sync result against 1 additional // allowed error code apart from OK and TRANSACTION_FAILED, which are always // allowed. -::testing::AssertionResult checkFailureSyncOrAsync( - Result result, Result allowedSyncResult, - std::future&& future) { - if (result == Result::OK) { - // No error reported synchronously - this is OK, but then we should get an - // async callback with a failure status - TransactionResult asyncResult; - if (!waitForCallback(std::forward>(future), - &asyncResult)) { - return ::testing::AssertionFailure() - << "Got successful sync result, then failed to receive async cb"; - } else if (asyncResult == TransactionResult::SUCCESS) { - return ::testing::AssertionFailure() - << "Got successful sync result, then unexpected successful async " - "result"; +::testing::AssertionResult checkFailureSyncOrAsync(Result result, Result allowedSyncResult, + std::future&& future) { + if (result == Result::OK) { + // No error reported synchronously - this is OK, but then we should get an + // async callback with a failure status + TransactionResult asyncResult; + if (!waitForCallback(std::forward>(future), &asyncResult)) { + return ::testing::AssertionFailure() + << "Got successful sync result, then failed to receive async cb"; + } else if (asyncResult == TransactionResult::SUCCESS) { + return ::testing::AssertionFailure() + << "Got successful sync result, then unexpected successful async " + "result"; + } + } else if (result != allowedSyncResult && result != Result::TRANSACTION_FAILED) { + return ::testing::AssertionFailure() + << "Got sync result " << asBaseType(result) << ", expected TRANSACTION_FAILED or " + << asBaseType(allowedSyncResult); } - } else if (result != allowedSyncResult && - result != Result::TRANSACTION_FAILED) { - return ::testing::AssertionFailure() << "Got sync result " - << asBaseType(result) << ", expected TRANSACTION_FAILED or " - << asBaseType(allowedSyncResult); - } - return ::testing::AssertionSuccess(); + return ::testing::AssertionSuccess(); } TEST_P(ContexthubTxnTest, TestSendMessageToNonExistentNanoApp) { - ContextHubMsg msg; - msg.appName = kNonExistentAppId; - msg.msgType = 1; - msg.msg.resize(4); - std::fill(msg.msg.begin(), msg.msg.end(), 0); + ContextHubMsg msg; + msg.appName = kNonExistentAppId; + msg.msgType = 1; + msg.msg.resize(4); + std::fill(msg.msg.begin(), msg.msg.end(), 0); - ALOGD("Sending message to non-existent nanoapp"); - Result result = hubApi->sendMessageToHub(getHubId(), msg); - if (result != Result::OK && - result != Result::BAD_PARAMS && - result != Result::TRANSACTION_FAILED) { - FAIL() << "Got result " << asBaseType(result) << ", expected OK, BAD_PARAMS" - << ", or TRANSACTION_FAILED"; - } + ALOGD("Sending message to non-existent nanoapp"); + Result result = hubApi->sendMessageToHub(getHubId(), msg); + if (result != Result::OK && result != Result::BAD_PARAMS && + result != Result::TRANSACTION_FAILED) { + FAIL() << "Got result " << asBaseType(result) << ", expected OK, BAD_PARAMS" + << ", or TRANSACTION_FAILED"; + } } TEST_P(ContexthubTxnTest, TestLoadEmptyNanoApp) { - cb->expectedTxnId = 0123; - NanoAppBinary emptyApp; + cb->expectedTxnId = 0123; + NanoAppBinary emptyApp; - emptyApp.appId = kNonExistentAppId; - emptyApp.appVersion = 1; - emptyApp.flags = 0; - emptyApp.targetChreApiMajorVersion = 1; - emptyApp.targetChreApiMinorVersion = 0; + emptyApp.appId = kNonExistentAppId; + emptyApp.appVersion = 1; + emptyApp.flags = 0; + emptyApp.targetChreApiMajorVersion = 1; + emptyApp.targetChreApiMinorVersion = 0; - ALOGD("Loading empty nanoapp"); - Result result = hubApi->loadNanoApp(getHubId(), emptyApp, cb->expectedTxnId); - EXPECT_TRUE(checkFailureSyncOrAsync(result, Result::BAD_PARAMS, - cb->promise.get_future())); + ALOGD("Loading empty nanoapp"); + Result result = hubApi->loadNanoApp(getHubId(), emptyApp, cb->expectedTxnId); + EXPECT_TRUE(checkFailureSyncOrAsync(result, Result::BAD_PARAMS, cb->promise.get_future())); } TEST_P(ContexthubTxnTest, TestUnloadNonexistentNanoApp) { - cb->expectedTxnId = 1234; + cb->expectedTxnId = 1234; - ALOGD("Unloading nonexistent nanoapp"); - Result result = hubApi->unloadNanoApp(getHubId(), kNonExistentAppId, - cb->expectedTxnId); - EXPECT_TRUE(checkFailureSyncOrAsync(result, Result::BAD_PARAMS, - cb->promise.get_future())); + ALOGD("Unloading nonexistent nanoapp"); + Result result = hubApi->unloadNanoApp(getHubId(), kNonExistentAppId, cb->expectedTxnId); + EXPECT_TRUE(checkFailureSyncOrAsync(result, Result::BAD_PARAMS, cb->promise.get_future())); } TEST_P(ContexthubTxnTest, TestEnableNonexistentNanoApp) { - cb->expectedTxnId = 2345; + cb->expectedTxnId = 2345; - ALOGD("Enabling nonexistent nanoapp"); - Result result = hubApi->enableNanoApp(getHubId(), kNonExistentAppId, - cb->expectedTxnId); - EXPECT_TRUE(checkFailureSyncOrAsync(result, Result::BAD_PARAMS, - cb->promise.get_future())); + ALOGD("Enabling nonexistent nanoapp"); + Result result = hubApi->enableNanoApp(getHubId(), kNonExistentAppId, cb->expectedTxnId); + EXPECT_TRUE(checkFailureSyncOrAsync(result, Result::BAD_PARAMS, cb->promise.get_future())); } TEST_P(ContexthubTxnTest, TestDisableNonexistentNanoApp) { - cb->expectedTxnId = 3456; + cb->expectedTxnId = 3456; - ALOGD("Disabling nonexistent nanoapp"); - Result result = hubApi->disableNanoApp(getHubId(), kNonExistentAppId, - cb->expectedTxnId); - EXPECT_TRUE(checkFailureSyncOrAsync(result, Result::BAD_PARAMS, - cb->promise.get_future())); + ALOGD("Disabling nonexistent nanoapp"); + Result result = hubApi->disableNanoApp(getHubId(), kNonExistentAppId, cb->expectedTxnId); + EXPECT_TRUE(checkFailureSyncOrAsync(result, Result::BAD_PARAMS, cb->promise.get_future())); } -// Return the test parameters of a vecter of tuples for all IContexthub services and each of its hub -// id: -static std::vector> get_parameters() { - std::vector> parameters; - std::vector service_names = - android::hardware::getAllHalInstanceNames(IContexthub::descriptor); - for (const std::string& service_name : service_names) { - std::vector ids = getHubIds(service_name); - for (const std::string& id : ids) { - parameters.push_back(std::make_tuple(service_name, id)); - } - } - - return parameters; -} - -static std::vector> kTestParameters = get_parameters(); - INSTANTIATE_TEST_SUITE_P(HubIdSpecificTests, ContexthubHidlTest, testing::ValuesIn(kTestParameters), android::hardware::PrintInstanceTupleNameToString<>); diff --git a/contexthub/1.1/Android.bp b/contexthub/1.1/Android.bp new file mode 100644 index 0000000000..9ee8e765e5 --- /dev/null +++ b/contexthub/1.1/Android.bp @@ -0,0 +1,15 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.contexthub@1.1", + root: "android.hardware", + srcs: [ + "types.hal", + "IContexthub.hal", + ], + interfaces: [ + "android.hardware.contexthub@1.0", + "android.hidl.base@1.0", + ], + gen_java: true, +} diff --git a/contexthub/1.1/IContexthub.hal b/contexthub/1.1/IContexthub.hal new file mode 100644 index 0000000000..a3b4bd4640 --- /dev/null +++ b/contexthub/1.1/IContexthub.hal @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 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.contexthub@1.1; + +import @1.0::IContexthub; +import @1.0::Result; + +interface IContexthub extends @1.0::IContexthub { + /** + * Notification sent by the framework to indicate that the user + * has changed a setting. + * + * @param setting User setting that has been modified. + * @param newValue The update value of the user setting. + */ + onSettingChanged(Setting setting, SettingValue newValue); +}; \ No newline at end of file diff --git a/contexthub/1.1/default/Android.bp b/contexthub/1.1/default/Android.bp new file mode 100644 index 0000000000..86858c0377 --- /dev/null +++ b/contexthub/1.1/default/Android.bp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 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_binary { + name: "android.hardware.contexthub@1.1-service.mock", + defaults: ["hidl_defaults"], + vendor: true, + relative_install_path: "hw", + init_rc: ["android.hardware.contexthub@1.1-service.rc"], + srcs: [ + "Contexthub.cpp", + "service.cpp", + ], + cflags: [ + "-Wall", + "-Werror", + ], + shared_libs: [ + "android.hardware.contexthub@1.0", + "android.hardware.contexthub@1.1", + "libbase", + "libcutils", + "libhardware", + "libhidlbase", + "liblog", + "libutils", + ], + vintf_fragments: ["android.hardware.contexthub@1.1.xml"], +} diff --git a/contexthub/1.1/default/Contexthub.cpp b/contexthub/1.1/default/Contexthub.cpp new file mode 100644 index 0000000000..19cc2628d7 --- /dev/null +++ b/contexthub/1.1/default/Contexthub.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2020 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 "Contexthub.h" + +#include + +namespace android { +namespace hardware { +namespace contexthub { +namespace V1_1 { +namespace implementation { + +using ::android::hardware::contexthub::V1_0::ContextHub; +using ::android::hardware::contexthub::V1_0::HubAppInfo; +using ::android::hardware::contexthub::V1_0::Result; + +namespace { + +constexpr uint32_t kMockHubId = 0; + +} // anonymous namespace + +Return Contexthub::getHubs(getHubs_cb _hidl_cb) { + ContextHub hub = {}; + hub.name = "Mock Context Hub"; + hub.vendor = "AOSP"; + hub.toolchain = "n/a"; + hub.platformVersion = 1; + hub.toolchainVersion = 1; + hub.hubId = kMockHubId; + hub.peakMips = 1; + hub.peakPowerDrawMw = 1; + hub.maxSupportedMsgLen = 4096; + hub.chrePlatformId = UINT64_C(0x476f6f6754000000); + hub.chreApiMajorVersion = 1; + hub.chreApiMinorVersion = 4; + + // Report a single mock hub + std::vector hubs; + hubs.push_back(hub); + + _hidl_cb(hubs); + return Void(); +} + +Return Contexthub::registerCallback(uint32_t hubId, const sp& cb) { + if (hubId == kMockHubId) { + mCallback = cb; + return Result::OK; + } + return Result::BAD_PARAMS; +} + +// We don't expose any nanoapps, therefore all nanoapp-related API calls return with BAD_PARAMS +Return Contexthub::sendMessageToHub(uint32_t /*hubId*/, const ContextHubMsg& /*msg*/) { + return Result::BAD_PARAMS; +} + +Return Contexthub::loadNanoApp(uint32_t /*hubId*/, const NanoAppBinary& /*appBinary*/, + uint32_t /*transactionId*/) { + return Result::BAD_PARAMS; +} + +Return Contexthub::unloadNanoApp(uint32_t /*hubId*/, uint64_t /*appId*/, + uint32_t /*transactionId*/) { + return Result::BAD_PARAMS; +} + +Return Contexthub::enableNanoApp(uint32_t /*hubId*/, uint64_t /*appId*/, + uint32_t /*transactionId*/) { + return Result::BAD_PARAMS; +} + +Return Contexthub::disableNanoApp(uint32_t /*hubId*/, uint64_t /*appId*/, + uint32_t /*transactionId*/) { + return Result::BAD_PARAMS; +} + +Return Contexthub::queryApps(uint32_t hubId) { + if (hubId == kMockHubId && mCallback != nullptr) { + std::vector nanoapps; + mCallback->handleAppsInfo(nanoapps); + return Result::OK; + } + return Result::BAD_PARAMS; +} + +Return Contexthub::onSettingChanged(Setting /*setting*/, SettingValue /*newValue*/) { + return Void(); +} + +} // namespace implementation +} // namespace V1_1 +} // namespace contexthub +} // namespace hardware +} // namespace android diff --git a/contexthub/1.1/default/Contexthub.h b/contexthub/1.1/default/Contexthub.h new file mode 100644 index 0000000000..0da61d1464 --- /dev/null +++ b/contexthub/1.1/default/Contexthub.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 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 + +namespace android { +namespace hardware { +namespace contexthub { +namespace V1_1 { +namespace implementation { + +class Contexthub : public V1_1::IContexthub { + using ContextHubMsg = ::android::hardware::contexthub::V1_0::ContextHubMsg; + using IContexthubCallback = ::android::hardware::contexthub::V1_0::IContexthubCallback; + using NanoAppBinary = ::android::hardware::contexthub::V1_0::NanoAppBinary; + using Result = ::android::hardware::contexthub::V1_0::Result; + + public: + // Methods from V1_0::IContexthub + Return getHubs(getHubs_cb _hidl_cb) override; + Return registerCallback(uint32_t hubId, + const ::android::sp& cb) override; + Return sendMessageToHub(uint32_t hubId, const ContextHubMsg& msg) override; + Return loadNanoApp(uint32_t hubId, const NanoAppBinary& appBinary, + uint32_t transactionId) override; + Return unloadNanoApp(uint32_t hubId, uint64_t appId, uint32_t transactionId) override; + Return enableNanoApp(uint32_t hubId, uint64_t appId, uint32_t transactionId) override; + Return disableNanoApp(uint32_t hubId, uint64_t appId, uint32_t transactionId) override; + Return queryApps(uint32_t hubId) override; + + // Methods from V1_1::IContexthub + Return onSettingChanged(Setting setting, SettingValue newValue) override; + + private: + sp mCallback; +}; + +} // namespace implementation +} // namespace V1_1 +} // namespace contexthub +} // namespace hardware +} // namespace android diff --git a/contexthub/1.1/default/OWNERS b/contexthub/1.1/default/OWNERS new file mode 100644 index 0000000000..90c233030e --- /dev/null +++ b/contexthub/1.1/default/OWNERS @@ -0,0 +1,3 @@ +arthuri@google.com +bduddie@google.com +stange@google.com diff --git a/contexthub/1.1/default/android.hardware.contexthub@1.1-service.rc b/contexthub/1.1/default/android.hardware.contexthub@1.1-service.rc new file mode 100644 index 0000000000..b00b1bd142 --- /dev/null +++ b/contexthub/1.1/default/android.hardware.contexthub@1.1-service.rc @@ -0,0 +1,6 @@ +service vendor.contexthub-hal-1-1-mock /vendor/bin/hw/android.hardware.contexthub@1.1-service.mock + interface android.hardware.contexthub@1.0::IContexthub default + interface android.hardware.contexthub@1.1::IContexthub default + class hal + user context_hub + group context_hub diff --git a/contexthub/1.1/default/android.hardware.contexthub@1.1.xml b/contexthub/1.1/default/android.hardware.contexthub@1.1.xml new file mode 100644 index 0000000000..388f7815f8 --- /dev/null +++ b/contexthub/1.1/default/android.hardware.contexthub@1.1.xml @@ -0,0 +1,11 @@ + + + android.hardware.contexthub + hwbinder + 1.1 + + IContexthub + default + + + diff --git a/contexthub/1.1/default/service.cpp b/contexthub/1.1/default/service.cpp new file mode 100644 index 0000000000..c5643f16df --- /dev/null +++ b/contexthub/1.1/default/service.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 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.contexthub@1.1-service" + +#include +#include +#include +#include +#include "Contexthub.h" + +using ::android::hardware::configureRpcThreadpool; +using ::android::hardware::joinRpcThreadpool; +using ::android::hardware::contexthub::V1_1::IContexthub; +using ::android::hardware::contexthub::V1_1::implementation::Contexthub; + +int main() { + configureRpcThreadpool(1, true /* callerWillJoin */); + + ::android::sp contexthub = new Contexthub(); + if (contexthub->registerAsService() != ::android::OK) { + ALOGE("Failed to register Contexthub HAL instance"); + return 1; + } + + joinRpcThreadpool(); + ALOGE("Service exited"); + return 1; +} diff --git a/wifi/hostapd/1.0/vts/functional/VtsHalWifiHostapdV1_0TargetTest.cpp b/contexthub/1.1/types.hal similarity index 63% rename from wifi/hostapd/1.0/vts/functional/VtsHalWifiHostapdV1_0TargetTest.cpp rename to contexthub/1.1/types.hal index 4b62b15699..885cf32d6e 100644 --- a/wifi/hostapd/1.0/vts/functional/VtsHalWifiHostapdV1_0TargetTest.cpp +++ b/contexthub/1.1/types.hal @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2020 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. @@ -14,8 +14,19 @@ * limitations under the License. */ -#include +package android.hardware.contexthub@1.1; -// TODO(b/143892896): Remove this file after wifi_hidl_test_utils.cpp is -// updated. -::testing::VtsHalHidlTargetTestEnvBase* gEnv = nullptr; +/** + * Used to indicate the type of user setting that has changed. + */ +enum Setting : uint8_t { + LOCATION, +}; + +/** + * Used to indicate the value of a user setting. + */ +enum SettingValue : uint8_t { + DISABLED, + ENABLED, +}; \ No newline at end of file diff --git a/contexthub/1.1/vts/functional/Android.bp b/contexthub/1.1/vts/functional/Android.bp new file mode 100644 index 0000000000..f1625a6b6b --- /dev/null +++ b/contexthub/1.1/vts/functional/Android.bp @@ -0,0 +1,30 @@ +// +// Copyright (C) 2020 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: "VtsHalContexthubV1_1TargetTest", + defaults: ["VtsHalTargetTestDefaults"], + srcs: ["VtsHalContexthubV1_1TargetTest.cpp"], + static_libs: [ + "android.hardware.contexthub@1.0", + "android.hardware.contexthub@1.1", + "VtsHalContexthubUtils", + ], + test_suites: [ + "general-tests", + "vts-core", + ], +} diff --git a/contexthub/1.1/vts/functional/OWNERS b/contexthub/1.1/vts/functional/OWNERS new file mode 100644 index 0000000000..161b2f069d --- /dev/null +++ b/contexthub/1.1/vts/functional/OWNERS @@ -0,0 +1,8 @@ +#Context Hub team +arthuri@google.com +bduddie@google.com +stange@google.com + +#VTS team +dshi@google.com +trong@google.com diff --git a/contexthub/1.1/vts/functional/VtsHalContexthubV1_1TargetTest.cpp b/contexthub/1.1/vts/functional/VtsHalContexthubV1_1TargetTest.cpp new file mode 100644 index 0000000000..f2fcdfca48 --- /dev/null +++ b/contexthub/1.1/vts/functional/VtsHalContexthubV1_1TargetTest.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 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 "contexthub_hidl_hal_test" + +#include "ContexthubCallbackBase.h" +#include "ContexthubHidlTestBase.h" +#include "VtsHalContexthubUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using ::android::hardware::contexthub::V1_1::IContexthub; +using ::android::hardware::contexthub::V1_1::Setting; +using ::android::hardware::contexthub::V1_1::SettingValue; +using ::android::hardware::contexthub::vts_utils::ContexthubCallbackBase; +using ::android::hardware::contexthub::vts_utils::ContexthubHidlTestBase; +using ::android::hardware::contexthub::vts_utils::getHalAndHubIdList; + +namespace { + +const std::vector> kTestParameters = + getHalAndHubIdList(); + +class ContexthubHidlTest : public ContexthubHidlTestBase {}; + +TEST_P(ContexthubHidlTest, TestOnSettingChanged) { + // In VTS, we only test that sending the values doesn't cause things to blow up - other test + // suites verify the expected E2E behavior in CHRE + ASSERT_OK(registerCallback(new ContexthubCallbackBase())); + hubApi->onSettingChanged(Setting::LOCATION, SettingValue::DISABLED); + hubApi->onSettingChanged(Setting::LOCATION, SettingValue::ENABLED); + ASSERT_OK(registerCallback(nullptr)); +} + +INSTANTIATE_TEST_SUITE_P(HubIdSpecificTests, ContexthubHidlTest, testing::ValuesIn(kTestParameters), + android::hardware::PrintInstanceTupleNameToString<>); + +} // anonymous namespace diff --git a/contexthub/common/vts/Android.bp b/contexthub/common/vts/Android.bp new file mode 100644 index 0000000000..3d5196a70e --- /dev/null +++ b/contexthub/common/vts/Android.bp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 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_library { + name: "VtsHalContexthubUtils", + srcs: [ + "VtsHalContexthubUtils.cpp", + ], + export_include_dirs: ["."], + shared_libs: [ + "android.hardware.contexthub@1.0", + "libhardware", + "libhidlbase", + "liblog", + "libutils", + ], +} diff --git a/contexthub/common/vts/ContexthubCallbackBase.h b/contexthub/common/vts/ContexthubCallbackBase.h new file mode 100644 index 0000000000..124a11601a --- /dev/null +++ b/contexthub/common/vts/ContexthubCallbackBase.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2020 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 +#include + +#include + +namespace android { +namespace hardware { +namespace contexthub { +namespace vts_utils { + +// Base callback implementation that just logs all callbacks by default, but +// records a failure if +class ContexthubCallbackBase : public V1_0::IContexthubCallback { + public: + virtual Return handleClientMsg(const V1_0::ContextHubMsg& /*msg*/) override { + ALOGD("Got client message callback"); + return Void(); + } + + virtual Return handleTxnResult(uint32_t txnId, V1_0::TransactionResult result) override { + ALOGD("Got transaction result callback for txnId %" PRIu32 " with result %" PRId32, txnId, + result); + return Void(); + } + + virtual Return handleHubEvent(V1_0::AsyncEventType evt) override { + ALOGD("Got hub event callback for event type %" PRIu32, evt); + return Void(); + } + + virtual Return handleAppAbort(uint64_t appId, uint32_t abortCode) override { + ALOGD("Got app abort notification for appId 0x%" PRIx64 " with abort code 0x%" PRIx32, + appId, abortCode); + return Void(); + } + + virtual Return handleAppsInfo(const hidl_vec& /*appInfo*/) override { + ALOGD("Got app info callback"); + return Void(); + } +}; + +} // namespace vts_utils +} // namespace contexthub +} // namespace hardware +} // namespace android diff --git a/contexthub/common/vts/ContexthubHidlTestBase.h b/contexthub/common/vts/ContexthubHidlTestBase.h new file mode 100644 index 0000000000..ee5b7d3cd5 --- /dev/null +++ b/contexthub/common/vts/ContexthubHidlTestBase.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 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 +#include + +#include +#include + +namespace android { +namespace hardware { +namespace contexthub { +namespace vts_utils { + +// Base fixture for Context Hub HAL tests. Parameterized by service name and hub ID. +template +class ContexthubHidlTestBase + : public ::testing::TestWithParam> { + public: + virtual void SetUp() override { fetchHubApi(); } + + void fetchHubApi() { + hubApi = IContexthubVersion::getService(std::get<0>(GetParam())); + ASSERT_NE(hubApi, nullptr); + } + + uint32_t getHubId() { return std::stoi(std::get<1>(GetParam())); } + + V1_0::Result registerCallback(sp cb) { + return hubApi->registerCallback(getHubId(), cb); + } + + sp hubApi; +}; + +} // namespace vts_utils +} // namespace contexthub +} // namespace hardware +} // namespace android diff --git a/contexthub/common/vts/OWNERS b/contexthub/common/vts/OWNERS new file mode 100644 index 0000000000..161b2f069d --- /dev/null +++ b/contexthub/common/vts/OWNERS @@ -0,0 +1,8 @@ +#Context Hub team +arthuri@google.com +bduddie@google.com +stange@google.com + +#VTS team +dshi@google.com +trong@google.com diff --git a/contexthub/common/vts/VtsHalContexthubUtils.cpp b/contexthub/common/vts/VtsHalContexthubUtils.cpp new file mode 100644 index 0000000000..5033b416c9 --- /dev/null +++ b/contexthub/common/vts/VtsHalContexthubUtils.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 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 "VtsHalContexthubUtils.h" + +#include +#include + +namespace android { +namespace hardware { +namespace contexthub { +namespace vts_utils { + +using ::android::hardware::contexthub::V1_0::ContextHub; +using ::android::hardware::contexthub::V1_0::IContexthub; + +// Synchronously queries IContexthub::getHubs() and returns the result +hidl_vec getHubsSync(IContexthub* hubApi) { + hidl_vec hubList; + std::promise barrier; + + hubApi->getHubs([&hubList, &barrier](const hidl_vec& hubs) { + hubList = hubs; + barrier.set_value(); + }); + barrier.get_future().wait_for(std::chrono::seconds(1)); + + return hubList; +} + +} // namespace vts_utils +} // namespace contexthub +} // namespace hardware +} // namespace android diff --git a/contexthub/common/vts/VtsHalContexthubUtils.h b/contexthub/common/vts/VtsHalContexthubUtils.h new file mode 100644 index 0000000000..8f9b6946d9 --- /dev/null +++ b/contexthub/common/vts/VtsHalContexthubUtils.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2020 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 +#include +#include +#include +#include + +#include +#include +#include + +namespace android { +namespace hardware { +namespace contexthub { +namespace vts_utils { + +#define ASSERT_OK(result) ASSERT_EQ(result, ::android::hardware::contexthub::V1_0::Result::OK) +#define EXPECT_OK(result) EXPECT_EQ(result, ::android::hardware::contexthub::V1_0::Result::OK) + +// Helper that does explicit conversion of an enum class to its underlying/base +// type. Useful for stream output of enum values. +template +inline constexpr typename std::underlying_type::type asBaseType(EnumType value) { + return static_cast::type>(value); +} + +// Synchronously queries IContexthub::getHubs() and returns the result +hidl_vec getHubsSync(V1_0::IContexthub* hubApi); + +// Create a vector of tuples that include each IContexthub service paired with each hub ID it +// exposes via getHubs(). Each tuple represents a test target that we should run the VTS suite +// against. +template +static std::vector> getHalAndHubIdList() { + std::vector> parameters; + std::vector serviceNames = + ::android::hardware::getAllHalInstanceNames(IContexthubVersion::descriptor); + for (const std::string& serviceName : serviceNames) { + sp hubApi = IContexthubVersion::getService(serviceName); + if (hubApi != nullptr) { + hidl_vec hubs = getHubsSync(hubApi.get()); + for (const V1_0::ContextHub& hub : hubs) { + parameters.push_back(std::make_tuple(serviceName, std::to_string(hub.hubId))); + } + } + } + + return parameters; +} + +} // namespace vts_utils +} // namespace contexthub +} // namespace hardware +} // namespace android diff --git a/current.txt b/current.txt index dfc2feafa6..594ceb632c 100644 --- a/current.txt +++ b/current.txt @@ -304,7 +304,6 @@ e88e520f8c98a62fccd8d5316c6687808f775de145d1405a7a9a66587ee6a001 android.hardwar fe28829dab10d171783b79ac9cc45412739f8ff275e90228d7c6370ef189b859 android.hardware.audio.effect@4.0::IVisualizerEffect 21c8a702579356480236c6851b5b2c16b9bd369ce12bdd6ffdc4626a89f34f73 android.hardware.audio.effect@4.0::types a0f93c768c353cecee6237fe479bce47404eb10b629fafe07e32a054fd67f2af android.hardware.automotive.audiocontrol@1.0::IAudioControl -ca515ff4b63c80cf5ad7b3395c997c57d6c56157361f6c367d1c96f23cc4860a android.hardware.automotive.audiocontrol@1.0::types f2904a4c108ad1b93eb2fa4e43b82bd01ce1ff26156316e49d1d9fc80dfecaad android.hardware.automotive.evs@1.0::IEvsCamera 94cba6ad04c83aa840de2ed52b74ba2126a26dd960225e61ac36703315279a80 android.hardware.automotive.evs@1.0::IEvsCameraStream 5ea36fb043d9e3b413219de3dfd7b046b48af4fda39f167f3528652e986cb76d android.hardware.automotive.evs@1.0::IEvsDisplay @@ -583,6 +582,23 @@ efbb061c969fa9553d243da6ee23b83fe5d4aa663a7b8896adc52e2b015bc2f3 android.hardwar cfa81f229b69f9011c58f48264fcb552447430fe68610eac514e811e65bc306a android.hardware.wifi.supplicant@1.2::types # ABI preserving changes to HALs during Android R +c3ec182ce325862b7d79e526f3e170c02cfee1497ed309d7c60d0de4ca636b0b android.hardware.automotive.audiocontrol@1.0::IAudioControl +1b6d0927615ddbf4c56a993fa1845bca15543e315fb6f48c77276e2fa2918ac5 android.hardware.automotive.evs@1.0::IEvsCamera +3901859d36b7b4d32910d61cd1e8982b0ffeb8fb77b457ac6349e8bf1abcd595 android.hardware.automotive.evs@1.0::IEvsCameraStream +578f640c653726d58f99c84a7e1bb63862e21ef7cbb4f7d95c3cc62de00dca35 android.hardware.automotive.evs@1.0::IEvsDisplay +f5bc6aa840db933cb9fd36668b06d3e2021cf5384bb70e459f22e2f2f921fba5 android.hardware.automotive.evs@1.0::IEvsEnumerator +d3a344b7bd4c0d2658ae7209f55a979b8f53f361fd00f4fca29d5baa56d11fd2 android.hardware.automotive.evs@1.0::types +2924c3e43858190ee3e2da4c2fb93bba8ae065fe314451f035a7ec52cb80c94a android.hardware.camera.device@3.2::ICameraDeviceCallback # b/155353799 +2410dd02d67786a732d36e80b0f8ccf55086604ef37f9838e2013ff2c571e404 android.hardware.camera.device@3.5::types +cd06a7911b9acd4a653bbf7133888878fbcb3f84be177c7a3f1becaae3d8618f android.hardware.camera.metadata@3.2::types +5cf81b1001296fbb3c5b3d275a859244f61cec5fa858d7be9cca46c5b7dfa733 android.hardware.camera.metadata@3.2::types # b/150331548 +a05277065c28ebecd58118bd240fb8c55757361e8648c01f7c4dacdb7f2a95dc android.hardware.camera.metadata@3.3::types +9cb3df2bde2c6cd5fd96b7c41555420cacd7e276a556c684af91b7461c86460f android.hardware.gnss@1.0::IGnssCallback +dd6cd9dba4fde99a1bc3cb1728d82309f509a6e6e1993e5042dfa5ffe4af5442 android.hardware.gnss@2.0::IGnssMeasurementCallback +af334f1fc85c62b343f84b74d0495eed6f495f7fecedb53463db10c202310058 android.hardware.gnss.measurement_corrections@1.0::types +33a6b20c43af00fdfb305df891bc5911c06d9a9130b912759649932e5a4a6e6d android.hardware.gnss.visibility_control@1.0::IGnssVisibilityControlCallback +bceee81ec1b59324abd05932b5620fda5a6589597c9cb3953ba7f3ea02cccd3e android.hardware.camera.provider@2.4::ICameraProvider +2ce820dc4f3c6d85721b65150ed2157c6e2e2055f866fb6c6ba4790f14408d66 android.hardware.camera.provider@2.4::ICameraProviderCallback b69a7615c508acf5c5201efd1bfa3262167874fc3594e2db5a3ff93addd8ac75 android.hardware.keymaster@4.0::IKeymasterDevice eb2fa0c883c2185d514be0b84c179b283753ef0c1b77b45b4f359bd23bba8b75 android.hardware.neuralnetworks@1.0::IPreparedModel 92e101b30e47bdf526a01c52cecfbe730def5997b8260ab497eb949eb2a6dcdf android.hardware.neuralnetworks@1.0::types @@ -595,18 +611,21 @@ ee1a0dee5be00a6fe2d4d3270068c78016dcb194d768fe07ed894ea20904037f android.hardwar a785a57447a81e9c130eef6904c3a5c256076c6a04588c40620ebd6fa2660d77 android.hardware.radio@1.2::types 1a6e2bd289f22931c526b21916910f1d4c436b7acb9556e4243de4ce8e6cc2e4 android.hardware.soundtrigger@2.0::ISoundTriggerHwCallback fd65298e1e09e0e3c781ab18305920d757dbe55a3b459ce17814ec5cf6dfee99 android.hardware.wifi@1.0::IWifiP2pIface +ff5dd821c2c7a9c78607159c4d788960b725487263c49d956ca5fa3d37008b45 android.hardware.wifi@1.2::IWifiStaIface +5751f230e86a36111e7c5b995577cbf89d8df76c8e6c7641199198f3db3a93f7 android.hardware.wifi@1.3::IWifiStaIface # HALs released in Android R -e966a3437d6a98d9d9e14e9d672088771716031900c0deb55a0946c751a03a44 android.hardware.audio@6.0::types -4540d12fe1cea996f21bd1712d4ae0906dcbd58177dac494efc605b004902d43 android.hardware.audio@6.0::IDevice +7241bd4596a927cd46d4b82f5e29e2cbe57f194aa1b25555f1d1d352e8b15c61 android.hardware.audio@6.0::IDevice 2402876cbc23c0de3690a665eca84fd3857d1808dba5cad25ce272f81ecef8c9 android.hardware.audio@6.0::IDevicesFactory bca5379d5065e2e08b6ad7308ffc8a71a972fc0698bec678ea32eea786d01cb5 android.hardware.audio@6.0::IPrimaryDevice -7318b521ea12fdd4b6e3f381085c71784c810d1ec7a8d701ec2250f3f86712e4 android.hardware.audio@6.0::IStream +fd1f1b29f26b42e886220f04a08086c00e5ade9d7b53f095438e578ab9d42a93 android.hardware.audio@6.0::IStream 2df5d5866b37776f25079c0e54b54350a2abe4e025a59c9e02a7d3abe8ca00e8 android.hardware.audio@6.0::IStreamIn -78e4138cc8307c11fc777c3bd376e581ba4ba48196b05ca1d7cdfa515c87b48a android.hardware.audio@6.0::IStreamOut +164826a380f4c1700183003f62d7532e367b67381c30ea44f946c0cf00008f85 android.hardware.audio@6.0::IStreamOut 997fdaad7a9d17ee7e01feb7031a753e2365e72ad30b11d950e9183fabdf3844 android.hardware.audio@6.0::IStreamOutCallback -b495a43bd6ff0c34a391824b0ba1a3f3f34b4a869690611a9a0afc404d75aa84 android.hardware.audio.common@6.0::types -817930d58412d662cb45e641c50cb62c727e4a3e3ffe7029a53cad9677b97d58 android.hardware.audio.effect@6.0::types +e7ca0db9a1098210f327a9b152fa6afe6bf019c41e5264c64829d04d50c0a526 android.hardware.audio@6.0::IStreamOutEventCallback +aa2211abd803e03d05ea11c18749db068f785fe026f8d99bce64bd764f63d194 android.hardware.audio@6.0::IStreamOutEventCallback # b/150175043 +822369cf4dc16a6f6b9622bcf86cbdc0b692dc82193fc15e967767175cbfdd8f android.hardware.audio@6.0::types +bee662c62d997d8065e2bcb5c1e7a9578931f22ce28fd02c219fdb4d0630abf7 android.hardware.audio.common@6.0::types 525bec6b44f1103869c269a128d51b8dccd73af5340ba863c8886c68357c7faf android.hardware.audio.effect@6.0::IAcousticEchoCancelerEffect 8d76bbe3719d051a8e9a1dcf9244f37f5b0a491feb249fa48391edf7cb4f3131 android.hardware.audio.effect@6.0::IAutomaticGainControlEffect 461b1114cb35d89f87e5694e0792ba53c112a7fa9a14d9b95188cf9c4764be23 android.hardware.audio.effect@6.0::IBassBoostEffect @@ -621,17 +640,80 @@ dd377f404a8e71f6191d295e10067db629b0f0c28e594af906f2bea5d87fe2cc android.hardwar 5237c42d3913ef569f07bec802568084b615155d05a7951e75085da54856508c android.hardware.audio.effect@6.0::IPresetReverbEffect 282193799d60bff27a84c65a36218c1e7d8f582f5828e2e059383d1b90aa56bd android.hardware.audio.effect@6.0::IVirtualizerEffect 0868e00f7c5ee16723bda1a8f57099763d04100ae7126a1c2d3a9a87c844a7e8 android.hardware.audio.effect@6.0::IVisualizerEffect +817930d58412d662cb45e641c50cb62c727e4a3e3ffe7029a53cad9677b97d58 android.hardware.audio.effect@6.0::types +ca515ff4b63c80cf5ad7b3395c997c57d6c56157361f6c367d1c96f23cc4860a android.hardware.automotive.audiocontrol@1.0::types +4bc4e8087f5c389f013370ed68bc8a1a29cb2f203237937697f35e005a5ad0b4 android.hardware.automotive.audiocontrol@2.0::IAudioControl +37ef585d6687cb31e35c67ab456140d70edba9c4333ce5a6ddd70e636e985773 android.hardware.automotive.audiocontrol@2.0::ICloseHandle +3cf3e5e48ba2642052bbccc1aa4e8bb142933ac960ff40eeedd16e4fe452e7a5 android.hardware.automotive.audiocontrol@2.0::IFocusListener +44c03f3341939524b5f5acb6680f8a91924d02e335a32840d56597616db7f1ea android.hardware.automotive.audiocontrol@2.0::types +949a2582c9efa3f6f631f56120eae3f02313f251dbf9246c327e419cdf0652a2 android.hardware.automotive.can@1.0::ICanBus +43cddb1907a30343bced68946884416ea25ab14ae2df4709357528b2bedba84c android.hardware.automotive.can@1.0::ICanController +272e826492b27b0dbdeda408e84a41ae43e98f29e57995b6452ded270aae4eee android.hardware.automotive.can@1.0::ICanErrorListener +07e387bd8bc0e4df5f372515ed960a0b1ae74ea7231d4490a0bb09b07046e4f1 android.hardware.automotive.can@1.0::ICanMessageListener +2166132d6c247630a193217b4338074f634d6691b1ed6796cb26b3812e90b46e android.hardware.automotive.can@1.0::ICloseHandle +83355471a3b6d7f777d3f129714585ffb77d9b6f8a3d0365741969631efb81b2 android.hardware.automotive.can@1.0::types +50bfbeef15d7451bd07e4ad460fbcb7ff80521b89bb8049035c0d458b4125ae4 android.hardware.automotive.evs@1.1::IEvsCamera +89ff5ab18b3069f21a57f559b290caa50670f0ae1b74178f630183aef39b496b android.hardware.automotive.evs@1.1::IEvsCameraStream +4c67f768067a0aceac74381f6f62e778ab3b6a18f47db3c9b98c58190ef5619d android.hardware.automotive.evs@1.1::IEvsDisplay +87958d728d7c0ee9b9391ab4a072b097914921a7b38f7dc3df427f933a5b528e android.hardware.automotive.evs@1.1::IEvsEnumerator +f53b4e8de6209c6d0fa9036005671b34a2f98328b51423d3a5137a43bf42c84d android.hardware.automotive.evs@1.1::IEvsUltrasonicsArray +0460bacbde906a846a3d71b2b7b33d6927cac3ff072e523ffac7853577464406 android.hardware.automotive.evs@1.1::IEvsUltrasonicsArrayStream +f27cf8283e7b953d33dd258734749d2fca9cc63502ea41353060ffa78d8ce9f6 android.hardware.automotive.evs@1.1::types +4e4904c4067dadae974ddf90351f362331dcd04bba1d890d313cc8ba91f68c15 android.hardware.automotive.sv@1.0::ISurroundView2dSession +63336e9d03f545020ff2982ff76d9d8c44fa76ad476293b5ef6732cbbd71e61b android.hardware.automotive.sv@1.0::ISurroundView3dSession +b7015428cd52ce8192d13bfcbf2c4455cda3727d57f2aac80d65a1747104f5ac android.hardware.automotive.sv@1.0::ISurroundViewService +7d2e77ad86766bbc213fa7377eab739f44cc0866e567e6d33c0e27e7f99e27a8 android.hardware.automotive.sv@1.0::ISurroundViewSession +d34769e55df919739bb46f25ae2e125e9c807733afa94daeca20feadd74de79c android.hardware.automotive.sv@1.0::ISurroundViewStream +affd9c591f48a69773fcf43dc4a716d292cd4bc5ba2be8649276af0aedea435d android.hardware.automotive.sv@1.0::types +140f8f62100ccf9cd282ae3685a0f4ef0a9f971d77dfbc7350ccb4e04cf295ec android.hardware.biometrics.fingerprint@2.2::IBiometricsFingerprint +82cad99f5feb2ea9bcd4579055edf4af8feb9fc602a6e4827ddd727d254d4991 android.hardware.biometrics.fingerprint@2.2::IBiometricsFingerprintClientCallback +ae6315fd42196478ac08441cb489d854118001bca5b9b9fd58af5110952be30e android.hardware.biometrics.fingerprint@2.2::types 362fd1c21641c2224f3b80c30d9797b988fa3f344243d531ba73c553779a5763 android.hardware.bluetooth@1.1::IBluetoothHci 40ab2c6866c18d32baf6e49e3053949e79601f56963a791e93e68b9ee18f718d android.hardware.bluetooth@1.1::IBluetoothHciCallbacks 07d0a252b2d8fa35887908a996ba395cf392968395fc30afab791f46e0c22a52 android.hardware.boot@1.1::IBootControl 74049a402be913963edfdd80828a53736570e9d8124a1bf18166b6ed46a6b0ab android.hardware.boot@1.1::types -d9df99be0f59d8f33a9699fe316c67bfd11818aa69440bb1123ba43e717cea85 android.hardware.dumpstate@1.1::types +b8c63679e1a3874b356f3e691aecce1191d38f59063cf2ed2dce8a9d4cabf00e android.hardware.camera.device@3.6::ICameraDevice +eb90c4d366f05a025d1d1a3672f8b4c3e33e420fa387f73f21b264645bfdf845 android.hardware.camera.device@3.6::ICameraDeviceSession +a718c8a3acaa938de5a57923e8c4625ed7ca051e05a1d930ba6998557d7b57c8 android.hardware.camera.device@3.6::ICameraOfflineSession +a35d5151b48505f06a775b38c0e2e265f80a845d92802324c643565807f81c53 android.hardware.camera.device@3.6::types +02bdf82dba7dce273a554b4474468a8fb1fb4f61ab65da95eb16e080df63fff6 android.hardware.camera.metadata@3.5::types +93cd94e47b22007bbf436c2f5c2703bb7b2859d1b714d6ae15520db55667ba6c android.hardware.camera.provider@2.6::ICameraProvider +8f8d9463508ff9cae88eb35c429fd0e2dbca0ca8f5de7fdf836cc0c4370becb6 android.hardware.camera.provider@2.6::ICameraProviderCallback +1edf7aef68ef3bd577a1175b1462fb82e3e39f01c6915dda61fba121028df283 android.hardware.camera.provider@2.6::types +c1aa508d00b66ed5feefea398fd5edf28fa651ac89773adad7dfda4e0a73a952 android.hardware.cas@1.2::ICas +9811f867def49b420d8c707f7e38d3bdd64f835244e1d2a5e9762ab9835672dc android.hardware.cas@1.2::ICasListener +f18695dd36ee205640b8326a17453858a7b4596653aaa6ef0016b0aef1bd4dac android.hardware.cas@1.2::IMediaCasService +4d85e814f94949dae4dc6cb82bbd7d6bb24ffafda6ddb2eac928d2a4fc2e21ce android.hardware.cas@1.2::types +8351cc01eed4c0b4482d9572b5c7ddfd17874d8edb51d6761d348116fc91dd18 android.hardware.contexthub@1.1::IContexthub +3581d0ba61663cdd45807494dcd697d01c074f27587df9140655f94346969cfe android.hardware.contexthub@1.1::types +66931c2506fbb5af61f20138cb05e0a09e7bf67d6964c231d27c648933bb33ec android.hardware.drm@1.3::ICryptoFactory +994d08ab27d613022c258a9ec48cece7adf2a305e92df5d76ef923e2c6665f64 android.hardware.drm@1.3::IDrmFactory 186bc152ae189ab48f3a761a44ddf5edd0d483073c5b6ca1f802f8b50488b754 android.hardware.dumpstate@1.1::IDumpstateDevice +d9df99be0f59d8f33a9699fe316c67bfd11818aa69440bb1123ba43e717cea85 android.hardware.dumpstate@1.1::types +c319e68b03829958404402c2d9c682019678087d60495807c0a7444e0a6af981 android.hardware.gnss@2.1::IGnss +ba5ac712b2a656dc07c83ab4a7a2c2f3bee1bbcb752e8b8ffa9b672f3b5b0728 android.hardware.gnss@2.1::IGnssAntennaInfo +0bc3ed97cbc3f6abc89c68f4e9f4d124f9f723431997dc88c2186cf4d2ad47ee android.hardware.gnss@2.1::IGnssAntennaInfoCallback +3541d83adfeac16ee3e45d183a58dffe06012ccb5aa5bcd2e4f6eeae269f69cd android.hardware.gnss@2.1::IGnssCallback +737d750017738f0753d13ba01a3310e0161f294b8ae80b3fd63eaa227e9d9c66 android.hardware.gnss@2.1::IGnssConfiguration +7913a11206a577b12ade86a7cf3f95c2639cb514d086673f279bf99238c9917e android.hardware.gnss@2.1::IGnssMeasurement +df52e2c39ed701a355b5e0fdbf83fe5fa7d04bfecd715116b39373d46dc3c682 android.hardware.gnss@2.1::IGnssMeasurementCallback +769d346927a94fd40ee80a5a976d8d15cf022ef99c5900738f4a82f26c0ed229 android.hardware.gnss@2.1::types +6670e7780803a8c696c6391fda5589a334b1b37dc7be9393792ed35035413633 android.hardware.gnss.measurement_corrections@1.1::IMeasurementCorrections +956c1576ca0d6f11b42980ef59052062836b6763fe973af6cb709da50787f710 android.hardware.gnss.measurement_corrections@1.1::types +a2dcf188b02102d3cf80ca0a280dce05d45a8df809751b3c52347426ed58ebbe android.hardware.graphics.allocator@4.0::IAllocator +0a90c665605df3d7d7b0fcafcc4178c3345a6e4ba7e3148fefe4973629827463 android.hardware.graphics.composer@2.4::IComposer +809b815bac3d9a5ead591b5fed20f08dbd2bcf7b5c6858201fdd0d8347db9177 android.hardware.graphics.composer@2.4::IComposerCallback +8006ee6e02453443f7e79cfcd9f85d9863bed53c4cfc55519de98b64ca72edc2 android.hardware.graphics.composer@2.4::IComposerClient +fda61f0a5a56cf4e0e8697fb4899a26a4dc450ff837f3425b53932fe34583d11 android.hardware.graphics.composer@2.4::types +4d674afdc4447f614c8cc466ed45c5955a0192fa3d3c70884b11dd3c88f0b468 android.hardware.graphics.mapper@4.0::IMapper +b58a5e83a8ab04ff6e500f6afc17a1129a1f3de044b296b4b6bd34a085220f87 android.hardware.graphics.mapper@4.0::types ce8dbe76eb9ee94b46ef98f725be992e760a5751073d4f4912484026541371f3 android.hardware.health@2.1::IHealth 26f04510a0b57aba5167c5c0a7c2f077c2acbb98b81902a072517829fd9fd67f android.hardware.health@2.1::IHealthInfoCallback e2f8bc1868fd4a3fd587c172773ea5a8c2f5a3deaf7958394102ca455252b255 android.hardware.health@2.1::types c5da8636c14cd30f1ae9f10c2219e35b4e29a64443103a5842352dd070afe514 android.hardware.keymaster@4.1::IKeymasterDevice ddcf89cd8ee2df0d32aee55050826446fb64f7aafde0a7cd946c64f61b1a364c android.hardware.keymaster@4.1::types +df9c79c4fdde2821550c6d5c3d07f5ec0adfb1b702561ce543c906ddef698703 android.hardware.media.c2@1.1::IComponent +a3eddd9bbdc87e8c22764070037dd1154f1cf006e6fba93364c4f85d4c134a19 android.hardware.media.c2@1.1::IComponentStore 65c16331e57f6dd68b3971f06f78fe9e3209afb60630c31705aa355f9a52bf0d android.hardware.neuralnetworks@1.3::IBuffer 278817920bfd5292a7713f97f1832cca53de3de640f7670e413d97c6e7fd581c android.hardware.neuralnetworks@1.3::IDevice 127ba11efb8220dc3aec9a8f441b59eaf1c68d7f03f577833e1824de75a36b17 android.hardware.neuralnetworks@1.3::IExecutionCallback @@ -641,10 +723,48 @@ eee3430cc86c97c7b407495863d8fb61da6f1a64b7721e77b9b4909b11b174e9 android.hardwar acf84925f8ee0a651f2ec547ac334034de266479b93af5434f6c1f25e66aba96 android.hardware.neuralnetworks@1.3::types e9080d04218e98512b63aace9ff3da52f0130238391f15cbbf7df396a3ec9072 android.hardware.neuralnetworks@1.3::types # b/155508675, b/155662254, b/155238914, b/155660285 583dc88b41e702e940fd954edda1beb8b4151eab55a5c6d7e69e2781bce84b59 android.hardware.neuralnetworks@1.3::types # b/156918813 -a5bcd595a5108312fe2eb402e716d0b7dab8eb689a2a5f54fdef3ff71f3babd5 android.hardware.radio@1.5::types b454df853441c12f6e425e8a60dd29fda20f5e6e39b93d1103e4b37495db38aa android.hardware.radio@1.5::IRadio fcbb0742a88215ee7a6d7ce0825d253eb2b50391fc6c8c48667f9fd7f6d4549e android.hardware.radio@1.5::IRadioIndication b809193970a91ca637a4b0184767315601d32e3ef3d5992ffbc7a8d14a14f015 android.hardware.radio@1.5::IRadioResponse +a5bcd595a5108312fe2eb402e716d0b7dab8eb689a2a5f54fdef3ff71f3babd5 android.hardware.radio@1.5::types +c2cc192edcc222a12b524fb0e0e7f17ef2b48d6b1c0be7b60bc114601793d7a9 android.hardware.secure_element@1.2::ISecureElement +3ca6616381080bdd6c08141ad12775a94ae868c58b02b1274ae3326f7de724ab android.hardware.sensors@2.1::ISensors +3d4141c6373cd9ca02fe221a7d12343840de2255d032c38248fe8e35816b58b2 android.hardware.sensors@2.1::ISensorsCallback +8051cc50fc90ed447f058a8b15d81f35a65f1bd9004b1de4f127edeb89b47978 android.hardware.sensors@2.1::types +b37f78e3fdc79af8b32a545b2b426f1fd1355b359d9e7835f3bf1ed0aa4518d8 android.hardware.soundtrigger@2.3::ISoundTriggerHw +4a6517ea4ad807855428b0101d8e1a486497bd88ab4300ba3b2be43d46d32580 android.hardware.soundtrigger@2.3::types +b878fcad575742925690303f717e2186058a378670be6e2f85e7e503841954aa android.hardware.tv.cec@2.0::IHdmiCec +009b9a02619b14da27027cb5d424fa01f098f6b6f6fa0829049497fc3612d67d android.hardware.tv.cec@2.0::IHdmiCecCallback +af1443272c4db47dea5adc5b6c4293dd09c20466a58684c68a5d88c0e7e46261 android.hardware.tv.cec@2.0::types +adab52e647d1a1ccfbdabdfc9c73352f8e834b61322e505bc6e3d3a0d3acc259 android.hardware.tv.tuner@1.0::IDemux +548e1a16fc4f779346e14968a63cd6f160e1e2c8b8c99256b2bac24a24b52a9a android.hardware.tv.tuner@1.0::IDescrambler +b84597d59f0f1d03c9997d60eb15280f3950c287d46016240d89859789db4d47 android.hardware.tv.tuner@1.0::IDvr +e512a8b4ddef0d0c29d9f68101363dca7616033a24bab86bfca0829661e7484c android.hardware.tv.tuner@1.0::IDvrCallback +c113b5bed45b3a67ee3f15de1482742a8fd8d96e45ec72e628ee9be6301d51d7 android.hardware.tv.tuner@1.0::IFilter +8bbc6bde44573edf800cda2b457852175786a3c73f36666bfb2d3afdce3d0dfa android.hardware.tv.tuner@1.0::IFilterCallback +ccd985e820ed92a5cb55f524b3549462483d21824ca2df0276f5bc2f42878ea3 android.hardware.tv.tuner@1.0::IFrontend +818587bab10f2534b5a1ef70ed9bae99019f7d453b2fcb2dda01c6db91c3a805 android.hardware.tv.tuner@1.0::IFrontendCallback +83de3964a800a0e707731adcbcbfbaa56868549233e4c8dcccc8969fab727d38 android.hardware.tv.tuner@1.0::ILnb +b2310785bdb55f97bbbb2176e2ee73ed8d2a7ce5895bd20c997b90c5f2868ad8 android.hardware.tv.tuner@1.0::ILnbCallback +4788787e662293d526ff7789fc24e82533e7f6ff99a967ebc3e3ec6b17628796 android.hardware.tv.tuner@1.0::ITimeFilter +c350c7783843e0c7cf30f90c918770b0d3c09fc0fe5e532e2f2e7210fcfe71c9 android.hardware.tv.tuner@1.0::ITuner +b335c3c732c799b299fa61c6de6260ab4d20cbd0ec21acd6db14d8156c745d0b android.hardware.tv.tuner@1.0::types +7746fda1fbf9c7c132bae701cc5a161309e4f5e7f3e8065811045975ee86196d android.hardware.usb.gadget@1.1::IUsbGadget +3e01d4446cd69fd1c48f8572efd97487bc179564b32bd795800b97bbe10be37b android.hardware.wifi@1.4::IWifi +84757251ff195b447dc511c0d3f055a684d4fed7847eb4d65a2455eb914059d2 android.hardware.wifi@1.4::IWifiApIface +2e07983a70a1b963bee8aece050e5ea503a3dde31e21a9f8352cf17e9113d347 android.hardware.wifi@1.4::IWifiChip +08c1912be480c08aa77f6d56bb083d66cd13eca11e01c2d3e727bcb3a1e4f79b android.hardware.wifi@1.4::IWifiChipEventCallback +ff66371a467d1f3caacea15d93f39b7b767b0d6b8cd41d040337cdabf2514b87 android.hardware.wifi@1.4::IWifiNanIface +10bd6f19198c281ee2052641048e970ad66b732e4f6ffe79a0f697ea15761e0f android.hardware.wifi@1.4::IWifiRttController +fcd92a4c898360185dd659f0a1e3001c25eb40b59c3457fd235acf3a8c407525 android.hardware.wifi@1.4::IWifiRttControllerEventCallback +d878c406d9c1cc67f9d9a3a66b16dcbd7d944e0ed520745fc9ad21d95031e839 android.hardware.wifi@1.4::types +c67aaf26a7a40d14ea61e70e20afacbd0bb906df1704d585ac8599fbb69dd44b android.hardware.wifi.hostapd@1.2::IHostapd +2b5a7ea572b736030c64a3b4043af244425477c4672301780fe15aba5ed393d9 android.hardware.wifi.hostapd@1.2::types +a64467bae843569f0d465c5be7f0c7a5b987985b55a3ef4794dd5afc68538650 android.hardware.wifi.supplicant@1.3::ISupplicant +159d48c9efb881f44d5deda8917b89fb4da26837f019446d6d73b73ea5010eca android.hardware.wifi.supplicant@1.3::ISupplicantStaIface +2ce1f7fb52e49f80b13a9b153d491bce530552f02357ea729acae922a8659f93 android.hardware.wifi.supplicant@1.3::ISupplicantStaIfaceCallback +77531c8d048f8f8ae532babd0ca86332a865ec9aace1b051226ef2b21123e645 android.hardware.wifi.supplicant@1.3::ISupplicantStaNetwork +98592d193a717066facf91428426e5abe211e3bd718bc372e29fb944ddbe6e7c android.hardware.wifi.supplicant@1.3::types # ABI preserving changes to HALs during Android S cd84ab19c590e0e73dd2307b591a3093ee18147ef95e6d5418644463a6620076 android.hardware.neuralnetworks@1.2::IDevice diff --git a/drm/1.1/vts/functional/AndroidTest.xml b/drm/1.1/vts/functional/AndroidTest.xml index 65c45accee..24eeb72fd2 100644 --- a/drm/1.1/vts/functional/AndroidTest.xml +++ b/drm/1.1/vts/functional/AndroidTest.xml @@ -19,6 +19,10 @@ + + +