So I have experienced some issue with in app purchase in my app.
I have started working on an old (8 months old) project I had earlier, but I am having some issue with app purchase. The app is already live on Play Store, so in app purchase is active.
In build.gradle (:app) I have changed from:
dependencies {
implementation 'com.android.billingclient:billing:2.2.1'
to this:
dependencies {
def billing_version = "3.0.0" // In App Purchase
implementation "com.android.billingclient:billing:$billing_version"
This is the full code of my UpgradeActivity.java:
package com.pinloop.testproj;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import java.util.ArrayList;
import java.util.List;
public class UpgradeActivity extends AppCompatActivity implements PurchasesUpdatedListener {
private Button upgradeButton;
private TextView restoreButton;
private BillingClient billingClient;
private List skuList = new ArrayList();
private String sku = "com.pinloop.testproj.pro";
private SkuDetails mSkuDetails;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_upgrade);
upgradeButton = (Button) findViewById(R.id.upgradeButton);
upgradeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
upgradeAction();
}
});
// Check In App Purchase
upgradeButton.setEnabled(true);
skuList.add(sku);
Boolean pro = getBoolFromPref(this,"myPref", sku);
if (pro)
{
Toast.makeText(this, "you are a premium user", Toast.LENGTH_SHORT).show();
//upgradeButton.setVisibility(View.INVISIBLE);
upgradeButton.setText(R.string.you_are_premium);
}
else
{
Toast.makeText(this, "not pro", Toast.LENGTH_SHORT).show();
setupBillingClient();
}
}
private void upgradeAction() {
BillingFlowParams billingFlowParams = BillingFlowParams
.newBuilder()
.setSkuDetails(mSkuDetails)
.build();
billingClient.launchBillingFlow(UpgradeActivity.this, billingFlowParams);
}
// In App Handler:
private void setupBillingClient() {
billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build();
billingClient.startConnection(new BillingClientStateListener(){
#Override
public void onBillingSetupFinished(BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// The BillingClient is setup successfully
loadAllSKUs();
}
}
#Override
public void onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
});
}
private void loadAllSKUs() {
Toast.makeText(this, "loadAllSKUs", Toast.LENGTH_SHORT).show();
if (billingClient.isReady())
{
Toast.makeText(this, "billingclient ready", Toast.LENGTH_SHORT).show();
SkuDetailsParams params = SkuDetailsParams.newBuilder()
.setSkusList(skuList)
.setType(BillingClient.SkuType.INAPP)
.build();
billingClient.querySkuDetailsAsync(params, new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
Toast.makeText(UpgradeActivity.this, "inside query" + billingResult.getResponseCode(), Toast.LENGTH_SHORT).show();
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
&& !skuDetailsList.isEmpty())
{
for (Object skuDetailsObject : skuDetailsList) {
final SkuDetails skuDetails = (SkuDetails) skuDetailsObject;
Toast.makeText(UpgradeActivity.this, "" + skuDetails.getSku(), Toast.LENGTH_SHORT).show();
if (skuDetails.getSku() == sku)
mSkuDetails = skuDetails;
upgradeButton.setEnabled(true);
upgradeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
BillingFlowParams billingFlowParams = BillingFlowParams
.newBuilder()
.setSkuDetails(skuDetails)
.build();
billingClient.launchBillingFlow(UpgradeActivity.this, billingFlowParams);
}
});
}
}
}
});
}
else
Toast.makeText(this, "billingclient not ready", Toast.LENGTH_SHORT).show();
}
#Override
public void onPurchasesUpdated(BillingResult billingResult, #Nullable List<Purchase> purchases) {
int responseCode = billingResult.getResponseCode();
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
&& purchases != null) {
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
}
else
if (responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
//Log.d(TAG, "User Canceled" + responseCode);
}
else if (responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
///mSharedPreferences.edit().putBoolean(getResources().getString(R.string.pref_remove_ads_key), true).commit();
///setAdFree(true);
setBoolInPref(this,"myPref",sku, true );
}
else {
//Log.d(TAG, "Other code" + responseCode);
// Handle any other error codes.
}
}
private void handlePurchase(Purchase purchase) {
if (purchase.getSku().equals(sku)) {
///mSharedPreferences.edit().putBoolean(getResources().getString(R.string.pref_remove_ads_key), true).commit();
///setAdFree(true);
setBoolInPref(this,"myPref",sku, true );
Toast.makeText(this, "Purchase done. you are now a premium member.", Toast.LENGTH_SHORT).show();
}
}
private Boolean getBoolFromPref(Context context, String prefName, String constantName) {
SharedPreferences pref = context.getSharedPreferences(prefName, 0); // 0 - for private mode
return pref.getBoolean(constantName, false);
}
private void setBoolInPref(Context context,String prefName, String constantName, Boolean val) {
SharedPreferences pref = context.getSharedPreferences(prefName, 0); // 0 - for private mode
SharedPreferences.Editor editor = pref.edit();
editor.putBoolean(constantName, val);
//editor.commit();
editor.apply();
// Update 2015: Android recommends the use of apply() now over commit(),
// because apply() operates on a background thread instead of storing the persistent data immediately,
// and possible blocking the main thread.
}
}
Basically what happens when pressing the upgradeButton is that the app crashes. I cannot figure out what I am doing wrong. I am using the correct SKU, and the app has been live on Play Store for over a year now.
This is the error log I get when pressing the upgradeButton:
I/zygote: Do partial code cache collection, code=124KB, data=72KB
After code cache collection, code=124KB, data=72KB
Increasing code cache capacity to 512KB
D/EGL_emulation: eglMakeCurrent: 0xdb928540: ver 3 0 (tinfo 0xdb92bbd0)
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.pinloop.testproj, PID: 13352
java.lang.IllegalArgumentException: SKU cannot be null.
at com.android.billingclient.api.BillingFlowParams$Builder.build(com.android.billingclient:billing##3.0.0:23)
at com.pinloop.testproj.UpgradeActivity.upgradeAction(UpgradeActivity.java:130)
at com.pinloop.testproj.UpgradeActivity.access$000(UpgradeActivity.java:31)
at com.pinloop.testproj.UpgradeActivity$1.onClick(UpgradeActivity.java:56)
at android.view.View.performClick(View.java:6294)
at android.view.View$PerformClick.run(View.java:24770)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Not sure what to look at here, but I can see that java.lang.IllegalArgumentException: SKU cannot be null., but sku has already been declared: private String sku = "com.pinloop.testproj.pro";. Any ideas?
Here is the update code
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import com.aumhum.aumhum.R;
import java.util.ArrayList;
import java.util.List;
public class UpgradeActivity extends AppCompatActivity implements PurchasesUpdatedListener {
private Button upgradeButton;
private TextView restoreButton;
private BillingClient billingClient;
private final List<String> skuList = new ArrayList();
private final String sku = "com.pinloop.testproj.pro";
private SkuDetails mSkuDetails;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_upgrade);
upgradeButton = (Button) findViewById(R.id.upgradeButton);
upgradeButton.setEnabled(false);
upgradeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
upgradeAction();
}
});
// Check In App Purchase
skuList.add(sku);
setButtonStatus();
}
private void setButtonStatus(){
Boolean pro = getBoolFromPref(this,"myPref", sku);
if (pro)
{
Toast.makeText(this, "you are a premium user", Toast.LENGTH_SHORT).show();
//upgradeButton.setVisibility(View.INVISIBLE);
upgradeButton.setText(R.string.you_are_premium);
}
else
{
Toast.makeText(this, "not pro", Toast.LENGTH_SHORT).show();
setupBillingClient();
}
}
private void upgradeAction() {
if (mSkuDetails==null){
Toast.makeText(this,"Please wait while we get details",Toast.LENGTH_SHORT).show();
return;
}
BillingFlowParams billingFlowParams = BillingFlowParams
.newBuilder()
.setSkuDetails(mSkuDetails)
.build();
billingClient.launchBillingFlow(UpgradeActivity.this, billingFlowParams);
}
// In App Handler:
private void setupBillingClient() {
billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build();
billingClient.startConnection(new BillingClientStateListener(){
#Override
public void onBillingSetupFinished(BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// The BillingClient is setup successfully
loadAllSKUs();
}
}
#Override
public void onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
});
}
private void loadAllSKUs() {
Toast.makeText(this, "loadAllSKUs", Toast.LENGTH_SHORT).show();
if (billingClient.isReady())
{
Toast.makeText(this, "billingclient ready", Toast.LENGTH_SHORT).show();
SkuDetailsParams params = SkuDetailsParams.newBuilder()
.setSkusList(skuList)
.setType(BillingClient.SkuType.INAPP)
.build();
billingClient.querySkuDetailsAsync(params, new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
Toast.makeText(UpgradeActivity.this, "inside query" + billingResult.getResponseCode(), Toast.LENGTH_SHORT).show();
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
&& !skuDetailsList.isEmpty())
{
for (SkuDetails skuDetails : skuDetailsList) {
Toast.makeText(UpgradeActivity.this, "" + skuDetails.getSku(), Toast.LENGTH_SHORT).show();
if (skuDetails.getSku().equals(sku)) {
mSkuDetails = skuDetails;
upgradeButton.setEnabled(true);
}
}
}
}
});
}
else
Toast.makeText(this, "billingclient not ready", Toast.LENGTH_SHORT).show();
}
#Override
public void onPurchasesUpdated(BillingResult billingResult, #Nullable List<Purchase> purchases) {
int responseCode = billingResult.getResponseCode();
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
&& purchases != null) {
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
}
else
if (responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
//Log.d(TAG, "User Canceled" + responseCode);
}
else if (responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
///mSharedPreferences.edit().putBoolean(getResources().getString(R.string.pref_remove_ads_key), true).commit();
///setAdFree(true);
setBoolInPref(this,"myPref",sku, true );
}
else {
//Log.d(TAG, "Other code" + responseCode);
// Handle any other error codes.
}
}
private void handlePurchase(Purchase purchase) {
if (purchase.getSku().equals(sku)) {
///mSharedPreferences.edit().putBoolean(getResources().getString(R.string.pref_remove_ads_key), true).commit();
///setAdFree(true);
setBoolInPref(this,"myPref",sku, true );
Toast.makeText(this, "Purchase done. you are now a premium member.", Toast.LENGTH_SHORT).show();
}
}
private Boolean getBoolFromPref(Context context, String prefName, String constantName) {
SharedPreferences pref = context.getSharedPreferences(prefName, 0); // 0 - for private mode
return pref.getBoolean(constantName, false);
}
private void setBoolInPref(Context context,String prefName, String constantName, Boolean val) {
SharedPreferences pref = context.getSharedPreferences(prefName, 0); // 0 - for private mode
SharedPreferences.Editor editor = pref.edit();
editor.putBoolean(constantName, val);
//editor.commit();
editor.apply();
// Update 2015: Android recommends the use of apply() now over commit(),
// because apply() operates on a background thread instead of storing the persistent data immediately,
// and possible blocking the main thread.
}
}
3 problems
You have enabled upgrade button by default and don't have null check in upgradeAction method
You are comparing strings by == instead of .equals in java
You are setting upgrade onclick listener 2 times
the 'mSkuDetails' attribute?
if (skuDetails.getSku() == sku){
mSkuDetails = skuDetails;
upgradeButton.setEnabled(true);
upgradeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
BillingFlowParams billingFlowParams = BillingFlowParams
.newBuilder()
.setSkuDetails(mSkuDetails)
.build();
billingClient.launchBillingFlow(UpgradeActivity.this, billingFlowParams);
}
});
}
a. Verify the sku is the same as product code set in Google Play In App Billing section.
b. Debug through the code to see that null is not passed as sku in the call to launch billing flow.
c. Refresh Play Store cache in the device your are testing
Related
I'm developing an app which can share local music on your phone to other devices. To connect several devices I am using Googles Nearby API. After starting to advertise and to discover on two devices, the onEndpointFound calls a found device on both devices. To accept this connection the app should show up an AlertDialog. This happens in the onConnectionInitiated method. But for some reason the app won't get to the part where the AlertDialog is built.
Does anybody know how to fix this?
This is the code:
package com.shuzo.musicshare;
import android.Manifest;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import com.google.android.gms.nearby.Nearby;
import com.google.android.gms.nearby.connection.AdvertisingOptions;
import com.google.android.gms.nearby.connection.ConnectionInfo;
import com.google.android.gms.nearby.connection.ConnectionLifecycleCallback;
import com.google.android.gms.nearby.connection.ConnectionResolution;
import com.google.android.gms.nearby.connection.ConnectionsClient;
import com.google.android.gms.nearby.connection.ConnectionsStatusCodes;
import com.google.android.gms.nearby.connection.DiscoveredEndpointInfo;
import com.google.android.gms.nearby.connection.DiscoveryOptions;
import com.google.android.gms.nearby.connection.EndpointDiscoveryCallback;
import com.google.android.gms.nearby.connection.Payload;
import com.google.android.gms.nearby.connection.PayloadCallback;
import com.google.android.gms.nearby.connection.PayloadTransferUpdate;
import com.google.android.gms.nearby.connection.Strategy;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MusicShare";
private static final String[] REQUIRED_PERMISSIONS =
new String[] {
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.ACCESS_COARSE_LOCATION,
};
private static final int REQUEST_CODE_REQUIRED_PERMISSIONS = 1;
private static final Strategy STRATEGY = Strategy.P2P_STAR;
EditText usernameEdit;
Button connectButton;
private ConnectionsClient connectionsClient;
// Callbacks for receiving payloads
private final PayloadCallback payloadCallback =
new PayloadCallback() {
#Override
public void onPayloadReceived(String endpointId, Payload payload) {
}
#Override
public void onPayloadTransferUpdate(String endpointId, PayloadTransferUpdate update) {
}
};
// Callbacks for connections to devices
private final ConnectionLifecycleCallback connectionLifecycleCallback =
new ConnectionLifecycleCallback() {
#Override
public void onConnectionInitiated(final String endpointId, ConnectionInfo connectionInfo) {
Log.i(TAG, "onConnectionInitiated: accepting connection");
AlertDialog.Builder alertDialog = new AlertDialog.Builder(getApplicationContext());
alertDialog
.setTitle(R.string.accept_connection_title + " " + connectionInfo.getEndpointName())
.setMessage(R.string.confirm_codes + " " + connectionInfo.getAuthenticationToken())
.setPositiveButton(
R.string.accept_connection,
(DialogInterface dialog, int which) ->
connectionsClient.acceptConnection(endpointId, payloadCallback))
.setNegativeButton(
android.R.string.cancel,
(DialogInterface dialog, int which) ->
connectionsClient.rejectConnection(endpointId))
.setIcon(android.R.drawable.ic_dialog_alert)
.show();
}
#Override
public void onConnectionResult(String endpointId, ConnectionResolution result) {
switch (result.getStatus().getStatusCode()) {
case ConnectionsStatusCodes.STATUS_OK:
Log.i(TAG, "onConnectionResult: status ok");
Toast.makeText(getApplicationContext(), "Connected", Toast.LENGTH_SHORT).show();
break;
case ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED:
Log.i(TAG, "onConnectionResult: connection rejected");
Toast.makeText(getApplicationContext(), "Connection rejected", Toast.LENGTH_SHORT).show();
break;
case ConnectionsStatusCodes.STATUS_ERROR:
Log.i(TAG, "onConnectionResult: error");
Toast.makeText(getApplicationContext(), "Connection error", Toast.LENGTH_SHORT).show();
break;
default:
// Unknown status code
}
}
#Override
public void onDisconnected(String endpointId) {
Log.i(TAG, "Device disconnected");
}
};
private final EndpointDiscoveryCallback endpointDiscoveryCallback =
new EndpointDiscoveryCallback() {
#Override
public void onEndpointFound(String endpointId, DiscoveredEndpointInfo info) {
Log.i(TAG, "onEndpointFound: endpoint found, connecting");
connectionsClient
.requestConnection(getUserNickname(), endpointId, connectionLifecycleCallback)
.addOnSuccessListener(
(Void unused) -> {
Log.i(TAG, "onEndpointFound: successful");
})
.addOnFailureListener(
(Exception e) -> {
Log.i(TAG, "onEndpointFound: failed");
});
}
#Override
public void onEndpointLost(String endpointId) {
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
connectionsClient = Nearby.getConnectionsClient(this);
connectButton = findViewById(R.id.connectButton);
connectButton.setOnClickListener(view -> {
startAdvertising();
startDiscovery();
connectButton.setEnabled(false);
});
}
#Override
protected void onStart() {
super.onStart();
if (!hasPermissions(this, REQUIRED_PERMISSIONS)) {
requestPermissions(REQUIRED_PERMISSIONS, REQUEST_CODE_REQUIRED_PERMISSIONS);
}
}
private static boolean hasPermissions(Context context, String... permissions) {
for (String permission : permissions) {
if (ContextCompat.checkSelfPermission(context, permission)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
private void startAdvertising() {
AdvertisingOptions advertisingOptions =
new AdvertisingOptions.Builder().setStrategy(STRATEGY).build();
connectionsClient
.startAdvertising(getUserNickname(), getPackageName(), connectionLifecycleCallback, advertisingOptions)
.addOnSuccessListener(
(Void unused) -> {
Toast.makeText(getApplicationContext(), R.string.advertising_success, Toast.LENGTH_SHORT).show();
})
.addOnFailureListener(
(Exception e) -> {
Toast.makeText(getApplicationContext(), R.string.advertising_fail, Toast.LENGTH_SHORT).show();
});
}
private void startDiscovery() {
DiscoveryOptions discoveryOptions =
new DiscoveryOptions.Builder().setStrategy(STRATEGY).build();
connectionsClient
.startDiscovery(getPackageName(), endpointDiscoveryCallback, discoveryOptions)
.addOnSuccessListener(
(Void unused) -> {
Toast.makeText(getApplicationContext(), R.string.discovery_success, Toast.LENGTH_SHORT).show();
})
.addOnFailureListener(
(Exception e) -> {
Toast.makeText(getApplicationContext(), R.string.discovery_failed, Toast.LENGTH_SHORT).show();
});
}
private String getUserNickname() {
usernameEdit = findViewById(R.id.usernameEdit);
return usernameEdit.getText().toString();
}
}
Given the additional detail in the comments, this appears to be a Bluetooth Classic issue. I looked in our records, and we have both reported devices so we'll try to reproduce on our end.
Some steps you can do:
1) Try rebooting the devices and see if the issue is reproducible.
2) Collect logs (adb logcat) or a bugreport (adb bugreport) from both devices and attach them to this post or email them to me directly at {my username}#google.com. These contain sensitive data, so only send it if you have test accounts on both devices. If you have a personal account, then filter the logs first (adb logcat NearbyConnections:* NearbyMediums:* *:S)
Disclaimer: I work on Nearby Connections
Hi I am currently facing issues with connection and reading of BLE devices. I think I have connected as the code below prints a message for connection, however the reading of values from the BLE device does not seem to work. The service uuid returns a null value.
package com.example.asp_sqllite;
import android.Manifest;
import android.app.Dialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.ParcelUuid;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.UUID;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class PlayActivity extends AppCompatActivity {
private static final int REQUEST_ENABLE_BT =1 ;
private Button btnPlay;
private Button btnConnect;
private ListView btList;
SQLiteDatabase db;
private Handler handler;
private ArrayList<String> deviceList = new ArrayList<>();
private ArrayAdapter<String> testAdapter;
private ArrayAdapter<String> deviceAdapter;
private BluetoothAdapter bluetoothAdapter;
private BluetoothLeScanner bleScanner;
private BluetoothGatt bleGatt;
private ArrayList<ScanResult> results = new ArrayList<>();
private ScanSettings settings;
private Intent intent;
private ListView bluetoothList;
private boolean completed = false;
static final UUID HR_SERVICE_UUID = UUID.fromString("0000110a-0000-1000-8000-00805f9b34fb");
private static final UUID HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb");
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play);
this.handler= new Handler();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ) {
checkPermission();
}
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
bluetoothAdapter = bluetoothManager.getAdapter();
this.btList = (ListView) findViewById(R.id.btlist);
deviceAdapter = new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_list_item_1, android.R.id.text1);
testAdapter = new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_list_item_1, android.R.id.text1);
intent = getIntent();
db = openOrCreateDatabase("myDB.db", MODE_PRIVATE, null);
checkBluetooth();
this.btnPlay = (Button) findViewById(R.id.btnPlay);
this.btnPlay.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(bleGatt!=null) {
final String username = intent.getStringExtra("username");
System.out.println(username+"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
String sqlStatement = "insert into records (Name) values( '" + username + "')";
String result = updateTable(sqlStatement);
/*
* Run query to get recid to be passed over to the next activity
*
* */
final Cursor cursor = db.rawQuery("SELECT recID From records", null);
int num = 0;
if (cursor != null) {
cursor.moveToLast();
num = cursor.getInt(0);
cursor.close();
db.close();
}
Intent intent = new Intent(PlayActivity.this, testPlayActivity.class);
intent.putExtra("ID", Integer.toString(num));
startActivity(intent);
}
else
Toast.makeText(getApplicationContext(),"Connect to BLE device", Toast.LENGTH_LONG).show();
//finish();
}
});
this.btnConnect = (Button) findViewById(R.id.connect);
this.btnConnect.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
startScan();
Dialog d = new Dialog(PlayActivity.this); //open up dialog box with listview
d.setContentView(R.layout.bluetooth_device);
d.setTitle("Devices");
d.show();
//stopScan();
Button scanBtn = d.findViewById(R.id.scanBluetooth);
bluetoothList = d.findViewById(R.id.bluetoothDeviceList);
bluetoothList.setAdapter(deviceAdapter);
bluetoothList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
ScanResult device = results.get(i);
Toast.makeText(getApplicationContext(), device.getDevice().getName(), Toast.LENGTH_LONG).show();
bleGatt = device.getDevice().connectGatt(getApplicationContext(), false, bleGattCallback);
System.out.println("##############################################testing 123");
//finish();
try {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
Method getUuidsMethod = BluetoothAdapter.class.getDeclaredMethod("getUuids", null);
ParcelUuid[] uuids = (ParcelUuid[]) getUuidsMethod.invoke(adapter, null);
if(uuids != null) {
for (ParcelUuid uuid : uuids) {
System.out.println(uuid.getUuid().toString()+"#############################");
}
}else{
System.out.println("fail");
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
});
scanBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) { //clear all list and adapters before scanning again
deviceList.clear();
deviceAdapter.clear();
results.clear();
startScan();
//stopScan();
handler.postDelayed(new Runnable() {
#Override
public void run() {
stopScan();
}
},5000);
}
});
handler.postDelayed(new Runnable() {
#Override
public void run() {
stopScan();
}
},5000);
}
});
}
public void checkPermission(){
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
){//Can add more as per requirement
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION},
123);
}
}
private void checkBluetooth()
{
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
}
private String updateTable(String sql) {
try {
db.beginTransaction();
db.execSQL(sql);
db.setTransactionSuccessful();
db.endTransaction();
} catch (Exception e) {
System.out.println(e.toString());
return ("Error");
}
Toast.makeText(this, "DB updated", Toast.LENGTH_LONG).show();
return ("Welcome");
}
private void stopScan(){
bleScanner = bluetoothAdapter.getBluetoothLeScanner();
bleScanner.stopScan(scanCallback);
}
private void startScan() {
bleScanner = bluetoothAdapter.getBluetoothLeScanner();
if (bleScanner != null) { //setting up of scanner
final ScanFilter scanFilter =new ScanFilter.Builder().build();
settings =new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
bleScanner.startScan(Arrays.asList(scanFilter), settings, scanCallback);
//stopScan();
}
else
checkBluetooth();
}
private ScanCallback scanCallback = new ScanCallback() { //scan and return device results
#Override
public void onScanResult(int callbackType, ScanResult result) {
System.out.println("######### "+callbackType + result);
if (bleScanner != null && !deviceList.contains(result.getDevice().getName())) {
deviceList.add(result.getDevice().getName());
String device = result.getDevice().getName() + "\n" + result.getDevice().getAddress();
deviceAdapter.add(device); //Store device name and address
results.add(result); //records found devices as ScanResult
}
}
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.e("TAG","onScanFailed");
}
};
private BluetoothGattCallback bleGattCallback = new BluetoothGattCallback()
{
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState== BluetoothProfile.STATE_CONNECTED){
System.out.println("#################################################################Connected");
}
else if (newState == BluetoothProfile.STATE_DISCONNECTED)
{
System.out.println("################################################################Not Connected");
}
gatt.discoverServices();
super.onConnectionStateChange(gatt, status, newState);
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
BluetoothGattService service = gatt.getService(HR_SERVICE_UUID);
System.out.println(service+"!!!!!!!!!!!!!!!!!!!!!!");
BluetoothGattCharacteristic temperatureCharacteristic = service.getCharacteristic(HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID);
gatt.readCharacteristic(temperatureCharacteristic);
super.onServicesDiscovered(gatt, status);
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status) {
final String value = characteristic.getStringValue(0);
runOnUiThread(new Runnable() {
#Override
public void run() {
if(HEART_RATE_MEASUREMENT_CHARACTERISTIC_UUID.equals(characteristic.getUuid())) {
//Toast.makeText(getApplicationContext(), "Correct Bluetooth: " + value, Toast.LENGTH_LONG).show();
System.out.println("###########################################################correct");
} else {
//Toast.makeText(getApplicationContext(), "Wrong Bluetooth", Toast.LENGTH_LONG).show();
System.out.println("##############################################################wrong");
}
}
});
BluetoothGattService service = gatt.getService(HR_SERVICE_UUID);
//readNextCharacteristic(gatt, characteristic);
super.onCharacteristicRead(gatt, characteristic, status);
}
};
}
The code is an example in arduino library(BLE_Example/BLE_HRM)
/*
* Copyright (c) 2016 RedBear
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/**
* #note This demo is Nordic HRM example.
* You could use nRF toolbox tool to test it.
*/
#include <nRF5x_BLE_API.h>
#define DEVICE_NAME "Nordic_HRM"
BLE ble;
Ticker ticker_task1;
static uint8_t hrmCounter = 100;
static uint8_t bpm[2] = {0x00, hrmCounter};
static const uint8_t location = 0x03;
static const uint16_t uuid16_list[] = {GattService::UUID_HEART_RATE_SERVICE};
// Create characteristic and service
GattCharacteristic hrmRate(GattCharacteristic::UUID_HEART_RATE_MEASUREMENT_CHAR, bpm, sizeof(bpm), sizeof(bpm), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY);
GattCharacteristic hrmLocation(GattCharacteristic::UUID_BODY_SENSOR_LOCATION_CHAR,(uint8_t *)&location, sizeof(location), sizeof(location),GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ);
GattCharacteristic *hrmChars[] = {&hrmRate, &hrmLocation, };
GattService hrmService(GattService::UUID_HEART_RATE_SERVICE, hrmChars, sizeof(hrmChars) / sizeof(GattCharacteristic *));
void disconnectionCallBack(const Gap::DisconnectionCallbackParams_t *params) {
Serial.println("Disconnected!");
Serial.println("Restarting the advertising process");
ble.startAdvertising();
}
void periodicCallback() {
if (ble.getGapState().connected) {
// Update the HRM measurement
// First byte = 8-bit values, no extra info, Second byte = uint8_t HRM value
// See --> https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml
hrmCounter++;
if (hrmCounter == 175)
hrmCounter = 100;
bpm[1] = hrmCounter;
ble.updateCharacteristicValue(hrmRate.getValueAttribute().getHandle(), bpm, sizeof(bpm));
}
}
void setup() {
// put your setup code here, to run once
Serial.begin(9600);
Serial.println("Nordic_HRM Demo ");
// Init timer task
ticker_task1.attach(periodicCallback, 1);
// Init ble
ble.init();
ble.onDisconnection(disconnectionCallBack);
// setup adv_data and srp_data
ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t*)uuid16_list, sizeof(uuid16_list));
ble.accumulateAdvertisingPayload(GapAdvertisingData::HEART_RATE_SENSOR_HEART_RATE_BELT);
ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
// set adv_type
ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
// add service
ble.addService(hrmService);
// set device name
ble.setDeviceName((const uint8_t *)DEVICE_NAME);
// set tx power,valid values are -40, -20, -16, -12, -8, -4, 0, 4
ble.setTxPower(4);
// set adv_interval, 100ms in multiples of 0.625ms.
ble.setAdvertisingInterval(160);
// set adv_timeout, in seconds
ble.setAdvertisingTimeout(0);
// start advertising
ble.startAdvertising();
}
void loop() {
// put your main code here, to run repeatedly:
ble.waitForEvent();
}
I and my team mates are using MI band 3 to record data from Google Fit API. We are using a third party app - 'Notify and Fitness for MI Band' to sync the data. We are unable to get the Instantaneous Heart Rate.
We are using DataType.TYPE_HEART_RATE_BPM, DataType.AGGREGATE_HEART_RATE_SUMMARY to get the average highest and lowest heart readings but the Instaneous readings are not showning
package com.example.shubchintak;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.fitness.Fitness;
import com.google.android.gms.fitness.FitnessOptions;
import com.google.android.gms.fitness.data.Bucket;
import com.google.android.gms.fitness.data.DataPoint;
import com.google.android.gms.fitness.data.DataSet;
import com.google.android.gms.fitness.data.DataSource;
import com.google.android.gms.fitness.data.DataType;
import com.google.android.gms.fitness.data.Field;
import com.google.android.gms.fitness.data.Subscription;
import com.google.android.gms.fitness.data.Value;
import com.google.android.gms.fitness.request.DataReadRequest;
import com.google.android.gms.fitness.request.DataSourcesRequest;
import com.google.android.gms.fitness.request.OnDataPointListener;
import com.google.android.gms.fitness.request.SensorRequest;
import com.google.android.gms.fitness.result.DataReadResponse;
import com.google.android.gms.fitness.result.DataReadResult;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.logging.LogWrapper;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static java.text.DateFormat.getDateInstance;
import static java.text.DateFormat.getTimeInstance;
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
private Toolbar mToolbar;
private DrawerLayout mdrawerLayoout;
public static final String TAG = "StepCounter";
private static final int REQUEST_OAUTH_REQUEST_CODE = 0x1001;
private static final int MY_PERMISSIONS_REQUEST_READ_CONTACTS = 0x1001;
static TextView stepsNo;
private FirebaseAuth mAuth;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mToolbar = (Toolbar) findViewById(R.id.main_page_toolbar);
setSupportActionBar(mToolbar);
getSupportActionBar().setTitle("Shubchintak");
stepsNo=(TextView)findViewById(R.id.main_stat);
mdrawerLayoout =(DrawerLayout)findViewById(R.id.main_activity);
NavigationView navigationView=findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
mAuth = FirebaseAuth.getInstance();
FitnessOptions fitnessOptions =
FitnessOptions.builder()
.addDataType(DataType.TYPE_HEART_RATE_BPM)
.addDataType(DataType.AGGREGATE_HEART_RATE_SUMMARY)
.build();
if (!GoogleSignIn.hasPermissions(GoogleSignIn.getLastSignedInAccount(this), fitnessOptions)) {
GoogleSignIn.requestPermissions(
this,
REQUEST_OAUTH_REQUEST_CODE,
GoogleSignIn.getLastSignedInAccount(this),
fitnessOptions);
} else {
subscribe();
}
if (!checkPermissions()) {
requestPermissions();
} else {
}
mHandler = new Handler();
startRepeatingTask();
ActionBarDrawerToggle toggle=new ActionBarDrawerToggle(this,mdrawerLayoout,mToolbar,
R.string.navigation_drawer_open,R.string.navigation_drawer_close);
mdrawerLayoout.addDrawerListener(toggle);
toggle.syncState();
}
private boolean checkPermissions() {
int permissionState = ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION);
int permissionState1 = ActivityCompat.checkSelfPermission(this,
Manifest.permission.BODY_SENSORS);
return permissionState == PackageManager.PERMISSION_GRANTED && permissionState1 == PackageManager.PERMISSION_GRANTED;
}
private void requestPermissions() {
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.BODY_SENSORS)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
Manifest.permission.BODY_SENSORS)) {
// Show an explanation to the user asynchronously -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed; request the permission
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.BODY_SENSORS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
} else {
// Permission has already been granted
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_OAUTH_REQUEST_CODE) {
subscribe();
}
}
}
public void subscribe() {
// To create a subscription, invoke the Recording API. As soon as the subscription is
// active, fitness data will start recording.
Fitness.getRecordingClient(this, GoogleSignIn.getLastSignedInAccount(this))
.subscribe(DataType.AGGREGATE_HEART_RATE_SUMMARY)
.addOnCompleteListener(
new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.i(TAG, "Successfully subscribed!");
} else {
Log.w(TAG, "There was a problem subscribing.", task.getException());
}
}
});
}
public static DataReadRequest queryFitnessData() {
// [START build_read_data_request]
// Setting a start and end date using a range of 1 week before this moment.
Calendar cal = Calendar.getInstance();
Date now = new Date();
cal.setTime(now);
long endTime = cal.getTimeInMillis();
cal.add(Calendar.HOUR_OF_DAY, -1);
long startTime = cal.getTimeInMillis();
java.text.DateFormat dateFormat = getDateInstance();
Log.i(TAG, "Range Start: " + dateFormat.format(startTime));
Log.i(TAG, "Range End: " + dateFormat.format(endTime));
DataReadRequest readRequest =
new DataReadRequest.Builder()
// The data request can specify multiple data types to return, effectively
// combining multiple data queries into one call.
// In this example, it's very unlikely that the request is for several hundred
// datapoints each consisting of a few steps and a timestamp. The more likely
// scenario is wanting to see how many steps were walked per day, for 7 days.
.aggregate(DataType.TYPE_HEART_RATE_BPM, DataType.AGGREGATE_HEART_RATE_SUMMARY) // Analogous to a "Group By" in SQL, defines how data should be aggregated.
// bucketByTime allows for a time span, whereas bucketBySession would allow
// bucketing by "sessions", which would need to be defined in code.
.bucketByTime(1, TimeUnit.DAYS)
.setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS)
.build();
// [END build_read_data_request]
return readRequest;
}
private void readData() {
DataReadRequest readRequest = queryFitnessData();
Fitness.getHistoryClient(this, GoogleSignIn.getLastSignedInAccount(this))
.readData(readRequest)
.addOnSuccessListener(
new OnSuccessListener<DataReadResponse>() {
#Override
public void onSuccess(DataReadResponse dataReadResponse) {
printData(dataReadResponse);
}
})
.addOnFailureListener(
new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(TAG, "There was a problem getting the step count.", e);
}
});
}
public static void printData(DataReadResponse dataReadResult) {
// [START parse_read_data_result]
// If the DataReadRequest object specified aggregated data, dataReadResult will be returned
// as buckets containing DataSets, instead of just DataSets.
if (dataReadResult.getBuckets().size() > 0) {
Log.i(
TAG, "Number of returned buckets of DataSets is: " + dataReadResult.getBuckets().size());
for (Bucket bucket : dataReadResult.getBuckets()) {
List<DataSet> dataSets = bucket.getDataSets();
for (DataSet dataSet : dataSets) {
dumpDataSet(dataSet);
}
}
} else if (dataReadResult.getDataSets().size() > 0) {
Log.i(
TAG, "Number of returned buckets of DataSets is:ajsbdajsb " );
}
// [END parse_read_data_result]
}
private static void dumpDataSet(DataSet dataSet) {
Log.i(TAG, "Data returned for Data type: " + dataSet.getDataType().getName());
DateFormat dateFormat = getTimeInstance();
for (DataPoint dp : dataSet.getDataPoints()) {
Log.i(TAG, "Data point:");
Log.i(TAG, "\tType: " + dp.getDataType().getName());
Log.i(TAG, "\tStart: " + dateFormat.format(dp.getStartTime(TimeUnit.MILLISECONDS)));
Log.i(TAG, "\tEnd: " + dateFormat.format(dp.getEndTime(TimeUnit.MILLISECONDS)));
for (Field field : dp.getDataType().getFields()) {
Log.i(TAG, "\tField: " + field.getName() + " Value: " + dp.getValue(field));
if(field.getName().equals("average")){
stepsNo.setText(dp.getValue(field).toString());
}
}
}
}
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
switch(menuItem.getItemId()) {
case R.id.nav_logout:
FirebaseAuth.getInstance().signOut();
sendToStart();
break;
case R.id.action_read_data:
Log.i(TAG, "Entered method read data");
readData();
break;
}
mdrawerLayoout.closeDrawer(GravityCompat.START);
return true;
}
#Override
public void onBackPressed() {
if(mdrawerLayoout.isDrawerOpen(GravityCompat.START)){
mdrawerLayoout.closeDrawer(GravityCompat.START);
}else {
super.onBackPressed();
}
}
#Override
public void onStart() {
super.onStart();
// Check if user is signed in (non-null) and update UI accordingly.
FirebaseUser currentUser = mAuth.getCurrentUser();
if(currentUser == null){
// sendToStart();
}
}
private void sendToStart() {
Intent startIntent = new Intent(MainActivity.this, StartActivity.class);
startActivity(startIntent);
finish();
}
private int mInterval = 3000; // 5 seconds by default, can be changed later
private Handler mHandler;
#Override
public void onDestroy() {
super.onDestroy();
stopRepeatingTask();
}
Runnable mStatusChecker = new Runnable() {
#Override
public void run() {
try {
readData(); //this function can change value of mInterval.
} finally {
// 100% guarantee that this always happens, even if
// your update method throws an exception
mHandler.postDelayed(mStatusChecker, mInterval);
}
}
};
void startRepeatingTask() {
mStatusChecker.run();
}
void stopRepeatingTask() {
mHandler.removeCallbacks(mStatusChecker);
}
}
Expected solution : to display the instaneous data
Fitness.getRecordingClient(this, GoogleSignIn.getLastSignedInAccount(this))
.subscribe(DataType.AGGREGATE_HEART_RATE_SUMMARY)
This won't work; you need to subscribe to DataType.TYPE_HEART_RATE_BPM.
Could you point me to where you're trying to read instantaneous heart rate? I couldn't see it in the code.
I have created a LoginActivity with the inbuilt template under new activity of Android Studio. I am using a SqlDatabase with PHP script for login purpose using post method.When i execute this code i get 2 errors:
1) Error:(370, 9) error: method does not override or implement a method from a supertype
C:\Users\Chethan\AndroidStudioProjects\CleanMyChennai\app\src\main\java\com\exa mple\chethan\myapplication\LoginPage.java
2) Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
Compilation failed; see the compiler error output for details.
How to fix these two errors. by the way i am a beginner in java coding.
Plz help me if you find where i have i gone wrong.. I haven't found any tutorial that makes use of Default "LoginActivity" layout provided in Android Studio. i want to connect loginactivity.java along with PHP-MySql connection
package com.example.chethan.myapplication;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import static android.Manifest.permission.READ_CONTACTS;
import static java.net.URLEncoder.encode;
/**
* A login screen that offers login via email/password.
*/
public class LoginPage extends AppCompatActivity implements LoaderCallbacks<Cursor> {
/**
* Id to identity READ_CONTACTS permission request.
*/
private static final int REQUEST_READ_CONTACTS = 0;
/**
* A dummy authentication store containing known user names and passwords.
* TODO: remove after connecting to a real authentication system.
*/
private static final String[] DUMMY_CREDENTIALS = new String[]{
"foo#example.com:hello", "bar#example.com:world"
};
/**
* Keep track of the login task to ensure we can cancel it if requested.
*/
private UserLoginTask mAuthTask = null;
// UI references.
private AutoCompleteTextView mEmailView;
private EditText mPasswordView;
private View mProgressView;
private View mLoginFormView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login_page);
// Set up the login form.
mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
populateAutoComplete();
mPasswordView = (EditText) findViewById(R.id.password);
mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
if (id == R.id.login || id == EditorInfo.IME_NULL) {
attemptLogin();
return true;
}
return false;
}
});
Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
mEmailSignInButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View view) {
attemptLogin();
}
});
mLoginFormView = findViewById(R.id.login_form);
mProgressView = findViewById(R.id.login_progress);
}
private void populateAutoComplete() {
if (!mayRequestContacts()) {
return;
}
getLoaderManager().initLoader(0, null, this);
}
private boolean mayRequestContacts() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
return true;
}
if (shouldShowRequestPermissionRationale(READ_CONTACTS)) {
Snackbar.make(mEmailView, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE)
.setAction(android.R.string.ok, new View.OnClickListener() {
#Override
#TargetApi(Build.VERSION_CODES.M)
public void onClick(View v) {
requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
}
});
} else {
requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
}
return false;
}
/**
* Callback received when a permissions request has been completed.
*/
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions,
#NonNull int[] grantResults) {
if (requestCode == REQUEST_READ_CONTACTS) {
if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
populateAutoComplete();
}
}
}
/**
* Attempts to sign in or register the account specified by the login form.
* If there are form errors (invalid email, missing fields, etc.), the
* errors are presented and no actual login attempt is made.
*/
private void attemptLogin() {
if (mAuthTask != null) {
return;
}
// Reset errors.
mEmailView.setError(null);
mPasswordView.setError(null);
// Store values at the time of the login attempt.
String email = mEmailView.getText().toString();
String password = mPasswordView.getText().toString();
boolean cancel = false;
View focusView = null;
// Check for a valid password, if the user entered one.
if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
mPasswordView.setError(getString(R.string.error_invalid_password));
focusView = mPasswordView;
cancel = true;
}
// Check for a valid email address.
if (TextUtils.isEmpty(email)) {
mEmailView.setError(getString(R.string.error_field_required));
focusView = mEmailView;
cancel = true;
} else if (!isEmailValid(email)) {
mEmailView.setError(getString(R.string.error_invalid_email));
focusView = mEmailView;
cancel = true;
}
if (cancel) {
// There was an error; don't attempt login and focus the first
// form field with an error.
focusView.requestFocus();
} else {
// Show a progress spinner, and kick off a background task to
// perform the user login attempt.
showProgress(true);
mAuthTask = new UserLoginTask(email, password);
// mAuthTask.execute((Void) null);
mAuthTask.execute();
}
}
private boolean isEmailValid(String email) {
//TODO: Replace this with your own logic
return email.contains("#");
}
private boolean isPasswordValid(String password) {
//TODO: Replace this with your own logic
return password.length() > 4;
}
/**
* Shows the progress UI and hides the login form.
*/
#TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
private void showProgress(final boolean show) {
// On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
// for very easy animations. If available, use these APIs to fade-in
// the progress spinner.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
mLoginFormView.animate().setDuration(shortAnimTime).alpha(
show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
});
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mProgressView.animate().setDuration(shortAnimTime).alpha(
show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
}
});
} else {
// The ViewPropertyAnimator APIs are not available, so simply show
// and hide the relevant UI components.
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
}
#Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
return new CursorLoader(this,
// Retrieve data rows for the device user's 'profile' contact.
Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,
// Select only email addresses.
ContactsContract.Contacts.Data.MIMETYPE +
" = ?", new String[]{ContactsContract.CommonDataKinds.Email
.CONTENT_ITEM_TYPE},
// Show primary email addresses first. Note that there won't be
// a primary email address if the user hasn't specified one.
ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
}
#Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
List<String> emails = new ArrayList<>();
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
emails.add(cursor.getString(ProfileQuery.ADDRESS));
cursor.moveToNext();
}
addEmailsToAutoComplete(emails);
}
#Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
}
private void addEmailsToAutoComplete(List<String> emailAddressCollection) {
//Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
ArrayAdapter<String> adapter =
new ArrayAdapter<>(LoginPage.this,
android.R.layout.simple_dropdown_item_1line, emailAddressCollection);
mEmailView.setAdapter(adapter);
}
private interface ProfileQuery {
String[] PROJECTION = {
ContactsContract.CommonDataKinds.Email.ADDRESS,
ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
};
int ADDRESS = 0;
int IS_PRIMARY = 1;
}
/**
* Represents an asynchronous login/registration task used to authenticate
* the user.
*/
public class UserLoginTask extends AsyncTask<String, String, String> {
private final String mEmail;
private final String mPassword;
UserLoginTask(String email, String password) {
mEmail = email;
mPassword = password;
}
#Override
protected String doInBackground(String... params) {
// TODO: attempt authentication against a network service.
try {
// Simulate network access.
//Thread.sleep(2000);
String username = (String)params[0];
String password = (String)params[1];
String link="http://localhost/login.php";
String data = encode("username", "UTF-8") + "=" + encode(username, "UTF-8");
data += "&" + encode("password", "UTF-8") + "=" + encode(password, "UTF-8");
URL url = new URL(link);
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
wr.write( data );
wr.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder sb = new StringBuilder();
String line = null;
// Read Server Response
while((line = reader.readLine()) != null)
{
sb.append(line);
break;
}
return sb.toString();
}
catch (Exception e) {
//return false;
return new String("Exception: " + e.getMessage());
}
/*
for (String credential : DUMMY_CREDENTIALS) {
String[] pieces = credential.split(":");
if (pieces[0].equals(mEmail)) {
// Account exists, return true if the password matches.
return pieces[1].equals(mPassword);
}
}
*/
/* TODO: register the new account here. */
/* return true; */
}
#Override
protected void onPostExecute(final Boolean success) {
// super.onPostExecute(success);
mAuthTask = null;
showProgress(false);
if (success) {
finish();
Toast.makeText(getApplicationContext(),"login success",Toast.LENGTH_SHORT).show();
Intent myIntent = new Intent(LoginPage.this,Authorities.class);
LoginPage.this.startActivity(myIntent);
} else {
mPasswordView.setError(getString(R.string.error_incorrect_password));
mPasswordView.requestFocus();
}
}
#Override
protected void onCancelled() {
mAuthTask = null;
showProgress(false);
}
}
}
I saw your problem, is in your onPostExecute have Boolean success as a parameter, change the type of success to String success.
You are returning String from doInBackground
Final implementation should look like this:
protected void onPostExecute(final String success) {
// super.onPostExecute(success);
mAuthTask = null;
showProgress(false);
if (success.equals("true")) {
finish();
Toast.makeText(getApplicationContext(),"login success",Toast.LENGTH_SHORT).show();
Intent myIntent = new Intent(LoginPage.this,Authorities.class);
LoginPage.this.startActivity(myIntent);
} else {
mPasswordView.setError(getString(R.string.error_incorrect_password));
mPasswordView.requestFocus();
}
}
For more info check this question Can't Override onPostExecute() method in AsyncTask Class or get it to trigger
Hope this helps!!
I've managed to create a class which posts on my facebook wall. But how do I change the code to share on my facebook fanpage instead? I can't find anything on google or stack overflow...
here is the class which shares on facebook:
package com.celticwolf.blahblah; <--- changed
import com.facebook.android.*;
import com.facebook.android.Facebook.DialogListener;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.Toast;
public class ShareOnFacebook extends Activity{
private static final String APP_ID = "35255389027859"; <--- changed
private static final String[] PERMISSIONS = new String[] {"publish_stream"};
private static final String TOKEN = "access_token";
private static final String EXPIRES = "expires_in";
private static final String KEY = "facebook-credentials";
private Facebook facebook;
private String messageToPost;
public boolean saveCredentials(Facebook facebook) {
Editor editor = getApplicationContext().getSharedPreferences(KEY, Context.MODE_PRIVATE).edit();
editor.putString(TOKEN, facebook.getAccessToken());
editor.putLong(EXPIRES, facebook.getAccessExpires());
return editor.commit();
}
public boolean restoreCredentials(Facebook facebook) {
SharedPreferences sharedPreferences = getApplicationContext().getSharedPreferences(KEY, Context.MODE_PRIVATE);
facebook.setAccessToken(sharedPreferences.getString(TOKEN, null));
facebook.setAccessExpires(sharedPreferences.getLong(EXPIRES, 0));
return facebook.isSessionValid();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
facebook = new Facebook(APP_ID);
restoreCredentials(facebook);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.facebook_dialog);
String facebookMessage = getIntent().getStringExtra("facebookMessage");
if (facebookMessage == null){
facebookMessage = "Test wall post";
}
messageToPost = facebookMessage;
}
public void doNotShare(View button){
finish();
}
public void share(View button){
if (! facebook.isSessionValid()) {
loginAndPostToWall();
}
else {
postToWall(messageToPost);
}
}
public void loginAndPostToWall(){
facebook.authorize(this, PERMISSIONS, Facebook.FORCE_DIALOG_AUTH, new LoginDialogListener());
}
public void postToWall(String message){
new MyAsyncTask().execute(message);
}
class MyAsyncTask extends AsyncTask<String,Void,Boolean>
{
public Boolean doInBackground(String ...message){
Bundle parameters = new Bundle();
parameters.putString("message", message[0]);
parameters.putString("description", "topic share");
try {
facebook.request("me");
String response = facebook.request("me/feed", parameters, "POST"); <--- I think here is the crucial part
Log.d("Tests", "got response: " + response);
if (response == null || response.equals("") ||
response.equals("false")) {
return Boolean.FALSE;
}
else {
return Boolean.TRUE;
}
} catch (Exception e) {
e.printStackTrace();
return Boolean.FALSE;
}
}
public void onPostExecute(Boolean result){
if(result == Boolean.TRUE){
showToast("posted successfully");
}else{
showToast("couldn't post to FB.");
}
finish();
}
}
class LoginDialogListener implements DialogListener {
public void onComplete(Bundle values) {
saveCredentials(facebook);
if (messageToPost != null){
postToWall(messageToPost);
}
}
public void onFacebookError(FacebookError error) {
showToast("Authentication with Facebook failed!");
finish();
}
public void onError(DialogError error) {
showToast("Authentication with Facebook failed!");
finish();
}
public void onCancel() {
showToast("Authentication with Facebook cancelled!");
finish();
}
}
private void showToast(String message){
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
}
}
thank you!
String response = facebook.request("me/feed", parameters, "POST");
me/feed becomes PAGE_ID/feed:
String response = facebook.request("PAGE_ID/feed", parameters, "POST");
Learn how to use the Graph API here: https://developers.facebook.com/docs/reference/api/
String response = facebook.request("PAGE_ID/feed", parameters, "POST");
This will be work but for posting in a Page you must need access token with Mangage_Pages and Publish_stream permissions.