Merge Android R

Bug: 168057903
Merged-In: I33c8b79d82aaaecd05cc1c74f6c6a8613c42fa68
Change-Id: I0586c24e16b7d052b0d209e2dffd6124492fcafa
This commit is contained in:
Xin Li
2020-09-09 20:21:17 -07:00
954 changed files with 78389 additions and 12064 deletions

View File

@@ -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",
],
}

View File

@@ -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++/)

View File

@@ -8,6 +8,9 @@
},
{
"name": "hal_implementation_test"
},
{
"name": "VtsHalTvInputV1_0TargetTest"
}
]
}

View File

@@ -12,6 +12,7 @@ hidl_interface {
"IStreamIn.hal",
"IStreamOut.hal",
"IStreamOutCallback.hal",
"IStreamOutEventCallback.hal",
],
interfaces: [
"android.hardware.audio.common@6.0",

View File

@@ -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<AudioPortConfig> sources, vec<AudioPortConfig> 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<AudioPortConfig> sources,
vec<AudioPortConfig> 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

View File

@@ -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

View File

@@ -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);
};

View File

@@ -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 <String, Datum>
*
* Datum = {
* (type_size_t) Type (the type index from type_as_value<T>.)
* (datum_size_t) Size (size of the Payload)
* (byte string) Payload<Type>
* }
*
* The data is specified in native endian order.
* Since the size of the Payload is always present, unknown types may be skipped.
*
* Payload<Fixed-size Primitive_Value>
* [ sizeof(Primitive_Value) in raw bytes ]
*
* Example of Payload<Int32> of 123:
* Payload<Int32>
* [ value of 123 ] = 0x7b 0x00 0x00 0x00 123
*
* Payload<String>
* [ (index_size_t) length, not including zero terminator.]
* [ (length) raw bytes ]
*
* Example of Payload<String> of std::string("hi"):
* [ (index_size_t) length ] = 0x02 0x00 0x00 0x00 2 strlen("hi")
* [ raw bytes "hi" ] = 0x68 0x69 "hi"
*
* Payload<Data>
* [ (index_size_t) entries ]
* [ raw bytes (entry 1) Key (Payload<String>)
* Value (Datum)
* ... (until #entries) ]
*
* Example of Payload<Data> of {{"hello", "world"},
* {"value", (int32_t)1000}};
* [ (index_size_t) #entries ] = 0x02 0x00 0x00 0x00 2 entries
* Key (Payload<String>)
* [ 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<String>
* [ (index_size_t) length ] = 0x05 0x00 0x00 0x00 5 strlen("world")
* [ raw bytes "world" ] = 0x77 0x6f 0x72 0x6c 0x64 "world"
* Key (Payload<String>)
* [ 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<Int32>
* [ raw bytes 1000 ] = 0xe8 0x03 0x00 0x00 1000
*
* The contents of audioMetadata is a Payload<Data>.
* 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<uint8_t> audioMetadata);
};

View File

@@ -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);
}

View File

@@ -66,6 +66,7 @@
</xs:element>
<xs:complexType name="globalConfiguration">
<xs:attribute name="speaker_drc_enabled" type="xs:boolean" use="required"/>
<xs:attribute name="call_screen_mode_supported" type="xs:boolean" use="optional"/>
<xs:attribute name="engine_library" type="engineSuffix" use="optional"/>
</xs:complexType>
<xs:complexType name="modules">

View File

@@ -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;
};

View File

@@ -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

View File

