Related
This question already has answers here:
Service crashes with nullpointerexception at onCreate
(2 answers)
Cant run background service
(3 answers)
Closed 4 years ago.
I'm trying to build a music app in Android, although I feel ridiculously newbie sometimes. I have researched a lot, tried my best at getting the hang of how MediaPlayer, MediaBrowserCompat / MediaBrowserServiceCompat, MediaController and Services work, tens of unfortunately old tutorials about how to build one.
My biggest issue was that most of them tended to use the IBinder functionality and intents to bind and start the musicPlaybackService, while google's documentation used these MediaBrowser and MediaBrowserService APIs, both approaches new and honestly quite hard and overwhelming for me.
I've learned a lot so far, but it was hard. The two slightly better tutorials I've found are
https://www.sitepoint.com/a-step-by-step-guide-to-building-an-android-audio-player-app/, and https://code.tutsplus.com/tutorials/background-audio-in-android-with-mediasessioncompat--cms-27030 and they use the first and the second approach. My version of the app is what I managed to make by putting together all the pieces I've learned.
A lot of bugs and issues I managed to figure out on my own, but I've encountered a NullPointer exception that I simply don't know how to solve. The debugger is weird as well on this one, it feels almost like every time the error comes from another place of the program; sometimes it stops at the breakpoints I put, and then running the debugger again with the exact same code and breakpoints, it skips them and goes straight into the Runtime error.
This is my Manifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ecebuc.gesmediaplayer">
<permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".MediaPlaybackService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<receiver android:name="android.support.v4.media.session.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
</application>
Then this is my MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private final int REQUEST_CODE_GESPLAYER_EXTERNAL_STORAGE = 101;
private static final int STATE_PAUSED = 0;
private static final int STATE_PLAYING = 1;
private static int currentState;
private MediaBrowserCompat gesMediaBrowser;
private MediaControllerCompat gesMediaController;
public MediaControllerCompat.TransportControls gesPlaybackController;
private Button playPauseToggleButton;
private MediaBrowserCompat.ConnectionCallback mediaBrowserCallbacks = new MediaBrowserCompat.ConnectionCallback() {
#Override
public void onConnected() {
super.onConnected();
try {
//create the media controller and register the callbacks to stay in sync
gesMediaController = new MediaControllerCompat(MainActivity.this, gesMediaBrowser.getSessionToken());
gesMediaController.registerCallback(mediaControllerCallbacks);
//save the controller and define the easy access transport controls in the object
MediaControllerCompat.setMediaController(MainActivity.this, gesMediaController);
gesPlaybackController = gesMediaController.getTransportControls();
//Display initial state
MediaMetadataCompat metadata = gesMediaController.getMetadata();
PlaybackStateCompat pbState = gesMediaController.getPlaybackState();
} catch( RemoteException e ) {
}
}
#Override
public void onConnectionSuspended() {
// The Service has crashed. Disable transport controls until it automatically reconnects
}
#Override
public void onConnectionFailed() {
// The Service has refused our connection
Log.d("onConnectionFail: ", "the service hasn't been able to connect");
}
};
private MediaControllerCompat.Callback mediaControllerCallbacks = new MediaControllerCompat.Callback() {
#Override
public void onMetadataChanged(MediaMetadataCompat metadata) {
super.onMetadataChanged(metadata);
}
#Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
super.onPlaybackStateChanged(state);
if( state == null ) {
Log.d("onPlaybackChange: ", "the state is null");
Toast.makeText(MainActivity.this,
"onPlaybackStateChange: the state is null",
Toast.LENGTH_SHORT)
.show();
return;
}
switch( state.getState() ) {
case PlaybackStateCompat.STATE_PLAYING: {
currentState = STATE_PLAYING;
break;
}
case PlaybackStateCompat.STATE_PAUSED: {
currentState = STATE_PAUSED;
break;
}
}
}
#Override
public void onSessionDestroyed(){
// Override to handle the session being destroyed
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//grab the buttons for media playback control
playPauseToggleButton = (Button)findViewById(R.id.playPause_btn);
//request permissions for external storage
if (ContextCompat.checkSelfPermission(this,
android.Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// Permission have not been granted
ActivityCompat.requestPermissions(this,
new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_CODE_GESPLAYER_EXTERNAL_STORAGE);
}
else{
//permissions have already been granted
}
//initiate connection to the MediaPlaybackService through MediaBrowser
gesMediaBrowser = new MediaBrowserCompat(this,
new ComponentName(this, MediaPlaybackService.class),
mediaBrowserCallbacks, getIntent().getExtras());
gesMediaBrowser.connect();
//Attach listeners to them
playPauseToggleButton.setOnClickListener(this);
// space here for other buttons
// sapce here for other buttons
}
#Override
protected void onStart() {
super.onStart();
//gesMediaBrowser.connect();
}
/*protected void onStop() {
super.onStop();
// (see "stay in sync with the MediaSession")
if( gesMediaController.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING ) {
gesPlaybackController.pause();
}
gesMediaBrowser.disconnect();
}*/
#Override
protected void onDestroy() {
super.onDestroy();
/*if (gesMediaController != null) {
gesMediaController.unregisterCallback(mediaControllerCallbacks);
gesMediaController = null;
}
if(gesMediaBrowser != null && gesMediaBrowser.isConnected()) {
gesMediaBrowser.disconnect();
gesMediaBrowser = null;
}*/
if( gesMediaController.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING ) {
gesPlaybackController.pause();
}
gesMediaBrowser.disconnect();
}
#Override
public void onClick(View v) {
switch (v.getId()){
case R.id.playPause_btn:
//has to be dealt with accordingly, based on the current state of mediaplayer
int currentState = gesMediaController.getPlaybackState().getState();
if(currentState == PlaybackStateCompat.STATE_PLAYING) {
gesPlaybackController.pause();
} else {
//gesPlaybackController.play();
gesPlaybackController.playFromMediaId(String.valueOf(R.raw.warner_tautz_off_broadway), null);
}
break;
}
#Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_GESPLAYER_EXTERNAL_STORAGE: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "onRequestPermissionResult: granted", Toast.LENGTH_SHORT).show();
} else {
//close the app if permissions aren't granted
//Toast.makeText(this, "onRequestPermissionResult: denied", Toast.LENGTH_SHORT).show();
finish();
}
return;
}
// other 'case' lines to check for other
// permissions this app might request.
}
}
And then the playbackService.java
public class MediaPlaybackService extends MediaBrowserServiceCompat implements
MediaPlayer.OnCompletionListener,
AudioManager.OnAudioFocusChangeListener {
public static final String COMMAND_EXAMPLE = "command_example";
public static boolean isServiceStarted = false;
/*public int audioIndex;
public ArrayList<Audio> audioList;
public Audio activeAudio;*/
private MediaPlayer gesMediaPlayer;
private MediaSessionCompat gesMediaSession;
private int pausedPosition;
//-------------------------------------Lifecycle methods--------------------------------------//
#Override
public void onCreate() {
super.onCreate();
Log.d("onCreate: ", "Service created");
initMediaPlayer();
initMediaSession();
callStateListener();
registerNoisyReceiver();
// Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player
PlaybackStateCompat.Builder playbackStateBuilder = new PlaybackStateCompat.Builder()
.setActions(
PlaybackStateCompat.ACTION_PLAY |
PlaybackStateCompat.ACTION_PLAY_PAUSE);
gesMediaSession.setPlaybackState(playbackStateBuilder.build());
}
#Override
public void onDestroy() {
super.onDestroy();
if (gesMediaPlayer != null) {
gesMediaPlayer.stop();
gesMediaPlayer.release();
}
//Disable the PhoneStateListener
if (phoneStateListener != null) {
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
}
removeAudioFocus();
unregisterReceiver(becomingNoisyReceiver);
//clear cached playlist
//new StorageUtils(getApplicationContext()).clearCachedAudioPlaylist();
NotificationManagerCompat.from(this).cancel(1);
stopSelf();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("onStartCommand: ", "Service has been started");
MediaButtonReceiver.handleIntent(gesMediaSession, intent);
return super.onStartCommand(intent, flags, startId);
}
#Override
public void onCompletion(MediaPlayer mediaPlayer) {
if( gesMediaPlayer != null ) {
gesMediaPlayer.release();
}
}
//----------------------------------------Initialisers----------------------------------------//
private void initMediaPlayer() {
gesMediaPlayer = new MediaPlayer();
gesMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
gesMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
gesMediaPlayer.setVolume(1.0f, 1.0f);
/*try {
//sets songPath as data source for media player
//gesMediaPlayer.setDataSource(songPath);
//sets current song as data source for media player
gesMediaPlayer.setDataSource(activeAudio.getData());
} catch (IOException e) {
e.printStackTrace();
stopSelf();
}
gesMediaPlayer.prepareAsync();*/
}
private void initMediaSession() {
ComponentName mediaButtonReceiver = new ComponentName(getApplicationContext(), MediaButtonReceiver.class);
gesMediaSession = new MediaSessionCompat(getApplicationContext(), "GESMediaService",
mediaButtonReceiver, null);
gesMediaSession.setCallback(mediaSessionCallbacks);
gesMediaSession.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS );
//this is for pre-Lollipop media button handling on those devices
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
mediaButtonIntent.setClass(this, MediaButtonReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0);
gesMediaSession.setMediaButtonReceiver(pendingIntent);
// Set the session's token so that client activities can communicate with it.
setSessionToken(gesMediaSession.getSessionToken());
}
private void registerNoisyReceiver() {
//Handles headphones coming unplugged. cannot be done through a manifest receiver
IntentFilter noisyFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
registerReceiver(becomingNoisyReceiver, noisyFilter);
}
private void initMediaSessionMetadata() {
MediaMetadataCompat.Builder metadataBuilder = new MediaMetadataCompat.Builder();
//Notification icon in card
metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
//lock screen icon for pre lollipop
metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "Display Title");
metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "Display Subtitle");
metadataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, 1);
metadataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, 1);
gesMediaSession.setMetadata(metadataBuilder.build());
}
//-----------------------------------Media Playback functions---------------------------------//
//TODO: read about the AssetFileDescriptor, and the ResultReceiver
private MediaSessionCompat.Callback mediaSessionCallbacks = new MediaSessionCompat.Callback() {
#Override
public void onPlay() {
super.onPlay();
if(!requestAudioFocus()) {
//failed to gain focus
return;
}
//check if service is started, not only bound
if(!isServiceStarted){
startService(new Intent(getApplicationContext(), MediaPlaybackService.class));
}
gesMediaSession.setActive(true);
setMediaPlaybackState(PlaybackStateCompat.STATE_PLAYING);
showPlayingNotification();
gesMediaPlayer.start();
}
#Override
public void onPause() {
super.onPause();
if( gesMediaPlayer.isPlaying() ) {
gesMediaPlayer.pause();
setMediaPlaybackState(PlaybackStateCompat.STATE_PAUSED);
showPausedNotification();
}
}
#Override
public void onPlayFromMediaId(String mediaId, Bundle extras) {
super.onPlayFromMediaId(mediaId, extras);
try {
AssetFileDescriptor afd = getResources().openRawResourceFd(Integer.valueOf(mediaId));
if( afd == null ) {
Log.d("afd: ", "afd in onPlayFromMediaId is null");
return;
}
try {
gesMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
} catch( IllegalStateException e ) {
gesMediaPlayer.release();
initMediaPlayer();
gesMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
}
afd.close();
initMediaSessionMetadata();
} catch (IOException e) {
e.printStackTrace();
return;
}
try {
gesMediaPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
Log.d("onPlayFromId: ", "mediaPlayer failed to prepare");
}
//Work with extras here if you want
}
#Override
public void onCommand(String command, Bundle extras, ResultReceiver cb) {
super.onCommand(command, extras, cb);
if( COMMAND_EXAMPLE.equalsIgnoreCase(command) ) {
//Custom command here
}
}
#Override
public void onSeekTo(long pos) {
super.onSeekTo(pos);
}
};
private void setMediaPlaybackState(int state) {
PlaybackStateCompat.Builder playbackstateBuilder = new PlaybackStateCompat.Builder();
if( state == PlaybackStateCompat.STATE_PLAYING ) {
playbackstateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE |
PlaybackStateCompat.ACTION_PAUSE);
} else {
playbackstateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE |
PlaybackStateCompat.ACTION_PLAY);
}
playbackstateBuilder.setState(state, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 0);
gesMediaSession.setPlaybackState(playbackstateBuilder.build());
}
//-------------------------------Audio Focus and Calls Handling-------------------------------//
//Handle incoming phone calls
private boolean ongoingCall = false;
private PhoneStateListener phoneStateListener;
private TelephonyManager telephonyManager;
private AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
#Override
public void onAudioFocusChange(int focusChange) {
switch( focusChange ) {
case AudioManager.AUDIOFOCUS_LOSS: {
// Lost focus for an unbounded amount of time:
// stop playback and release media player
if( gesMediaPlayer.isPlaying() ) {
gesMediaPlayer.stop();
}
/*gesMediaPlayer.release();
gesMediaPlayer = null;*/
break;
}
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: {
// Lost focus for a short time; Pause only and do not
// release the media player as playback is likely to resume
if (gesMediaPlayer.isPlaying()) {
gesMediaPlayer.pause();
}
break;
}
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
// Lost focus for a short time (ex. notification sound)
// but it's ok to keep playing at a temporarily attenuated level
if( gesMediaPlayer != null ) {
gesMediaPlayer.setVolume(0.2f, 0.2f);
}
break;
}
case AudioManager.AUDIOFOCUS_GAIN: {
//Invoked when the audio focus of the system is updated.
if( gesMediaPlayer != null ) {
if( !gesMediaPlayer.isPlaying() ) {
gesMediaPlayer.start();
}
gesMediaPlayer.setVolume(1.0f, 1.0f);
}
break;
}
}
}
private boolean requestAudioFocus() {
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this,
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
return result == AudioManager.AUDIOFOCUS_GAIN;
}
private boolean removeAudioFocus() {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
audioManager.abandonAudioFocus(this);
}
private void callStateListener() {
// Get the telephony manager
telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
//Starting listening for PhoneState changes
phoneStateListener = new PhoneStateListener() {
#Override
public void onCallStateChanged(int state, String incomingNumber) {
switch (state) {
//if at least one call exists or the phone is ringing
//pause the MediaPlayer
case TelephonyManager.CALL_STATE_OFFHOOK:
case TelephonyManager.CALL_STATE_RINGING:
if (gesMediaPlayer != null && gesMediaPlayer.isPlaying()) {
gesMediaPlayer.pause();
pausedPosition = gesMediaPlayer.getCurrentPosition();
ongoingCall = true;
}
break;
case TelephonyManager.CALL_STATE_IDLE:
// Phone idle. Start/resume playing.
if (gesMediaPlayer != null) {
if (ongoingCall) {
ongoingCall = false;
gesMediaPlayer.seekTo(pausedPosition);
gesMediaPlayer.start();
}
}
break;
}
}
};
// Register the listener with the telephony manager
// Listen for changes to the device call state.
telephonyManager.listen(phoneStateListener,
PhoneStateListener.LISTEN_CALL_STATE);
}
private BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if( gesMediaPlayer != null && gesMediaPlayer.isPlaying() ) {
gesMediaPlayer.pause();
}
}
};
//------------------------------------Less important methods----------------------------------//
#Nullable
#Override
public BrowserRoot onGetRoot(#NonNull String clientPackageName, int clientUid, #Nullable Bundle rootHints) {
if(TextUtils.equals(clientPackageName, getPackageName())) {
return new BrowserRoot(getString(R.string.app_name), null);
}
return null;
}
//Not important for general audio service, required for class
#Override
public void onLoadChildren(#NonNull String parentId, #NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
result.sendResult(null);
}
}
I apologize for the whole code but at this point I do not know what am I doing wrong anymore or where to look. Especially since the app did work in a previous version. Any suggestions are well appreciated if you guys see anything specific in my code. I try to catch exceptions where I can, but at the same time I am never sure where should I be putting the try-catch constructs. I'm trying to learn
Thank you to anyone!
I have an android app to connect to a BLE device and write to it. I can successfully connect, read and write to it. As a part of testing, we are trying different disconnection scenarios.
Sometimes, if BLE device disconnects the connection, I get the connection change as disconnect with status value as 19. Also if there is any bond error, status equals 22. If I programmatically disconnect the connection, this status gives me 0. But none of these states except 0 are specified in android documentation.
Posting a sample BluetoothGattCallback
private BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
Log.i(TAG, "onConnectionStateChange status: "+status+", newState: "+newState);
/*i need to know the possible values for this status variable*/
if(newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices();
} else {
gatt.close();
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
Log.i(TAG, "onServicesDiscovered service discovered");
}
};
Does anyone face this same problem and sorted out the list of statuses. I need to know the possible values for status variable in onConnectionStateChange method
Here is the list of codes i have
Programmatically disconnected - 0
Device went out of range - 8
Disconnected by device - 19
Issue with bond - 22
Device not found - 133(some phone it gives 62)
I have tested disconnect scenario's in 5.0.2, 5.1, 6.0 and 6.0.1. But only found this bond issue code in 6.0.1 android version.
Sorry to bring up an old question, but here is the solution for many of the problems i've had with Bluetooth (BLE) 4.0. Sorry again for the big classes below but be sure they are needed and no method there is irrelevant or unused.
public abstract class AbstractBluetoothBroadcaster extends BroadcastReceiver {
protected static final String LOG_TAG = BluetoothLowEnergy.LOG_TAG;
protected BluetoothLowEnergy bluetoothLowEnergy;
public AbstractBluetoothBroadcaster(BluetoothLowEnergy bluetoothLowEnergy, String action){
super();
this.bluetoothLowEnergy = bluetoothLowEnergy;
IntentFilter intentFilterStateChange = new IntentFilter(action);
intentFilterStateChange.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
this.bluetoothLowEnergy.getActivity().registerReceiver(this, intentFilterStateChange);
}
public void onDestroy(){
this.bluetoothLowEnergy.getActivity().unregisterReceiver(this);
}
}
public class BluetoothBondStateBroadcaster extends AbstractBluetoothBroadcaster {
private BluetoothLowEnergy bluetoothLowEnergy;
private boolean deviceBonded;
public BluetoothBondStateBroadcaster(BluetoothLowEnergy bluetoothLowEnergy) {
super(bluetoothLowEnergy, BluetoothDevice.ACTION_BOND_STATE_CHANGED);
this.bluetoothLowEnergy = bluetoothLowEnergy;
this.deviceBonded = false;
}
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null){
return;
}
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED) &&
bluetoothDevice != null &&
bluetoothDevice.getAddress().equals(bluetoothLowEnergy.getDeviceUUID())) {
int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
switch (state) {
case BluetoothDevice.BOND_NONE:
Log.d(LOG_TAG, " NOT BONDED - dev " + bluetoothDevice.getAddress());
this.deviceBonded = false;
break;
case BluetoothDevice.BOND_BONDING:
Log.d(LOG_TAG, " BONDING ... - dev " + bluetoothDevice.getAddress());
break;
case BluetoothDevice.BOND_BONDED:
Log.d(LOG_TAG, " BONDED - dev " + bluetoothDevice.getAddress());
deviceBonded = true;
bluetoothLowEnergy.onBluetoothBonded();
break;
default:
break;
}
}
}
public void resetDeviceBonded(){
this.deviceBonded = false;
}
public boolean isDeviceBonded() {
return deviceBonded;
}
}
public class BluetoothPairingBroadcaster extends AbstractBluetoothBroadcaster {
private String devicePIN;
public BluetoothPairingBroadcaster(BluetoothLowEnergy bluetoothLowEnergy){
super(bluetoothLowEnergy, BluetoothDevice.ACTION_PAIRING_REQUEST);
this.devicePIN = "";
}
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null){
return;
}
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
int pairingType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST) &&
bluetoothDevice != null &&
bluetoothDevice.getAddress().equals(bluetoothLowEnergy.getDeviceUUID()) &&
!getDevicePIN().isEmpty()) {
if (pairingType == BluetoothDevice.PAIRING_VARIANT_PIN){
bluetoothDevice.setPin(getDevicePIN().getBytes());
Log.d(LOG_TAG," Auto-entering pin - " + getDevicePIN());
bluetoothDevice.createBond();
Log.d(LOG_TAG," pin entered and request sent...");
abortBroadcast();
}
}
}
public void setDevicePIN(String pin){
this.devicePIN = pin;
}
public String getDevicePIN(){
return this.devicePIN ;
}
}
public class BluetoothLowEnergy extends BluetoothGattCallback {
// listener that has the methods that the application (activity)
// will use to send / receive data, or to reflect the system state
// in the UI
public interface BluetoothListener {
/**
* Triggered when the scanning has started successfully
*/
void onBluetoothStartScan();
/**
* Triggered when the scanning stops
* #param scanResults results of the scanning
*/
void onBluetoothStopScan(Collection<BluetoothDevice> scanResults);
/**
* Triggered when the device is ready to send/receive data
*/
void onBluetoothConnectionReady();
/**
* Triggered when a bluetooth msg is received
* #param msg message received
*/
void onBluetoothReceiveMsg(String msg);
/**
* Triggered whenever data is send
* #param success true means data was sent fine to the remote device, false otherwise
*/
void onBluetoothSend(String data, boolean success);
/**
* Triggered if no bluetooth is connected, and we need a connection
* to send / receive / discover services
*/
void onBluetoothNotConnected();
}
// custom exceptions
public class BluetoothNotEnabledException extends Exception { }
public class BluetoothLowEnergyNotSupported extends Exception { }
public class BluetoothDeviceNotFound extends Exception { }
// service and characteristic uuids that are going to be used to
// send / receive data between central and peripheral GATTs
private static final String SERVICE_UUID = "FFE0-";
private static final String CHARACTERISTIC_UUID = "FFE1-";
// timeout for bluetooth scan (in ms)
public static final int SCAN_TIMEOUT = 5000;
// BLE LOG TAG
public static final String LOG_TAG = "BLUETOOTH_BLE";
// model
private boolean bluetoothScanning;
private boolean bluetoothConnected;
private Map<String, BluetoothDevice> bluetoothScanResults;
// gui
private Activity activity;
// bluetooth
private BluetoothAdapter bluetoothAdapter;
private BluetoothLeScanner bluetoothLeScanner;
private ScanCallback bluetoothScanCallback;
private BluetoothGatt bluetoothGatt;
private BluetoothGattCharacteristic characteristic;
public BluetoothLowEnergy(Activity activity, BluetoothListener bluetoothListener){
this.activity = activity;
this.bluetoothListener = bluetoothListener;
// this keeps track of the scanning and connection states
this.bluetoothScanning = this.bluetoothConnected = false;
// keeps track of the scanning results
this.bluetoothScanResults = new HashMap<>();
// set bluetooth pairing request and bonded callback
// these broadcasters will be responsible to detect and validate
// the bonded state of your device
this.pairingRequestBroadcaster = new BluetoothPairingBroadcaster(this);
this.bondedBroadcaster = new BluetoothBondStateBroadcaster(this);
// set the scan callback methods that will add results to
// this.bluetoothScanResults map
this.bluetoothScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
addScanResult(result);
}
#Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
for (ScanResult result: results) {
addScanResult(result);
}
}
#Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.e(LOG_TAG, "Scan Failed with code " + errorCode);
}
private void addScanResult(ScanResult result) {
BluetoothDevice device = result.getDevice();
String deviceAddress = device.getAddress();
bluetoothScanResults.put(deviceAddress, device);
Log.d(LOG_TAG, "Found device " + deviceAddress);
}
};
// Use this to determine whether BLE is supported on the device.
if (!this.activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
throw new BluetoothLowEnergyNotSupported();
}
}
/**
* This method should be called when the activity is destroyed
*/
public void onDestroy(){
this.bondedBroadcaster.onDestroy();
this.pairingRequestBroadcaster.onDestroy();
this.disconnect();
}
/**
* This method is called when we finish pairing/bonding to the device
*/
public void onBluetoothBonded(){
// if we have the services already discovered, then we can
// send/receive data, to do so we call the bluetooth listener below
if (servicesDiscovered){
this.bluetoothListener.onBluetoothConnectionReady();
// if we know we have a connection established, then we can
// discover services
} else if (bluetoothConnected){
bluetoothGatt.discoverServices();
}
}
/**
* This method is called whenever a connection is established or a disconnection happens
*/
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
BluetoothDevice bluetoothDevice = gatt.getDevice();
// if these conditions == true, then we have a disconnect
if ( status == BluetoothGatt.GATT_FAILURE ||
status != BluetoothGatt.GATT_SUCCESS ||
newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.d(LOG_TAG, String.format(Locale.getDefault(),
"Disconnected from %s (%s) - status %d - state %d",
bluetoothDevice.getName(),
bluetoothDevice.getAddress(),
status,
newState
));
this.disconnect();
// if these conditions == true, then we have a successful connection
} else if (newState == BluetoothProfile.STATE_CONNECTED) {
bluetoothConnected = true;
Log.d(LOG_TAG, String.format(Locale.getDefault(),
"Connected to %s (%s) - status %d - state %d",
bluetoothDevice.getName(),
bluetoothDevice.getAddress(),
status,
newState
));
// this sleep is here to avoid TONS of problems in BLE, that occur whenever we start
// service discovery immediately after the connection is established
try {
Thread.sleep(600);
} catch (InterruptedException e) {
e.printStackTrace();
}
gatt.discoverServices();
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status != BluetoothGatt.GATT_SUCCESS) {
return;
}
// BEGIN - find the service and characteristic that we want (defined as a static attribute
// of the BluetoothLowEnergy class)
Log.d(LOG_TAG, "Discovering services ...");
BluetoothGattService service = null;
for (BluetoothGattService serv: gatt.getServices()){
Log.d(LOG_TAG, "Found service " + serv.getUuid().toString());
if (serv.getUuid().toString().toUpperCase().contains(SERVICE_UUID)){
service = serv;
Log.d(LOG_TAG, "---> Selected service " + serv.getUuid().toString());
break;
}
}
if (service == null){
return;
}
for (BluetoothGattCharacteristic charac: service.getCharacteristics()){
Log.d(LOG_TAG, "Found characteristic " + charac.getUuid().toString());
if (charac.getUuid().toString().toUpperCase().contains(CHARACTERISTIC_UUID)){
this.characteristic = charac;
Log.d(LOG_TAG, "---> Selected characteristic " + charac.getUuid().toString());
break;
}
}
if (this.characteristic == null){
return;
}
Log.d(LOG_TAG, "Setting write and notification to the characteristic ...");
bluetoothAdapter.cancelDiscovery();
// END - find the service and characteristic
// set that we want to write to the selected characteristic and be notified if
// it changes (the remote GATT peripheral sends data to the Android's GATT Center)
this.characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
gatt.setCharacteristicNotification(this.characteristic, true);
// we finished service discovery
this.servicesDiscovered = true;
// if we have paired/bonded then we are ready to send/receive data
if (pairingRequestBroadcaster.getDevicePIN().isEmpty() || bondedBroadcaster.isDeviceBonded()) {
this.bluetoothListener.onBluetoothConnectionReady();
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic charac, int status) {
super.onCharacteristicRead(gatt, charac, status);
restartDisconnectTimeout();
if (status != BluetoothGatt.GATT_SUCCESS) {
return;
}
try {
String characValue = new String(charac.getValue(), CHARSET)
.replaceAll(DATA_FILTER_REGEX,"");
Log.i(LOG_TAG, String.format(Locale.getDefault(),
"Characteristic Read - %s",
characValue
));
if (charac.getUuid().equals(this.characteristic.getUuid())) {
this.bluetoothListener.onBluetoothReceiveMsg(characValue);
}
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Characteristic Read - Failed to convert message string to byte array");
}
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic charac, int status) {
super.onCharacteristicWrite(gatt, charac, status);
restartDisconnectTimeout();
try {
String characValue = new String(charac.getValue(), CHARSET);
Log.i(LOG_TAG, String.format(Locale.getDefault(),
"Characteristic Write - SUCCESS - %s",
characValue
));
bluetoothListener.onBluetoothSend( characValue, (status == BluetoothGatt.GATT_SUCCESS) );
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Characteristic Write - Failed to convert message string to byte array");
}
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic charac) {
super.onCharacteristicChanged(gatt, charac);
Log.d(LOG_TAG,"Characteristic Changed");
onCharacteristicRead(gatt, charac, BluetoothGatt.GATT_SUCCESS);
}
/**
* Remove pairing/bonding of the device
* #param device Device to remove bonding
*/
public static void removeBond(BluetoothDevice device){
try {
if (device == null){
throw new Exception();
}
Method method = device.getClass().getMethod("removeBond", (Class[]) null);
method.invoke(device, (Object[]) null);
Log.d(LOG_TAG, "removeBond() called");
Thread.sleep(600);
Log.d(LOG_TAG, "removeBond() - finished method");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Clears the GATT services cache, so that new services can be discovered
* #param bluetoothGatt GATT Client to clear service's discovery cache
*/
public static void refresh(BluetoothGatt bluetoothGatt){
try {
Method method = bluetoothGatt.getClass().getMethod("refresh", (Class[]) null);
method.invoke(bluetoothGatt, (Object[]) null);
} catch (Exception e){
e.printStackTrace();
}
}
/**
* Connect to the GATT Peripheral device
* #param uuid GATT Peripheral address / mac / uuid to connect to
* #param pin PIN to authenticate and pair to the device
*/
public void connect(String uuid, String pin) throws BluetoothNotEnabledException, BluetoothDeviceNotFound {
checkBluetooth();
// do not connect twice
if (this.isConnected()){
return;
}
// get device
BluetoothDevice device = this.bluetoothScanResults.get(uuid);
if (device == null){
throw new BluetoothDeviceNotFound();
}
this.deviceUUID = uuid;
pairingRequestBroadcaster.setDevicePIN(pin);
removeBond(device);
// create connection to the bluetooth device
bluetoothGatt = device.connectGatt(activity, false, this);
refresh(bluetoothGatt);
}
/**
* Disconnect from BLE device. This method should be called whenever we want to
* close the APP, or the BLE connection.
*/
public void disconnect() {
Log.d(LOG_TAG, "disconnect() - executed");
if (bluetoothGatt != null) {
if (characteristic != null) {
bluetoothGatt.setCharacteristicNotification(characteristic, false);
}
//remove device authorization/ bond/ pairing
removeBond(bluetoothGatt);
// disconnect now
bluetoothGatt.disconnect();
bluetoothGatt.close();
Log.d(LOG_TAG, "disconnect() - bluetoothGatt disconnect happened");
}
bluetoothGatt = null;
characteristic = null;
bluetoothConnected = false;
servicesDiscovered = false;
// set device as not bonded anymore
bondedBroadcaster.resetDeviceBonded();
}
/**
* bluetooth nearby devices scan is on
* #return true if scanning is on, false otherwise
*/
public boolean isScanning(){
return (this.bluetoothScanning);
}
/**
* Check bluetooth system state (on or off)
* #return true if system is on, false otherwise
*/
public boolean isEnabled(){
try {
checkBluetooth();
return bluetoothAdapter.isEnabled();
} catch (BluetoothNotEnabledException e) {
return false;
}
}
/**
* Check bluetooth connection
* #return true if connected, false otherwise
*/
public boolean isConnected(){
return (this.bluetoothConnected);
}
/**
* Start bluetooth scan for nearby devices
* #param filters Scan filters that define what devices to scan for
*/
public void startScan(List<ScanFilter> filters)
throws BluetoothNotEnabledException{
checkBluetooth();
// dont run two scans simultaneously
if (isScanning()) {
return;
}
// disconnect previously connected devices
if (isConnected()) {
this.disconnect();
return;
}
// setup bluetooth scanning settings
ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
.build();
// start scanning
this.bluetoothScanning = true;
this.bluetoothScanResults.clear();
this.bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
// Stops scanning after a pre-defined scan period.
Handler bluetoothHandler = new Handler();
bluetoothHandler.postDelayed(new Runnable() {
#Override
public void run() {
stopScan();
}
}, SCAN_TIMEOUT);
// start scan with default scan callback
this.bluetoothLeScanner.startScan(filters, settings, bluetoothScanCallback);
// we have started successfully the BLE scanning
bluetoothListener.onBluetoothStartScan();
}
/**
* Stop bluetooth scan for nearby devices
*/
public void stopScan(){
if (!bluetoothScanning) {
return;
}
// set app scan state to false
bluetoothScanning = false;
if (bluetoothLeScanner != null) {
bluetoothLeScanner.stopScan(bluetoothScanCallback);
bluetoothLeScanner = null;
}
// we have stopped BLE scanning, call the user's callback
bluetoothListener.onBluetoothStopScan(bluetoothScanResults.values());
}
/**
* Send a message via bluetooth
* #param msg message to send
*/
public void send(String msg) {
if (!bluetoothConnected || characteristic == null){
bluetoothListener.onBluetoothNotConnected();
return;
}
try {
msg = msg.replaceAll(DATA_FILTER_REGEX, "") + TERMINATION_CHAR;
Log.d(LOG_TAG, String.format(Locale.getDefault(),
"Sending message: %s",
msg));
characteristic.setValue(msg.getBytes(CHARSET));
bluetoothGatt.writeCharacteristic(characteristic);
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG,
"BluetoothLowEnergy.send: Failed to convert message string to byte array");
}
}
public String getDeviceUUID(){
return deviceUUID;
}
public Activity getActivity(){
return activity;
}
/**
* Check if bluetooth is enabled and working properly
*/
private void checkBluetooth() throws BluetoothNotEnabledException{
if (bluetoothAdapter == null) {
final BluetoothManager bluetoothManager =
(BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager == null){
throw new BluetoothNotEnabledException();
}
bluetoothAdapter = bluetoothManager.getAdapter();
}
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
throw new BluetoothNotEnabledException();
}
}
}
The key methods and functions to avoid problems used above are:
Thread.sleep(600)
removeBond(device)
refresh(gatt)
gatt.disconnect()
gatt.close()
In my case I got this response from bluetooth stack because the device was already bonded with my phone. I removed it from my settings and the error 22 vanished.
in aosp (android source code). you can find any error in bluetooth source code, and know the meaning of status code.
the file path is system/bt/stack/include/gatt_api.h
Here's the link: https://android.googlesource.com/platform/system/bt/+/ea7ab70a711e642653dd5922b83aa04a53af9e0e/stack/include/gatt_api.h but it all display by hex.
for example:
hex
Decimal
reason
0x08
8
connection timeout
0x13
19
connection terminate by peer user
0x16
22
connectionterminated by local host
0x22
34
connection fail for LMP response tout
0x85
133
gatt_error
I wrote a custom plugin to read blocks of data from an NfcA(i.e.non-ndef) tag. It seems to work fine , but only after the second scan. I am using Activity intent to derive the "NfcAdapter.EXTRA_TAG" to later use it for reading the values. I am also updating the Intents in onNewIntent(). OnNewIntent gets called after the second scan and after that I get result all the time.But in the first scan onNewIntent does not gets called, hence I end up using the Activity tag that does not have "NfcAdapter.EXTRA_TAG", hence I get null. Please see the my code below.
SE_NfcA.java(my native code for plugin)
#Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
String Result = "";
String TypeOfTalking = "";
if (action.contains("TalkToNFC"))
{
JSONObject arg_object = args.getJSONObject(0);
TypeOfTalking = arg_object.getString("type");
if(TypeOfTalking != "")
{
if (TypeOfTalking.contains("readBlock"))
{
if(TypeOfTalking.contains("#"))
{
try
{
String[] parts = TypeOfTalking.split("#");
int index = Integer.parseInt(parts[1]);
Result = Readblock(cordova.getActivity().getIntent(),(byte)index);
callbackContext.success(Result);
}
catch(Exception e)
{
callbackContext.error("Exception Reading "+ TypeOfTalking + "due to "+ e.toString());
return false;
}
}
}
else
{
return false;
}
}
else
{
return false;
}
}
else
{
return false;
}
return true;
}
#Override
public void onNewIntent(Intent intent) {
ShowAlert("onNewIntent called");
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
super.onNewIntent(intent);
getActivity().setIntent(intent);
savedTag = tagFromIntent;
savedIntent = intent;
}
#Override
public void onPause(boolean multitasking) {
Log.d(TAG, "onPause " + getActivity().getIntent());
super.onPause(multitasking);
if (multitasking) {
// nfc can't run in background
stopNfc();
}
}
#Override
public void onResume(boolean multitasking) {
Log.d(TAG, "onResume " + getActivity().getIntent());
super.onResume(multitasking);
startNfc();
}
public String Readblock(Intent Intent,byte block) throws IOException{
byte[] response = new byte[]{};
if(Intent != null)
{
Tag myTag = Intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if(savedTag != null)
myTag = savedTag;
if(myTag != null)
{
try{
Reader nTagReader = new Reader(myTag);
nTagReader.close();
nTagReader.connect();
nTagReader.SectorSelect(Sector.Sector0);
response = nTagReader.fast_read(block, block);
nTagReader.close();
return ConvertH(response);
}catch(Exception e){
ShowAlert(e.toString());
}
}
else
ShowAlert("myTag is null.");
}
return null;
}
private void createPendingIntent() {
if (pendingIntent == null) {
Activity activity = getActivity();
Intent intent = new Intent(activity, activity.getClass());
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP| Intent.FLAG_ACTIVITY_CLEAR_TOP);
pendingIntent = PendingIntent.getActivity(activity, 0, intent, 0);
}
}
private void startNfc() {
createPendingIntent(); // onResume can call startNfc before execute
getActivity().runOnUiThread(new Runnable() {
public void run() {
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
if (nfcAdapter != null && !getActivity().isFinishing()) {
try {
nfcAdapter.enableForegroundDispatch(getActivity(), getPendingIntent(), getIntentFilters(), getTechLists());
if (p2pMessage != null) {
nfcAdapter.setNdefPushMessage(p2pMessage, getActivity());
}
} catch (IllegalStateException e) {
// issue 110 - user exits app with home button while nfc is initializing
Log.w(TAG, "Illegal State Exception starting NFC. Assuming application is terminating.");
}
}
}
});
}
private void stopNfc() {
Log.d(TAG, "stopNfc");
getActivity().runOnUiThread(new Runnable() {
public void run() {
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
if (nfcAdapter != null) {
try {
nfcAdapter.disableForegroundDispatch(getActivity());
} catch (IllegalStateException e) {
// issue 125 - user exits app with back button while nfc
Log.w(TAG, "Illegal State Exception stopping NFC. Assuming application is terminating.");
}
}
}
});
}
private Activity getActivity() {
return this.cordova.getActivity();
}
private PendingIntent getPendingIntent() {
return pendingIntent;
}
private IntentFilter[] getIntentFilters() {
return intentFilters.toArray(new IntentFilter[intentFilters.size()]);
}
private String[][] getTechLists() {
//noinspection ToArrayCallWithZeroLengthArrayArgument
return techLists.toArray(new String[0][0]);
}
}
My index.js file
nfc.addTagDiscoveredListener(
function(nfcEvent){
console.log(nfcEvent.tag.id);
alert(nfcEvent.tag.id);
window.echo("readBlock#88");//call to plugin
},
function() {
alert("Listening for NFC tags.");
},
function() {
alert("NFC activation failed.");
}
);
SE_NfcA.js(plugin interface for interaction b/w index.js and SE_NfcA.java)
window.echo = function(natureOfTalk)
{
alert("Inside JS Interface, arg =" + natureOfTalk);
cordova.exec(function(result){alert("Result is : "+result);},
function(error){alert("Some Error happened : "+ error);},
"SE_NfcA","TalkToNFC",[{"type": natureOfTalk}]);
};
I guess I have messed up with the Intents/Activity Life-Cycle, please help. TIA!
I found a tweak/hack and made it to work.
Before making any call to read or write, I made one dummy Initialize call.
window.echo("Initialize");
window.echo("readBlock#88");//call to plugin to read.
And in the native code of the plugin, on receiving the "Initialize" token I made a startNFC() call.
else if(TypeOfTalking.equalsIgnoreCase("Initialize"))
{
startNfc();
}
i want to search and listing bluetooth devices in android, my program now able to list all the active devices but not able to send pairing request to the other devices .I want to implement this onItemClick of list element.And also if bluetooth is not enabled of my device then show a permission to active device,if i go for yes then ok,but if i go for no then permission show again until i press yes..how can i do this?plz help with code..here is my code..
public class Main extends Activity {
TextView out;
private static final int REQUEST_ENABLE_BT = 1;
private BluetoothAdapter btAdapter;
private ArrayList<BluetoothDevice> btDeviceList = new ArrayList<BluetoothDevice>();
private ArrayList<String> mylist= new ArrayList<String>();
private ListView lv;
private Button btn;
public Parcelable[] uuidExtra;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void search(View view)
{
//Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothDevice.ACTION_UUID);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(ActionFoundReceiver, filter); // Don't forget to unregister during onDestroy
// Getting the Bluetooth adapter
btAdapter = BluetoothAdapter.getDefaultAdapter();
Toast.makeText(getApplicationContext(),"\nAdapter: " + btAdapter,5000).show();
CheckBTState();
}
private void setDeviceList(ArrayList<String> list) {
lv = (ListView) findViewById(R.id.listView);
ArrayAdapter<String> adapter= new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,list);
lv.setAdapter(adapter);
}
/* This routine is called when an activity completes.*/
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_ENABLE_BT) {
CheckBTState();
}
}
#Override
protected void onDestroy() {
super.onDestroy();
if (btAdapter != null) {
btAdapter.cancelDiscovery();
}
unregisterReceiver(ActionFoundReceiver);
}
private void CheckBTState() {
// Check for Bluetooth support and then check to make sure it is turned on
// If it isn't request to turn it on
// List paired devices
// Emulator doesn't support Bluetooth and will return null
if(btAdapter==null) {
Toast.makeText(getApplicationContext(),"\nBluetooth NOT supported. Aborting.",5000).show();
return;
} else {
if (btAdapter.isEnabled()) {
Toast.makeText(getApplicationContext(),"\nBluetooth is enabled...",5000).show();
// Starting the device discovery
btAdapter.startDiscovery();
} else if (!btAdapter.isEnabled()){
Intent enableBtIntent = new Intent(btAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
/* else{
Intent intent = new Intent(btAdapter.ACTION_STATE_CHANGED);
startActivityForResult(intent, RESULT_CANCELED);
}*/
}
}
private final BroadcastReceiver ActionFoundReceiver = new BroadcastReceiver(){
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Toast.makeText(getApplicationContext(),"\n Device: " + device.getName() + ", " + device,5000).show();
mylist.add(device.getName());
setDeviceList(mylist);
} else {
if(BluetoothDevice.ACTION_UUID.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
Parcelable[] uuidExtra = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID);
for (int i=0; i<uuidExtra.length; i++) {
Toast.makeText(getApplicationContext(),"\n Device: " + device.getName() + ", " + device + ", Service: " + uuidExtra[i].toString(),5000).show();
}
} else {
if(BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
Toast.makeText(getApplicationContext(),"\nDiscovery Started...",5000).show();
} else {
if(BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
Toast.makeText(getApplicationContext(),"\nDiscovery Finished",5000).show();
Iterator<BluetoothDevice> itr = btDeviceList.iterator();
while (itr.hasNext()) {
// Get Services for paired devices
BluetoothDevice device = itr.next();
Toast.makeText(getApplicationContext(),"\nGetting Services for " + device.getName() + ", " + device,5000).show();
if(!device.fetchUuidsWithSdp()) {
Toast.makeText(getApplicationContext(),"\nSDP Failed for " + device.getName(),5000).show();
}
}
}
}
}
}
}
};
}
It's too late but here is code -> You need to use background thread to connect with bluetooth device as a client. and UUID is Universal Uniquely identification you can use online UUID generator. for secure connections and then get socket with device and connect with it;
ConnectWithDevice(context : ConnectWithBluetooth, device : BluetoothDevice) : Thread(){
private val mContext : ConnectWithBluetooth = context
private val mmSocket : BluetoothSocket
private val mmDevice : BluetoothDevice
// Default UUID
private val mmDefaultUUID = UUID.fromString("78c374fd-f84d-4a9e-aa5b-9b0b6292952e")
init {
var temp : BluetoothSocket? = null
mmDevice = device
try {
// Try here device.createInsecureConnect if it's work then start with this;
temp = device.createRfcommSocketToServiceRecord(mmDevice.uuids[0].uuid)
}catch (en : NullPointerException){
en.printStackTrace()
// Try here device.createInsecureConnect if it's work then start with this;
temp = device.createRfcommSocketToServiceRecord(mmDefaultUUID)
}catch (e : IOException){
e.printStackTrace()
Log.e("TAG","Socket's create() method failed",e)
}
mmSocket = temp!!
Log.i("TAG","Got the Socket")
}
override fun run() {
// Cancel discovery because it otherwise slows down the connection.
if(mContext.bluetoothAdapter != null){
mContext.bluetoothAdapter!!.cancelDiscovery()
}
try{
// Connect to the remote device through the socket. This call blocks
// until it succeeds or throws an exception.
Log.i("TAG","Connecting...")
mmSocket.connect()
Log.i("TAG","Bluetooth Successfully Connected")
}catch (connectException : IOException){
// Unable to connect; close the socket and return.
try{
mmSocket.close()
}catch (closeException : IOException){
Log.e("TAG","Could not close the client socket",closeException)
}
return
}
// The connection attempt succeeded. Perform work associated with
// the connection in a separate thread.
Log.i("TAG","Device is Connected")
//manageMyConnectedSocket(mmSocket)
}
// Closes the client socket and causes the thread to finish.
// Call this method from the main activity to shut down the connection.
fun cancel(){
try {
mmSocket.close()
} catch (e: IOException) {
Log.e(ContentValues.TAG, "Could not close the client socket", e)
}
}
}
I've got a problem with my IntentService - my intention is to outsource the networking and processing from the activity (I previously used an AsyncTask which worked excellent, however I also want to extend it to a widget).
The thing is, I don't get any result from the service - it almost seems like it never gets called (or something is wrong in the code that's supposed to retreive the data)...
Can someone with more experience in using services than me take a look and spot the obvious (or hidden) errors I've made?
Would be greatly appreciated!
Contents of the service:
public class StateCheckerService extends IntentService {
public StateCheckerService() {
super("StateCheckerService");
// TODO Auto-generated constructor stub
}
String pageContent;
public static final String API_URL = "http://omegav.no/api/dooropen.php", INTENT_ACTION="omegavdoor.FETCH_COMPLETE", EXTRA_STATUS="status", EXTRA_TIME="time", KEY_STATUS="";
SharedPreferences settings;
private int timeMins = 0, timeoutMillis = 5000, resultCode;
int status_code = 0;
public void onCreate() {
super.onCreate();
// Declares the SharedPreferences object to use
settings = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(null, resultCode, resultCode);
return START_FLAG_RETRY;
}
#Override
protected void onHandleIntent(Intent intent) {
// TODO Auto-generated method stub
if (checkConnection()) {
// Start process of retrieving status
try {
getData();
} catch (IOException e) {
returnResult(11);
} finally {
returnResult(resultCode);
}
} else {
// Notify the user of missing connection
returnResult(0); // Error: connection unavailable
}
}
private boolean checkConnection() {
// Declare connection manager and NetworkInfo objects
ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeInfo = connMgr.getActiveNetworkInfo();
// Check network connection
if (activeInfo != null && activeInfo.isConnected()) {
return true;
} else {
return false;
}
}
/** Function to get data from the remote server */
public void getData() throws IOException {
// Create URL object to connect to
URL url = new URL(API_URL);
// Open new HTTP connection
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
// Set a connection timeout to prevent app lockup if it can't reach the server
urlConnection.setConnectTimeout(timeoutMillis);
// Attempt to connect and retrieve data
try { // Return exception if the stream is unreachable
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
// Process the contents of the stream
readStream(in);
}
finally {
// Disconnect after retrieving data
urlConnection.disconnect();
}
}
public void readStream(InputStream input) throws IOException {
// Reads the content of the page
try {
// String length varies with the time value - read some extra to avoid missing the end
pageContent = readIt(input, 40);
// Remove the extra white spaces at the end
} catch (UnsupportedEncodingException e) {
resultCode = 10;
}
if (pageContent == null) {
// Stop further processing (and cause the UI to report error)
resultCode = 13;
} else {
// Checks to see whether the "open" flag exists
if (pageContent.charAt(9) == '1') {
// Find out how long it's been open
int openTime = Integer.parseInt(pageContent.substring(20, pageContent.lastIndexOf("}")));
// Convert from seconds to minutes
timeMins = openTime / 60;
if (timeMins > 0) {
resultCode = 1; // Display how long it's been open
} else {
resultCode = 2; // If it just opened
}
} else {
// Find out how long it's been closed
int closedTime = Integer.parseInt(pageContent.substring(19, pageContent.lastIndexOf("}"))); // TODO: change 19 to 20 to support the API change
// Convert from seconds to minutes
timeMins = closedTime / 60;
if (timeMins > 0) {
resultCode = 3; // Display how long it's been open
} else {
resultCode = 4; // If it just closed
}
}
}
}
// Reads an InputStream and converts it to a String.
public String readIt(InputStream stream, int len) throws IOException, UnsupportedEncodingException {
// Initialize reader object
Reader reader = null;
// Decode the input stream
reader = new InputStreamReader(stream, "UTF-8");
char[] buffer = new char[len];
reader.read(buffer);
return new String(buffer);
}
private void returnResult(int resultCode) {
Intent resultIntent = new Intent();
resultIntent.setAction(INTENT_ACTION);
resultIntent.putExtra(EXTRA_STATUS, resultCode)
.putExtra(EXTRA_TIME, timeMins);
}
#Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
}
Contents of the calling activity:
public class StateChecker extends Activity {
String pageContent;
boolean doorIsOpen = false, notFirstRun = false, error = false;
private static final int transitionDuration = 250;
private ResponseReceiver receiver;
TransitionDrawable transition;
TextView text_doorState;
Button button_getState;
ProgressBar door_progress;
LinearLayout background;
int timeMins;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Inflate the XML layout
setContentView(R.layout.state_checker);
// Declare the views to be adressed
text_doorState = (TextView) findViewById(R.id.tv_doorState);
button_getState = (Button) findViewById(R.id.button_refreshState);
door_progress = (ProgressBar) findViewById(R.id.doorState_progressBar);
background = (LinearLayout) findViewById(R.id.doorStateView);
IntentFilter filter = new IntentFilter(ResponseReceiver.ACTION_RESP);
filter.addCategory(Intent.CATEGORY_DEFAULT);
receiver = new ResponseReceiver();
registerReceiver(receiver, filter);
}
/** Receiver class to listen to and handle result from checker service */
public class ResponseReceiver extends BroadcastReceiver {
public static final String ACTION_RESP = StateCheckerService.INTENT_ACTION;
#Override
public void onReceive(Context context, Intent intent) {
int result = intent.getIntExtra(StateCheckerService.EXTRA_STATUS, 0);
timeMins = intent.getIntExtra(StateCheckerService.EXTRA_TIME, 0);
Toast.makeText(context, "Result received", Toast.LENGTH_SHORT).show();
processResult(result);
}
}
public void onStart() {
super.onStart();
// Checks the door status on app launch
go();
}
public void onStop() {
super.onStop();
unregisterReceiver(receiver);
}
private void go() {
// Update text and progress bar to indicate it's working
text_doorState.setText(R.string.text_stateUpdating);
door_progress.setVisibility(View.VISIBLE);
// Fade the color back to grey if it is something else
if (notFirstRun) {
transition.reverseTransition(transitionDuration);
}
if (checkConnection()) {
Intent intent = new Intent(this, StateCheckerService.class);
startService(intent);
notify("service started");
} else {
// Notify the user of missing connection
notify(getString(R.string.error_connection_unavailable));
}
}
private boolean checkConnection() {
// Declare connection manager and NetworkInfo objects
ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeInfo = connMgr.getActiveNetworkInfo();
// Check network connection
if (activeInfo != null && activeInfo.isConnected()) {
return true;
} else {
return false;
}
}
protected void processResult(Integer resultCode) {
switch (resultCode) {
case 1:
if (timeMins < 60) {
// Notify of open door and display minutes
notify(getString(R.string.text_stateOpenMins, timeMins));
} else {
// If it's been more than an hour, display the time in hours
int openTimeHours = timeMins / 60;
int remainder = timeMins - (openTimeHours * 60);
notify(getString(R.string.text_stateOpenHours, openTimeHours, remainder));
}
break;
case 2:
// Notify of open door (just opened)
notify(getString(R.string.text_stateOpenNow));
break;
case 3:
if (timeMins < 60) {
// Notify of closed door and display minutes
notify(getString(R.string.text_stateClosedMins, timeMins));
} else {
// If it's been more than an hour, display the time in hours
int closedTimeHours = timeMins / 60;
int remainder = timeMins - (closedTimeHours * 60);
notify(getString(R.string.text_stateClosedHours, closedTimeHours, remainder));
}
break;
case 4:
// Notify of closed door (just closed
notify(getString(R.string.text_stateClosedNow));
break;
case 10:
// Error message: unsupported stream format
notify(getString(R.string.error_stream_unsupported));
break;
case 11:
// Error message: connection failed
notify(getString(R.string.error_connection_failed));
break;
// Case 12 reserved
case 13:
// Error message: null data stream
notify(getString(R.string.error_stream_retrieve));
break;
}
if (resultCode >= 10) {
error = true;
} else {
error = false;
}
updateDisplay();
}
protected void updateDisplay() {
door_progress.setVisibility(View.GONE);
if (!error) {
/** Update the UI to reflect the door state */
if(doorIsOpen) {
// Update the text view to display an open door state
text_doorState.setText(getString(R.string.text_stateOpen));
// Change the background color
background.setBackgroundResource(R.drawable.trans_open);
transition = (TransitionDrawable) background.getBackground();
transition.startTransition(transitionDuration);
} else {
// Update the text view to display a closed door state
text_doorState.setText(getString(R.string.text_stateClosed));
// Change the background color
background.setBackgroundResource(R.drawable.trans_close);
transition = (TransitionDrawable) background.getBackground();
transition.startTransition(transitionDuration);
}
// Indicates that the app has gone through a successful execution
notFirstRun = true;
} else {
// If it failed to execute, display error message
text_doorState.setText(R.string.text_stateFailed);
// Revert to grey background
background.setBackgroundResource(R.drawable.background);
}
}
/** Helper class used to display toast notifications */
private void notify(String message) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
}
Edit (AndroidManifest):
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="tovine.omegavdoor.widget"
android:versionCode="5"
android:versionName="1.0.1" >
<uses-sdk
android:minSdkVersion="7"
android:targetSdkVersion="14" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:icon="#drawable/ic_launcher"
android:label="#string/app_name" >
<activity
android:name=".Settings"
android:label="#string/title_activity_settings" >
</activity>
<activity
android:name=".StateChecker"
android:configChanges="orientation|screenSize"
android:title="#string/app_name"
android:windowSoftInputMode="stateAlwaysHidden" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.google.ads.AdActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize" />
<receiver
android:name=".WidgetStateProvider" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="omegavdoor.FETCH_COMPLETE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="#xml/doorwidget" />
</receiver>
<service android:name="tovine.omegavdoor.widget.WidgetUpdateService">
<intent-filter>
<action android:name="omegavdoor.FETCH_COMPLETE" />
</intent-filter>
</service>
<service android:name="tovine.omegavdoor.widget.StateCheckerService"
android:process=":checker_process">
</service>
-->
<!-- <activity
android:name="Probability"
android:label="#string/title_activity_probability" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="StateChecker" />
</activity>
<activity
android:name=".LoadWebImg"
android:label="TestClass" /> -->
</application>
</manifest>
I can be wrong but it seems your returnResult() function doesn't send anything. Maybe you forgot to add sendBroadcst(resultIntent); call to it?
For intent service we does not need to implement/override the method onStartCommand
So remove the following coding
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(null, resultCode, resultCode);
return START_FLAG_RETRY;
}
from the "Contents of the service"
Then try it.