Tapping a Notification from OneSignal does not open a result activity!!!! - Android - notifications

Now I know there are lot of questions on this, but I have faced no luck at all and thought to ask a question here.
I have an application which just runs a splashscreen followed by a MainActivity(Which is just a WebView)
Now I integrated this with OneSignal for receiving push notifications.
Everything works good, I mean I get a notification when sent through the onesignal website to my phone - but the thing I am facing is, tapping the notification does not get my ResultActivity(Just a activity displaying a Toast of message).
My code snippets looks as below:
splashscreen.java:
public class splashscreen extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.splash);
Thread splashThread = new Thread() {
#Override
public void run() {
try {
int waited = 0;
while (waited < 5000) {
sleep(100);
waited += 100;
}
} catch (InterruptedException e) {
// do nothing
} finally {
finish();
Intent i = new Intent();
i.setClassName("com.google",
"com.google.Main");
startActivity(i);
}
}
};
splashThread.start();
}
#Override
protected void onPause() {
super.onPause();
OneSignal.onPaused();
}
#Override
protected void onResume() {
super.onResume();
OneSignal.onResumed();
}
}
Main.java:
#SuppressLint("SetJavaScriptEnabled") public class Main extends Activity {
/** Called when the activity is first created. */
WebView web;
private static Activity currentActivity;
Intent resultIntent = new Intent(this, ResultActivity.class);
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
currentActivity = this;
web = (WebView) findViewById(R.id.my_webview);
web.setWebViewClient(new myWebClient());
web.getSettings().setJavaScriptEnabled(true);
web.loadUrl("http://google.com");
OneSignal.init(this, "xxxxxxx", "xxx-xxx-xxxx-xxxx-xxxxxx", new ExampleNotificationOpenedHandler());
}
#Override
public boolean onCreateOptionsMenu(Menu menu){
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.exit:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public class myWebClient extends WebViewClient
{
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
// TODO Auto-generated method stub
super.onPageStarted(view, url, favicon);
}
public boolean shouldOverrideUrlLoading(WebView view, String url) {
String url2="http://google.com";
// all links with in ur site will be open inside the webview
//links that start with your domain example(http://www.example.com/)
if (url != null && url.startsWith(url2)){
return false;
}
// all links that points outside the site will be open in a normal android browser
else {
view.getContext().startActivity(
new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
return true;
}
}
}
// To handle "Back" key press event for WebView to go back to previous screen.
#Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if ((keyCode == KeyEvent.KEYCODE_BACK) && web.canGoBack()) {
web.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
#Override
protected void onPause() {
super.onPause();
OneSignal.onPaused();
}
#Override
protected void onResume() {
super.onResume();
OneSignal.onResumed();
}
// NotificationOpenedHandler is implemented in its own class instead of adding implements to MainActivity so we don't hold on to a reference of our first activity if it gets recreated.
private class ExampleNotificationOpenedHandler implements NotificationOpenedHandler {
/**
* Callback to implement in your app to handle when a notification is opened from the Android status bar or
* a new one comes in while the app is running.
* This method is located in this activity as an example, you may have any class you wish implement NotificationOpenedHandler and define this method.
*
* #param message The message string the user seen/should see in the Android status bar.
* #param additionalData The additionalData key value pair section you entered in on onesignal.com.
* #param isActive Was the app in the foreground when the notification was received.
*/
#Override
public void notificationOpened(String message, JSONObject additionalData, boolean isActive) {
String messageTitle = "OneSignal Example" + isActive, messageBody = message;
try {
if (additionalData != null) {
if (additionalData.has("title"))
messageTitle = additionalData.getString("title");
if (additionalData.has("actionSelected"))
messageBody += "\nPressed ButtonID: " + additionalData.getString("actionSelected");
messageBody = message + "\n\nFull additionalData:\n" + additionalData.toString();
}
} catch (JSONException e) { }
/*
new AlertDialog.Builder(Main.currentActivity)
.setTitle(messageTitle)
.setMessage(messageBody)
.setCancelable(true)
.setPositiveButton("OK", null)
.create().show();
*/
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(Main.currentActivity)
.setSmallIcon(R.drawable.cc)
.setContentTitle(messageTitle)
.setDefaults(
Notification.DEFAULT_SOUND
| Notification.DEFAULT_VIBRATE
| Notification.FLAG_AUTO_CANCEL)
.setContentText(messageBody);
resultIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent resultPendingIntent =
PendingIntent.getActivity(
Main.currentActivity,
0,
resultIntent,
PendingIntent.FLAG_UPDATE_CURRENT
| PendingIntent.FLAG_ONE_SHOT
);
mBuilder.setContentIntent(resultPendingIntent);
NotificationManager mNotifyMgr =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mNotifyMgr.notify(001, mBuilder.build());
}
}
}
ResultActivity.java:
public class ResultActivity extends Activity {
/** Called when the activity is first created. */
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
/*
ImageView image = new ImageView(this);
image.setImageDrawable(getResources().getDrawable(R.drawable.ic_launcher));
setContentView(image);
Toast.makeText(getApplicationContext(),
"Do Something NOW",
Toast.LENGTH_LONG).show();
*/
super.onCreate(savedInstanceState);
setContentView(R.layout.splash);
}
}
Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="21" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission android:name="com.google.permission.C2D_MESSAGE" android:protectionLevel="signature" />
<uses-permission android:name="com.google.permission.C2D_MESSAGE" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#android:style/Theme.NoTitleBar.Fullscreen" >
<meta-data android:name="com.google.android.gms.version"
android:value="#integer/google_play_services_version" />
<activity android:name="com.onesignal.NotificationOpenedActivity" android:theme="#android:style/Theme.NoDisplay">
</activity>
<receiver
android:name="com.onesignal.GcmBroadcastReceiver"
android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.google" />
</intent-filter>
</receiver>
<service android:name="com.onesignal.GcmIntentService" />
<activity android:name=".splashscreen" android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".Main" android:label="#string/app_name" >
</activity>
<activity android:name=".ResultActivity"
android:label="#string/app_name"
android:exported="true">
</activity>
<receiver
android:name="com.google.OneSignalBackgroundDataReceiver"
android:exported="false">
<intent-filter>
<action android:name="com.onesignal.BackgroundBroadcast.RECEIVE" />
</intent-filter>
</receiver>
</application>
</manifest>
I have tried all these answers but none worked:
Notification Not open Acivity onCLick
Android Status Bar Notifications - Opening the correct activity when selecting a notification
Android :Tap on Push Notification does not open Application
Android click on notification does not open the attached Activity
It is painfull to modify one line and test it on device! Since onesignal allows only testing in device. Please help or atleast guide me how to debug.
Device on which the apk was tested : Samsung Galaxy S4 running Lolipop.

