Signal strength in p2p connection with Wifi Direct? - android

I am taking a look at the WifiDirect demo for Android and wanted to know whether it was possible to get something as the RSSI or signal strength between two connected devices.
Sorry if the question makes no sense, I know that one can get RSSI from access points but what I want to know is if this concept of RSSI exists in p2p connections among devices in Wifi Direct.
Thank you for your time.

Note: This answer was true for the API level 14, I don't know if it still applies to the latest Android versions.
A non documented file named WifiP2pPeer exists in the Android code source. It contains some "interesting" lines.
We can see that a RSSI value is hard-coded (mRssi = 60; //TODO: fix), so the feature may not be implemented yet... (Like others in Android 14 regarding WifiP2p).
public class WifiP2pPeer extends Preference {
private static final int[] STATE_SECURED = {R.attr.state_encrypted};
public WifiP2pDevice device;
private int mRssi;
private ImageView mSignal;
private static final int SIGNAL_LEVELS = 4;
public WifiP2pPeer(Context context, WifiP2pDevice dev) {
super(context);
device = dev;
setWidgetLayoutResource(R.layout.preference_widget_wifi_signal);
mRssi = 60; //TODO: fix
}
#Override
protected void onBindView(View view) {
if (TextUtils.isEmpty(device.deviceName)) {
setTitle(device.deviceAddress);
} else {
setTitle(device.deviceName);
}
mSignal = (ImageView) view.findViewById(R.id.signal);
if (mRssi == Integer.MAX_VALUE) {
mSignal.setImageDrawable(null);
} else {
mSignal.setImageResource(R.drawable.wifi_signal);
mSignal.setImageState(STATE_SECURED, true);
}
refresh();
super.onBindView(view);
}
#Override
public int compareTo(Preference preference) {
if (!(preference instanceof WifiP2pPeer)) {
return 1;
}
WifiP2pPeer other = (WifiP2pPeer) preference;
// devices go in the order of the status
if (device.status != other.device.status) {
return device.status < other.device.status ? -1 : 1;
}
// Sort by name/address
if (device.deviceName != null) {
return device.deviceName.compareToIgnoreCase(other.device.deviceName);
}
return device.deviceAddress.compareToIgnoreCase(other.device.deviceAddress);
}
int getLevel() {
if (mRssi == Integer.MAX_VALUE) {
return -1;
}
return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
}
private void refresh() {
if (mSignal == null) {
return;
}
Context context = getContext();
mSignal.setImageLevel(getLevel());
String[] statusArray = context.getResources().getStringArray(R.array.wifi_p2p_status);
setSummary(statusArray[device.status]);
}
}

Related

Agora Raw Audio on Android

My end goal is to visualize the remote audio in a one on one call using the Agora Api. The agora api and available examples are quite vast but I did not find a example that allows me to access the audio as it is streamed in so that I can then get the samples max amplitude and send to a visualizer.The byte array would do just fine.
I have looked through the examples provide at https://github.com/AgoraIO/API-Examples, which seemed promising but I have not been able to solve this. Any help is appreciated.
(Within the API_Example on Github, I have attempted to implement ProcessRawData and AudioRecordService)
Update: The APIExample allows me to grab the raw data as it flows through and that is what I am looking for. The issue arises when I try to duplicate the "ProcessRawData" class in a new project. The call back for the audio observer is never called. I have gone through my code and it matches everything in the example. The only thing I can think of is that the method to import the "lib-raw-data" folder was incorrect. I simply copied the entrire folder 'lib-raw-data' from the example api project and into my own. I then added the library directory to the gradle.settings file as well as the gradle(app) file. Outisde of that, I simply made sure the code matches the example provided.
Below is the most basic form of my application with the imported library "lib-raw-data" as described. I have no errors within Android Studio so I don't know where to look. The example in the github above works, but the same code below does not.
MainActivity.java
public class MainActivity extends AppCompatActivity {
private SessionVideoCall videoCall;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initVideoCall();
}
private void initVideoCall(){
videoCall = new SessionVideoCall(this);
videoCall.setChannelName(#CHANNEL_NAME);
videoCall.attachView();
videoCall.startCall();
}
}
SessionVideoCall.java
public class SessionVideoCall implements MediaDataAudioObserver {
private Handler handler;
private final String TAG = this.getClass().getSimpleName();
private static final int PERMISSION_REQ_ID = 44;
private static final String[] REQUESTED_PERMISSIONS = {
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA,
android.Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
};
private final Activity ACTIVITTY;
private MediaDataObserverPlugin mediaDataObserverPlugin;
private String channelName;
private FrameLayout mLocalContainer;
private FrameLayout mRemoteContainer;
private VideoCanvas mLocalVideo;
private VideoCanvas mRemoteVideo;
private Timer timer;
boolean isVisualizerAttached = true;
public static RtcEngine engine;
// set up engine
public SessionVideoCall(Activity activity){
this.ACTIVITTY = activity;
handler = new Handler(Looper.getMainLooper());
}
// set channel name
public void setChannelName(String channelName){
this.channelName = channelName;
}
public void attachView(){
mLocalContainer = ACTIVITTY.findViewById(R.id.local_video_view_container);
mRemoteContainer = ACTIVITTY.findViewById(R.id.remote_video_view_container);
}
// start call
public void startCall(){
if(hasPermissions())
initEngineAndJoinChannel();
}
// end call
public void endCall(){
removeFromParent(mLocalVideo);
mLocalVideo = null;
removeFromParent(mRemoteVideo);
mRemoteVideo = null;
if (mediaDataObserverPlugin != null) {
mediaDataObserverPlugin.removeAudioObserver(this);
mediaDataObserverPlugin.removeAllBuffer();
}
MediaPreProcessing.releasePoint();
leaveChannel();
}
private void initEngineAndJoinChannel() {
initializeEngine();
setupObserver();
setupAudioConfig();
setupVideoConfig();
setupLocalVideo();
joinChannel();
}
private void initializeEngine() {
try {
engine = RtcEngine.create(ACTIVITTY.getApplicationContext(), ACTIVITTY.getString(R.string.agora_app_id), mRtcEventHandler);
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
throw new RuntimeException("NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e));
}
}
private void setupObserver(){
mediaDataObserverPlugin = MediaDataObserverPlugin.the();
MediaPreProcessing.setCallback(mediaDataObserverPlugin);
MediaPreProcessing.setAudioPlayByteBuffer(mediaDataObserverPlugin.byteBufferAudioPlay);
mediaDataObserverPlugin.addAudioObserver(this);
}
private void setupAudioConfig(){
engine.setChannelProfile(Constants.CHANNEL_PROFILE_LIVE_BROADCASTING);
engine.setClientRole(IRtcEngineEventHandler.ClientRole.CLIENT_ROLE_BROADCASTER);
engine.setDefaultAudioRoutetoSpeakerphone(false);
engine.setEnableSpeakerphone(false);
engine.setPlaybackAudioFrameParameters(4000, 1, RAW_AUDIO_FRAME_OP_MODE_READ_ONLY, 1024);
}
private void setupVideoConfig() {
engine.enableVideo();
engine.setVideoEncoderConfiguration(new VideoEncoderConfiguration(
VideoEncoderConfiguration.VD_640x360,
VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
VideoEncoderConfiguration.STANDARD_BITRATE,
VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT));
}
private void setupLocalVideo() {
SurfaceView view = RtcEngine.CreateRendererView(ACTIVITTY);
view.setZOrderMediaOverlay(true);
mLocalContainer.addView(view);
mLocalVideo = new VideoCanvas(view, VideoCanvas.RENDER_MODE_HIDDEN, 0);
engine.setupLocalVideo(mLocalVideo);
}
private void joinChannel() {
String token = ACTIVITTY.getString(R.string.agora_access_token);
if (TextUtils.isEmpty(token) || TextUtils.equals(token, "#YOUR ACCESS TOKEN#")) {
token = null; // default, no token
}
engine.joinChannel(token, channelName, "Extra Optional Data", 0);
}
private void leaveChannel(){
if (mediaDataObserverPlugin != null) {
mediaDataObserverPlugin.removeAudioObserver(this);
mediaDataObserverPlugin.removeAllBuffer();
}
MediaPreProcessing.releasePoint();
engine.leaveChannel();
if(timer != null)
timer.cancel();
}
private void removeFromParent(VideoCanvas canvas) {
if (canvas != null) {
ViewParent parent = canvas.view.getParent();
if (parent != null) {
ViewGroup group = (ViewGroup) parent;
group.removeView(canvas.view);
//return group;
}
}
//return null;
}
private void setupRemoteVideo(int uid) {
ViewGroup parent = mRemoteContainer;
if (parent.indexOfChild(mLocalVideo.view) > -1) {
parent = mLocalContainer;
}
if (mRemoteVideo != null) {
return;
}
SurfaceView view = RtcEngine.CreateRendererView(ACTIVITTY);
view.setZOrderMediaOverlay(parent == mLocalContainer);
parent.addView(view);
mRemoteVideo = new VideoCanvas(view, VideoCanvas.RENDER_MODE_HIDDEN, uid);
// Initializes the video view of a remote user.
engine.setupRemoteVideo(mRemoteVideo);
}
private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
#Override
public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
super.onJoinChannelSuccess(channel, uid, elapsed);
setupTimer();
Log.d(TAG,"onJoinChannelSuccess: ");
}
#Override
public void onFirstRemoteVideoDecoded(final int uid, int width, int height, int elapsed) {
ACTIVITTY.runOnUiThread(new Runnable() {
#Override
public void run() {
Log.d(TAG,"First remote video decoded, uid: " + (uid & 0xFFFFFFFFL));
setupRemoteVideo(uid);
}
});
}
#Override
public void onUserOffline(final int uid, int reason) {
super.onUserOffline(uid, reason);
// when remote user logs off
}
#Override
public void onUserJoined(int uid, int elapsed) {
super.onUserJoined(uid, elapsed);
Log.i(TAG, "onUserJoined->" + uid);
Log.d(TAG, "user has joined call: " + uid);
handler.post(() ->
{
if (mediaDataObserverPlugin != null) {
mediaDataObserverPlugin.addDecodeBuffer(uid);
}
});
}
#Override
public void onRemoteAudioStateChanged(int uid, int state, int reason, int elapsed)
{
super.onRemoteAudioStateChanged(uid, state, reason, elapsed);
Log.i(TAG, "onRemoteAudioStateChanged->" + uid + ", state->" + state + ", reason->" + reason);
}
};
private boolean hasPermissions(){
return (checkSelfPermission(REQUESTED_PERMISSIONS[0]) &&
checkSelfPermission(REQUESTED_PERMISSIONS[1]) &&
checkSelfPermission(REQUESTED_PERMISSIONS[2]) &&
checkSelfPermission(REQUESTED_PERMISSIONS[3]));
}
private boolean checkSelfPermission(String permission) {
if (ContextCompat.checkSelfPermission(ACTIVITTY, permission) !=
PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(ACTIVITTY, REQUESTED_PERMISSIONS, PERMISSION_REQ_ID);
return false;
}
return true;
}
private void setupTimer(){
timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
if(maxAmplitude > 0)
Log.e(TAG, "Amplitude Greater than 0: " + maxAmplitude);
}
},0,50);
}
#Override
public void onRecordAudioFrame(byte[] data, int audioFrameType, int samples, int bytesPerSample, int channels, int samplesPerSec, long renderTimeMs, int bufferLength) {
Log.e(TAG, "onRecordAudioFrame: ");
}
private int maxAmplitude = 0;
#Override
public void onPlaybackAudioFrame(byte[] data, int audioFrameType, int samples, int bytesPerSample, int channels, int samplesPerSec, long renderTimeMs, int bufferLength) {
if(isVisualizerAttached) {
short[] rawAudio = new short[data.length/2];
ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).asShortBuffer().get(rawAudio);
short amplitude = 0;
for(short num: rawAudio){
if(num > amplitude)
amplitude = num;
}
Log.e(TAG, "onPlaybackAudioFrame: Supposedly we have data -> max: " + amplitude);
}
Log.e(TAG, "onPlaybackAudioFrame:");
}
#Override
public void onPlaybackAudioFrameBeforeMixing(int uid, byte[] data, int audioFrameType, int samples, int bytesPerSample, int channels, int samplesPerSec, long renderTimeMs, int bufferLength) {
Log.e(TAG, "onPlaybackAudioFrameBeforeMixing: ");
}
#Override
public void onMixedAudioFrame(byte[] data, int audioFrameType, int samples, int bytesPerSample, int channels, int samplesPerSec, long renderTimeMs, int bufferLength) {
Log.e(TAG, "onMixedAudioFrame: ");
}
}
Did you give/add permissions in the manifest? Because Agora does not show any runtime error when you don't give/add permissions; instead, your voice call goes blank, and the microphone and speaker buttons, if added, won't do anything. This is what I went through one day dealing with some code on GitHub by Agora (that is, it didn't look up the manifest file).
According to the Agora documentation, the following permissions are required:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
I have discovered where in the API Example (provided by the github repo link above), where to interact with the remote audio data. In the CustomAudioSource class, within the AsyncTask object at the bottom of the file. Had to run on physical device to take advantage of it.
Now I am need of finding the max amplitude of the audio byte array. The formula given within this example is "lengthInByte = sampleRate/1000 × 2 × channels × audio duration (ms)"

Ble with MVVM and Architecture Components, proper way?

I'm working with Bluetooth LE devices and I was thinking about my current approach and best practices. Currently I have an activity which handles the connection and the GattCallbacks and I decided to restructure the code to get an better overview and maintainability cause its quite messy actually.
I found the BleManager from NordicSemiconductor https://github.com/NordicSemiconductor/Android-BLE-Library/
It's an abstraction of the basic steps for connecting with a BLE device and it handles the GattCallbacks + providing an appropriate interface to use it from a service or a ViewModel.
I'd like to use the ViewModel approach but I'm not so familiar with MVC, MVP, MVVM patterns and there are some questions that I still can't reply
This class is extending the BleManager (BlinkyManager.java)
It shows how to make use of the BleManager so I adopted the class and called it ECountBleManager.
EDIT:
The last 6 days I did reaearches especially facing the MVVM pattern and the Architecture Components. Unfortunately there are still a lot of questions that I can't reply myself. But I really want to get better so I made a draft of my current concept. I hope you can help me answering my questions and improving my project.
I'm especially interested in best practices.
Here is my draft:
And here are my class implementations:
ECountActivity.java
public class ECountActivity extends AppCompatActivity {
private ECountViewModel viewModel;
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.detail_view);
// hide magnifier icon
GifImageView customLoader = findViewById(R.id.progressBar);
customLoader.setVisibility(View.GONE);
// Get additional data from previous activity
final BluetoothDevice device = getIntent().getParcelableExtra("device");
initViewModel();
viewModel.connect(device);
}
private void initViewModel() {
viewModel = ViewModelProviders.of(this).get(ECountViewModel.class);
subscribeDataStreams(viewModel);
}
private void subscribeDataStreams(ECountViewModel viewModel) {
viewModel.isDeviceReady().observe(this, deviceReady -> openOptionsFragment());
viewModel.isConnected().observe(this, status -> {
// Todo: ...
});
}
private void openOptionsFragment() {
// load options fragment
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.contentFragment, new OptionsFragment());
ft.commitNow();
}
}
OtaFragment.java
public class OtaFragment extends Fragment implements FolderChooserDialog.FolderCallback,
FileChooserDialog.FileCallback {
private Button partialOtaButton;
private Button fullOtaButton;
private Button submitButton;
private SeekBar mtuSeekBar;
private EditText mtuInput;
private LinearLayout stacklayout;
private Button browseAppButton;
private TextView folderPathText;
private TextView appFileNameText;
private MaterialDialog otaPrepareDialog;
private MaterialDialog otaProgressDialog;
private ECountViewModel viewModel;
private OtaViewModel otaViewModel;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initViewModel();
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// inflate the layout for this fragment
View view = inflater.inflate(R.layout.ota_fragment, container, false);
initViews(view);
return view;
}
#Override
public void onFolderSelection(#NonNull FolderChooserDialog dialog, #NonNull File folder) {
final String otaFolderPath = folder.toString();
otaViewModel.setOtaFolderPath(otaFolderPath);
folderPathText.setText(otaFolderPath.substring(otaFolderPath.lastIndexOf("/")));
// enable app browse
browseAppButton.setClickable(true);
browseAppButton.setEnabled(true);
}
#Override
public void onFolderChooserDismissed(#NonNull FolderChooserDialog dialog) {}
#Override
public void onFileSelection(#NonNull FileChooserDialog dialog, #NonNull File file) {
final String otaAppFilePath = file.toString();
otaViewModel.setOtaAppFilePath(otaAppFilePath);
appFileNameText.setText(otaAppFilePath.substring(otaAppFilePath.lastIndexOf("/")));
// enable submitButton button
submitButton.setClickable(true);
submitButton.setEnabled(true);
}
#Override
public void onFileChooserDismissed(#NonNull FileChooserDialog dialog) {}
private void subscribeDataStreams(ECountViewModel viewModel) {
viewModel.isOtaMode().observe(this, otaMode -> {
otaPrepareDialog.dismiss();
initOtaProgressDialog();
otaProgressDialog.show();
// Todo: how can i get mtu?
viewModel.requestMtu(512);
});
}
private void initViewModel() {
viewModel = ViewModelProviders.of(getActivity()).get(ECountViewModel.class);
otaViewModel = ViewModelProviders.of(getActivity()).get(OtaViewModel.class);
subscribeDataStreams(viewModel);
}
private void initViews(View view) {
// get resources
final Button browseFolderButton = view.findViewById(R.id.browseFolder);
final Button cancelButton = view.findViewById(R.id.ota_cancel);
final SeekBar prioritySeekBar = view.findViewById(R.id.connection_seekBar);
partialOtaButton = view.findViewById(R.id.radio_ota);
fullOtaButton = view.findViewById(R.id.radio_ota_full);
browseAppButton = view.findViewById(R.id.browseApp);
folderPathText = view.findViewById(R.id.folderPathText);
appFileNameText = view.findViewById(R.id.appFileNameText);
stacklayout = view.findViewById(R.id.stacklayout);
submitButton = view.findViewById(R.id.ota_proceed);
mtuSeekBar = view.findViewById(R.id.mtu_seekBar);
mtuInput = view.findViewById(R.id.mtu_value);
// set initial states
mtuSeekBar.setMax(512-23);
mtuSeekBar.setProgress(244);
prioritySeekBar.setMax(2);
prioritySeekBar.setProgress(1);
browseAppButton.setClickable(false);
browseAppButton.setEnabled(false);
submitButton.setClickable(false);
submitButton.setEnabled(false);
mtuInput.setOnEditorActionListener((v, actionId, event) -> {
final Editable mtuText = mtuInput.getText();
if (mtuText != null) {
int mtu = Integer.valueOf(mtuText.toString());
if (mtu < 23) mtu = 23;
if (mtu > 512) mtu = 512;
mtuSeekBar.setProgress(mtu);
viewModel.setMtu(mtu);
}
return false;
});
mtuSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
#Override
public void onStartTrackingTouch(SeekBar seekBar) {}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {}
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mtuInput.setText(String.valueOf(progress));
viewModel.setMtu(progress);
}
});
prioritySeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener(){
#Override
public void onStartTrackingTouch(SeekBar seekBar) {}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {}
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
viewModel.setPriority(progress);
}
});
browseFolderButton.setOnClickListener(v -> new FolderChooserDialog.Builder(getActivity())
.chooseButton(R.string.positiveTextChoose)
.tag("#folder")
.show(getChildFragmentManager()));
browseAppButton.setOnClickListener(v -> new FileChooserDialog.Builder(getActivity())
.initialPath(otaViewModel.getOtaFolderPath())
.extensionsFilter(".ebl")
.tag("#app")
.show(getChildFragmentManager()));
cancelButton.setOnClickListener(v -> Log.i("ota", "cancelButton"));
submitButton.setOnClickListener(v -> {
// disable OTA submitButton button
submitButton.setClickable(false);
submitButton.setEnabled(false);
// init OTA process
viewModel.initOtaMode();
// show OTA preparing dialog
otaPrepareDialog.show();
});
fullOtaButton.setOnClickListener(v -> {
stacklayout.setVisibility(View.VISIBLE);
partialOtaButton.setBackgroundColor(getResources().getColor(R.color.colorPrimary));
fullOtaButton.setBackgroundColor(getResources().getColor(R.color.colorPrimaryDark));
});
partialOtaButton.setOnClickListener(v -> {
stacklayout.setVisibility(View.GONE);
partialOtaButton.setBackgroundColor(getResources().getColor(R.color.colorPrimaryDark));
fullOtaButton.setBackgroundColor(getResources().getColor(R.color.colorPrimary));
});
otaPrepareDialog = new MaterialDialog.Builder(getActivity())
.title(R.string.otaDialogHeaderText)
.content(R.string.waiting)
.progress(true, 0)
.progressIndeterminateStyle(true)
.build();
otaProgressDialog = new MaterialDialog.Builder(getActivity())
.title("test")
.customView(R.layout.ota_progress2, false)
.build();
}
private void initOtaProgressDialog() {
// Todo: ...
}
}
ECountViewModel.java
public class ECountViewModel extends AndroidViewModel implements ECountBleManagerCallbacks {
private final ECountBleManager eCountBleManager;
// Connection states Connecting, Connected, Disconnecting, Disconnected etc.
private final MutableLiveData<String> connectionState = new MutableLiveData<>();
// Flag to determine if the device is connected
private final MutableLiveData<Boolean> isConnected = new MutableLiveData<>();
// Flag to determine if the device is ready
private final MutableLiveData<Void> onDeviceReady = new MutableLiveData<>();
// Flag to determine if the device is in OTA mode
private final MutableLiveData<Void> onOtaMode = new MutableLiveData<>();
public LiveData<Void> isDeviceReady() {
return onDeviceReady;
}
public LiveData<Void> isOtaMode() {
return onOtaMode;
}
public LiveData<String> getConnectionState() {
return connectionState;
}
public LiveData<Boolean> isConnected() {
return isConnected;
}
public ECountViewModel(#NonNull final Application application) {
super(application);
// Initialize the manager
eCountBleManager = new ECountBleManager(getApplication());
eCountBleManager.setGattCallbacks(this);
}
/**
* Connect to peripheral
*/
public void connect(final BluetoothDevice device) {
eCountBleManager.connect(device);
}
/**
* Disconnect from peripheral
*/
private void disconnect() {
eCountBleManager.disconnect();
}
#Override
protected void onCleared() {
super.onCleared();
if (eCountBleManager.isConnected()) {
disconnect();
}
}
#Override
public void onDeviceConnecting(BluetoothDevice device) {
}
#Override
public void onDeviceConnected(BluetoothDevice device) {
isConnected.postValue(true);
}
#Override
public void onDeviceDisconnecting(BluetoothDevice device) {
isConnected.postValue(false);
}
#Override
public void onDeviceDisconnected(BluetoothDevice device) {
isConnected.postValue(false);
}
#Override
public void onLinklossOccur(BluetoothDevice device) {
isConnected.postValue(false);
}
#Override
public void onServicesDiscovered(BluetoothDevice device, boolean optionalServicesFound) {
}
#Override
public void onDeviceReady(BluetoothDevice device) {
onDeviceReady.postValue(null);
}
#Override
public void onOptionalServiceSupported(BluetoothDevice device) {
onOtaMode.postValue(null);
}
#Override
public void onBondingRequired(BluetoothDevice device) {
}
#Override
public void onBonded(BluetoothDevice device) {
}
#Override
public void onError(BluetoothDevice device, String message, int errorCode) {
}
#Override
public void onDeviceNotSupported(BluetoothDevice device) {
disconnect();
}
// delegate call from options fragment to ECountBleManager
public String getDeviceId() {
return BinaryUtils.byteArrayToHexString(eCountBleManager.getDeviceId());
}
// delegate call from ota fragment to ECountBleManager
public void setMtu(final int value) {
eCountBleManager.setMtu(value);
}
public void setPriority(final int value) {
eCountBleManager.setPriority(value);
}
ECountBleManager.java
public class ECountBleManager extends BleManager<BleManagerCallbacks> {
private static final String TAG = ECountBleManager.class.getSimpleName();
private final Handler handler;
private BluetoothGattCharacteristic authCharacteristic;
private BluetoothGattCharacteristic deviceIdCharacteristic;
private BluetoothGattCharacteristic deviceVersionCharacteristic;
private BluetoothGattCharacteristic configIdCharacteristic;
private BluetoothGattCharacteristic configTransmissionIntervalCharacteristic;
private BluetoothGattCharacteristic configKeepAliveIntervalCharacteristic;
private BluetoothGattCharacteristic configRadioModeCharacteristic;
private BluetoothGattCharacteristic configGpsCharacteristic;
private BluetoothGattCharacteristic configRadarCharacteristic;
private BluetoothGattCharacteristic configOperationModeCharacteristic;
private BluetoothGattCharacteristic configLoRaAppEuiCharacteristic;
private BluetoothGattCharacteristic configLoRaAppKeyCharacteristic;
private BluetoothGattCharacteristic configLoRaDeviceEuiCharacteristic;
private BluetoothGattCharacteristic operationCmdCharacteristic;
private BluetoothGattCharacteristic nemeusStatusCharacteristic;
private BluetoothGattCharacteristic gmrStatusCharacteristic;
private BluetoothGattCharacteristic radarStatusCharacteristic;
private BluetoothGattCharacteristic otaControlCharacteristic;
private BluetoothGattCharacteristic otaDataCharacteristic;
private byte[] configTransmissionInterval;
private byte[] configKeepAliveInterval;
private byte[] configRadioMode;
private byte[] configOperationMode;
private byte[] configId;
private byte[] deviceId;
private byte[] deviceVersion;
private byte[] configGps;
private byte[] configRadar;
private byte[] configLoRaAppEui;
private byte[] configLoRaAppKey;
private byte[] configLoRaDeviceEui;
private byte[] operationCmd;
private byte[] nemeusStatus;
private byte[] gmrStatus;
private byte[] radarStatus;
// OTA flags
private boolean isOtaProcessing = false;
private boolean isReconnectRequired = false;
private MutableLiveData<Boolean> isOtaMode = new MutableLiveData<>();
// OTA variables
private int mtu = 512;
private int priority = BluetoothGatt.CONNECTION_PRIORITY_HIGH;
private byte[] otaAppFileStream;
////////////////////////////
public ECountBleManager(Context context) {
super(context);
handler = new Handler();
}
#Override
protected BleManagerGattCallback getGattCallback() {
return gattCallback;
}
#Override
protected boolean shouldAutoConnect() {
return true;
}
/**
* BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc
*/
private final BleManagerGattCallback gattCallback = new BleManagerGattCallback() {
#Override
protected void onDeviceReady() {
super.onDeviceReady();
}
#Override
protected void onOptionalServiceSupported() {
super.onOptionalServiceSupported();
isOtaMode.postValue(true);
}
#Override
protected boolean isOptionalServiceSupported(BluetoothGatt gatt) {
final BluetoothGattService otaService = gatt.getService(DC_UUID.otaService);
otaDataCharacteristic = otaService.getCharacteristic(DC_UUID.otaData);
return otaDataCharacteristic != null;
}
#Override
protected boolean isRequiredServiceSupported(BluetoothGatt gatt) {
final BluetoothGattService dcService = gatt.getService(DC_UUID.dcService);
final BluetoothGattService otaService = gatt.getService(DC_UUID.otaService);
if (dcService == null || otaService == null) return false;
authCharacteristic = dcService.getCharacteristic(DC_UUID.authentication);
deviceIdCharacteristic = dcService.getCharacteristic(DC_UUID.deviceId);
deviceVersionCharacteristic = dcService.getCharacteristic(DC_UUID.deviceVersion);
configIdCharacteristic = dcService.getCharacteristic(DC_UUID.configId);
configTransmissionIntervalCharacteristic = dcService.getCharacteristic(DC_UUID.configTransmissionInterval);
configKeepAliveIntervalCharacteristic = dcService.getCharacteristic(DC_UUID.configKeepAliveInterval);
configRadioModeCharacteristic = dcService.getCharacteristic(DC_UUID.configRadioMode);
configGpsCharacteristic = dcService.getCharacteristic(DC_UUID.configGps);
configRadarCharacteristic = dcService.getCharacteristic(DC_UUID.configRadar);
configOperationModeCharacteristic = dcService.getCharacteristic(DC_UUID.configOperationMode);
configLoRaAppEuiCharacteristic = dcService.getCharacteristic(DC_UUID.configLoRaAppEui);
configLoRaAppKeyCharacteristic = dcService.getCharacteristic(DC_UUID.configLoRaAppKey);
configLoRaDeviceEuiCharacteristic = dcService.getCharacteristic(DC_UUID.configLoRaDeviceEui);
operationCmdCharacteristic = dcService.getCharacteristic(DC_UUID.operationCmd);
nemeusStatusCharacteristic = dcService.getCharacteristic(DC_UUID.nemeusStatus);
gmrStatusCharacteristic = dcService.getCharacteristic(DC_UUID.gmrStatus);
radarStatusCharacteristic = dcService.getCharacteristic(DC_UUID.radarStatus);
otaControlCharacteristic = otaService.getCharacteristic(DC_UUID.otaControl);
return authCharacteristic != null &&
deviceIdCharacteristic != null &&
deviceVersionCharacteristic != null&&
configIdCharacteristic != null &&
configTransmissionIntervalCharacteristic != null &&
configKeepAliveIntervalCharacteristic != null &&
configRadioModeCharacteristic != null &&
configGpsCharacteristic != null &&
configRadarCharacteristic != null &&
configOperationModeCharacteristic != null &&
configLoRaAppEuiCharacteristic != null &&
configLoRaAppKeyCharacteristic != null &&
configLoRaDeviceEuiCharacteristic != null &&
operationCmdCharacteristic != null &&
nemeusStatusCharacteristic != null &&
gmrStatusCharacteristic != null &&
radarStatusCharacteristic != null &&
otaControlCharacteristic != null;
}
#Override
protected Deque<Request> initGatt(BluetoothGatt gatt) {
final LinkedList<Request> requests = new LinkedList<>();
requests.push(Request.readRequest(deviceIdCharacteristic));
requests.push(Request.readRequest(deviceVersionCharacteristic));
requests.push(Request.readRequest(configIdCharacteristic));
requests.push(Request.readRequest(configTransmissionIntervalCharacteristic));
requests.push(Request.readRequest(configKeepAliveIntervalCharacteristic));
requests.push(Request.readRequest(configRadioModeCharacteristic));
requests.push(Request.readRequest(configGpsCharacteristic));
requests.push(Request.readRequest(configRadarCharacteristic));
requests.push(Request.readRequest(configOperationModeCharacteristic));
requests.push(Request.readRequest(configLoRaAppEuiCharacteristic));
requests.push(Request.readRequest(configLoRaAppKeyCharacteristic));
requests.push(Request.readRequest(operationCmdCharacteristic));
requests.push(Request.readRequest(configLoRaDeviceEuiCharacteristic));
requests.push(Request.readRequest(nemeusStatusCharacteristic));
requests.push(Request.readRequest(gmrStatusCharacteristic));
requests.push(Request.readRequest(radarStatusCharacteristic));
// write authentication key to characteristic
requests.push(Request.writeRequest(authCharacteristic));
// perform server authentication
requests.push(Request.readRequest(authCharacteristic));
return requests;
}
#Override
protected void onDeviceDisconnected() {
authCharacteristic = null;
deviceIdCharacteristic = null;
deviceVersionCharacteristic = null;
configIdCharacteristic = null;
configTransmissionIntervalCharacteristic = null;
configKeepAliveIntervalCharacteristic = null;
configRadioModeCharacteristic = null;
configGpsCharacteristic = null;
configRadarCharacteristic = null;
configOperationModeCharacteristic = null;
configLoRaAppEuiCharacteristic = null;
configLoRaAppKeyCharacteristic = null;
configLoRaDeviceEuiCharacteristic = null;
nemeusStatusCharacteristic = null;
gmrStatusCharacteristic = null;
radarStatusCharacteristic = null;
otaDataCharacteristic = null;
}
#Override
protected void onMtuChanged(int mtu) {
super.onMtuChanged(mtu);
ECountBleManager.this.mtu = mtu;
}
#Override
protected void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicRead(gatt, characteristic);
if (characteristic.getUuid().equals(DC_UUID.authentication)) {
byte encryptedData[];
try {
encryptedData = BinaryUtils.encryptByteArray(characteristic.getValue());
} catch (Exception e) {
e.printStackTrace();
return;
}
characteristic.setValue(encryptedData);
} else if (characteristic.getUuid().equals(DC_UUID.deviceId)) {
deviceId = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.deviceVersion)) {
deviceVersion = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configId)) {
configId = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configTransmissionInterval)) {
configTransmissionInterval = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configKeepAliveInterval)) {
configKeepAliveInterval = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configRadioMode)) {
configRadioMode = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configGps)) {
configGps = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configRadar)) {
configRadar = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configOperationMode)) {
configOperationMode = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configLoRaAppEui)) {
configLoRaAppEui = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configLoRaAppKey)) {
configLoRaAppKey = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.configLoRaDeviceEui)) {
configLoRaDeviceEui = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.nemeusStatus)) {
nemeusStatus = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.gmrStatus)) {
gmrStatus = characteristic.getValue();
} else if (characteristic.getUuid().equals(DC_UUID.radarStatus)) {
radarStatus = characteristic.getValue();
}
}
#Override
protected void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
super.onCharacteristicWrite(gatt, characteristic);
if (characteristic.getUuid().equals(DC_UUID.otaControl)) {
final byte[] otaControl = characteristic.getValue();
if (otaControl.length == 1) {
// OTA client initiates the update process
if (otaControl[0] == (byte) 0x00) {
// set OTA process flag
isOtaProcessing = true;
// check whether device is in OTA mode
if (isOtaMode.getValue()) {
// request MTU size
requestMtu(mtu);
// start update process,but ensure MTU size has been requested
handler.postDelayed(() -> uploadOta(), 2000);
} else {
// reconnect to establish OTA mode
isReconnectRequired = true;
// enforces device to reconnect
gatt.disconnect();
}
}
// OTA client finishes the update process
if (otaControl[0] == (byte) 0x03) {
if (isOtaProcessing) { // if device is in OTA mode and update process was successful
isOtaProcessing = false;
disconnect();
} else { // if device is in OTA mode, but update process was not established
// enforces device to reconnect
gatt.disconnect();
}
}
}
}
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
}
};
public byte[] getDeviceId() {
return deviceId;
}
public void setDeviceId(final byte[] value) {
writeCharacteristic(deviceIdCharacteristic,
BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT,
value);
}
// Todo: implement other getters and setters
// Here I have to get the otaAppFilePath which I discovered in OtaFragment
public void uploadOta(final String otaAppFilePath) {
if (otaDataCharacteristic != null) {
otaDataCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
byte[] ebl = null;
try {
FileInputStream fileInputStream = new FileInputStream(otaAppFilePath);
int size = fileInputStream.available();
byte[] temp = new byte[size];
fileInputStream.read(temp);
fileInputStream.close();
ebl = temp;
} catch (Exception e) {
Logger.e(TAG, "Couldn't open file " + e);
}
otaAppFileStream = ebl;
pack = 0;
// start update process in another thread
Thread otaUploadThread = new Thread(() -> otaWriteDataReliable());
otaUploadThread.start();
}
}
private void writeCharacteristic(final BluetoothGattCharacteristic c,
final int writeType,
final byte[] value) {
if (c == null)
return;
c.setWriteType(writeType);
c.setValue(value);
writeCharacteristic(c); // will call the underlying API of BleManager
}
}
The code covers the basic use cases but I'm still not sure how to link the particular components with each other.
While reading about MVVM I noticed that there is always more than one possible solution/approach. I discovered the following questions:
Is the ECountBleManager the right place to store the variables that I got by calling characteristics.getValue() and when yes, should I place the variables that I discover in OtaFragment in it too (that would mean, that I have to forward the values e.g. of mtu to the ECountBleManager)? Consider that I have to access the variables that I discover in OtaFragment and maybe other Fragments.
Where do I store the variables from OtaFragment? In ECountVieModel or in ECountBleManager or do I create an OtaViewModel (but how could I access the ECountBleManager instance that I already created in ECountViewModel within the OtaViewModel?)
How can I access mtu, priority and otaAppFile which were discovered in OtaFragment within the ECountBleManager?
Do I have one ViewModel for every activity and fragment? But how to solve the problem with the ECountBleManager instance, see question 2?
How does the ECountBleManager fit in the MVVM pattern? I would guess it is part of the Model?! But which part? Repository, Interactor, Controller, Mediator?
The code is not less so I'm sorry but you see I'm really try harding and I want to get better. I hope someone can help me with my questions and to improve my code. Thanks in advance!
My 5 cents about BLE and an architecture:
you don't need to mix up the app architecture and BLE layer.
just think about BLE as a data-source/-repository (based on Rx, Coroutines)
split your functionality into several parts: BLE-scanner, BLE-connector, BLE-command-executor. Cover these parts by using the BLE-Fasade.
BLE layer is generally the async stuff (and it's too complicated to make them is 100% testable)
BLE layer in a deep look is the thread-safe handling of bytes. Don't handle it on the Main thread.

BleGattException when writing to a particular characteristic

Writing to a specific characteristic crashes the application and throws the following exception:
Caused by: BleGattException{status=8, bleGattOperation=BleGattOperation{description='CHARACTERISTIC_WRITE'}}
at com.polidea.rxandroidble.internal.connection.RxBleGattCallback.propagateStatusErrorIfGattErrorOccurred(RxBleGattCallback.java:245)
at com.polidea.rxandroidble.internal.connection.RxBleGattCallback.access$100(RxBleGattCallback.java:26)
at com.polidea.rxandroidble.internal.connection.RxBleGattCallback$1.onCharacteristicWrite(RxBleGattCallback.java:110)
at android.bluetooth.BluetoothGatt$1.onCharacteristicWrite(BluetoothGatt.java:407)
at android.bluetooth.IBluetoothGattCallback$Stub.onTransact(IBluetoothGattCallback.java:279)
A connection is established to the device, and other methods that both read and write seem to work fine.
Code being used:
mConnectoin.writeCharacteristic(UUID, bytes)
.observeOn(AndroidSchedulers.mainThread());
My first thought was that perhaps the characteristic does not have a write permission enabled,
but the following log statement for characteristic.getProperties() returns 8, indicating it does in fact have write permissions:
.getCharacteristic(CharacteristicUUID)
.subscribe(new Action1<BluetoothGattCharacteristic>() {
#Override
public void call(BluetoothGattCharacteristic characteristic) {
Log.d(TAG, "characteristic permissions: " + characteristic.getPermissions());
Log.d(TAG, "characteristic properties: " + characteristic.getProperties());
}
});
So what might the issue be?
BleGattException is emitted in BluetoothGattCallback class onCharacteristicWrite() callback method. (it is inside the RxBleGattCallback class).
status comes from the Android OS BLE stack. These are described for instance here: https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/android-5.1.0_r1/stack/include/gatt_api.h
status=8 indicates #define GATT_INSUF_AUTHORIZATION 0x08 - so it seems that you're trying to write a BluetoothCharacteristic that needs an encrypted connection (a paired device).
Unfortunately as for the moment RxAndroidBle doesn't help with pairing the devices.
As #s_noopy has pointed out, there's no specific support for pairing the device inside the library, but there are options for creating your little helper that can assist you before connecting.
RxBluetooth library has support for doing bonds in a reactive way.
I extracted the particular pieces of code and I created a small gist to share (that only does the bonding process). Feel free to fork it or improve it (the code is based on RxBluetooth). Also if someone finds a better way or there's something wrong, just point it out!
The original gist:
Update
I modified the source code of the RxBluetooth (previously named RxBluetoothHelper) as there were a bug when doing unbonding. In Android when you bond you have to listen for the bonding process in a intent (but when you unbond is not necessary, as the system is only removing the stored keys). This updated version addresses this that the previous not. Also if for some reason when you call bond the method returns false, the observable will emit onError. The gist is also updated!
public class BluetoothCompat {
public static boolean createBondCompat(final BluetoothDevice device)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return device.createBond();
} else {
Method method = device.getClass().getMethod("createBond", (Class[]) null);
final Object invoke = method.invoke(device, (Object[]) null);
return (Boolean) invoke;
}
}
public static boolean removeBondCompat(final BluetoothDevice device) throws NoSuchMethodException, InvocationTargetException,
IllegalAccessException {
Method method = device.getClass().getMethod("removeBond", (Class[]) null);
final Object invoke = method.invoke(device, (Object[]) null);
return (Boolean) invoke;
}
private BluetoothCompat() {
throw new AssertionError("No instances!");
}
}
public class RxBluetooth {
private final Context context;
private final BluetoothAdapter adapter;
private static final Observable.Transformer BOND_STATUS_TRANSFORMER = statusObservable -> statusObservable.map(status -> {
switch (status) {
case BluetoothDevice.BOND_NONE:
default:
return BondStatus.NONE;
case BluetoothDevice.BOND_BONDING:
return BondStatus.BONDING;
case BluetoothDevice.BOND_BONDED:
return BondStatus.BONDED;
}
});
public enum BondStatus {
NONE,
BONDING,
BONDED
}
public RxBluetooth(Context context, BluetoothAdapter bluetoothAdapter) {
this.context = context.getApplicationContext();
this.adapter = bluetoothAdapter;
}
public Observable bondStatus(#NonNull final BluetoothDevice device) {
return Observable.defer(() -> Observable.just(device.getBondState()).compose(BOND_STATUS_TRANSFORMER));
}
public Observable bond(#NonNull final BluetoothDevice device) {
return Observable.create(subscriber -> {
bondStatus(device).subscribe(bondStatus -> {
switch (bondStatus) {
case NONE:
observeDeviceBonding(context, device).compose(BOND_STATUS_TRANSFORMER).subscribe(subscriber);
try {
final boolean bonding = BluetoothCompat.createBondCompat(device);
if (!bonding) {
subscriber.onError(new BluetoothBondingException("Can't initiate a bonding operation!"));
}
} catch (Exception e) {
subscriber.onError(new BluetoothIncompatibleBondingException(e));
}
break;
case BONDING:
subscriber.onError(new BluetoothBondingException("device is already in the process of bonding"));
break;
case BONDED:
subscriber.onNext(BondStatus.BONDED);
subscriber.onCompleted();
break;
}
});
});
}
public Observable removeBond(#NonNull final BluetoothDevice device) {
return Observable.defer(() -> {
for (BluetoothDevice bondedDevice : adapter.getBondedDevices()) {
if (bondedDevice.getAddress().equals(device.getAddress())) {
try {
final boolean removeBond = BluetoothCompat.removeBondCompat(device);
if (!removeBond) {
return Observable.error(new BluetoothBondingException("Can't delete the bonding for this device!"));
}
} catch (Exception e) {
return Observable.error(new BluetoothIncompatibleBondingException(e));
}
}
}
return Observable.just(BondStatus.NONE);
});
}
private static Observable observeDeviceBonding(#NonNull final Context context, #NonNull final BluetoothDevice device) {
return observeBroadcast(context, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)).filter(pair -> {
BluetoothDevice bondingDevice = pair.getValue1().getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
return bondingDevice.equals(device);
})
.map(pair1 -> pair1.getValue1().getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1))
.skipWhile(state -> state != BluetoothDevice.BOND_BONDING)
.takeUntil(state -> state == BluetoothDevice.BOND_BONDED || state == BluetoothDevice.BOND_NONE);
}
private static Observable> observeBroadcast(final Context context, final IntentFilter filter) {
return Observable.create(new Observable.OnSubscribe>() {
#Override public void call(Subscriber> subscriber) {
Enforcer.onMainThread();
final BroadcastReceiver receiver = new BroadcastReceiver() {
#Override public void onReceive(Context context, Intent intent) {
subscriber.onNext(Pair.with(context, intent));
}
};
context.registerReceiver(receiver, filter);
subscriber.add(Subscriptions.create(() -> context.unregisterReceiver(receiver)));
}
});
}
}

