Answering my own question. Hope it helps someone else.
- Usages map to stream types. Usages are a newer concept that are supposed to replace explicit use of streams, but as of Oreo, the system just statically maps the usage to a legacy stream.
- Streams map to strategies.
- Strategies map to (logical) devices.
So for example, if the app chooses usage USAGE_NOTIFICATION_RINGTONE, this gets mapped to stream type STREAM_DTMF. That stream gets mapped to strategy STRATEGY_DTMF.
The magic then happens in frameworks/av/service/audiopolicy/enginedefault/src/Engine.cpp in method getDeviceForStrategyInt(). This method has a load of hand-coded logic for deciding the device for the strategy. So for example, for a test I wanted to block everything but USAGE_NOTIFICATION_RINGTONE from going to USB audio, I put this at the end of that method:
if (strategy != STRATEGY_ACCESSIBILITY) {
if ((device & AUDIO_DEVICE_OUT_USB_DEVICE) ||
(device & AUDIO_DEVICE_OUT_USB_ACCESSORY) ||
(device & AUDIO_DEVICE_OUT_USB_HEADSET)) {
device = AUDIO_DEVICE_NONE;
}
}
As a side note, I found, in the "examples" folder, some files with the extends .pfw (e.g., device_for_strategy_media.pfw) that appear to be a way of defining a strategy to device mapping via configuration. However, our image doesn't actually install any of these files so it's not clear to me if this is functional or to what degree it's functional.