Take a look at this link:
https://documentation.onesignal.com/docs/android-customizations#section-background-data-and-notification-overriding
(Search : "Changing the open action of a notification" in the page to go to the exact paragraph).
And this is an example:
http://androidbash.com/android-push-notification-service-using-onesignal/
I don't have time to read your code carefully, but seems like it has some problems:
You initialize OneSignal in the wrong place.
"Make sure you are initializing OneSignal with
setNotificationOpenedHandler in the onCreate method in your
Application class. You will need to call startActivity from this callback" (OneSignal's document).
You don't need any other receivers in AndroidManifest to catch intent and open your target activity, OneSignal.NotificationOpenedHandler already handle this. But don't forget this line to prevent OneSignal open your launcher activity:
<application ...>
<meta-data android:name="com.onesignal.NotificationOpened.DEFAULT" android:value="DISABLE" />
</application>
I use this solution in my app and it works fine. Because it's the way it is.

OneSignal.init must be called from your launcher Activity, you will need to move it to your splashscreen Activity. This will get your ExampleNotificationOpenedHandler to fire when you open a OneSignal notification.
Make sure to also copy the calls to OneSignal.onPaused(); and OneSignal.onResumed(); into your splashscreen Activity. These need to be called in every Activity in the onPuase() and onResume() methods.

Related

GeofenceTransitionsIntentService' has no default constructor