How to detect if phone is connected to Android Auto

I am making an app that should change its behavior when the phone is connected to an Android Auto. It does not have any Auto capabilities and will not be marketed/submitted as an Android Auto app.
Is there a way to detect if the phone is connected to an Android Auto? I know it is possible for Auto media apps to detect their connection status via a BroadcastReceiver registered for com.google.android.gms.car.media.STATUS action. The documentation is not 100% clear, so will this reliably work also for all other non-Auto apps?
Edit: with Android 12, the solution doesn't work anymore and instead, it's better to use CarConnection API documented here
I know this is an old thread but since it comes first in Google, here is the answer from another question
public static boolean isCarUiMode(Context c) {
UiModeManager uiModeManager = (UiModeManager) c.getSystemService(Context.UI_MODE_SERVICE);
if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) {
LogHelper.d(TAG, "Running in Car mode");
return true;
} else {
LogHelper.d(TAG, "Running on a non-Car mode");
return false;
}
}
Configuration.UI_MODE_TYPE_CAR is not working on Anroid 12. You can use CarConnection API in the androidx.car.app:app library. But that is too heavy to import entire library only for car connections if you don't need other features.
So I write a piece of code base on the CarConnection to detect Android Auto connection, as below:
class AutoConnectionDetector(val context: Context) {
companion object {
const val TAG = "AutoConnectionDetector"
// columnName for provider to query on connection status
const val CAR_CONNECTION_STATE = "CarConnectionState"
// auto app on your phone will send broadcast with this action when connection state changes
const val ACTION_CAR_CONNECTION_UPDATED = "androidx.car.app.connection.action.CAR_CONNECTION_UPDATED"
// phone is not connected to car
const val CONNECTION_TYPE_NOT_CONNECTED = 0
// phone is connected to Automotive OS
const val CONNECTION_TYPE_NATIVE = 1
// phone is connected to Android Auto
const val CONNECTION_TYPE_PROJECTION = 2
private const val QUERY_TOKEN = 42
private const val CAR_CONNECTION_AUTHORITY = "androidx.car.app.connection"
private val PROJECTION_HOST_URI = Uri.Builder().scheme("content").authority(CAR_CONNECTION_AUTHORITY).build()
}
private val carConnectionReceiver = CarConnectionBroadcastReceiver()
private val carConnectionQueryHandler = CarConnectionQueryHandler(context.contentResolver)
fun registerCarConnectionReceiver() {
context.registerReceiver(carConnectionReceiver, IntentFilter(ACTION_CAR_CONNECTION_UPDATED))
queryForState()
}
fun unRegisterCarConnectionReceiver() {
context.unregisterReceiver(carConnectionReceiver)
}
private fun queryForState() {
carConnectionQueryHandler.startQuery(
QUERY_TOKEN,
null,
PROJECTION_HOST_URI,
arrayOf(CAR_CONNECTION_STATE),
null,
null,
null
)
}
inner class CarConnectionBroadcastReceiver : BroadcastReceiver() {
// query for connection state every time the receiver receives the broadcast
override fun onReceive(context: Context?, intent: Intent?) {
queryForState()
}
}
internal class CarConnectionQueryHandler(resolver: ContentResolver?) : AsyncQueryHandler(resolver) {
// notify new queryed connection status when query complete
override fun onQueryComplete(token: Int, cookie: Any?, response: Cursor?) {
if (response == null) {
Log.w(TAG, "Null response from content provider when checking connection to the car, treating as disconnected")
notifyCarDisconnected()
return
}
val carConnectionTypeColumn = response.getColumnIndex(CAR_CONNECTION_STATE)
if (carConnectionTypeColumn < 0) {
Log.w(TAG, "Connection to car response is missing the connection type, treating as disconnected")
notifyCarDisconnected()
return
}
if (!response.moveToNext()) {
Log.w(TAG, "Connection to car response is empty, treating as disconnected")
notifyCarDisconnected()
return
}
val connectionState = response.getInt(carConnectionTypeColumn)
if (connectionState == CONNECTION_TYPE_NOT_CONNECTED) {
Log.i(TAG, "Android Auto disconnected")
notifyCarDisconnected()
} else {
Log.i(TAG, "Android Auto connected")
notifyCarConnected()
}
}
}
}
This solution works on android 6~12. If you need to detect car connection status on android 5, use the Configuration.UI_MODE_TYPE_CAR solution.
Recomendation G.Zxuan work perfect, but we must add in dependecies "androidx.car.app:app-projected:1.1.0" in build.gradle
See https://developer.android.com/training/cars/apps#car-connection
val Context.isAndroidAutoConnected: LiveData<Boolean>
get() = CarConnection(this).type
.map { it == CarConnection.CONNECTION_TYPE_PROJECTION }
app/build.gradle:
dependencies {
implementation 'androidx.car.app:app:1.1.0'
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
}
#G.Zxuan 's Kotlin solution works great. I've transferred it into Java and added a listener interface. Tested on Android 12
public class AutoConnectionDetector {
private final Context context;
private static String TAG = "AutoConnectionDetector";
private final CarConnectionBroadcastReceiver carConnectionReceiver = new CarConnectionBroadcastReceiver();
private final CarConnectionQueryHandler carConnectionQueryHandler;
// columnName for provider to query on connection status
private static final String CAR_CONNECTION_STATE = "CarConnectionState";
// auto app on your phone will send broadcast with this action when connection state changes
private final String ACTION_CAR_CONNECTION_UPDATED = "androidx.car.app.connection.action.CAR_CONNECTION_UPDATED";
// phone is not connected to car
private static final int CONNECTION_TYPE_NOT_CONNECTED = 0;
// phone is connected to Automotive OS
private final int CONNECTION_TYPE_NATIVE = 1;
// phone is connected to Android Auto
private final int CONNECTION_TYPE_PROJECTION = 2;
private final int QUERY_TOKEN = 42;
private final String CAR_CONNECTION_AUTHORITY = "androidx.car.app.connection";
private final Uri PROJECTION_HOST_URI = new Uri.Builder().scheme("content").authority(CAR_CONNECTION_AUTHORITY).build();
public interface OnCarConnectionStateListener {
void onCarConnected();
void onCarDisconnected();
}
private static OnCarConnectionStateListener listener;
public void setListener(OnCarConnectionStateListener listener) {
AutoConnectionDetector.listener = listener;
}
public AutoConnectionDetector(Context context) {
this.context = context;
carConnectionQueryHandler = new CarConnectionQueryHandler(context.getContentResolver());
}
public void registerCarConnectionReceiver() {
context.registerReceiver(carConnectionReceiver, new IntentFilter(ACTION_CAR_CONNECTION_UPDATED));
queryForState();
Log.i(TAG, "registerCarConnectionReceiver: ");
}
public void unRegisterCarConnectionReceiver() {
context.unregisterReceiver(carConnectionReceiver);
Log.i(TAG, "unRegisterCarConnectionReceiver: ");
}
private void queryForState() {
String[] projection = {CAR_CONNECTION_STATE};
carConnectionQueryHandler.startQuery(
QUERY_TOKEN,
null,
PROJECTION_HOST_URI,
projection,
null,
null,
null
);
}
private static void notifyCarConnected() {
listener.onCarConnected();
}
private static void notifyCarDisconnected() {
listener.onCarDisconnected();
}
class CarConnectionBroadcastReceiver extends BroadcastReceiver {
// query for connection state every time the receiver receives the broadcast
#Override
public void onReceive(Context context, Intent intent) {
queryForState();
}
}
private static class CarConnectionQueryHandler extends AsyncQueryHandler {
public CarConnectionQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
#Override
protected void onQueryComplete(int token, Object cookie, Cursor response) {
if (response == null) {
Log.w(TAG, "Null response from content provider when checking connection to the car, treating as disconnected");
notifyCarDisconnected();
return;
}
int carConnectionTypeColumn = response.getColumnIndex(CAR_CONNECTION_STATE);
if (carConnectionTypeColumn < 0) {
Log.w(TAG, "Connection to car response is missing the connection type, treating as disconnected");
notifyCarDisconnected();
return;
}
if (!response.moveToNext()) {
Log.w(TAG, "Connection to car response is empty, treating as disconnected");
notifyCarDisconnected();
return;
}
int connectionState = response.getInt(carConnectionTypeColumn);
if (connectionState == CONNECTION_TYPE_NOT_CONNECTED) {
Log.i(TAG, "Android Auto disconnected");
notifyCarDisconnected();
} else {
Log.i(TAG, "Android Auto connected");
Log.i(TAG, "onQueryComplete: " + connectionState);
notifyCarConnected();
}
}
}
}

