How to use CarProjectionManager
Everyone, where you are going, can take a copilot with you by using Android Auto, Apple CarPlay, or other software. The Android Automotive Operating System is able to help you to integrate any mirroring application.
CarProjectionManager allows applications implementing projection to register/unregister themselves with the projection manager and listen for voice notifications.
Within CarProjectionManager we have ProjectionStatusListener where we are receiving data as soon as a new device is connected to the system and it is projection capable.
It looks like this:
What is the projection state:
The projection state could have 4 states based on its availability:
/** This state indicates that projection is not actively running and no compatible mobile
* devices available. *
public static final int PROJECTION_STATE_INACTIVE = 0;/** At least one phone connected and ready to project. */
public static final int PROJECTION_STATE_READY_TO_PROJECT = 1;/** Projecting in the foreground */
public static final int PROJECTION_STATE_ACTIVE_FOREGROUND = 2;/** Projection is running in the background */
public static final int PROJECTION_STATE_ACTIVE_BACKGROUND = 3;
More details about the Projection Status object you can find here.
You may have to take into consideration from Projection Status the possible available transports:
public static final int PROJECTION_TRANSPORT_NONE = 0;
public static final int PROJECTION_TRANSPORT_USB = 1;
public static final int PROJECTION_TRANSPORT_WIFI = 2;
How does it work
Taking into consideration the code displayed above (part of the AOSP platform) I will go describe the following steps that we did to be able to fully explain the whole functionality of the Projection Status Listener:
- start the system without having any device connected -> empty list
- plugin an Android device via USB -> list with one element
- connect via Bluetooth (do the pairing process) -> list with two elements
- start the wireless projection on one of the connected devices
- put the projection in the background
- stop the projection
No device connected
state: 0package: nulldetails: []
The projection state is inactive and the list is empty because we don’t have devices connected. This is the same reason for null package.
One device connected via USB
state: 1package: com.google.android.embedded.projectiondetails: [ProjectionStatus{mPackageName='com.google.android.embedded.projection', mState=1, mTransport=0, mConnectedMobileDevices=[MobileDevice{mId=0, mName='Google Pixel', mAvailableTransports=[1], mProjecting=false, (has extras)}]}]
The connected device is ready to project and the purpose of the package is to highlight the type of the connected device is an Android one. The only available transport is USB, the mAvailableTransports list has only one element (1 = PROJECTION_TRANSPORT_USB). In case that the actual device is connected with both options, USB and Bluetooth, then the list will contain 2 elements, such as:
[ProjectionStatus{mPackageName='com.google.android.embedded.projection', mState=1, mTransport=0, mConnectedMobileDevices=[MobileDevice{mId=0, mName='Google Pixel', mAvailableTransports=[1, 2], mProjecting=false, (has extras)}]}]
One device already connected and add a new device via Bluetooth
state: 1package: com.google.android.embedded.projectiondetails: [ProjectionStatus{mPackageName='com.google.android.embedded.projection', mState=1, mTransport=0, mConnectedMobileDevices=[MobileDevice{mId=1, mName='Nexus 5X', mAvailableTransports=[2], mProjecting=false, (has extras)}, MobileDevice{mId=0, mName='Google Pixel', mAvailableTransports=[1], mProjecting=false, (has extras)}]}]
At least one connected device is ready to project, the reason why the state is 1 (READY_TO_PROJECT). Because we have 2 connected devices the details list contains 2 elements, Nexus 5X and Google Pixel. The content of the details list says that the Nexus 5X is connected via Bluetooth and the Google Pixel via USB.
mTransport = 0 means PROJECTION_TRANSPORT_NONE and this one is received when a projection is not actively running.
Start the projection
state: 2package: com.google.android.embedded.projectiondetails: [ProjectionStatus{mPackageName='com.google.android.embedded.projection',mState=2, mTransport=2, mConnectedMobileDevices=[MobileDevice{mId=1, mName='Nexus 5X',mAvailableTransports=[2], mProjecting=true, (has extras)}, MobileDevice{mId=0,mName='Google Pixel', mAvailableTransports=[1], mProjecting=false, (has extras)}]}]
The projection has been started on the Nexus 5X, therefore the current state is 2=PROJECTION_STATE_ACTIVE_FOREGROUND because one of the devices that are part of the details list has the attribute mProjecting set on TRUE.
The mTransport = 2 means PROJECTION_TRANSPORT_WIFI and this one is received when a projection is actively running on WIFI.
Changing the projection state from foreground to background
state: 3package: com.google.android.embedded.projectiondetails: [ProjectionStatus{mPackageName='com.google.android.embedded.projection', mState=3, mTransport=2, mConnectedMobileDevices=[MobileDevice{mId=1, mName='Nexus 5X', mAvailableTransports=[2], mProjecting=true, (has extras)}, MobileDevice{mId=0, mName='Google Pixel', mAvailableTransports=[1], mProjecting=false, (has extras)}]}]
We have an ongoing projection that has been moved to the background. The state right now is 3 = PROJECTION_STATE_ACTIVE_BACKGROUND. The mProjecting flag is still set on TRUE and also the mTransport value is not changed (PROJECTION_TRANSPORT_WIFI).
Stop the projection
state: 1package: com.google.android.embedded.projectiondetails: [ProjectionStatus{mPackageName='com.google.android.embedded.projection', mState=1, mTransport=0, mConnectedMobileDevices=[MobileDevice{mId=1, mName='Nexus 5X', mAvailableTransports=[2], mProjecting=false, (has extras)}, MobileDevice{mId=0, mName='Google Pixel', mAvailableTransports=[1], mProjecting=false, (has extras)}]}]
The state has been changed from PROJECTION_STATE_ACTIVE_FOREGROUND / PROJECTION_STATE_ACTIVE_BACKGROUND into PROJECTION_STATE_READY_TO_PROJECT because there is no device anymore with the flag mProjecting = TRUE.
The expectation when we have more than one package available
[ProjectionStatus{mPackageName='com.google.android.embedded.projection', mState=1, mTransport=0, mConnectedMobileDevices=[MobileDevice{mId=0, mName='LGE Nexus 5X', mAvailableTransports=[1], mProjecting=false, (has extras)}]}, ProjectionStatus{mPackageName='another.projection.package', mState=1, mTransport=1, mConnectedMobileDevices=[MobileDevice{mId=1007, mName='Marcel's Device, mAvailableTransports=[1], mProjecting=false, (has extras)}]}]
The status, the transport, and the list of connected devices will be based on a package.
Note:
If a device is unplugged or disconnected then the details list will not contain it anymore. In the case when only one device is connected we will end up having an empty list.
Create a new Projection Status object
By adding a new Projection Status object within the list, the only thing that you have to do is the following
Create a Projection Status object. A simple example to create an empty list using Java code could be:
pivate ProjectionStatus createProjectionStatus(int event) {
return ProjectionStatus
.builder(PACKAGE_NAME, event)
.setProjectionTransport(
ProjectionStatus.PROJECTION_TRANSPORT_NONE
)
.build();
}
How to deal with different Bluetooth constraints
For different technologies or OEM constraints, you may have to apply some Bluetooth policies when a projection is ongoing, and go back to the previous state when the projection is stopped.
For this type of constraint, we have provided by CarProjectionManager a power mechanism to do, inhibit profile when we want to disconnect a profile and release inhibit when we want to go back to the previous state. Let's have a look over these methods:
example to disable the HFP profile for a device identified by MAC Address using Kotlin code:
private fun disableHfpDevices(macAddress: String?) {
logDebug("disableHfp for device: ${macAddress}")
try {
val device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress) carProjectionManager.requestBluetoothProfileInhibit(device, 16) // 16 = HFP
} catch (e: IllegalArgumentException) {
logDebug("disableHfp for $macAddress --- ERROR --- $e")
}
}
In order to be able to integrate any mirroring application first, you must have a contract with a framework provider. For Android Auto, you must have a contract with Google in order to receive the Android Auto apk and for Apple CarPlay you must have a collaboration ongoing to be able to have access to their framework. As soon as a contract is signed you will receive documentation with details about implementation details and expectations in order to pass the Google & Apple certification.
Just code it!