Im working with geofencing.
I wrote the serivice class GeofenceTransitionsIntentService, and its showing red mark and saying has no Default constructor. please help me.
Its my manifest file :
<?xml version="1.0" encoding="utf-8"?>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
<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">
<!--optional (needed if default theme has no action bar) -->
<activity android:name=".loginpage"></activity>
<activity android:name=".Userlogin" />
<activity android:name=".Register" />
<activity android:name=".MainActivity">
<meta-data
android:name="com.google.android.gms.version"
android:value="#integer/google_play_services_version" />
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyB9DNBzfrYiTcmUThheWGNdAKY3lRU3pi8" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".GeofenceTransitionsIntentService"/> // here's the error. it turned up red.
</application>
here is my java class. it has a constructor but not a default one, if i put a constructor manually, it is showing error.
package com.example.foodtag;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.GeofencingEvent;
import java.util.ArrayList;
import java.util.List;
public class GeofenceTransitionsIntentService extends IntentService {
GeofencingEvent geofencingEvent;
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* #param name Used to name the worker thread, important only for debugging.
*/
public GeofenceTransitionsIntentService(String name) { //here is the constructor but it is not default constructor.
super(name);
}
GeofenceTransitionsIntentService(){ // and if i create constructor without parameters, it is showing an error "There is no default constructor available in 'android.app.IntentService'"
}
#Override
protected void onHandleIntent( Intent intent) {
geofencingEvent = GeofencingEvent.fromIntent(intent);
if(geofencingEvent.hasError()){
Toast.makeText(this, "Event has error", Toast.LENGTH_SHORT).show();
return;
}
int geoFenceTransition = geofencingEvent.getGeofenceTransition();
if (geoFenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER){
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
// String geofenceTransitionDetails = getGeofenceTransitionDetails(this, geoFenceTransition,triggeringGeofences);
// sendNotofication();
}else {
Toast.makeText(this, "geofence transition invalid", Toast.LENGTH_SHORT).show(); }
}
private String getGeofenceTransitionDetails(
Context context,
int geofenceTransition,
List<Geofence> triggeringGeofences){
// String geofenceTransitionString = getTransitionString(geofenceTransition);//getTransitionString(geoFenceTransition)
ArrayList triggeringgeofencesIdsList = new ArrayList();
for (Geofence geofence : triggeringGeofences){
triggeringgeofencesIdsList.add(geofence.getRequestId());
}
String triggeringgeofencesIdsString = TextUtils.join(",",triggeringgeofencesIdsList);
return triggeringgeofencesIdsString;
}
}
Create a default constructor (without parameters) and put super with class name.
public GeofenceTransitionsIntentService(){
super("GeofenceTransitionsIntentService"); //add this to avoid the error.
}

textView.setMovementMethod in Android data binding

I want to achieve clickable link in a textview. I am now migrating all my UI to Android data binding and was wondering how to achieve it.
textView.setMovementMethod.
Any suggestions?
Best,
SK
I found out a way to do it. here it is
Create a static method and add BindingAdapter annotation preferably in a separate class
#BindingAdapter("app:specialtext")
public static void setSpecialText(TextView textView, SpecialText specialText) {
SpannableString ss = new SpannableString(specialText.getText());
ClickableSpan clickableSpan = new ClickableSpan() {
#Override
public void onClick(View textView) {
Log.d("tag", "onSpecialClick: ");
}
#Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(true);
}
};
ss.setSpan(clickableSpan, specialText.getStartIndex(), ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setText(ss);
}
In your binding layout file
<variable
name="specialText"
type="com.example.test.data.SpecialText" />
<TextView
android:id="#+id/st_textView"
app:specialtext="#{specialText}"/>
In your activity or fragment where you are using the layout
SpecialText specialText = new TCText();
specialText.setStartIndex(15);
specialText.setText(getActivity().getString(R.string.special_text));
binding.setSpecialText(tcText);
Let me know if you know any better way to do it. Thanks !

react-native landscape mode Orientation

