I am new to Android development and I can't understand how to properly turn all ble notifications and get all of them.
I've tried to loop through all services
for(BluetoothGattService service : gatt.getServices()){
if( service.getUuid().equals(Step_UUID)) {
BluetoothGattCharacteristic characteristicData = service.getCharacteristic(Step_UUID);
for (BluetoothGattDescriptor descriptor : characteristicData.getDescriptors()) {
descriptor.setValue( BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
gatt.writeDescriptor(descriptor);
}
gatt.setCharacteristicNotification(characteristicData, true);
}
}
but I don't get any notifications back.
Please, help I do not understand what I am doing wrong..
EDIT
Right now I use this method to enable notifications after services are discovered:
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {
// Setting default write type according to CDT 222486
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
String serviceUUID = characteristic.getService().getUuid().toString();
String serviceName = GattAttributes.lookupUUID(characteristic.getService().getUuid(), serviceUUID);
String characteristicUUID = characteristic.getUuid().toString();
String characteristicName = GattAttributes.lookupUUID(characteristic.getUuid(), characteristicUUID);
String descriptorUUID = GattAttributes.CLIENT_CHARACTERISTIC_CONFIG;
String descriptorName = GattAttributes.lookupUUID(UUIDDatabase.UUID_CLIENT_CHARACTERISTIC_CONFIG, descriptorUUID);
if (characteristic.getDescriptor(UUID.fromString(GattAttributes.CLIENT_CHARACTERISTIC_CONFIG)) != null) {
if (enabled == true) {
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(GattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
boolean aaa = mBluetoothGatt.writeDescriptor(descriptor);
aaa = mBluetoothGatt.writeDescriptor(descriptor);
aaa = false;
} else {
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(GattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
}
boolean aaaa = mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
aaaa = false;
}
The problem is that first characteristic notifies well, but all others I am trying to enable fails on the line
mBluetoothGatt.writeDescriptor(descriptor);
Don't know what to do...
My problem was resolved by overiding onDescriptorWrite method. I made a flag inside, and if it is true I continue to notify all left characteristics.
#Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
String serviceUUID = descriptor.getCharacteristic().getService().getUuid().toString();
String serviceName = GattAttributes.lookupUUID(descriptor.getCharacteristic().getService().getUuid(), serviceUUID);
String characteristicUUID = descriptor.getCharacteristic().getUuid().toString();
String characteristicName = GattAttributes.lookupUUID(descriptor.getCharacteristic().getUuid(), characteristicUUID);
String descriptorUUID = descriptor.getUuid().toString();
String descriptorName = GattAttributes.lookupUUID(descriptor.getUuid(), descriptorUUID);
if (status == BluetoothGatt.GATT_SUCCESS) {
written = true;
numberLeft--;
} else {
written = true;
numberLeft--;
}
}
Related
I am trying to create an indoor location app to detect own location and wayfinding. I have already set up my bluetooth app to scan and display found devices and beacons. However i found out that some of the devices found were repeated. What do i need to do to stop it and i wanted to find only certain devices mac address. May i know how to use the method ScanFilter and what is the difference between ScanFilter and ScanFilter.Builder? Do i need to implement ScanSetting?
i have try putting this
ScanFilter scanFilterMac = new ScanFilter.Builder().setDeviceAddress("88:88:88:B0:03:DB").build();
I change the mac address only but didn't get any result. and can i do something like this since i know the first 6 digit is the same
ScanFilter scanFilterMac = new ScanFilter.Builder().setDeviceAddress("68:54:F5:([A-Fa-f0-9]{2}:){2}([A-Fa-
f0-9]{2})").build();
public void startScanning() {
final BluetoothLeScanner btScanner = btAdapter.getBluetoothLeScanner();
if (btScanner == null) {
// not enabled yet or not supported
return;
}
System.out.println("start scanning");
peripheralTextView.setText("");
startScanningButton.setVisibility(View.INVISIBLE);
stopScanningButton.setVisibility(View.VISIBLE);
AsyncTask.execute(new Runnable() {
#Override
public void run() {
ScanFilter scanFilterMac = new ScanFilter.Builder().setDeviceAddress("68:54:F5:88:88:88").build();
listFilter.add(scanFilterMac);
btScanner.startScan(listFilter,leScanCallback);
}
});
}
final class BleScanCallback extends ScanCallback {
// address -> device map
private final Map<String, BluetoothDevice> scanResults;
public void onScanResult(int callbackType, ScanResult result) {
this.addScanResult(result);
}
public void onBatchScanResults( List results) {
for (final Object result1 : results) {
ScanResult result = (ScanResult) result1;
this.addScanResult(result);
}
}
public void onScanFailed(int errorCode) {
}
private void addScanResult(ScanResult result) {
BluetoothDevice device = result.getDevice();
String name = device.getName();
if (name != null && !this.scanResults.containsKey(device.getAddress())) {
String var6 = device.getAddress();
this.scanResults.put(var6, device);
} else {
StringBuilder var10001 = (new StringBuilder()).append("Ble addScanResult ignore ");
Log.d("Ble", var10001.append(device.getName()).append(' ').append(device.getType()).append(' ').append(device.getAddress()).append(' ').toString());
}
}
public BleScanCallback( Map<String, BluetoothDevice> scanResults) {
super();
this.scanResults = scanResults;
}
}
I've been working on a searching auto-complete feature with Bootstrap-3-Typeahead ,GWT and data comes from http://dawa.aws.dk/dok/api by JsonP request..
The Typahead creation method is below
private Typeahead<Location> createTypeAhead() {
typeAhead = new Typeahead<>(new Dataset<Location>() {
#Override
public void findMatches(final String query, final SuggestionCallback<Location> callback) {
requestCounter--;
startSendingRequest = true;
clear.setIcon(IconType.SPINNER);
clear.setIconSpin(true);
final Set<Suggestion<Location>> suggestions = new HashSet<>();
queryLower = query.toLowerCase();
JsonpRequestBuilder jsonpRequestBuilder;
if (!streetSelected) {
jsonpRequestBuilder = new JsonpRequestBuilder();
jsonpRequestBuilder.requestObject("https://dawa.aws.dk/vejnavne/autocomplete?side=1&per_side=500&noformat=1&q=" + queryLower + "*", new AsyncCallback<MyJsArray<VejAutocomplete>>() {
#Override
public void onFailure(Throwable caught) {
Notify.notify("suggestion matches failed");
}
#Override
public void onSuccess(MyJsArray<VejAutocomplete> result) {
Set<Location> locationSet = new LinkedHashSet<>();
for (VejAutocomplete item : result.getAsList()) {
String lowerCase = item.getTekst().toLowerCase();
if (lowerCase.startsWith(queryLower)) {
locationSet.add(new Location(Location.LocationType.STREET, item.getTekst(), item));
locationArrayList.clear();
locationArrayList.addAll(locationSet);
}
}
}
});
}
for (Location address : locationArrayList) {
String value = address.getValue();
Suggestion<Location> s = Suggestion.create(value, address, this);
if (address.getValue().toLowerCase().startsWith(queryLower)) {
suggestions.add(s);
}
}
callback.execute(suggestions);
if (typeAhead.getValue().length() != 0 && queryLower.length() <= 5 && requestCounter < 5 && requestCounter > 0) {
new Timer() {
#Override
public void run() {
findMatches(queryLower, callback);
}
}.schedule(500);
} else {
clear.setIconSpin(false);
clear.setIcon(IconType.CLOSE);
requestCounter = 5;
}
}
});
return typeAhead;
}
The result is like below:
I used recursion to send 4-5 times of request because it's not showing the suggestion list with the keyword of single letter. And It still won't work with some single letters like "s" or "e". Data successfully retrieved from the API but doesn't show in the suggestion list like below:
I assume that I should cache all search results then recreate the auto-complete from scratch, it becomes complicated in that case.
Any good idea to solve this problem?
SITUATION
In the code below you can see 2 REST services which both should return a MessageVO. The first service (serviceThatDoesWork) returns a MessageVO as excpected, but the second service (serviceThatDoesNotWork) refuses to, it doesn't even give any output at all.
However returning a Response (java.ws.rs.core.Response) with serviceThatDoesNotWork does give an output. Even when I skip the 'doStuff'-methods and create a dummy-MessageVO that is exactly the same for each service, the 2nd one doesn't return anything.
QUESTION
Why does the 2nd service fail to return a MessageVO? It doens't return anything when I try returning a MessageVO, and nothing out of the ordinary appears in the logging.
The two services need to return exactly the same kind of thing but still one of them doesn't want to return anything, what am I not seeing here?
Could it be because of the path (and/or the amount of parameters)?
CODE
MyServices.java:
#Path("/myService")
...
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/myPath/{param1}/{param2}/{param3}")
public MessageVO serviceThatDoesWork(#PathParam("param1") Integer param1_id, #PathParam("param2") Integer param2_id, #PathParam("param2") Integer param2_id)
{
List<SomethingVO> lstO = MyRestServiceBusiness.doStuff(param1_id, param2_id, param3_id);
//return SUCCESS or FAIL message
MessageVO msg = new MessageVO();
if(lstO.size() > 0)
{
List<String> s = new ArrayList<String>();
for(SomethingVO k : lstO)
{
s.add(k.getId().toString());
}
msg.setItem_ids(s);
msg.setMsg("SUCCESS");
}
else
{
msg.setMsg("FAIL");
}
return msg;
}
...
#GET
#Path("/myPath/{param1}/{param2}/{param3}/{param4}/.../{param15}{a:(/a/[^/]+?)?}{b:(/b/[^/]+?)?}")
public Response serviceThatDoesNotWork(#PathParam("param1")Integer param1_id, ..., #PathParam("param15") Integer param15_id,
#PathParam("a") String a_id, #PathParam("b") String b_id)
{
//PUT 'OPTIONAL' PARAMS IN A LIST
List<Integer> lstI = new ArrayList<Integer>();
String aId = a_id != null ? a_id.split("/")[2] : null;
String bId = b_id != null ? b_id.split("/")[2] : null;
if(aId != null)
{
lstI.add(Integer.parseInt(aId ));
}
if(bId != null)
{
lstI.add(Integer.parseInt(bId ));
}
//DO STUFF
String afsId = "";
if(lstI.size() > 0)
{
afsId = MyRestServiceBusiness.doStuff(param1, ..., lstI);
}
//return SUCCESS or FAIL message
MessageVO msg = new MessageVO();
if(afsId != null && !afsId.isEmpty())
{
List<String> s = new ArrayList<String>();
s.add(afsId);
msg.setItem_ids(s);
msg.setMsg("SUCCESS");
}
else
{
List<String> s = new ArrayList<String>();
for(Integer i : lstI)
{
s.add(i.toString());
}
msg.setItem_ids(s);
msg.setMsg("FAIL");
}
//WENT THROUGH ALL ABOVE CODE AS EXPECTED, MESSAGEVO HAS BEEN FILLED PROPERLY
return msg;
}
CODE MessageVO.java:
#XmlRootElement
public class MessageVO
{
private String msg;
private List<String> item_ids;
//GETTERS
#XmlElement(name = "Message")
public String getMsg() {
return msg;
}
#XmlElement(name = "Item ID's")
public List<String> getItem_ids() {
return item_ids;
}
//SETTERS
public void setMsg(String msg) {
this.msg = msg;
}
public void setItem_ids(List<String> item_ids) {
this.item_ids = item_ids;
}
If I need to provide extra information please ask, this is my first attempt at (REST-) services.
As Vaseph mentioned in a comment I just forgot the #Produces annotation in the 2nd service.
I wanted to harvest some data on specific apps in
Google play marketplace.
I have used this unofficial API:
https://code.google.com/p/android-market-api/
Here is my code, that basically gets list of apps' names
and try to fetch other data on each app:
public void printAllAppsData(ArrayList<AppHarvestedData> dataWithAppsNamesOnly)
{
MarketSession session = new MarketSession();
session.login("[myGamil]","[myPassword]");
session.getContext().setAndroidId("dead00beef");
final ArrayList<AppHarvestedData> finalResults = new ArrayList<AppHarvestedData>();
for (AppHarvestedData r : dataWithAppsNamesOnly)
{
String query = r.name;
AppsRequest appsRequest = AppsRequest.newBuilder()
.setQuery(query)
.setStartIndex(0).setEntriesCount(10)
//.setCategoryId("SOCIAL")
.setWithExtendedInfo(true)
.build();
session.append(appsRequest, new Callback<AppsResponse>() {
#Override
public void onResult(ResponseContext context, AppsResponse response) {
List<App> apps = response.getAppList();
for (App app : apps) {
AppHarvestedData r = new AppHarvestedData();
r.title = app.getTitle();
r.description = app.getExtendedInfo().getDescription();
String tmp = app.getExtendedInfo().getDownloadsCountText();
tmp = tmp.replace('<',' ').replace('>',' ');
int indexOf = tmp.indexOf("-");
tmp = (indexOf == -1) ? tmp : tmp.substring(0, indexOf);
r.downloads = tmp.trim();
r.rating = app.getRating();
r.version = app.getVersion();
r.userRatingCount = String.valueOf(app.getRatingsCount());
finalResults.add(r);
}
}
});
session.flush();
}
for(AppHarvestedData res : finalResults)
{
System.out.println(res.toString());
}
}
}
Should I realyy call session.flush(); at this point?
all my quesries return empty collection as a result,
even though I see there are some names as input.
It works fine when I send only one hard coded app name as a query.
session.flush() close you session
you should open session for each query. pay attention the user can be locked for few minutes so you should have many users to to those queries.
if you have the AppId you should use that query:
String query = r.name;
AppsRequest appsRequest = AppsRequest.newBuilder()
.setAppId("com.example.android")
.setWithExtendedInfo(true)
.build();
session.append(appsRequest, new Callback<AppsResponse>() {
#Override
public void onResult(ResponseContext context, AppsResponse response) {
List<App> apps = response.getAppList();
for (App app : apps) {
AppHarvestedData r = new AppHarvestedData();
r.title = app.getTitle();
r.description = app.getExtendedInfo().getDescription();
String tmp = app.getExtendedInfo().getDownloadsCountText();
tmp = tmp.replace('<',' ').replace('>',' ');
int indexOf = tmp.indexOf("-");
tmp = (indexOf == -1) ? tmp : tmp.substring(0, indexOf);
r.downloads = tmp.trim();
r.rating = app.getRating();
r.version = app.getVersion();
r.userRatingCount = String.valueOf(app.getRatingsCount());
finalResults.add(r);
}
}
});
session.flush();
}
if you want to download also screenshoot or images you should call this query:
GetImageRequest? imgReq; imgReq = GetImageRequest?.newBuilder().setAppId(appId).setImageUsage(AppImageUsage?.SCREENSHOT).setImageId("0").build();
session.append(imgReq, new Callback<GetImageResponse>() {
#Override public void onResult(ResponseContext? context, GetImageResponse? response) {
Log.d(tag, "------------------> Images response <----------------"); if(response != null) {
try {
//imageDownloader.download(image, holder.image, holder.imageLoader);
Log.d(tag, "Finished downloading screenshot 1...");
} catch(Exception ex) {
ex.printStackTrace();
}
} else {
Log.e(tag, "Response was null");
} Log.d(tag, "------------> End of Images response <------------");
}
});
I was wondering how could I detect if my own service is enabled. So I could check if my service is not enabled, then tell the user to enable it.
Below is the method to check if your accessibility service is enabled or not.
Note: Change value of YOURAccessibilityService with your Service.
// To check if service is enabled
private boolean isAccessibilitySettingsOn(Context mContext) {
int accessibilityEnabled = 0;
final String service = getPackageName() + "/" + YOURAccessibilityService.class.getCanonicalName();
try {
accessibilityEnabled = Settings.Secure.getInt(
mContext.getApplicationContext().getContentResolver(),
android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
Log.v(TAG, "accessibilityEnabled = " + accessibilityEnabled);
} catch (Settings.SettingNotFoundException e) {
Log.e(TAG, "Error finding setting, default accessibility to not found: "
+ e.getMessage());
}
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
if (accessibilityEnabled == 1) {
Log.v(TAG, "***ACCESSIBILITY IS ENABLED*** -----------------");
String settingValue = Settings.Secure.getString(
mContext.getApplicationContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
mStringColonSplitter.setString(settingValue);
while (mStringColonSplitter.hasNext()) {
String accessibilityService = mStringColonSplitter.next();
Log.v(TAG, "-------------- > accessibilityService :: " + accessibilityService + " " + service);
if (accessibilityService.equalsIgnoreCase(service)) {
Log.v(TAG, "We've found the correct setting - accessibility is switched on!");
return true;
}
}
}
} else {
Log.v(TAG, "***ACCESSIBILITY IS DISABLED***");
}
return false;
}
And to call this method:
if (!isAccessibilitySettingsOn(getApplicationContext())) {
startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
}
This will check and launch accessibility settings if not enabled.
This is a modified version of Jakub Bláha's answer in java.
public boolean isAccessServiceEnabled(Context context, Class accessibilityServiceClass)
{
String prefString = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
return prefString!= null && prefString.contains(context.getPackageName() + "/" + accessibilityServiceClass.getName());
}
This is somehow a smaller version, but it is working.
fun isAccessServiceEnabled(context: Context): Boolean {
val prefString =
Settings.Secure.getString(context.contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
return prefString.contains("${context.packageName}/${context.packageName}.${context.getString(R.string.access_service_name)}")
}
Feel free to correct me if there is something missing.
1:Kotlin Based answer
2:Added a current package name check as well cos it will return true only if accessibility service will be Enabled for current package, Recently it was returning true if any package accessibility service enabled
private fun checkAccessibilityPermission(): Boolean {
var isAccessibilityEnabled = false
(requireActivity().getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager).apply {
installedAccessibilityServiceList.forEach { installedService ->
installedService.resolveInfo.serviceInfo.apply {
if (getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK).any { it.resolveInfo.serviceInfo.packageName == packageName && it.resolveInfo.serviceInfo.name == name && permission == Manifest.permission.BIND_ACCESSIBILITY_SERVICE && it.resolveInfo.serviceInfo.packageName == requireActivity().packageName })
isAccessibilityEnabled = true
}
}
}
if (isAccessibilityEnabled.not())
startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
return isAccessibilityEnabled
}
Kotlin version based on answer of #Pankaj Kumar
inline fun <reified T> Context.isAccessibilityEnabled(): Boolean {
var enabled = 0
try {
enabled = Settings.Secure.getInt(contentResolver, Settings.Secure.ACCESSIBILITY_ENABLED)
} catch (e: SettingNotFoundException) {
Timber.e(e)
}
if (enabled == 1) {
val name = ComponentName(applicationContext, T::class.java)
val services = Settings.Secure.getString(contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
return services?.contains(name.flattenToString()) ?: false
}
return false
}