@@ -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;
};
/**

View File

@@ -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<AudioChannelMask>(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<audio_usage_t>(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<AudioChannelMask>(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<AudioEncapsulationMode>(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<audio_encapsulation_mode_t>(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,

View File

@@ -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<AudioPortConfig>* configs);
static std::unique_ptr<audio_port_config[]> audioPortConfigsToHal(
const hidl_vec<AudioPortConfig>& configs);
const hidl_vec<AudioPortConfig>& 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);

View File

@@ -80,6 +80,7 @@ int main(int /* argc */, char* /* argv */ []) {
const std::vector<InterfacesList> 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",

View File

@@ -171,7 +171,8 @@ std::tuple<Result, sp<IStreamOut>> 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<Result, sp<IStreamIn>> 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<bool> Device::supportsAudioPatches() {
Return<void> Device::createAudioPatch(const hidl_vec<AudioPortConfig>& sources,
const hidl_vec<AudioPortConfig>& sinks,
createAudioPatch_cb _hidl_cb) {
auto [retval, patch] = createOrUpdateAudioPatch(
static_cast<AudioPatchHandle>(AudioHandleConsts::AUDIO_PATCH_HANDLE_NONE), sources,
sinks);
_hidl_cb(retval, patch);
return Void();
}
std::tuple<Result, AudioPatchHandle> Device::createOrUpdateAudioPatch(
AudioPatchHandle patch, const hidl_vec<AudioPortConfig>& sources,
const hidl_vec<AudioPortConfig>& sinks) {
Result retval(Result::NOT_SUPPORTED);
AudioPatchHandle patch = 0;
if (version() >= AUDIO_DEVICE_API_VERSION_3_0) {
std::unique_ptr<audio_port_config[]> halSources(HidlUtils::audioPortConfigsToHal(sources));
std::unique_ptr<audio_port_config[]> halSinks(HidlUtils::audioPortConfigsToHal(sinks));
audio_patch_handle_t halPatch = AUDIO_PATCH_HANDLE_NONE;
audio_patch_handle_t halPatch = static_cast<audio_patch_handle_t>(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<void> Device::createAudioPatch(const hidl_vec<AudioPortConfig>& sources,
patch = static_cast<AudioPatchHandle>(halPatch);
}
}
_hidl_cb(retval, patch);
return Void();
return {retval, patch};
}
Return<Result> Device::releaseAudioPatch(int32_t patch) {
@@ -438,6 +448,19 @@ Return<Result> Device::removeDeviceEffect(AudioPortHandle device, uint64_t effec
}
}
Return<void> Device::updateAudioPatch(int32_t previousPatch,
const hidl_vec<AudioPortConfig>& sources,
const hidl_vec<AudioPortConfig>& sinks,
createAudioPatch_cb _hidl_cb) {
if (previousPatch != static_cast<int32_t>(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

View File

@@ -176,6 +176,13 @@ Return<Result> PrimaryDevice::addDeviceEffect(AudioPortHandle device, uint64_t e
Return<Result> PrimaryDevice::removeDeviceEffect(AudioPortHandle device, uint64_t effectId) {
return mDevice->removeDeviceEffect(device, effectId);
}
Return<void> PrimaryDevice::updateAudioPatch(int32_t previousPatch,
const hidl_vec<AudioPortConfig>& sources,
const hidl_vec<AudioPortConfig>& 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<Result> 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;

View File

@@ -22,6 +22,8 @@
//#define LOG_NDEBUG 0
#define ATRACE_TAG ATRACE_TAG_AUDIO
#include <string.h>
#include <memory>
#include <android/log.h>
@@ -453,20 +455,22 @@ int StreamOut::asyncCallback(stream_callback_event_t event, void*, void* cookie)
sp<IStreamOutCallback> callback = self->mCallback;
if (callback.get() == nullptr) return 0;
ALOGV("asyncCallback() event %d", event);
Return<void> 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<Result> StreamOut::selectPresentation(int32_t /*presentationId*/, int32_t
}
#endif
#if MAJOR_VERSION >= 6
Return<void> StreamOut::getDualMonoMode(getDualMonoMode_cb _hidl_cb) {
_hidl_cb(Result::NOT_SUPPORTED, DualMonoMode::OFF);
return Void();
}
Return<Result> StreamOut::setDualMonoMode(DualMonoMode /*mode*/) {
return Result::NOT_SUPPORTED;
}
Return<void> StreamOut::getAudioDescriptionMixLevel(getAudioDescriptionMixLevel_cb _hidl_cb) {
_hidl_cb(Result::NOT_SUPPORTED, -std::numeric_limits<float>::infinity());
return Void();
}
Return<Result> StreamOut::setAudioDescriptionMixLevel(float /*leveldB*/) {
return Result::NOT_SUPPORTED;
}
Return<void> 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<Result> StreamOut::setPlaybackRateParameters(const PlaybackRate& /*playbackRate*/) {
return Result::NOT_SUPPORTED;
}
Return<Result> StreamOut::setEventCallback(const sp<IStreamOutEventCallback>& 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<StreamOut*>(cookie);
sp<IStreamOutEventCallback> eventCallback = self->mEventCallback;
if (eventCallback.get() == nullptr) return 0;
ALOGV("%s event %d", __func__, event);
Return<void> result;
switch (event) {
case STREAM_EVENT_CBK_TYPE_CODEC_FORMAT_CHANGED: {
hidl_vec<uint8_t> 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

View File

@@ -118,6 +118,9 @@ struct Device : public IDevice, public ParametersUtil {
Return<Result> close() override;
Return<Result> addDeviceEffect(AudioPortHandle device, uint64_t effectId) override;
Return<Result> removeDeviceEffect(AudioPortHandle device, uint64_t effectId) override;
Return<void> updateAudioPatch(int32_t previousPatch, const hidl_vec<AudioPortConfig>& sources,
const hidl_vec<AudioPortConfig>& sinks,
createAudioPatch_cb _hidl_cb) override;
#endif
Return<void> debug(const hidl_handle& fd, const hidl_vec<hidl_string>& options) override;
@@ -136,6 +139,9 @@ struct Device : public IDevice, public ParametersUtil {
virtual ~Device();
Result doClose();
std::tuple<Result, AudioPatchHandle> createOrUpdateAudioPatch(
AudioPatchHandle patch, const hidl_vec<AudioPortConfig>& sources,
const hidl_vec<AudioPortConfig>& sinks);
// Methods from ParametersUtil.
char* halGetParameters(const char* keys) override;

View File

@@ -100,6 +100,9 @@ struct PrimaryDevice : public IPrimaryDevice {
Return<Result> close() override;
Return<Result> addDeviceEffect(AudioPortHandle device, uint64_t effectId) override;
Return<Result> removeDeviceEffect(AudioPortHandle device, uint64_t effectId) override;
Return<void> updateAudioPatch(int32_t previousPatch, const hidl_vec<AudioPortConfig>& sources,
const hidl_vec<AudioPortConfig>& sinks,
updateAudioPatch_cb _hidl_cb) override;
#endif
Return<void> debug(const hidl_handle& fd, const hidl_vec<hidl_string>& options) override;

View File

@@ -157,6 +157,10 @@ Return<void> StreamMmap<T>::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<void> StreamMmap<T>::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);

View File

@@ -121,16 +121,31 @@ struct StreamOut : public IStreamOut {
Return<void> updateSourceMetadata(const SourceMetadata& sourceMetadata) override;
Return<Result> selectPresentation(int32_t presentationId, int32_t programId) override;
#endif
#if MAJOR_VERSION >= 6
Return<void> getDualMonoMode(getDualMonoMode_cb _hidl_cb) override;
Return<Result> setDualMonoMode(DualMonoMode mode) override;
Return<void> getAudioDescriptionMixLevel(getAudioDescriptionMixLevel_cb _hidl_cb) override;
Return<Result> setAudioDescriptionMixLevel(float leveldB) override;
Return<void> getPlaybackRateParameters(getPlaybackRateParameters_cb _hidl_cb) override;
Return<Result> 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<Result> setEventCallback(const sp<IStreamOutEventCallback>& callback) override;
#endif
private:
const sp<Device> mDevice;
audio_stream_out_t* mStream;
const sp<Stream> mStreamCommon;
const sp<StreamMmap<audio_stream_out_t>> mStreamMmap;
sp<IStreamOutCallback> mCallback;
sp<IStreamOutCallback> mCallback; // Callback for non-blocking write and drain
#if MAJOR_VERSION >= 6
sp<IStreamOutEventCallback> mEventCallback;
#endif
std::unique_ptr<CommandMQ> mCommandMQ;
std::unique_ptr<DataMQ> mDataMQ;
std::unique_ptr<StatusMQ> 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

View File

@@ -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) {

View File

@@ -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<int32_t>(AudioHandleConsts::AUDIO_PATCH_HANDLE_NONE),
hidl_vec<AudioPortConfig>(), hidl_vec<AudioPortConfig>(), returnIn(res, ignored)));
ASSERT_RESULT(Result::INVALID_ARGUMENTS, res);
}
using DualMonoModeAccessorHidlTest = AccessorHidlTest<DualMonoMode, OutputStreamTest>;
TEST_P(DualMonoModeAccessorHidlTest, DualMonoModeTest) {
doc::test("Check that dual mono mode can be set and retrieved");
testAccessors<OPTIONAL>(&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<float, OutputStreamTest>;
TEST_P(AudioDescriptionMixLevelHidlTest, AudioDescriptionMixLevelTest) {
doc::test("Check that audio description mix level can be set and retrieved");
testAccessors<OPTIONAL>(
&OutputStreamTest::getStream, "audio description mix level",
Initial{-std::numeric_limits<float>::infinity()}, {-48.0f, -1.0f, 0.0f, 1.0f, 48.0f},
&IStreamOut::setAudioDescriptionMixLevel, &IStreamOut::getAudioDescriptionMixLevel,
{48.5f, 1000.0f, std::numeric_limits<float>::infinity()});
}
INSTANTIATE_TEST_CASE_P(AudioDescriptionMixLevelHidl, AudioDescriptionMixLevelHidlTest,
::testing::ValuesIn(getOutputDeviceConfigParameters()),
&DeviceConfigParameterToString);
using PlaybackRateParametersHidlTest = AccessorHidlTest<PlaybackRate, OutputStreamTest>;
TEST_P(PlaybackRateParametersHidlTest, PlaybackRateParametersTest) {
doc::test("Check that playback rate parameters can be set and retrieved");
testAccessors<OPTIONAL>(
&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<void> onCodecFormatChanged(const hidl_vec<uint8_t>& 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");
}
}

View File

@@ -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<IDevice> 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<IDevicesFactory> getDevicesFactory() const {
return DevicesFactoryManager::getInstance().get(getFactoryName());
}
sp<IDevice> 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<IPrimaryDevice> 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 <Optionality optionality = REQUIRED, class Getter, class Setter>
void testAccessors(const string& propertyName, const Initial expectedInitial,
list<Property> valuesToTest, Setter setter, Getter getter,
const vector<Property>& invalidValues = {}) {
template <Optionality optionality = REQUIRED, class IUTGetter, class Getter, class Setter>
void testAccessors(IUTGetter iutGetter, const string& propertyName,
const Initial expectedInitial, list<Property> valuesToTest, Setter setter,
Getter getter, const vector<Property>& 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 <Optionality optionality = REQUIRED, class Getter, class Setter>
void testAccessors(const string& propertyName, const Initial expectedInitial,
list<Property> valuesToTest, Setter setter, Getter getter,
const vector<Property>& invalidValues = {}) {
testAccessors<optionality>(&BaseTestClass::getDevice, propertyName, expectedInitial,
valuesToTest, setter, getter, invalidValues);
}
};
@@ -881,6 +891,11 @@ class StreamHelper {
template <class Stream>
class OpenStreamTest : public AudioHidlTestWithDeviceConfigParameter {
public:
// public access to avoid annoyances when using this method in template classes
// derived from test classes
sp<Stream> getStream() const { return stream; }
protected:
OpenStreamTest() : AudioHidlTestWithDeviceConfigParameter(), helper(stream) {}
template <class Open>

View File

@@ -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<uint32_t>(sizeof(uint32_t) + configSize)];
memset(halResult, 0, sizeof(halResult));
std::vector<uint32_t> halResult(alignedSizeIn<uint32_t>(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<uint8_t> halResult(static_cast<size_t>(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<void> Effect::setAndGetVolume(const hidl_vec<uint32_t>& volumes,
uint32_t halDataSize;
std::unique_ptr<uint8_t[]> halData = hidlVecToHal(volumes, &halDataSize);
uint32_t halResultSize = halDataSize;
uint32_t halResult[volumes.size()];
std::vector<uint32_t> 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<uint32_t> result;
if (retval == Result::OK) {
result.setToExternal(&halResult[0], halResultSize);
@@ -581,8 +579,6 @@ Return<void> Effect::getSupportedAuxChannelsConfigs(uint32_t maxConfigs,
}
Return<void> Effect::getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) {
uint32_t halResult[alignedSizeIn<uint32_t>(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<void> Effect::getAuxChannelsConfig(getAuxChannelsConfig_cb _hidl_cb) {
}
Return<Result> Effect::setAuxChannelsConfig(const EffectAuxChannelsConfig& config) {
uint32_t halCmd[alignedSizeIn<uint32_t>(sizeof(uint32_t) + sizeof(channel_config_t))];
std::vector<uint32_t> halCmd(
alignedSizeIn<uint32_t>(sizeof(uint32_t) + sizeof(channel_config_t)), 0);
halCmd[0] = EFFECT_FEATURE_AUX_CHANNELS;
effectAuxChannelsConfigToHal(config, reinterpret_cast<channel_config_t*>(&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<Result> Effect::setAudioSource(AudioSource source) {
@@ -692,12 +689,11 @@ Return<void> Effect::getCurrentConfigForFeature(uint32_t featureId, uint32_t con
Return<Result> Effect::setCurrentConfigForFeature(uint32_t featureId,
const hidl_vec<uint8_t>& configData) {
uint32_t halCmd[alignedSizeIn<uint32_t>(sizeof(uint32_t) + configData.size())];
memset(halCmd, 0, sizeof(halCmd));
std::vector<uint32_t> halCmd(alignedSizeIn<uint32_t>(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<Result> Effect::close() {

View File

@@ -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;

View File

@@ -342,6 +342,7 @@
<xs:enumeration value="AUDIO_USAGE_GAME"/>
<xs:enumeration value="AUDIO_USAGE_VIRTUAL_SOURCE"/>
<xs:enumeration value="AUDIO_USAGE_ASSISTANT"/>
<xs:enumeration value="AUDIO_USAGE_CALL_ASSISTANT"/>
</xs:restriction>
</xs:simpleType>
@@ -370,6 +371,7 @@
<xs:enumeration value="AUDIO_FLAG_NO_MEDIA_PROJECTION"/>
<xs:enumeration value="AUDIO_FLAG_MUTE_HAPTIC"/>
<xs:enumeration value="AUDIO_FLAG_NO_SYSTEM_CAPTURE"/>
<xs:enumeration value="AUDIO_FLAG_CAPTURE_PRIVATE"/>
</xs:restriction>
</xs:simpleType>

View File

@@ -1,4 +1,5 @@
randolphs@google.com
pirozzoj@google.com
twasilczyk@google.com
pfg@google.com
gurunagarajan@google.com
keunyoung@google.com

View File

@@ -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);

View File

@@ -29,10 +29,5 @@ cc_binary {
"liblog",
"libutils",
],
cflags: [
"-DLOG_TAG=\"AudCntrlDrv\"",
"-O0",
"-g",
],
vintf_fragments: ["audiocontrol_manifest.xml"],
}

View File

@@ -0,0 +1,11 @@
<manifest version="1.0" type="device">
<hal format="hidl">
<name>android.hardware.automotive.audiocontrol</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>IAudioControl</name>
<instance>default</instance>
</interface>
</hal>
</manifest>

View File

@@ -25,5 +25,8 @@ cc_test {
static_libs: [
"android.hardware.automotive.audiocontrol@1.0",
],
test_suites: ["general-tests"],
test_suites: [
"general-tests",
"vts",
],
}

View File

@@ -25,11 +25,12 @@
#include <utils/Errors.h>
#include <utils/StrongPointer.h>
#include <android/hardware/automotive/audiocontrol/1.0/types.h>
#include <android/hardware/automotive/audiocontrol/1.0/IAudioControl.h>
#include <android/hardware/automotive/audiocontrol/1.0/types.h>
#include <android/log.h>
#include <VtsHalHidlTargetTestBase.h>
#include <gtest/gtest.h>
#include <hidl/GtestPrinter.h>
#include <hidl/ServiceManagement.h>
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<IAudioControl>(); }
private:
CarAudioControlHidlEnvironment() {}
};
// The main test class for the automotive AudioControl HAL
class CarAudioControlHidlTest : public ::testing::VtsHalHidlTargetTestBase {
public:
class CarAudioControlHidlTest : public ::testing::TestWithParam<std::string> {
public:
virtual void SetUp() override {
// Make sure we can connect to the driver
pAudioControl = ::testing::VtsHalHidlTargetTestBase::getService<IAudioControl>(
CarAudioControlHidlEnvironment::Instance()->
getServiceName<IAudioControl>());
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);

View File

@@ -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,
}

View File

@@ -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<AudioUsage> usage, int32_t zoneId,
bitfield<AudioFocusChange> 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);
};

View File

@@ -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();
};

View File

@@ -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<AudioUsage> usage, int32_t zoneId,
bitfield<AudioFocusChange> 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<AudioUsage> usage, int32_t zoneId);
};

View File

@@ -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",
],
}

View File

@@ -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 <stdio.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include <hidl/HidlTransportSupport.h>
#include <hwbinder/IPCThreadState.h>
#include <private/android_filesystem_config.h>
#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<sp<ICloseHandle>> AudioControl::registerFocusListener(const sp<IFocusListener>& listener) {
LOG(DEBUG) << "registering focus listener";
sp<ICloseHandle> 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<void> 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<void> 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<void> AudioControl::onAudioFocusChange(hidl_bitfield<AudioUsage> usage, int zoneId,
hidl_bitfield<AudioFocusChange> focusChange) {
LOG(INFO) << "Focus changed: " << static_cast<int>(focusChange) << " for usage "
<< static_cast<int>(usage) << " in zone " << zoneId;
return Void();
}
Return<void> AudioControl::debug(const hidl_handle& fd, const hidl_vec<hidl_string>& 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<hidl_string>& 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 <USAGE> <ZONE_ID> <FOCUS_GAIN>: requests audio focus for specified "
"usage (int), audio zone ID (int), and focus gain type (int)\n");
dprintf(fd,
"--abandon <USAGE> <ZONE_ID>: abandons audio focus for specified usage (int) and "
"audio zone ID (int)\n");
}
void AudioControl::cmdRequestFocus(int fd, const hidl_vec<hidl_string>& options) {
if (!checkCallerHasWritePermissions(fd) || !checkArgumentsSize(fd, options, 3)) return;
hidl_bitfield<AudioUsage> 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<AudioFocusChange> 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<hidl_string>& options) {
if (!checkCallerHasWritePermissions(fd) || !checkArgumentsSize(fd, options, 2)) return;
hidl_bitfield<AudioUsage> 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<hidl_string>& 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

View File

@@ -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 <android/hardware/audio/common/6.0/types.h>
#include <android/hardware/automotive/audiocontrol/2.0/IAudioControl.h>
#include <android/hardware/automotive/audiocontrol/2.0/ICloseHandle.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>
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<sp<ICloseHandle>> registerFocusListener(const sp<IFocusListener>& listener);
Return<void> onAudioFocusChange(hidl_bitfield<AudioUsage> usage, int zoneId,
hidl_bitfield<AudioFocusChange> focusChange);
Return<void> setBalanceTowardRight(float value) override;
Return<void> setFadeTowardFront(float value) override;
Return<void> debug(const hidl_handle& fd, const hidl_vec<hidl_string>& options) override;
// Implementation details
AudioControl();
private:
sp<IFocusListener> mFocusListener;
static bool checkArgumentsSize(int fd, const hidl_vec<hidl_string>& 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<hidl_string>& options);
void cmdHelp(int fd) const;
void cmdRequestFocus(int fd, const hidl_vec<hidl_string>& options);
void cmdAbandonFocus(int fd, const hidl_vec<hidl_string>& options);
void dump(int fd);
};
} // namespace android::hardware::automotive::audiocontrol::V2_0::implementation
#endif // ANDROID_HARDWARE_AUTOMOTIVE_AUDIOCONTROL_V2_0_AUDIOCONTROL_H

View File

@@ -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<void> CloseHandle::close() {
const auto wasClosed = mIsClosed.exchange(true);
if (wasClosed) return {};
if (mCallback) mCallback();
return {};
}
} // namespace android::hardware::automotive::audiocontrol::V2_0::implementation

View File

@@ -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 <android-base/macros.h>
#include <android/hardware/automotive/audiocontrol/2.0/ICloseHandle.h>
namespace android::hardware::automotive::audiocontrol::V2_0::implementation {
/** Generic ICloseHandle implementation ignoring double-close events. */
class CloseHandle : public ICloseHandle {
public:
using Callback = std::function<void()>;
/**
* 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<void> close() override;
private:
const Callback mCallback;
std::atomic<bool> mIsClosed = false;
DISALLOW_COPY_AND_ASSIGN(CloseHandle);
};
} // namespace android::hardware::automotive::audiocontrol::V2_0::implementation

View File

@@ -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

View File

@@ -0,0 +1,11 @@
<manifest version="1.0" type="device">
<hal format="hidl">
<name>android.hardware.automotive.audiocontrol</name>
<transport>hwbinder</transport>
<version>2.0</version>
<interface>
<name>IAudioControl</name>
<instance>default</instance>
</interface>
</hal>
</manifest>

View File

@@ -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 <unistd.h>
#include <android-base/logging.h>
#include <hidl/HidlTransportSupport.h>
#include <utils/Errors.h>
#include <utils/StrongPointer.h>
#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<IAudioControl> 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;
}

View File

@@ -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,
};

View File

@@ -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",
],
}

View File

@@ -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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include <hidl/GtestPrinter.h>
#include <stdio.h>
#include <string.h>
#include <hidl/HidlTransportSupport.h>
#include <hwbinder/ProcessState.h>
#include <log/log.h>
#include <utils/Errors.h>
#include <utils/StrongPointer.h>
#include <android/hardware/audio/common/6.0/types.h>
#include <android/hardware/automotive/audiocontrol/2.0/IAudioControl.h>
#include <android/hardware/automotive/audiocontrol/2.0/types.h>
#include <android/log.h>
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<std::string> {
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<IAudioControl> 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<void>, requestAudioFocus,
(hidl_bitfield<AudioUsage> usage, int zoneId,
hidl_bitfield<AudioFocusChange> focusGain));
MOCK_METHOD(Return<void>, abandonAudioFocus, (hidl_bitfield<AudioUsage> 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<FocusListenerMock> listener = new FocusListenerMock();
auto hidlResult = pAudioControl->registerFocusListener(listener);
ASSERT_TRUE(hidlResult.isOk());
sp<FocusListenerMock> listener2 = new FocusListenerMock();
auto hidlResult2 = pAudioControl->registerFocusListener(listener2);
ASSERT_TRUE(hidlResult2.isOk());
const sp<ICloseHandle>& 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);

View File

@@ -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,
}

View File

@@ -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<CanMessageFilter> 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);
};

View File

@@ -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<string> 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<string> 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<InterfaceType> 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);
};

View File

@@ -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);
};

View File

@@ -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);
};

View File

@@ -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();
};

View File

@@ -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",
],
}

View File

@@ -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 <android-base/logging.h>
#include <libnetdevice/can.h>
#include <libnetdevice/libnetdevice.h>
#include <linux/can.h>
#include <linux/can/error.h>
#include <linux/can/raw.h>
namespace android::hardware::automotive::can::V1_0::implementation {
/** Whether to log sent/received packets. */
static constexpr bool kSuperVerbose = false;
Return<Result> CanBus::send(const CanMessage& message) {
std::lock_guard<std::mutex> 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<void> CanBus::listen(const hidl_vec<CanMessageFilter>& filter,
const sp<ICanMessageListener>& listenerCb, listen_cb _hidl_cb) {
std::lock_guard<std::mutex> lck(mIsUpGuard);
if (listenerCb == nullptr) {
_hidl_cb(Result::INVALID_ARGUMENTS, nullptr);
return {};
}
if (!mIsUp) {
_hidl_cb(Result::INTERFACE_DOWN, nullptr);
return {};
}
std::lock_guard<std::mutex> lckListeners(mMsgListenersGuard);
sp<CloseHandle> closeHandle = new CloseHandle([this, listenerCb]() {
std::lock_guard<std::mutex> 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<std::mutex> lck(mIsUpGuard);
CHECK(!mIsUp) << "Interface is still up while being destroyed";
std::lock_guard<std::mutex> 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<std::mutex> 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<wp<ICloseHandle>> listenersToClose;
{
std::lock_guard<std::mutex> 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<std::mutex> lck(mMsgListenersGuard);
CHECK(mMsgListeners.empty()) << "Listeners list wasn't emptied";
}
void CanBus::clearErrListeners() {
std::lock_guard<std::mutex> lck(mErrListenersGuard);
mErrListeners.clear();
}
Return<sp<ICloseHandle>> CanBus::listenForErrors(const sp<ICanErrorListener>& listener) {
if (listener == nullptr) {
return new CloseHandle();
}
std::lock_guard<std::mutex> upLck(mIsUpGuard);
if (!mIsUp) {
listener->onError(ErrorEvent::INTERFACE_DOWN, true);
return new CloseHandle();
}
std::lock_guard<std::mutex> errLck(mErrListenersGuard);
mErrListeners.emplace_back(listener);
return new CloseHandle([this, listener]() {
std::lock_guard<std::mutex> lck(mErrListenersGuard);
std::erase(mErrListeners, listener);
});
}
bool CanBus::down() {
std::lock_guard<std::mutex> 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<CanMessageFilter>& 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<std::mutex> 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<uint8_t>(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<std::mutex> 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

View File

@@ -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 <android-base/unique_fd.h>
#include <android/hardware/automotive/can/1.0/ICanBus.h>
#include <android/hardware/automotive/can/1.0/ICanController.h>
#include <utils/Mutex.h>
#include <atomic>
#include <thread>
namespace android::hardware::automotive::can::V1_0::implementation {
struct CanBus : public ICanBus {
using ErrorCallback = std::function<void()>;
virtual ~CanBus();
Return<Result> send(const CanMessage& message) override;
Return<void> listen(const hidl_vec<CanMessageFilter>& filter,
const sp<ICanMessageListener>& listener, listen_cb _hidl_cb) override;
Return<sp<ICloseHandle>> listenForErrors(const sp<ICanErrorListener>& 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<ICanMessageListener> callback;
hidl_vec<CanMessageFilter> filter;
wp<ICloseHandle> 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<CanMessageListener> mMsgListeners GUARDED_BY(mMsgListenersGuard);
std::mutex mErrListenersGuard;
std::vector<sp<ICanErrorListener>> mErrListeners GUARDED_BY(mErrListenersGuard);
std::unique_ptr<CanSocket> 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

View File

@@ -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 <android-base/logging.h>
#include <libnetdevice/can.h>
#include <libnetdevice/libnetdevice.h>
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

View File

@@ -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

View File

@@ -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 <android-base/logging.h>
#include <libnetdevice/can.h>
#include <libnetdevice/libnetdevice.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <termios.h>
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<uint32_t, std::string> 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<std::string> 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

View File

@@ -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 <linux/serial.h>
#include <linux/tty.h>
#include <net/if.h>
#include <termios.h>
#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

View File

@@ -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 <android-base/logging.h>
#include <libnetdevice/libnetdevice.h>
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

View File

@@ -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

View File

@@ -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 <android-base/logging.h>
#include <android/hidl/manager/1.2/IServiceManager.h>
#include <automotive/filesystem>
#include <fstream>
#include <regex>
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<void> 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<UsbCanIface> 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<something>, 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<std::string> 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<UsbCanIface> findUsbDevice(const hidl_vec<hidl_string>& 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<ICanController::Result> CanController::upInterface(const ICanController::BusConfig& config) {
LOG(VERBOSE) << "Attempting to bring interface up: " << toString(config);
std::lock_guard<std::mutex> 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<CanBus> 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<CanBus> 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<bool> CanController::downInterface(const hidl_string& name) {
LOG(VERBOSE) << "Attempting to bring interface down: " << name;
std::lock_guard<std::mutex> 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

View File

@@ -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 <android/hardware/automotive/can/1.0/ICanController.h>
namespace android::hardware::automotive::can::V1_0::implementation {
struct CanController : public ICanController {
Return<void> getSupportedInterfaceTypes(getSupportedInterfaceTypes_cb _hidl_cb) override;
Return<ICanController::Result> upInterface(const ICanController::BusConfig& config) override;
Return<bool> downInterface(const hidl_string& name) override;
private:
std::mutex mCanBusesGuard;
std::map<std::string, sp<CanBus>> mCanBuses GUARDED_BY(mCanBusesGuard);
};
} // namespace android::hardware::automotive::can::V1_0::implementation

View File

@@ -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 <android-base/logging.h>
#include <libnetdevice/can.h>
#include <libnetdevice/libnetdevice.h>
#include <linux/can.h>
#include <utils/SystemClock.h>
#include <chrono>
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> 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<CanSocket>(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

View File

@@ -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 <android-base/macros.h>
#include <android-base/unique_fd.h>
#include <linux/can.h>
#include <atomic>
#include <chrono>
#include <thread>
namespace android::hardware::automotive::can::V1_0::implementation {
/** Wrapper around SocketCAN socket. */
struct CanSocket {
using ReadCallback = std::function<void(const struct canfd_frame&, std::chrono::nanoseconds)>;
using ErrorCallback = std::function<void(int errnoVal)>;
/**
* 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<CanSocket> 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<bool> mStopReaderThread = false;
std::atomic<bool> mReaderThreadFinished = false;
DISALLOW_COPY_AND_ASSIGN(CanSocket);
};
} // namespace android::hardware::automotive::can::V1_0::implementation

View File

@@ -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<void> CloseHandle::close() {
const auto wasClosed = mIsClosed.exchange(true);
if (wasClosed) return {};
if (mCallback != nullptr) mCallback();
return {};
}
} // namespace android::hardware::automotive::can::V1_0::implementation

View File

@@ -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 <android-base/macros.h>
#include <android/hardware/automotive/can/1.0/ICloseHandle.h>
namespace android::hardware::automotive::can::V1_0::implementation {
/** Generic ICloseHandle implementation ignoring double-close events. */
struct CloseHandle : public ICloseHandle {
using Callback = std::function<void()>;
/**
* 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<void> close() override;
private:
const Callback mCallback;
std::atomic<bool> mIsClosed = false;
DISALLOW_COPY_AND_ASSIGN(CloseHandle);
};
} // namespace android::hardware::automotive::can::V1_0::implementation

View File

@@ -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

View File

@@ -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
---

View File

@@ -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,
},
},
}

File diff suppressed because it is too large Load Diff

View File

@@ -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 <Windows.h>
#else
#include <dirent.h>
#endif
#include <errno.h>
#include "filesystem_common.h"
namespace android::hardware::automotive::filesystem {
namespace detail {
namespace {
#if !defined(_LIBCPP_WIN32API)
template <class DirEntT, class = decltype(DirEntT::d_type)>
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 <class DirEntT>
static file_type get_file_type(DirEntT* ent, long) {
return file_type::none;
}
static pair<string_view, file_type> 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<void> 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<void> 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<void> 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<void> 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<void> 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 */

View File

@@ -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 <array>
#include <chrono>
#include <cstdlib>
#include <climits>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/time.h> // for ::utimes as used in __last_write_time
#include <fcntl.h> /* 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<char, 256> 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<size_t>(ret) < size_with_null) {
result.assign(local_buff.data(), static_cast<size_t>(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<size_t>(ret) + 1;
result.__resize_default_init(size_with_null - 1);
ret = ::vsnprintf(&result[0], size_with_null, msg, args);
_LIBCPP_ASSERT(static_cast<size_t>(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 <class Arg>
Arg const& unwrap(Arg const& a) {
static_assert(!is_class<Arg>::value, "cannot pass class here");
return a;
}
template <class... Args>
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 <class T>
T error_value();
template <>
_LIBCPP_CONSTEXPR_AFTER_CXX11 void error_value<void>() {}
template <>
bool error_value<bool>() {
return false;
}
template <>
uintmax_t error_value<uintmax_t>() {
return uintmax_t(-1);
}
template <>
_LIBCPP_CONSTEXPR_AFTER_CXX11 file_time_type error_value<file_time_type>() {
return file_time_type::min();
}
template <>
path error_value<path>() {
return {};
}
template <class T>
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<T>();
}
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 <class... Args>
T report(const error_code& m_ec, const char* msg, Args const&... args) const {
if (ec) {
*ec = m_ec;
return error_value<T>();
}
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 <class... Args>
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 <class FileTimeT, class TimeT,
bool IsFloat = is_floating_point<typename FileTimeT::rep>::value>
struct time_util_base {
using rep = typename FileTimeT::rep;
using fs_duration = typename FileTimeT::duration;
using fs_seconds = duration<rep>;
using fs_nanoseconds = duration<rep, nano>;
using fs_microseconds = duration<rep, micro>;
static constexpr rep max_seconds =
duration_cast<fs_seconds>(FileTimeT::duration::max()).count();
static constexpr rep max_nsec =
duration_cast<fs_nanoseconds>(FileTimeT::duration::max() -
fs_seconds(max_seconds))
.count();
static constexpr rep min_seconds =
duration_cast<fs_seconds>(FileTimeT::duration::min()).count();
static constexpr rep min_nsec_timespec =
duration_cast<fs_nanoseconds>(
(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_duration>(
fs_nanoseconds(min_nsec_timespec) -
duration_cast<fs_nanoseconds>(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<long long, ratio<3600 * 24 * 365> > Years;
return duration_cast<Years>(fs_seconds(max_seconds)) > Years(250) &&
duration_cast<Years>(fs_seconds(min_seconds)) < Years(-250);
}
return max_seconds >= numeric_limits<TimeT>::max() &&
min_seconds <= numeric_limits<TimeT>::min();
}
static_assert(check_range(), "the representable range is unacceptable small");
#endif
};
template <class FileTimeT, class TimeT>
struct time_util_base<FileTimeT, TimeT, true> {
using rep = typename FileTimeT::rep;
using fs_duration = typename FileTimeT::duration;
using fs_seconds = duration<rep>;
using fs_nanoseconds = duration<rep, nano>;
using fs_microseconds = duration<rep, micro>;
static const rep max_seconds;
static const rep max_nsec;
static const rep min_seconds;
static const rep min_nsec_timespec;
};
template <class FileTimeT, class TimeT>
const typename FileTimeT::rep
time_util_base<FileTimeT, TimeT, true>::max_seconds =
duration_cast<fs_seconds>(FileTimeT::duration::max()).count();
template <class FileTimeT, class TimeT>
const typename FileTimeT::rep time_util_base<FileTimeT, TimeT, true>::max_nsec =
duration_cast<fs_nanoseconds>(FileTimeT::duration::max() -
fs_seconds(max_seconds))
.count();
template <class FileTimeT, class TimeT>
const typename FileTimeT::rep
time_util_base<FileTimeT, TimeT, true>::min_seconds =
duration_cast<fs_seconds>(FileTimeT::duration::min()).count();
template <class FileTimeT, class TimeT>
const typename FileTimeT::rep
time_util_base<FileTimeT, TimeT, true>::min_nsec_timespec =
duration_cast<fs_nanoseconds>((FileTimeT::duration::min() -
fs_seconds(min_seconds)) +
fs_seconds(1))
.count();
template <class FileTimeT, class TimeT, class TimeSpecT>
struct time_util : time_util_base<FileTimeT, TimeT> {
using Base = time_util_base<FileTimeT, TimeT>;
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 <class CType, class ChronoType>
static _LIBCPP_CONSTEXPR_AFTER_CXX11 bool checked_set(CType* out,
ChronoType time) {
using Lim = numeric_limits<CType>;
if (time > Lim::max() || time < Lim::min())
return false;
*out = static_cast<CType>(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<fs_seconds>(tm.time_since_epoch());
auto nsecs = duration_cast<fs_nanoseconds>(tm.time_since_epoch() - secs);
if (nsecs.count() < 0) {
secs = secs + fs_seconds(1);
nsecs = nsecs + fs_seconds(1);
}
using TLim = numeric_limits<TimeT>;
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_duration>(fs_nanoseconds(tm.tv_nsec)));
} else { // tm.tv_sec < 0
auto adj_subsec = duration_cast<fs_duration>(fs_seconds(1) -
fs_nanoseconds(tm.tv_nsec));
auto Dur = fs_seconds(tm.tv_sec + 1) - adj_subsec;
return FileTimeT(Dur);
}
}
template <class SubSecT>
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<fs_seconds>(dur);
auto subsec_dur = duration_cast<fs_nanoseconds>(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<file_time_type, time_t, TimeSpec>;
#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<TimeSpec, 2> 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<microseconds>(nanoseconds(nsec)).count();
return static_cast<int_type>(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<TimeSpec, 2> 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<TimeSpec, 2> 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 */

File diff suppressed because it is too large Load Diff

View File

@@ -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"],
}

View File

@@ -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 <android-base/logging.h>
namespace android::netdevice::impl {
static struct rtattr* nlmsg_tail(struct nlmsghdr* n) {
return reinterpret_cast<struct rtattr*>( //
reinterpret_cast<uintptr_t>(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<uintptr_t>(nlmsg_tail(n)) - reinterpret_cast<uintptr_t>(nest);
nest->rta_len = nestLen;
}
} // namespace android::netdevice::impl

View File

@@ -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 <android-base/macros.h>
#include <linux/rtnetlink.h>
#include <string>
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 <class T, unsigned int BUFSIZE = 128>
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 <class A>
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<struct ifinfomsg> 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

View File

@@ -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 <android-base/logging.h>
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<struct sockaddr*>(&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<struct nlmsghdr*>(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<struct nlmsgerr*>(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

View File

@@ -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 <android-base/macros.h>
#include <android-base/unique_fd.h>
#include <linux/netlink.h>
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 <class T, unsigned int BUFSIZE>
bool send(NetlinkRequest<T, BUFSIZE>& 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

View File

@@ -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 <libnetdevice/libnetdevice.h>
#include "NetlinkRequest.h"
#include "NetlinkSocket.h"
#include "common.h"
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <linux/can.h>
#include <linux/can/error.h>
#include <linux/can/netlink.h>
#include <linux/can/raw.h>
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<struct sockaddr*>(&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<struct ifinfomsg> 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

View File

@@ -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 <android-base/logging.h>
#include <net/if.h>
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

View File

@@ -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 <string>
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

View File

@@ -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 <android-base/unique_fd.h>
#include <string>
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

View File

@@ -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 <optional>
#include <string>
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<bool> 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

View File

@@ -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 <libnetdevice/libnetdevice.h>
#include "NetlinkRequest.h"
#include "NetlinkSocket.h"
#include "common.h"
#include <android-base/logging.h>
#include <linux/can.h>
#include <net/if.h>
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<bool> 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<struct ifinfomsg> 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<struct ifinfomsg> req(RTM_DELLINK, NLM_F_REQUEST);
req.addattr(IFLA_IFNAME, dev);
NetlinkSocket sock(NETLINK_ROUTE);
return sock.send(req) && sock.receiveAck();
}
} // namespace android::netdevice

View File

@@ -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 <android-base/logging.h>
#include <hidl/HidlTransportSupport.h>
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> 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
}

View File

@@ -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,
}

View File

@@ -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<ISomeInterface> iface;
* hidlObject->someMethod(arg1, arg2, hidl_utils::fill(&result, &iface)).assertOk();
* // use result and iface
*/
template <typename... T>
struct fill : public std::function<void(const T&...)> {
/**
* 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<T*...> mTargets;
template <int Pos, typename First>
inline void copy(const First& first) {
*std::get<Pos>(mTargets) = first;
}
template <int Pos, typename First, typename... Rest>
inline void copy(const First& first, const Rest&... rest) {
*std::get<Pos>(mTargets) = first;
copy<Pos + 1, Rest...>(rest...);
}
};
} // namespace android::hardware::automotive::hidl_utils

View File

@@ -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",
],
}

View File

@@ -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 <android-base/logging.h>
#include <android-base/parseint.h>
#include <android/hardware/automotive/can/1.0/ICanController.h>
#include <android/hidl/manager/1.2/IServiceManager.h>
#include <hidl-utils/hidl-utils.h>
#include <libcanhaltools/libcanhaltools.h>
#include <iostream>
#include <string>
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 <bus name> <type> <interface> [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 <bus name>" << 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<ICanController::InterfaceType> 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);
}

View File

@@ -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 <android-base/logging.h>
#include <android/hardware/automotive/can/1.0/ICanBus.h>
#include <android/hardware/automotive/can/1.0/ICanMessageListener.h>
#include <android/hidl/manager/1.2/IServiceManager.h>
#include <hidl-utils/hidl-utils.h>
#include <linux/can.h>
#include <chrono>
#include <iomanip>
#include <iostream>
#include <string>
#include <thread>
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<void> 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<void> 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 <bus name>" << 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<ICanBus> 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<V1_0::ICloseHandle> 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);
}

View File

@@ -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 <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/strings.h>
#include <android/hardware/automotive/can/1.0/ICanBus.h>
#include <android/hidl/manager/1.2/IServiceManager.h>
#include <iostream>
#include <string>
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 <bus name> <can id>#<data>" << 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<ICanBus> 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<V1_0::CanMessage> 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<uint8_t> 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);
}

View File

@@ -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",
],
}

View File

@@ -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 <android-base/logging.h>
#include <android/hardware/automotive/can/1.0/ICanController.h>
#include <libcanhaltools/libcanhaltools.h>
#include <chrono>
#include <thread>
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;
}

View File

@@ -0,0 +1,3 @@
service canhalconfigurator /system/bin/canhalconfigurator
class core
oneshot

View File

@@ -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 <android-base/logging.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/text_format.h>
#include <hidl/HidlTransportSupport.h>
#include <libcanhaltools/libcanhaltools.h>
#include <fstream>
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<std::string> 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<CanBusConfig> 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<std::string, 3> 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<ICanController::BusConfig> 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<ICanController::InterfaceType> 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

Some files were not shown because too many files have changed in this diff Show More