Im still beginner and here is one page that I want it to display with landscape mode when I open up the page. I installed react-native-orientation, but im not sure how I can use this.
I want landscape mode when I open the app, so I believe that I should set Orientation when I use, componentWillMount(){
Orientation
}
but im not sure how to set it up... could anyone tell me how?
Try Following package may be help you.
react-native-orientation
only this one line add on your project in your
android:screenOrientation="landscape"
android->app->src->main->AndroidManifest.xml
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:screenOrientation="landscape"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
I came across the same issue instead of using third party module I created my own.
My React Native Module:
public class OrientationHelperModule extends ReactContextBaseJavaModule {
private static final String TAG = OrientationHelperModule.class.getSimpleName();
private static final String MODULE_NAME = "OrientationHelperModule";
private final ReactApplicationContext reactAppContext;
#Override
public String getName() {
return MODULE_NAME;
}
public OrientationHelperModule(ReactApplicationContext reactAppContext) {
super(reactAppContext);
this.reactAppContext = reactAppContext;
}
#ReactMethod
public void lockLandscape() {
OrientationUtils.lockOrientationLandscape(getCurrentActivity());
}
#ReactMethod
public void unlockOrientation() {
OrientationUtils.unlockOrientation(getCurrentActivity());
}
#ReactMethod
public void lockPortrait() {
OrientationUtils.lockOrientationPortrait(getCurrentActivity());
}
}
The Helper class to handle orientation lock
import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.Build;
import android.view.Surface;
import android.view.WindowManager;
/* * This class is used to lock orientation of Android app in any Android devices
*/
public class OrientationUtils {
private OrientationUtils() {
}
/**
* Locks the device window in landscape mode.
*/
public static void lockOrientationLandscape(Activity activity) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
}
/**
* Locks the device window in portrait mode.
*/
public static void lockOrientationPortrait(Activity activity) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
/**
* Locks the device window in actual screen mode.
*/
public static void lockOrientation(Activity activity) {
final int orientation = activity.getResources().getConfiguration().orientation;
final int rotation = ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay()
.getRotation();
// Copied from Android docs, since we don't have these values in Froyo
// 2.2
int SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8;
int SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9;
// Build.VERSION.SDK_INT <= Build.VERSION_CODES.FROYO
if (!(Build.VERSION.SDK_INT <= Build.VERSION_CODES.FROYO)) {
SCREEN_ORIENTATION_REVERSE_LANDSCAPE = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
SCREEN_ORIENTATION_REVERSE_PORTRAIT = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
}
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_90) {
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
} else if (rotation == Surface.ROTATION_180 || rotation == Surface.ROTATION_270) {
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
}
}
}
/**
* Unlocks the device window in user defined screen mode.
*/
public static void unlockOrientation(Activity activity) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
}
}
Import it in React Native
'use strict';
import { NativeModules } from 'react-native';
module.exports = NativeModules.OrientationHelperModule;
Import OrientationHelperModule in your component
import OrientationHelperModule from './src/modules/OrientationHelperModule'
And use it to lock orientation
componentDidMount = () => {
OrientationHelperModule.lockLandscape();
}

Auto connecting to a BLE device