How to transfer objects between Android and PC using Bluetooth and ObjectOutputStream and ObjectInputStream?

I have met a problem when implementing communication between PC and android using BT. I have an application which works correctly on WIFI communication, using standard java Socket, and its streams. I am trying to add Bt communication. Android side code for opening streams uses standard BluetoothSocket (communication is established, and it's ok):
mOos = new ObjectOutputStream(btSocket.getOutputStream());
mOos.flush();
mOis = new ObjectInputStream(btSocket.getInputStream());
On PC side I use Bluecove 2.1.
mOos = new ObjectOutputStream(mStreamConn.openOutputStream());
mOos .flush();
mOis = new ObjectInputStream(mStreamConn.openInputStream());
Streams are initialized properly. I am sending initial message from android to PC
protected synchronized void sendAwaitingMsg() throws IOException {
Message msg;
while((msg = mOutgoingMsgQueue.poll()) != null) {
mOos.writeObject(msg);
}
mOos.flush();
}
And then try to read it on PC side
protected void getIncomingMsg() throws IOException, ClassNotFoundException {
if(mOis.available() > 0) {
Message msg = (Message)mOis.readObject();
if(msg.mControlHeader > 0) {
mKeepRunning = false;
} else {
msg.setHandlerId(mId);
mConnectionManager.acceptNewMessage(msg);
}
}
}
But mOis.available() is always 0 which means that it does not receive send message. My Message object class:
public class Message extends LinkedHashMap implements Serializable, Comparable {
static final long serialVersionUID = 10275539472837495L;
protected long mHandlerId;
protected int mType;
protected int mPriority;
public int mControlHeader = 0;
public int getType() {
return mType;
}
public void setType(int type) {
this.mType = type;
}
public long getHandlerId() {
return mHandlerId;
}
public void setHandlerId(long handlerId) {
this.mHandlerId = handlerId;
}
public int getPriority() {
return mPriority;
}
public void setPriority(int priority) {
mPriority = priority;
}
#Override
public int compareTo(Object o) {
return mPriority - ((Message)o).mPriority;
}
}
The same operations, on standard java socket, and network communication works like a charm. Where is the problem?
I had similar troubles with bluetooh communication between android and PC. I finally found some info about bluecove (implementation of bluetooth libraries in java) and a great commented example here in the following link:
http://fivedots.coe.psu.ac.th/~ad/jg/blue4/blueCoveEcho.pdf
Here there's another example but without using bluecove:
http://fivedots.coe.psu.ac.th/~ad/jg/blue1/blueEcho.pdf
I hope it helps you.

Categories

Resources