I am a complete novice in Java and Android. I am trying to create a test app to listen for BLE and BT devices nearby. I have another device where I wrote some logic to broadcast its BLE beacons. I verified it using a playstore app. Now I am trying to write my own app on Android.
I have been reading the Android developer pages for guidance. I have literally followed every step of the following pages
https://developer.android.com/guide/topics/connectivity/bluetooth/setup
https://developer.android.com/guide/topics/connectivity/bluetooth/permissions
https://developer.android.com/guide/topics/connectivity/bluetooth/find-bluetooth-devices
https://developer.android.com/guide/topics/connectivity/bluetooth/find-ble-devices
Also, Note that I have used BARE MINIMUM CODE from the Android Developers page So here is what I have done.
1. First off I have added my permissions under AndroidManifest
Note1 : I am deploying this app to My phone running Android 11
Note2 : All this code is written inside MainActivity. I have not created any other activity class
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
2. Next I check if my BT is enabled.
if (bluetoothAdapter == null) {
blefinder.append("\nDEVICE DOES NOT SUPPORT BLUETOOTH");
}
else {
blefinder.append("\nDEVICE SUPPORTS BLUETOOTH");
}
I get the success message that BT is of course enabled
3. Next I check if my device supports BLE
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
blefinder.append("\nBLE NOT SUPPORTED ON THIS DEVICE : ");
finish();
}
else{
blefinder.append("\nBLE IS SUPPORTED ON THIS DEVICE : ");
}
I get the message that BLE is supported
4. Next I list my already paired/bonded devices
For this I call ListPairedAndBondedDevices(); in onCreate() itself right after the above steps. Function Definition Below.
private void ListPairedAndBondedDevices(){
#SuppressLint("MissingPermission") Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
if (pairedDevices.size() > 0) {
// There are paired devices. Get the name and address of each paired device.
blefinder.append("\nPAIRED/BONDED DEVICES");
for (BluetoothDevice device : pairedDevices) {
blefinder.append("\n" + device.getName() + " | " + device.getAddress());
}
}
}
This also works like a charm and prints out my paired devices. The next 2 parts is where I face the problem.
5. The Problem Step | Part 1:
Here I register a Broadcast receiver to discover all BT devices in the vicinity. I've unbonded my BT headphones and kept it in pairing mode to verify this.
ListPairedAndBondedDevices(); // From previous code snippet
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); // New code statement
registerReceiver(BTReceiver, filter);// New code statement
Broadcast Receiver implementation
private final BroadcastReceiver BTReceiver = new BroadcastReceiver() {
#SuppressLint("MissingPermission")
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Discovery has found a device. Get the BluetoothDevice
// object and its info from the Intent.
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
blefinder.append("\n" + device.getName() + " | " + device.getAddress());
}
}
};
So This part didn't Work :(
If you see above, I am registering the BTReceiver in onCreate right after listing the already paired devices (by calling ListPairedAndBondedDevices()).
When I ran the debugger, this broadcast receiver never gets called.
6. The Problem Step | Part 2:
Right after this I try to scan for BLE Devices as well by callin scanLeDevice()
ListPairedAndBondedDevices(); // From previous snippet
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); // From previous snippet
registerReceiver(BTReceiver, filter);// From previous snippet
scanLeDevice(); // ---------------->>> CALLING THIS FUNCTION TO SCAN FOR BLE DEVICES
Implementation of scanLeDevice()
private void scanLeDevice() {
if (!scanning) {
// Stops scanning after a predefined scan period.
handler.postDelayed(new Runnable() {
#Override
public void run() {
scanning = false;
bluetoothLeScanner.stopScan(leScanCallback);
blefinder.append("\nSTOPPING BLE SCAN... TIMEOUT REACHED");
}
}, SCAN_PERIOD);
scanning = true;
bluetoothLeScanner.startScan(leScanCallback);
} else {
scanning = false;
bluetoothLeScanner.stopScan(leScanCallback);
blefinder.append("\nSTOPPING BLE SCAN");
}
}
Unfortunately this also fails. The debugger tells me that this part of the code is getting called.
And after 30 seconds of SCAN_PERIOD (The TIMEOUT that I've set), I get the message that the scanning has stopped (STOPPING BLE SCAN)
Now I have implemented the leScanCallback as well (i.e the Device Scan Callback)
private ScanCallback leScanCallback =
new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
blefinder.append("SOMETHING GOT SCANNED?");
blefinder.append("\n"+result.getDevice().toString());
// leDeviceListAdapter.addDevice(result.getDevice());
// leDeviceListAdapter.notifyDataSetChanged();
}
};
Notice that I am not using a ListAdapter since I have no idea about that concept. Hence for starters I am just trying to dump the results in a TextView represented by blefinder . This blefinder prints all the other texts so there is nothing wrong with that TextView variable. When I ran using the, debugger, it is not entering into the leScanCallback piece of code definition at all, even after 30 seconds, after scanLeDevice() function is executed.
I am a little lost here. Is there something I may be missing or doing wrong. It is supposed to be a simple, list the ble/bt devices around my vicinity.
I am happy to share any further information if I have missed. Just let me know in the comments.
Assuming you've done with the permissions that I've mentioned in the comments, we can implement a clean bluetooth LE scanner object and then use it in the UI.
First we implement a result consumer interface in order to deliver the results to the consumers which call the BleScanner.scan() method.
public interface ScanResultConsumer {
public void onDeviceFound(BluetoothDevice device, byte[] scanRecord, int rssi);
public void onScanningStarted();
public void onScanningStopped();
}
Now we need to implement the scanner object that manages the scanning events:
public class BleScanner {
private static final String TAG = BleScanner.class.getSimpleName();
private BluetoothLeScanner leScanner = null;
private BluetoothAdapter bleAdapter = null;
private Handler uiHandler = new Handler(Looper.getMainLooper);
private ScanResultConsumer scanResultConsumer;
private boolean scanning = false;
private final ArrayList<BluetoothDevice> foundDeviceList = new ArrayList<>();
public BleScanner(Context context) {
final BluetoothManager bluetoothManager = (BluetoothManager)
context.getSystemService(Context.BLUETOOTH_SERVICE);
bleAdapter = bluetoothManager.getAdapter();
if(bleAdapter == null) {
Log.d(TAG, "No bluetooth hardware.");
}
else if(!bleAdapter.isEnabled()){
Log.d(TAG, "Blutooth is off.");
}
}
public void scan(ScanResultConsumer scanResultConsumer, long scanTime){
foundDeviceList.clear();
if (scanning){
Log.d(TAG, "Already scanning.");
return;
}
Log.d(TAG, "Scanning...");
if(leScanner == null){
leScanner = bleAdapter.getBluetoothLeScanner();
}
if(scanTimeMs > 0) {
uiHandler.postDelayed(()-> {
if (scanning) {
Log.d(TAG, "Scanning is stopping.");
if(leScanner != null)
leScanner.stopScan(scanCallBack);
else
Log.d(TAG,"Scanner null");
setScanning(false);
}
}, scanTimeMs);
}
this.scanResultConsumer = scanResultConsumer;
leScanner.startScan(scanCallBack);
setScanning(true);
}
private final ScanCallback scanCallBack = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
if (!scanning){
return;
}
if(foundDeviceList.contains(result.getDevice())) {
// This device has already been found
return;
}
// New device found, add it to the list in order to prevent duplications
foundDeviceList.add(result.getDevice());
if(scanResultConsumer != null) {
uiHandler.post(() -> {
scanResultConsumer.onDeviceFound(result.getDevice(),
result.getScanRecord().getBytes(), result.getRssi());
});
}
}
};
public boolean isScanning(){
return scanning;
}
void setScanning(boolean scanning){
this.scanning = scanning;
uiHandler.post(() -> {
if(scanResultConsumer == null) return;
if(!scanning){
scanResultConsumer.onScanningStopped();
// Nullify the consumer in order to prevent UI crashes
scanResultConsumer = null;
} else{
scanResultConsumer.onScanningStarted();
}
});
}
}
Finally we can use this clean implementation in anywhere we need. But do note that a context must be provided in order to create a BleScanner object.
public class MainActivity extends AppCompatActivity {
private BleScanner bleScanner;
private Button buttonScan
// Other codes...
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Other codes...
bleScanner = new BleScanner(getApplicationContext());
// Other codes...
// For example if you want to start scanning on a button press
// Let's say you have a button called buttonScan and initiated it
buttonScan = findViewById(R.id.scan_button);
buttonScan.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
bleScanner.scan(new ScanResultConsumer {
#Override
public void onDeviceFound(BluetoothDevice device, byte[] scanRecord, int rssi) {
// TODO Here you have a newly found device, do something.
}
#Override
q public void onScanningStarted() {
// TODO Scanning has just started, you may want to make some UI changes.
}
#Override
public void onScanningStopped() {
// TODO Scanning has just stopped, you may want to make some UI changes.
}
});
}
});
}
}
Note: I written this code in a plain editor not in Android Studio. So there may be some errors, let me know if any.
First you should check if your app was granted the location permission(s) in the Settings app > Apps <your_app> > permissions. Some permissions (like ACCESS_*_LOCATION and BLUETOOTH_ADMIN) need to be requested at runtime and granted by the user through a popup. Normally you should get a SecurityException or a logcat warning when trying to execute code requiring permissions which your app doesn't have, but it's not uncommon for android to skip over error handling.
Consider using this method to start the scan in order check its result code for potential additional info about what is (not) going on.
You might also get some clues by logging all actions received in BTReceiver.onReceive(), not just action found.
Lastly check if the location settings on your device to ensure that bluetooth scanning is turned on (Settings app > location > wifi and bluetooth scanning )
Related
I'm trying to handle the event when a user presses "ok" or "cancel" on the automatic permission dialog presented when I connect a "known" USB device to the android phone.
I'm using the android.usb.host library and can send and receive between the android phone and the device. Futhermore do I handle the "USB_DEVICE_ATTACHED" and "USB_DEVICE_DETACHED" using a BroadcastReceiver without any problems.
I want to enable a sort of "autoconnect" feature and therefore I need to know when the user has pressed "ok" in the automatically displayed permission dialog, but I can't find anything online at all. All I find is "bypass dialog", but this is not what I want or need.
When I connect the usb device to the android phone, a permission dialog is automatically displayed because I use the "device_filter.xml" solution from androids documentation which can be seen here Android Usb Docs.
This is how I handle the USB_DEVICE_ATTATCHED and USB_DEVICE_DETACHED events:
public NativeUsbService(ReactApplicationContext reactContext) {
...
// register device attached/detached event listeners
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
reactContext.registerReceiver(usbReceiver, filter);
...
}
And then the Broadcast Receiver:
private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
synchronized (this) {
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if(device != null){
usbDevice = device;
} else {
Log.d(TAG, "onReceive: DEVICE WAS ATTACHED AND WAS NULL :(");
}
}
} else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
Log.d(TAG, "onReceive: Device was detached!");
if(connection != null) {
connection.releaseInterface(usbDeviceInterface);
connection.close();
}
connection = null;
usbDevice = null;
endpointIn = null;
endpointOut = null;
}
}
};
I have tried multiple different approaches, but nothing has worked.
I have tried getting the user response in from the intent, like with a manual permission request like below:
private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
synchronized (this) {
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if(device != null){
usbDevice = device;
// THIS DOES NOT WORK ↓↓↓
if(intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
// The code never gets here...
}
} else {
Log.d(TAG, "onReceive: DEVICE WAS ATTACHED AND WAS NULL :(");
sendEvent("onDeviceAttached", false);
}
}
} else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
if(connection != null) {
connection.releaseInterface(usbDeviceInterface);
connection.close();
}
connection = null;
usbDevice = null;
endpointIn = null;
endpointOut = null;
}
}
};
I have also tried by adding a usb permission listener to the broadcast receiver by first adding the action name to my class variables:
private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
Then adding the action to my intent filter like so:
public NativeUsbService(ReactApplicationContext reactContext) {
// register device attached/detached event listeners
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
filter.addAction(ACTION_USB_PERMISSION); // added action to my intent filter
reactContext.registerReceiver(usbReceiver, filter);
}
And finally reacting to the action like so:
private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
synchronized (this) {
UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
if(device != null){
usbDevice = device;
}
}
} else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
Log.d(TAG, "onReceive: Device was detached!");
if(connection != null) {
connection.releaseInterface(usbDeviceInterface);
connection.close();
}
connection = null;
usbDevice = null;
endpointIn = null;
endpointOut = null;
sendEvent("onDeviceDetached", true);
}
else if (action.equals(ACTION_USB_PERMISSION)) {
Log.d(TAG, "onReceive: ACTION_USB_PERMISSION");
if(intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
Log.d(TAG, "onReceive: EXTRA_PERMISSION_GRANTED = true");
} else Log.d(TAG, "onReceive: EXTRA_PERMISSION_GRANTED = false");
}
}
};
Please make me aware of any missing information.
Any help is greatly appreciated.
Answering my own question in case someone else is facing the same issue.
I though about manually requesting the permission again, after permission was granted, since it is possible to handle this manual permission request when user presses an option in the dialog. I discarded this idea, not because it wouldn't work, but because I saw it as unecessary for the user to also have to press another dialog after the initial (automatic dialog).
I must add that I have not implemented this solution, so I do not know with certainty that it would prompt the user again, but I have had trouble with the manual permission request previously. If you want to try this approach the method belongs to the UsbManager class and is invoke like so usbManger.requestPermission(usbDevice).
I ended up with a solution where I start a thread which runs a loop calling usbManager.hasPermission(usbDevice) until it has permission and then emits an event (emitting this event is my use case, implement it how you like).
The solution can be seen here:
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import com.facebook.react.bridge.ReactApplicationContext;
...
private static volatile boolean permissionThreadShouldStop = false;
private static Thread activePermissionThread = null;
...
public static void usbPermissionEventEmitter(ReactApplicationContext reactContext, UsbManager usbManager, UsbDevice usbDevice) {
if((activePermissionThread != null && activePermissionThread.isAlive())) {
activePermissionThread.interrupt();
}
permissionThreadShouldStop = false;
activePermissionThread = new Thread(new Runnable() {
#Override
public void run() {
while (!usbManager.hasPermission(usbDevice) && !permissionThreadShouldStop) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
if(usbManager.hasPermission(usbDevice)) {
sendEvent(reactContext, "onUsbPermissionGranted", true);
}
}
});
activePermissionThread.start();
}
The ReactApplicationContext can be swapped with the normal android context. But this is for a react native module, so I use the reactContext.
I hope this will be helpful for someone, because i'm honestly really surpriced how scarse the android documentation is in regards to implementing Usb functionality using the android.hardware.usb library.
Also in general when searching for information online, I have often found myself lost since there is very little information on this subject.
I am trying to discover all available bluetooth devices and pass to another activity.
However even when looking at the Android Docs, I am unable to figure out why I cannot discover any devices and my ArrayList remains empty.
OnClick execute this:
mBluetoothAdapter.startDiscovery();
My broadcast listener also works but nothing is every returned and ArrayList remains empty.
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
mDeviceList.add(device);
showToast("Found device " + device.getName());
}
if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
if (state == BluetoothAdapter.STATE_ON) {
showToast("Enabled");
showEnabled();
}
}
if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
mDeviceList = new ArrayList<>();
mProgressDlg.show();
}
if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
mProgressDlg.dismiss();
Intent newIntent = new Intent(MainScreen.this, DeviceListActivity.class);
if (mDeviceList.isEmpty()){
Log.d("onReceive: ", "EMPTY");
}
newIntent.putParcelableArrayListExtra("device.list", mDeviceList);
startActivity(newIntent);
}
}
};
GIST
Answered my own question.
The fix was to turn location on with app permissions.
Apparently this is required for Android Marshmallow
I'm currently making an app that adapt the volume depending on the bluetooth device you are connected to.
I am having issue when I want to change the volume of the music
My app detect when a bluetooth device is connected and it can change the volume but it is like it goes too quickly
Here is some of the code:
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
BluetoothDevice d = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
deviceConnected(d);
}
else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
deviceDisconnected(d);
}
}
};
private void deviceConnected(BluetoothDevice bluetoothDevice) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
boolean showToasts = prefs.getBoolean("show_toasts", false);
if (showToasts) { Toast.makeText(this, getResources().getString(R.string.connected_to) + " " + bluetoothDevice.getName(), Toast.LENGTH_SHORT).show(); }
mDeviceDAO = new DeviceDAO(this);
mDeviceDAO.open();
DeviceOptions device = mDeviceDAO.select(bluetoothDevice.getAddress());
if (device == null) { return; }
if(device.getActivated() == 0) {
return;
}
int v = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
saveLastVolume(v);
int volume = device.getVolume();
int flag = 0;
if(prefs.getBoolean("show_am_ui", false)) {
flag = AudioManager.FLAG_SHOW_UI;
}
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
volume,
flag);
}
If add a delay of around 3000ms when I change the volume, it will work properly but it's not a clean solution. In fact the toast for the connection will be displayed before the bluetooth device is "completely" connected
I also found an app that do the same thing but it seems that I'm doing the same thing.
Here is its githublink
What's your capture is the ACL connected or ACL disconnected, yes you are right once your toast appears i.e. the ACL connected does not mean the Bluetooth profile connected(exactly what you said "completely" connected), it only means the physical connection established between two device, you might as well to receive other event, e.g. HFP/A2dp connected, that's the real profile connected.
However the ACL disconnected is the milestone that the Bluetooth connection is really disconnected.
I'm attempting to request ACCESS_FINE_LOCATION permissions in order to get the user's current location.
My logging indicates that my app does not currently have this permission when querying ContextCompat.checkSelfPermission(), but when calling ActivityCompat.requestPermissions() nothing is displayed.
My Google map code (implementing OnMapReadyCallback and ActivityCompat.OnRequestPermissionsResultCallback()) is in a FragmentActivity.
I have managed to get the requestPermissions() function working successfully in other Activities in the app, it's just the one with the Google map. It doesn't work when placed in the onCreate() method of the Activity, or in onMapReady() (where it needs to go).
if(ContextCompat.checkSelfPermission(LocationActivity.this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "not granted");
final String[] permissions = new String[] {android.Manifest.permission.ACCESS_FINE_LOCATION};
if(ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.ACCESS_FINE_LOCATION)) {
Log.d(TAG, "rationale");
// Explain to the user why permission is required, then request again
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("We need permissions")
.setCancelable(false)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
ActivityCompat.requestPermissions(LocationActivity.this, permissions, 1);
}
});
AlertDialog alert = builder.create();
alert.show();
} else {
Log.d(TAG, "request" + android.Manifest.permission.ACCESS_FINE_LOCATION);
// If permission has not been denied before, request the permission
ActivityCompat.requestPermissions(LocationActivity.this, permissions, 1);
}
} else {
Log.d(TAG, "granted");
}
Any ideas? Is it something to do with my Activity's class (FragmentActivity), or possible the Google map calling the permissions request asynchronously?
After stripping out my class completely, and it still not working, I realised that this Activity is being instantiated using a TabHost.
When I stop using the TabHost, the prompt is displayed successfully. I guess TabHosts are not supported by the new permissions prompts - is this a bug?
Same problem as App requests aren't showing up
I ended up creating a PermissionsRequestActivity which handles the permission request and response on behalf of my TabHost, then exits (pass the requested permission information in through the Intent extras Bundle).
It passes back the response to the request as a Broadcast, which is picked up by my TabHost.
Bit of a hack but works OK!
Check that you have already added the requested permission in Android's manifest file like before Android M, only then you will get expected behaviour.
Add the permission to your manifest so you can request it via
ActivityCompat.requestPermissions:
<uses-permission android:name="android.permission. ACCESS_FINE_LOCATION" />
I've faced the same problem on a project which use TabHost.
Basis on #Robin solution, I use the EventBus library for send a message from child activity to the TabActity.
EventBus : https://github.com/greenrobot/EventBus
Create an event object :
public class MessageEvent {
private String message;
public MessageEvent(String message){
this.message = message;
}
public String getMessage(){
return this.message;
}
}
In your main Activity :
private EventBus eventBus = EventBus.getDefault();
#Override
protected void onCreate(Bundle savedInstanceState) {
eventBus.register(this);
}
#Override
protected void onDestroy() {
eventBus.unregister(this);
super.onDestroy();
}
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
if (event.getMessage().equals("contacts")){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(android.Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainPage.this,new String[]{android.Manifest.permission.WRITE_CONTACTS}, 100 );
}
}
};
Set a different message for the permission your want to request.
In your child activity you can than post the adequate message :
EventBus.getDefault().post(new MessageEvent("contacts"));
Be aware of onRequestPermissionsResult callback and the request code ;)! It will only work in the main activity.
Developing a music app. In my Music Service, I have written a custom broadcast receiver. It works with Intent.ACTION_HEADSET_PLUG but not with Intent.ACTION_MEDIA_BUTTON.
Please guide on how to control music controls from bluetooth devices (Play/Pause/Next/Previous).
Code for Intent.ACTION_HEADSET_PLUG is:
#Override
public void onReceive(Context context, Intent intent) {
// aux
if(intent.getAction().equals(Intent.ACTION_HEADSET_PLUG))
{
int state = intent.getIntExtra("state", -1);
if(state == 0)
{
// Headset is unplugged. PAUSE
pauseSong();
sendBroadcast();
}
else if(state == 1)
{
// headset is plugged
resumeSong();
sendBroadcast();
}
}
}
As explained in the Media playback the right way talk, you must be registered as the 'preferred media app'. This is much easier when you use MediaSessionCompat as explained in the MediaSessionCompat video:
ComponentName mediaButtonReceiver =
new ComponentName(context, YourBroadcastReceiver.class);
MediaSessionCompat mediaSession =
new MediaSessionCompat(context,
tag, // Debugging tag, any string
mediaButtonReceiver,
null);
mediaSession.setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mediaSession.setCallback(this); // a MediaSessionCompat.Callback
// This is what enables media buttons and should be called
// Immediately after getting audio focus
mediaSession.setActive(true);
The accepted answer doesn't seem to work
I achieved it the other way with ExoPlayer
step #1
added dependencies
implementation 'com.google.android.exoplayer:exoplayer:2.13.1'
implementation 'com.google.android.exoplayer:extension-mediasession:2.13.1'
step #2
mediaSession = MediaSessionCompat(this, "Vion")
val mediaSessionConnector = MediaSessionConnector(mediaSession!!)
mediaSessionConnector.setMediaButtonEventHandler(object: MediaSessionConnector.MediaButtonEventHandler{
override fun onMediaButtonEvent(player: Player, controlDispatcher: ControlDispatcher, mediaButtonEvent: Intent): Boolean {
val event: KeyEvent = mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT) ?: return false
if(event.action == KeyEvent.ACTION_UP) {
when(event.keyCode) {
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> {
}
KeyEvent.KEYCODE_MEDIA_PAUSE -> {
}
KeyEvent.KEYCODE_MEDIA_PLAY -> {
}
KeyEvent.KEYCODE_MEDIA_NEXT -> {
}
}
}
return true
}
})
mediaSessionConnector.setPlayer(exoPlayer)