I am working on an application to coommunicate against a BLE device, currently I am trying to create a Service that starts with the application and auto connect to TI's CC2541 keyfob.
Problem is the gatt server seem to fail EVERY TIME....
I have no clue whats wrong with my code since by google API's and some tutorials I saw
It seems that all the pieces are in their place, yet still nothing works... =(
Here is my service -
package com.example.bluetoothgatt;
import java.util.UUID;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
public class BLE extends Service implements BluetoothAdapter.LeScanCallback {
private final IBinder mBinder = new BluetoothLeBinder();
private final static String TAG = "BLE";
private static final String DEVICE_NAME = "Keyfobdemo";
private BluetoothManager mBluetoothManager;
public BluetoothGatt mConnectedGatt;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothDevice mDevice;
private String mDeviceAddress;
private int mConnectionState = STATE_DISCONNECTED;
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;
/*******************************
*******************************
****** Service Inherited ****** Methods **********
*******************************/
#Override
public void onCreate() {
super.onCreate();
mBluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
Thread discoverDevices = new Thread(mStartRunnable);
discoverDevices.setPriority(discoverDevices.MAX_PRIORITY);
discoverDevices.start();
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
#Override
public boolean onUnbind(Intent intent) {
close();
return super.onUnbind(intent);
}
// Implements callback methods for GATT events that the app cares about.
// For example, connection change and services discovered.
private final BluetoothGattExecutor mExecutor = new BluetoothGattExecutor() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (newState == BluetoothProfile.STATE_CONNECTED) {
mConnectionState = STATE_CONNECTED;
mConnectedGatt = gatt;
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
mConnectionState = STATE_DISCONNECTED;
Log.i(TAG, "Disconnected from GATT server.");
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
}
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
}
};
/**
* Return a reference for the current class
*/
public class BluetoothLeBinder extends Binder {
BLE getService() {
return BLE.this;
}
}
private Runnable mStartRunnable = new Runnable() {
#Override
public void run() {
startScan();
}
};
private void startScan() {
if (mConnectionState == STATE_DISCONNECTED) {
mBluetoothAdapter.startLeScan(this);
mHandler.postDelayed(mStopRunnable, 2500);
}
}
private Runnable mStopRunnable = new Runnable() {
#Override
public void run() {
stopScan();
}
};
private void stopScan() {
mBluetoothAdapter.stopLeScan(this);
}
#Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
/*
* We are looking for SensorTag devices only, so validate the name that
* each device reports before adding it to our collection
*/
if (DEVICE_NAME.equals(device.getName())) {
mDevice = device;
mDeviceAddress = mDevice.getAddress();
connect(mDeviceAddress);
mConnectionState = STATE_CONNECTING;
if(device.getBondState() == BluetoothDevice.BOND_BONDED) {
} else if (device.getBondState() == BluetoothDevice.BOND_BONDING) {
} else if(device.getBondState() == BluetoothDevice.BOND_NONE) {
connect(device.getAddress());
}
}
}
/**
* Connects to the GATT server hosted on the Bluetooth LE device.
*
* #param address
* The device address of the destination device.
*
* #return Return true if the connection is initiated successfully. The
* connection result is reported asynchronously through the
* {#code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG,
"BluetoothAdapter not initialized or unspecified address.");
return false;
}
// Previously connected device. Try to reconnect.
if (mDeviceAddress != null && address.equals(mDeviceAddress)
&& mConnectedGatt != null) {
Log.d(TAG,
"Trying to use an existing BluetoothGatt for connection.");
if (mConnectedGatt.connect()) {
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
}
final BluetoothDevice device = mBluetoothAdapter
.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
// We want to directly connect to the device, so we are setting the
// autoConnect
// parameter to false.
mConnectedGatt = device.connectGatt(this, false, mExecutor);
Log.d(TAG, "Trying to create a new connection.");
mDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
return true;
}
/**
* Disconnects an existing connection or cancel a pending connection. The
* disconnection result is reported asynchronously through the
* BluetoothGattCallback >>
* onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)
* callback.
*/
public void disconnect() {
if (mBluetoothAdapter == null || mConnectedGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mConnectedGatt.disconnect();
}
/**
* After using a given BLE device, the app must call this method to ensure
* resources are released properly.
*/
public void close() {
if (mConnectedGatt == null) {
return;
}
mConnectedGatt.close();
mConnectedGatt = null;
}
private final UUID IMMEDIATE_ALERT_UUID = UUID
.fromString("00001802-0000-1000-8000-00805f9b34fb");
private final UUID ALERT_LEVEL_UUID = UUID
.fromString("00002a06-0000-1000-8000-00805f9b34fb");
public void Buzz(BluetoothGatt gatt, int level) {
BluetoothGattService alertService = gatt
.getService(IMMEDIATE_ALERT_UUID);
if (alertService == null) {
Log.d(TAG, "Immediate Alert service not found!");
return;
}
BluetoothGattCharacteristic alertLevel = alertService
.getCharacteristic(ALERT_LEVEL_UUID);
if (alertLevel == null) {
Log.d(TAG, "Alert Level charateristic not found!");
return;
}
alertLevel.setValue(level, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
gatt.writeCharacteristic(alertLevel);
Log.d(TAG, "Alert");
}
private final UUID BATTERY_SERVICE_UUID = UUID
.fromString("0000180F-0000-1000-8000-00805f9b34fb");
private final UUID BATTERY_LEVEL_UUID = UUID
.fromString("00002a19-0000-1000-8000-00805f9b34fb");
public int getbattery(BluetoothGatt mBluetoothGatt) {
BluetoothGattService batteryService = mConnectedGatt
.getService(BATTERY_SERVICE_UUID);
if (batteryService == null) {
Log.d(TAG, "Battery service not found!");
return 0;
}
BluetoothGattCharacteristic batteryLevel = batteryService
.getCharacteristic(BATTERY_LEVEL_UUID);
if (batteryLevel == null) {
Log.d(TAG, "Battery level not found!");
return 0;
}
mBluetoothGatt.readCharacteristic(batteryLevel);
return batteryLevel.getIntValue(
BluetoothGattCharacteristic.FORMAT_SINT8, 0);
}
/*
* We have a Handler to process event results on the main thread
*/
private static final int MSG_PROGRESS = 201;
private static final int MSG_DISMISS = 202;
private static final int MSG_CLEAR = 301;
private Handler mHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
BluetoothGattCharacteristic characteristic;
switch (msg.what) {
case MSG_PROGRESS:
break;
case MSG_DISMISS:
break;
case MSG_CLEAR:
break;
}
}
};
public void MakeBuzz() {
Thread t = new Thread(new Runnable() {
#Override
public void run() {
mConnectedGatt = mDevice.connectGatt(getApplicationContext(),
true, mExecutor);
BluetoothGattService alertService = mConnectedGatt
.getService(IMMEDIATE_ALERT_UUID);
int x = getbattery(mConnectedGatt);
Buzz(mConnectedGatt, 2);
}
});
t.start();
}
}
This it the Application class -
package com.example.bluetoothgatt;
import android.app.Application;
import android.content.Intent;
public class ApplicationBleTest extends Application {
// Application variables
public final String SMOKE_TALK_PACKAGE_NAME = "com.smoketalk";
private BluetoothLEService mBleService;
private static int MODE_PRIVATE;
/**
* Application OnCreate event initiate the class parameters
*/
public void onCreate() {
super.onCreate();
getApplicationContext().startService(new Intent(this, BLE.class));
}
}
And this is the main activity (I am trying to make the keyfob alaram buzz on a button click)
package com.example.bluetoothgatt;
import com.example.bluetoothgatt.BluetoothLowEnergyService.BluetoothLeBinder;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
/**
* Created by Dave Smith Double Encore, Inc. MainActivity
*/
public class MainActivity extends Activity {
BluetoothLowEnergyService mBluetoothService;
boolean isBound = false;
Button buzz;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, BluetoothLowEnergyService.class);
bindService(intent, mBleServiceConnection, Context.BIND_AUTO_CREATE);
buzz = (Button) findViewById(R.id.btn1);
buzz.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
mBluetoothService.MakeBuzz();
}
});
}
private ServiceConnection mBleServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
BluetoothLeBinder binder = (BluetoothLeBinder) service;
mBluetoothService = binder.getService();
isBound = true;
}
public void onServiceDisconnected(ComponentName arg0) {
isBound = false;
}
};
}
And the menifest file -
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.bluetoothgatt"
android:versionCode="1"
android:versionName="1.0" >
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="true" />
<uses-sdk
android:minSdkVersion="18"
android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED"/>
<application
android:name="com.example.bluetoothgatt.ApplicationBleTest"
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="SensorTag Weather" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="com.example.bluetoothgatt.BLE" />
</application>
</manifest>
and last one the layout for the main activity -
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:padding="#dimen/activity_horizontal_margin"
android:text="Android BLE Test"
android:textSize="42sp" />
<Button
android:id="#+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/textView1"
android:layout_centerHorizontal="true"
android:layout_marginTop="56dp"
android:text="Buzz" />
</RelativeLayout>
ANY help will be appreciated since I rellay have no clue what goes wrong... =(
For starters, I would recommend commenting out the bond code (Everything after if(device.getBondState().. in the onLeScan method) The whole bonding process was unstable on 4.3 (Nexus devices at least) and became more stable on 4.4.
You should be able to discover devices, and with the BluetoothDevice the user selects you should call ConnectGatt after stopping discovery. This will attempt to connect to the Gatt server on the device. If the connection is successful, you should receive a callback on your connectionStateChange indicating that the connection was successful.
The concept behind bonding is related to pairing with the device and exchanging keys if your characteristics are encrypted. Normally you should be able to connect to the Gatt server without needing to bond, but once you are connected, if you do try to read an encrypted characteristic, it will fail.
I tried your code and it works. you need to follow this process:
BluetoothAdapter.startLeScan(leCallback)
In the onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) in leCallback, call btDevice.connectGatt(Context context, Boolean autoConnect, BluetoothGattCallback gattCallback);
In the onConnectionStateChange(BluetoothGatt gatt, int status, int newState) in gattCallBack, check if newState is BluetoothProfile.STATE_CONNECTED, if yes, call gatt.discoverServices();
In the onServicesDiscovered(BluetoothGatt gatt, int status) in gattCallBack, check if status is BluetoothGatt.GATT_SUCCESS, if yes, get the service by UUID like this: BluetoothGattService service = gatt.getService(YOUR_SERVICE_UUID);
If the service is null, it means the service has not yet been discovered, you need to check again when the next service is discovered and the onServicesDiscovered will be called again.
By the time all the services has been discovered, you should already got your service, unless the device does not support it.
Now you can use your service in your Buzz method.
Also worth noting is that the BLE actions must all be serialized by you. Eg, if you made a read/write to a characteristic you need to wait for the callback before doing another. If not, this will result in an error.
Since you are running from a service you can try running connect on the main thread like this:
public void connectToDevice( String deviceAddress) {
mDeviceAddress = deviceAddress;
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mDeviceAddress);
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
if (device != null) {
mGatt = device.connectGatt(getApplicationContext(), true, mGattCallback);
scanLeDevice(false);// will stop after first device detection
}
}
});
}
Hope it helps.

Android - Preferences that work on both 2.2 and 4.0 [duplicate]

Trying the different preference activities in the ApiDemos for Android 4.0, I see in the code that some methods are deprecated in PreferencesFromCode.java, for example.
So my question is: if I use PreferenceFragment, will it work for all version or only 3.0 or 4.0 and up?
If so, what should I use that works for 2.2 and 2.3 as well?
PreferenceFragment will not work on 2.2 and 2.3 (only API level 11 and above). If you want to offer the best user experience and still support older Android versions, the best practice here seems to be to implement two PreferenceActivity classes and to decide at runtime which one to invoke. However, this method still includes calling deprecated APIs, but you can't avoid that.
So for instance, you have a preference_headers.xml:
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android" >
<header android:fragment="your.package.PrefsFragment"
android:title="...">
<extra android:name="resource" android:value="preferences" />
</header>
</preference-headers>
and a standard preferences.xml (which hasn't changed much since lower API levels):
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:title="...">
...
</PreferenceScreen>
Then you need an implementation of PreferenceFragment:
public static class PrefsFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
And finally, you need two implementations of PreferenceActivity, for API levels supporting or not supporting PreferenceFragments:
public class PreferencesActivity extends PreferenceActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
addPreferencesFromResource(R.xml.other);
}
}
and:
public class OtherPreferencesActivity extends PreferenceActivity {
#Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.preference_headers, target);
}
}
At the point where you want to display the preference screen to the user, you decide which one to start:
if (Build.VERSION.SDK_INT < 11) {
startActivity(new Intent(this, PreferencesActivity.class));
} else {
startActivity(new Intent(this, OtherPreferencesActivity.class));
}
So basically, you have an xml file per fragment, you load each of these xml files manually for API levels < 11, and both Activities use the same preferences.
#Mef Your answer can be simplified even more so that you do not need both of the PreferencesActivity and OtherPreferencesActivity (having 2 PrefsActivities is a PITA).
I have found that you can put the onBuildHeaders() method into your PreferencesActivity and no errors will be thrown by Android versions prior to v11. Having the loadHeadersFromResource() inside the onBuildHeaders did not throw and exception on 2.3.6, but did on Android 1.6. After some tinkering though, I found the following code will work in all versions so that only one activity is required (greatly simplifying matters).
public class PreferencesActivity extends PreferenceActivity {
protected Method mLoadHeaders = null;
protected Method mHasHeaders = null;
/**
* Checks to see if using new v11+ way of handling PrefFragments.
* #return Returns false pre-v11, else checks to see if using headers.
*/
public boolean isNewV11Prefs() {
if (mHasHeaders!=null && mLoadHeaders!=null) {
try {
return (Boolean)mHasHeaders.invoke(this);
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
}
return false;
}
#Override
public void onCreate(Bundle aSavedState) {
//onBuildHeaders() will be called during super.onCreate()
try {
mLoadHeaders = getClass().getMethod("loadHeadersFromResource", int.class, List.class );
mHasHeaders = getClass().getMethod("hasHeaders");
} catch (NoSuchMethodException e) {
}
super.onCreate(aSavedState);
if (!isNewV11Prefs()) {
addPreferencesFromResource(R.xml.preferences);
addPreferencesFromResource(R.xml.other);
}
}
#Override
public void onBuildHeaders(List<Header> aTarget) {
try {
mLoadHeaders.invoke(this,new Object[]{R.xml.pref_headers,aTarget});
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
}
}
This way you only need one activity, one entry in your AndroidManifest.xml and one line when you invoke your preferences:
startActivity(new Intent(this, PreferencesActivity.class);
UPDATE Oct 2013:
Eclipse/Lint will warn you about using the deprecated method, but just ignore the warning. We are using the method only when we have to, which is whenever we do not have v11+ style preferences and must use it, which is OK. Do not be frightened about Deprecated code when you have accounted for it, Android won’t remove deprecated methods anytime soon. If it ever did occur, you won’t even need this class anymore as you would be forced to only target newer devices. The Deprecated mechanism is there to warn you that there is a better way to handle something on the latest API version, but once you have accounted for it, you can safely ignore the warning from then on. Removing all calls to deprecated methods would only result in forcing your code to only run on newer devices — thus negating the need to be backward compatible at all.
There's a newish lib that might help.
UnifiedPreference is a library for working with all versions of the
Android Preference package from API v4 and up.
Problem with previous answers is that it will stack all preferences to a single screen on pre-Honecomb devices (due to multiple calls of addPreferenceFromResource()).
If you need first screen as list and then the screen with preferences (such as using preference headers), you should use Official guide to compatible preferences
I wanted to point out that if you start at http://developer.android.com/guide/topics/ui/settings.html#PreferenceHeaders and work your way down to the section for "Supporting older versions with preference headers" it will make more sense. The guide there is very helpful and does work well. Here's an explicit example following their guide:
So start with file preference_header_legacy.xml for android systems before HoneyComb
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<Preference
android:title="OLD Test Title"
android:summary="OLD Test Summary" >
<intent
android:targetPackage="example.package"
android:targetClass="example.package.SettingsActivity"
android:action="example.package.PREFS_ONE" />
</Preference>
Next create file preference_header.xml for android systems with HoneyComb+
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
<header
android:fragment="example.package.SettingsFragmentOne"
android:title="NEW Test Title"
android:summary="NEW Test Summary" />
</preference-headers>
Next create a preferences.xml file to hold your preferences...
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<CheckBoxPreference
android:key="pref_key_auto_delete"
android:summary="#string/pref_summary_auto_delete"
android:title="#string/pref_title_auto_delete"
android:defaultValue="false" />
</PreferenceScreen>
Next create the file SettingsActivity.java
package example.project;
import java.util.List;
import android.annotation.SuppressLint;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceActivity;
public class SettingsActivity extends PreferenceActivity{
final static String ACTION_PREFS_ONE = "example.package.PREFS_ONE";
#SuppressWarnings("deprecation")
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String action = getIntent().getAction();
if (action != null && action.equals(ACTION_PREFS_ONE)) {
addPreferencesFromResource(R.xml.preferences);
}
else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
// Load the legacy preferences headers
addPreferencesFromResource(R.xml.preference_header_legacy);
}
}
#SuppressLint("NewApi")
#Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.preference_header, target);
}
}
Next create the class SettingsFragmentOne.java
package example.project;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.preference.PreferenceFragment;
#SuppressLint("NewApi")
public class SettingsFragmentOne extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
AndroidManifest.xml, added this block between my <application> tags
<activity
android:label="#string/app_name"
android:name="example.package.SettingsActivity"
android:exported="true">
</activity>
and finally, for the <wallpaper> tag...
<wallpaper xmlns:android="http://schemas.android.com/apk/res/android"
android:description="#string/description"
android:thumbnail="#drawable/ic_thumbnail"
android:settingsActivity="example.package.SettingsActivity"
/>
I am using this library, which has an AAR in mavenCentral so you can easily include it if you are using Gradle.
compile 'com.github.machinarius:preferencefragment:0.1.1'