I have tried connecting to the Nearby Messages API, and have successfully been able to subscribe.
Now, my mMessageListener field is never getting callbacks for some reason.
I have already configured my beacons using the proximity beacon api using the Android beacon service demo app.
public class MainActivity extends AppCompatActivity implements
GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
// Declaration of member variables
private GoogleApiClient mGoogleApiClient;
private final String TAG = "Bridge.MainActivity";
private boolean mResolvingError = false;
private static final int REQUEST_RESOLVE_ERROR = 100;
private static final int REQUEST_PERMISSION = 42;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Initializing the Google API client
}
private MessageListener mMessageListener = new MessageListener() {
#Override
public void onFound(Message message) {
// Do something with the message
Log.i(TAG, " Found Message : " + message.toString());
}
#Override
public void onLost(Message message) {
super.onLost(message);
Log.i(TAG, " Found Message : " + message.toString());
}
};
#Override
public void onConnected(#Nullable Bundle bundle) {
Log.d(TAG, "GoogleAPi Client Connected");
foregorundSubscribeBeacons();
}
#Override
public void onConnectionSuspended(int i) {
Log.d(TAG, "Google Api Connection Suspended : " + i);
}
#Override
public void onConnectionFailed(#NonNull ConnectionResult connectionResult) {
Log.d(TAG, "GoogleApi Connection failed : " + connectionResult.getErrorMessage());
}
public void foregorundSubscribeBeacons() {
// Subscribe to receive messages
Log.i(TAG, "Trying to subscribe");
if (!mGoogleApiClient.isConnected()) {
if (!mGoogleApiClient.isConnecting()) {
mGoogleApiClient.connect();
}
} else {
SubscribeOptions options = new SubscribeOptions.Builder()
.setStrategy(Strategy.BLE_ONLY)
.setCallback(new SubscribeCallback() {
#Override
public void onExpired() {
Log.i(TAG, "No longer subscribing.");
}
}).build();
Nearby.Messages.subscribe(mGoogleApiClient, mMessageListener, options)
.setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
if (status.isSuccess()) {
Log.i(TAG, "Subscribed successfully.");
} else {
Log.i(TAG, "Could not subscribe.");
// Check whether consent was given;
// if not, prompt the user for consent.
handleUnsuccessfulNearbyResult(status);
}
}
});
}
}
private void handleUnsuccessfulNearbyResult(Status status) {
Log.i(TAG, "Processing error, status = " + status);
if (mResolvingError) {
// Already attempting to resolve an error.
return;
} else if (status.hasResolution()) {
try {
mResolvingError = true;
status.startResolutionForResult(this,
REQUEST_RESOLVE_ERROR);
} catch (IntentSender.SendIntentException e) {
mResolvingError = false;
Log.i(TAG, "Failed to resolve error status.", e);
}
} else {
if (status.getStatusCode() == CommonStatusCodes.NETWORK_ERROR) {
Toast.makeText(this.getApplicationContext(),
"No connectivity, cannot proceed. Fix in 'Settings' and try again.",
Toast.LENGTH_LONG).show();
} else {
// To keep things simple, pop a toast for all other error messages.
Toast.makeText(this.getApplicationContext(), "Unsuccessful: " +
status.getStatusMessage(), Toast.LENGTH_LONG).show();
}
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_RESOLVE_ERROR) {
// User was presented with the Nearby opt-in dialog and pressed "Allow".
mResolvingError = false;
if (resultCode == RESULT_OK) {
// Execute the pending subscription and publication tasks here.
foregorundSubscribeBeacons();
} else if (resultCode == RESULT_CANCELED) {
// User declined to opt-in. Reset application state here.
} else {
Toast.makeText(this, "Failed to resolve error with code " + resultCode,
Toast.LENGTH_LONG).show();
}
}
}
#Override
protected void onStart() {
super.onStart();
//Initiate connection to Play Services
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Nearby.MESSAGES_API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
mGoogleApiClient.connect();
//The location permission is required on API 23+ to obtain BLE scan results
int result = ActivityCompat
.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION);
if (result != PackageManager.PERMISSION_GRANTED) {
//Ask for the location permission
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
REQUEST_PERMISSION);
}
}
#Override
protected void onStop() {
super.onStop();
//Tear down Play Services connection
if (mGoogleApiClient.isConnected()) {
Log.d(TAG, "Un-subscribing…");
mGoogleApiClient.disconnect();
}
}
Make sure both the Beacon Service Demo App and the app using Nearby Messages are part of the same Google Developers Console project. You will only see messages attached by your own project.
If you have successfully done the beacon registration, adding the attachment to beacon on the Google Beacon Registry Server; then on subscribed successfully on device.
PRE-PREPARATION IS NECESSARY, ELSE YOU WON'T GET THE THINGS ON DEVICE.
So when beacon gets detected, the onFound(Message msg) gets called for each attachment of respective beacon.
THEY CLEARLY SAID, "DO SOMETHING WITH MESSAGE" in onFound(), so process your attachment only there, not the outside of onFound().
Here, if you print variable msg into Log, it should look like this :
Message{namespace='yourprojectname-1234', type='abcd', content=[614 bytes], devices=[NearbyDevice{id=NearbyDeviceId{UNKNOWN}}]}
Get the attachment content with msg.getContent() into String variable. This is normal text, and not in the base64 format.
Once you get the string content, DO WHATEVER you wanted to do.
Now it's up to you what content goes into attachment.
I have used JSON in the attachment and successfully processed for my purpose.
Related
I have implemented the Google Fit Sensors API in my project and it does not seem to be finding my watch when scanning for data sources
Here is my code for scanning for data sources
private void findFitnessDataSources() {
Log.i(TAG, "findFitnessDataSources called");
// [START find_data_sources]
// Note: Fitness.SensorsApi.findDataSources() requires the ACCESS_FINE_LOCATION permission.
Fitness.getSensorsClient(this, GoogleSignIn.getLastSignedInAccount(this))
.findDataSources(
new DataSourcesRequest.Builder()
.setDataTypes(DataType.TYPE_HEART_RATE_BPM)
.setDataSourceTypes(DataSource.TYPE_RAW)
.build())
.addOnSuccessListener(
new OnSuccessListener<List<DataSource>>() {
#Override
public void onSuccess(List<DataSource> dataSources) {
Log.i(TAG, "Listener Success");
Log.i(TAG, "DataSources: " + dataSources.size());
for (DataSource dataSource : dataSources) {
Log.i(TAG, "Data source found: " + dataSource.toString());
Log.i(TAG, "Data Source type: " + dataSource.getDataType().getName());
// Let's register a listener to receive Activity data!
if (dataSource.getDataType().equals(DataType.TYPE_HEART_RATE_BPM)
&& mListener == null) {
Log.i(TAG, "Data source for HEART RATE found! Registering.");
registerFitnessDataListener(dataSource);
}
}
}
})
.addOnFailureListener(
new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.e(TAG, "failed", e);
}
});
// [END find_data_sources]
}
The result is no data sources found. My watch had google fit updated and signed in on the same account as I select in the application. As far as I can tell it is successfully connecting to the API it is simply not reading any sensors to pull information out of. I am, of course, possibly wrong about that. If anyone can shed any light on this it would be much appreciated.
Below is all my code for this activity if it is relevant
public class Test_Page extends AppCompatActivity {
TextView hR;
public static final String TAG = "BasicSensorsApi";
private static final int REQUEST_OAUTH_REQUEST_CODE = 1;
private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34;
// [START mListener_variable_reference]
// Need to hold a reference to this listener, as it's passed into the "unregister"
// method in order to stop all sensors from sending data to this listener.
private OnDataPointListener mListener;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_page);
hR = findViewById(R.id.heartRate);
// When permissions are revoked the app is restarted so onCreate is sufficient to check for
// permissions core to the Activity's functionality.
if (hasRuntimePermissions()) {
findFitnessDataSourcesWrapper();
} else {
requestRuntimePermissions();
}
}
/**
* A wrapper for {#link #findFitnessDataSources}. If the user account has OAuth permission,
* continue to {#link #findFitnessDataSources}, else request OAuth permission for the account.
*/
private void findFitnessDataSourcesWrapper() {
Log.i(TAG, "findFitnessDataSourcesWrapper called");
if (hasOAuthPermission()) {
findFitnessDataSources();
} else {
requestOAuthPermission();
}
}
/** Gets the {#link FitnessOptions} in order to check or request OAuth permission for the user. */
private FitnessOptions getFitnessSignInOptions() {
Log.i(TAG, "getFitnessSignInOptions calles");
return FitnessOptions.builder().addDataType(DataType.TYPE_HEART_RATE_BPM, FitnessOptions.ACCESS_READ).build();
}
/** Checks if user's account has OAuth permission to Fitness API. */
private boolean hasOAuthPermission() {
Log.i(TAG, "hasOAuthPermission called");
FitnessOptions fitnessOptions = getFitnessSignInOptions();
return GoogleSignIn.hasPermissions(GoogleSignIn.getLastSignedInAccount(this), fitnessOptions);
}
/** Launches the Google SignIn activity to request OAuth permission for the user. */
private void requestOAuthPermission() {
Log.i(TAG, "requestOAuthPermission called");
FitnessOptions fitnessOptions = getFitnessSignInOptions();
GoogleSignIn.requestPermissions(
this,
REQUEST_OAUTH_REQUEST_CODE,
GoogleSignIn.getLastSignedInAccount(this),
fitnessOptions);
}
#Override
protected void onResume() {
super.onResume();
// This ensures that if the user denies the permissions then uses Settings to re-enable
// them, the app will start working.
findFitnessDataSourcesWrapper();
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.i(TAG, "onActivityResult called");
if (resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_OAUTH_REQUEST_CODE) {
findFitnessDataSources();
}
}
}
// [END auth_oncreate_setup]
/** Finds available data sources and attempts to register on a specific {#link DataType}. */
private void findFitnessDataSources() {
Log.i(TAG, "findFitnessDataSources called");
// [START find_data_sources]
// Note: Fitness.SensorsApi.findDataSources() requires the ACCESS_FINE_LOCATION permission.
Fitness.getSensorsClient(this, GoogleSignIn.getLastSignedInAccount(this))
.findDataSources(
new DataSourcesRequest.Builder()
.setDataTypes(DataType.TYPE_HEART_RATE_BPM)
.setDataSourceTypes(DataSource.TYPE_RAW)
.build())
.addOnSuccessListener(
new OnSuccessListener<List<DataSource>>() {
#Override
public void onSuccess(List<DataSource> dataSources) {
Log.i(TAG, "Listener Success");
Log.i(TAG, "DataSources: " + dataSources.size());
for (DataSource dataSource : dataSources) {
Log.i(TAG, "Data source found: " + dataSource.toString());
Log.i(TAG, "Data Source type: " + dataSource.getDataType().getName());
// Let's register a listener to receive Activity data!
if (dataSource.getDataType().equals(DataType.TYPE_HEART_RATE_BPM)
&& mListener == null) {
Log.i(TAG, "Data source for HEART RATE found! Registering.");
registerFitnessDataListener(dataSource);
}
}
}
})
.addOnFailureListener(
new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.e(TAG, "failed", e);
}
});
// [END find_data_sources]
}
/**
* Registers a listener with the Sensors API for the provided {#link DataSource} and {#link
* DataType} combo.
*/
private void registerFitnessDataListener(DataSource dataSource) {
Log.i(TAG, "registerFitnessDataListener called");
// [START register_data_listener]
mListener =
new OnDataPointListener() {
#Override
public void onDataPoint(DataPoint dataPoint) {
for (final Field field : dataPoint.getDataType().getFields()) {
final Value val = dataPoint.getValue(field);
runOnUiThread(new Runnable() {
#Override
public void run() {
hR.setText(val.toString());
}
});
}
}
};
Fitness.getSensorsClient(this, GoogleSignIn.getLastSignedInAccount(this))
.add(
new SensorRequest.Builder()
.setDataSource(dataSource) // Optional but recommended for custom data sets.
.setDataType(DataType.TYPE_HEART_RATE_BPM) // Can't be omitted.
.setSamplingRate(1, TimeUnit.SECONDS)
.build(),
mListener)
.addOnCompleteListener(
new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.i(TAG, "Listener registered!");
} else {
Log.e(TAG, "Listener not registered.", task.getException());
}
}
});
// [END register_data_listener]
}
/** Unregisters the listener with the Sensors API. */
private void unregisterFitnessDataListener() {
Log.i(TAG, "unRegisterFitnessDataListener called");
if (mListener == null) {
// This code only activates one listener at a time. If there's no listener, there's
// nothing to unregister.
return;
}
// [START unregister_data_listener]
// Waiting isn't actually necessary as the unregister call will complete regardless,
// even if called from within onStop, but a callback can still be added in order to
// inspect the results.
Fitness.getSensorsClient(this, GoogleSignIn.getLastSignedInAccount(this))
.remove(mListener)
.addOnCompleteListener(
new OnCompleteListener<Boolean>() {
#Override
public void onComplete(#NonNull Task<Boolean> task) {
if (task.isSuccessful() && task.getResult()) {
Log.i(TAG, "Listener was removed!");
} else {
Log.i(TAG, "Listener was not removed.");
}
}
});
// [END unregister_data_listener]
}
/** Initializes a custom log class that outputs both to in-app targets and logcat. */
/** Returns the current state of the permissions needed. */
private boolean hasRuntimePermissions() {
Log.i(TAG, "hasRuntimePermissions called");
int permissionState =
ActivityCompat.checkSelfPermission(this, Manifest.permission.BODY_SENSORS);
return permissionState == PackageManager.PERMISSION_GRANTED;
}
private void requestRuntimePermissions() {
boolean shouldProvideRationale =
ActivityCompat.shouldShowRequestPermissionRationale(
this, Manifest.permission.BODY_SENSORS);
if (shouldProvideRationale) {
Log.i(TAG, "Displaying permission rationale to provide additional context.");
} else {
Log.i(TAG, "Requesting permission");
// Request permission. It's possible this can be auto answered if device policy
// sets the permission in a given state or the user denied the permission
// previously and checked "Never ask again".
ActivityCompat.requestPermissions(
Test_Page.this,
new String[] {Manifest.permission.BODY_SENSORS},
REQUEST_PERMISSIONS_REQUEST_CODE);
}
}
/** Callback received when a permissions request has been completed. */
#Override
public void onRequestPermissionsResult(
int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
Log.i(TAG, "onRequestPermissionResult called");
if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) {
if (grantResults.length <= 0) {
// If user interaction was interrupted, the permission request is cancelled and you
// receive empty arrays.
Log.i(TAG, "User interaction was cancelled.");
} else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission was granted.
findFitnessDataSourcesWrapper();
} else {
// Permission denied.
Log.i(TAG, "Permission denied");
}
}
}
}
I try to implement a small test application for Googles Nearby Connections API. Unfortunately on 2 of 3 tested devices Google Play Services chrash when discovering or advertising. (OnePlus One, Android 6.1; Acer Iconia, Android 4.4)
I see other devices, but when i connect to one of them play service crash (only my Honor 8 keeps on working). It says the connection is suspendend with error code 1. According to Google this means "A suspension cause informing that the service has been killed."
Maybe some of you could help. I made this code based on this tutorial.
Code without imports:
MainActivity.java
package com.example.steffen.nearbyconnectionsdemo;
public class MainActivity extends Activity implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
View.OnClickListener,
Connections.ConnectionRequestListener,
Connections.MessageListener,
Connections.EndpointDiscoveryListener {
// Identify if the device is the host
private boolean mIsHost = false;
GoogleApiClient mGoogleApiClient = null;
Button bt_ad, bt_search, bt_send;
TextView tv_status;
CheckBox checkBox;
Context c;
String globalRemoteEndpointId = "";
EditText editText;
final int MY_PERMISSIONS_REQUEST = 666;
private static int[] NETWORK_TYPES = {ConnectivityManager.TYPE_WIFI,
ConnectivityManager.TYPE_ETHERNET};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
c = this;
checkPermisson();
editText = (EditText) findViewById(R.id.editText);
bt_ad = (Button) findViewById(R.id.bt_ad);
bt_ad.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
bt_search.setEnabled(false);
startAdvertising();
}
});
bt_search = (Button) findViewById(R.id.bt_search);
bt_search.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
startDiscovery();
}
});
bt_send = (Button) findViewById(R.id.bt_send);
bt_send.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String message = "message: " + editText.getText().toString();
Toast.makeText(c, "Sending: " + message, Toast.LENGTH_SHORT).show();
byte[] payload = message.getBytes();
Nearby.Connections.sendReliableMessage(mGoogleApiClient, globalRemoteEndpointId, payload);
}
});
tv_status = (TextView) findViewById(R.id.tv_status);
checkBox = (CheckBox) findViewById(R.id.checkBox);
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Nearby.CONNECTIONS_API)
.build();
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
private boolean isConnectedToNetwork() {
ConnectivityManager connManager =
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
for (int networkType : NETWORK_TYPES) {
NetworkInfo info = connManager.getNetworkInfo(networkType);
if (info != null && info.isConnectedOrConnecting()) {
return true;
}
}
return false;
}
private void startAdvertising() {
if (!isConnectedToNetwork()) {
// Implement logic when device is not connected to a network
tv_status.setText("No Network");
return;
}
// Identify that this device is the host
mIsHost = true;
checkBox.setChecked(mIsHost);
// Advertising with an AppIdentifer lets other devices on the
// network discover this application and prompt the user to
// install the application.
List<AppIdentifier> appIdentifierList = new ArrayList<>();
appIdentifierList.add(new AppIdentifier(getPackageName()));
AppMetadata appMetadata = new AppMetadata(appIdentifierList);
// The advertising timeout is set to run indefinitely
// Positive values represent timeout in milliseconds
long NO_TIMEOUT = 0L;
String name = null;
Nearby.Connections.startAdvertising(mGoogleApiClient, name, appMetadata, NO_TIMEOUT,
this).setResultCallback(new ResultCallback<Connections.StartAdvertisingResult>() {
#Override
public void onResult(Connections.StartAdvertisingResult result) {
if (result.getStatus().isSuccess()) {
// Device is advertising
tv_status.setText("Advertising");
} else {
int statusCode = result.getStatus().getStatusCode();
// Advertising failed - see statusCode for more details
tv_status.setText("Error: " + statusCode);
}
}
});
}
private void startDiscovery() {
if (!isConnectedToNetwork()) {
// Implement logic when device is not connected to a network
tv_status.setText("No Network");
return;
}
String serviceId = getString(R.string.service_id);
// Set an appropriate timeout length in milliseconds
long DISCOVER_TIMEOUT = 1000L;
// Discover nearby apps that are advertising with the required service ID.
Nearby.Connections.startDiscovery(mGoogleApiClient, serviceId, DISCOVER_TIMEOUT, this)
.setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
if (status.isSuccess()) {
// Device is discovering
tv_status.setText("Discovering");
} else {
int statusCode = status.getStatusCode();
// Advertising failed - see statusCode for more details
tv_status.setText("Error: " + statusCode);
}
}
});
}
#Override
public void onEndpointFound(final String endpointId, String deviceId,
String serviceId, final String endpointName) {
// This device is discovering endpoints and has located an advertiser.
// Write your logic to initiate a connection with the device at
// the endpoint ID
Toast.makeText(this, "Found Device: " + serviceId + ", " + endpointName + ". Start Connection Try", Toast.LENGTH_SHORT).show();
connectTo(endpointId, endpointName);
}
private void connectTo(String remoteEndpointId, final String endpointName) {
// Send a connection request to a remote endpoint. By passing 'null' for
// the name, the Nearby Connections API will construct a default name
// based on device model such as 'LGE Nexus 5'.
tv_status.setText("Connecting");
String myName = null;
byte[] myPayload = null;
Nearby.Connections.sendConnectionRequest(mGoogleApiClient, myName,
remoteEndpointId, myPayload, new Connections.ConnectionResponseCallback() {
#Override
public void onConnectionResponse(String remoteEndpointId, Status status,
byte[] bytes) {
if (status.isSuccess()) {
// Successful connection
tv_status.setText("Connected to " + endpointName);
globalRemoteEndpointId = remoteEndpointId;
} else {
// Failed connection
tv_status.setText("Connecting failed");
}
}
}, this);
}
#Override
public void onConnectionRequest(final String remoteEndpointId, String remoteDeviceId,
final String remoteEndpointName, byte[] payload) {
if (mIsHost) {
byte[] myPayload = null;
// Automatically accept all requests
Nearby.Connections.acceptConnectionRequest(mGoogleApiClient, remoteEndpointId,
myPayload, this).setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
if (status.isSuccess()) {
String statusS = "Connected to " + remoteEndpointName;
Toast.makeText(c, statusS,
Toast.LENGTH_SHORT).show();
tv_status.setText(statusS);
globalRemoteEndpointId = remoteEndpointId;
} else {
String statusS = "Failed to connect to: " + remoteEndpointName;
Toast.makeText(c, statusS,
Toast.LENGTH_SHORT).show();
tv_status.setText(statusS);
}
}
});
} else {
// Clients should not be advertising and will reject all connection requests.
Nearby.Connections.rejectConnectionRequest(mGoogleApiClient, remoteEndpointId);
}
}
#Override
public void onMessageReceived(String endpointId, byte[] payload, boolean b) {
String message = payload.toString();
Toast.makeText(this, "Received from " + endpointId + ": " + message, Toast.LENGTH_SHORT).show();
}
#Override
public void onClick(View view) {
}
#Override
public void onStart() {
super.onStart();
mGoogleApiClient.connect();
}
#Override
public void onStop() {
super.onStop();
if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
mGoogleApiClient.disconnect();
}
}
#Override
public void onConnected(Bundle bundle) {
}
#Override
public void onConnectionSuspended(int i) {
tv_status.setText("Connection suspended because of " + i);
mGoogleApiClient.reconnect();
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
tv_status.setText("Connection Failed");
}
#Override
public void onEndpointLost(String s) {
tv_status.setText("Endpoint lost: " + s);
}
#Override
public void onDisconnected(String s) {
tv_status.setText("Disconnected: " + s);
}
public void checkPermisson(){
Toast.makeText(c, "Check permission", Toast.LENGTH_SHORT).show();
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_NETWORK_STATE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_NETWORK_STATE}, MY_PERMISSIONS_REQUEST);
}
}
#Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST:
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
Toast.makeText(c, "Permission granted", Toast.LENGTH_SHORT).show();
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
Toast.makeText(c, "Permission not granted, app may fail", Toast.LENGTH_SHORT).show();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
I get a funny message from Google in my logcat.
Connection lost. Reason: Service Disconnected
Here is how I use Google's code,in order to connect their services.
private void buildFitnessClient() {
// Create the Google API Client
mClient = new GoogleApiClient.Builder(this)
.addApi(Fitness.SENSORS_API)
.addApi(Fitness.HISTORY_API)
.addApi(Fitness.RECORDING_API)
.addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE))
.addScope(new Scope(Scopes.FITNESS_BODY_READ_WRITE))
.addScope(new Scope(Scopes.FITNESS_LOCATION_READ))
.addConnectionCallbacks(
new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(Bundle bundle) {
Log.i(TAG, "Connected!!!");
Log.i(TAG, "Connected!!!");
new InsertAndVerifyDataTask().execute();
}
#Override
public void onConnectionSuspended(int i) {
// If your connection to the sensor gets lost at some point,
// you'll be able to determine the reason and react to it here.
if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) {
Log.i(TAG, "Connection lost. Cause: Network Lost.");
} else if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) {
Log.i(TAG, "Connection lost. Reason: Service Disconnected");
}
}
}
)
.addOnConnectionFailedListener(
new GoogleApiClient.OnConnectionFailedListener() {
// Called whenever the API client fails to connect.
#Override
public void onConnectionFailed(ConnectionResult result) {
Log.i(TAG, "Connection failed. Cause: " + result.toString());
if (!result.hasResolution()) {
// Show the localized error dialog
GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(),
League.this, 0).show();
return;
}
// The failure has a resolution. Resolve it.
// Called typically when the app is not yet authorized, and an
// authorization dialog is displayed to the user.
if (!authInProgress) {
try {
Log.i(TAG, "Attempting to resolve failed connection");
authInProgress = true;
result.startResolutionForResult(League.this,
REQUEST_OAUTH);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG,
"Exception while starting resolution activity", e);
}
}
}
}
)
.build();
}
If you look more closely in the code that message is appeared inside the
onConnectionSuspended(int i)
method. In other words,it doesn't let me to connect. I wasn't having this problem before.
That was easy I had to add the following piece of code,which I forgot!
#Override
protected void onStart() {
super.onStart();
active = true;
mClient.connect();
//checkLeague(username, password);
}
#Override
protected void onDestroy() {
super.onDestroy();
//showUser(username,password);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_OAUTH) {
authInProgress = false;
if (resultCode == RESULT_OK) {
// Make sure the app is not already connected or attempting to connect
if (!mClient.isConnecting() && !mClient.isConnected()) {
mClient.connect();
}
}
}
}
#Override
protected void onStop() {
super.onStop();
active = false;
}
}
I am currently trying to work with Google Fit API.This is my first App using the API, and I have been mainly by following Google's documentation.
Below is the code that I have which seems to have a problem
The problem I have is that it doesn't seem to be updating the step counter.
public class MainActivity extends Activity
implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
private static final String TAG = "FitActivity";
//[START Auth_Variable_References]
private static final int REQUEST_OAUTH = 1;
// [END auth_variable_references]
private GoogleApiClient mClient = null;
int mInitialNumberOfSteps = 0;
private TextView mStepsTextView;
private boolean mFirstCount = true;
// Create Builder View
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mStepsTextView = (TextView) findViewById(R.id.textview_number_of_steps);
}
private void connectFitness() {
Log.i(TAG, "Connecting...");
// Create the Google API Client
mClient = new GoogleApiClient.Builder(this)
// select the Fitness API
.addApi(Fitness.API)
// specify the scopes of access
.addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ))
.addScope(new Scope(Scopes.FITNESS_LOCATION_READ))
.addScope(new Scope(Scopes.FITNESS_BODY_READ_WRITE))
// provide callbacks
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
// Connect the Google API client
mClient.connect();
}
// Manage OAuth authentication
#Override
public void onConnectionFailed(ConnectionResult result) {
// Error while connecting. Try to resolve using the pending intent returned.
if (result.getErrorCode() == ConnectionResult.SIGN_IN_REQUIRED ||
result.getErrorCode() == FitnessStatusCodes.NEEDS_OAUTH_PERMISSIONS) {
try {
// Request authentication
result.startResolutionForResult(this, REQUEST_OAUTH);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Exception connecting to the fitness service", e);
}
} else {
Log.e(TAG, "Unknown connection issue. Code = " + result.getErrorCode());
}
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_OAUTH) {
if (resultCode == RESULT_OK) {
// If the user authenticated, try to connect again
mClient.connect();
}
}
}
#Override
public void onConnectionSuspended(int i) {
// If your connection gets lost at some point,
// you'll be able to determine the reason and react to it here.
if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) {
Log.i(TAG, "Connection lost. Cause: Network Lost.");
} else if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) {
Log.i(TAG, "Connection lost. Reason: Service Disconnected");
}
}
#Override
public void onConnected(Bundle bundle) {
Log.i(TAG, "Connected!");
// Now you can make calls to the Fitness APIs.
invokeFitnessAPIs();
}
private void invokeFitnessAPIs() {
// Create a listener object to be called when new data is available
OnDataPointListener listener = new OnDataPointListener() {
#Override
public void onDataPoint(DataPoint dataPoint) {
for (Field field : dataPoint.getDataType().getFields()) {
Value val = dataPoint.getValue(field);
updateTextViewWithStepCounter(val.asInt());
}
}
};
//Specify what data sources to return
DataSourcesRequest req = new DataSourcesRequest.Builder()
.setDataSourceTypes(DataSource.TYPE_DERIVED)
.setDataTypes(DataType.TYPE_STEP_COUNT_DELTA)
.build();
// Invoke the Sensors API with:
// - The Google API client object
// - The data sources request object
PendingResult<DataSourcesResult> pendingResult =
Fitness.SensorsApi.findDataSources(mClient, req);
// Build a sensor registration request object
SensorRequest sensorRequest = new SensorRequest.Builder()
.setDataType(DataType.TYPE_STEP_COUNT_CUMULATIVE)
.setSamplingRate(1, TimeUnit.SECONDS)
.build();
// Invoke the Sensors API with:
// - The Google API client object
// - The sensor registration request object
// - The listener object
PendingResult<Status> regResult =
Fitness.SensorsApi.add(mClient,
new SensorRequest.Builder()
.setDataType(DataType.TYPE_STEP_COUNT_DELTA)
.build(),
listener);
// 4. Check the result asynchronously
regResult.setResultCallback(new ResultCallback<Status>()
{
#Override
public void onResult(Status status) {
if (status.isSuccess()) {
Log.d(TAG, "listener registered");
// listener registered
} else {
Log.d(TAG, "listener not registered");
// listener not registered
}
}
});
}
// Update the Text Viewer with Counter of Steps..
private void updateTextViewWithStepCounter(final int numberOfSteps) {
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(getBaseContext(), "On Datapoint!", Toast.LENGTH_SHORT);
if(mFirstCount && (numberOfSteps != 0)) {
mInitialNumberOfSteps = numberOfSteps;
mFirstCount = false;
}
if(mStepsTextView != null){
mStepsTextView.setText(String.valueOf(numberOfSteps - mInitialNumberOfSteps));
}
}
});
}
//Start
#Override
protected void onStart() {
super.onStart();
mFirstCount = true;
mInitialNumberOfSteps = 0;
if (mClient == null || !mClient.isConnected()) {
connectFitness();
}
}
//Stop
#Override
protected void onStop() {
super.onStop();
if(mClient.isConnected() || mClient.isConnecting()) mClient.disconnect();
mInitialNumberOfSteps = 0;
mFirstCount = true;
}
}
First of all,
Follow these steps to enable the Fitness API in the Google API Console and get an OAuth 2.0 client ID.
1. Go to the Google API Console.
2. Select a project, or create a new one. Use the same project for the Android and REST versions of your app.
3. Click Continue to enable the Fitness API.
4. Click Go to credentials.
5. Click New credentials, then select OAuth Client ID.
6. Under Application type select Android.
7. In the resulting dialog, enter your app's SHA-1 fingerprint and package name. For example:
BB:0D:AC:74:D3:21:E1:43:67:71:9B:62:91:AF:A1:66:6E:44:5D:75
com.example.android.fit-example
8. Click Create. Your new Android OAuth 2.0 Client ID and secret appear in the list of IDs for your project. An OAuth 2.0 Client ID is a string of characters, something like this:
780816631155-gbvyo1o7r2pn95qc4ei9d61io4uh48hl.apps.googleusercontent.com
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.Scopes;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Scope;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.fitness.Fitness;
import com.google.android.gms.fitness.data.DataPoint;
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.Value;
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.DataSourcesResult;
import java.util.concurrent.TimeUnit;
/**
* Created by Admin on Dec/8/2016.
* <p/>
* <p/>
* http://stackoverflow.com/questions/28476809/step-counter-google-fit-api?rq=1
*/
public class StackOverflowActivity extends AppCompatActivity
{
private static final String TAG = "FitActivity";
private GoogleApiClient mClient = null;
private OnDataPointListener mListener;
// Create Builder View
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
protected void onResume() {
super.onResume();
connectFitness();
}
private void connectFitness() {
if (mClient == null){
mClient = new GoogleApiClient.Builder(this)
.addApi(Fitness.SENSORS_API)
.addScope(new Scope(Scopes.FITNESS_LOCATION_READ)) // GET STEP VALUES
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(Bundle bundle) {
Log.e(TAG, "Connected!!!");
// Now you can make calls to the Fitness APIs.
findFitnessDataSources();
}
#Override
public void onConnectionSuspended(int i) {
// If your connection to the sensor gets lost at some point,
// you'll be able to determine the reason and react to it here.
if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) {
Log.i(TAG, "Connection lost. Cause: Network Lost.");
} else if (i
== GoogleApiClient.ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) {
Log.i(TAG,
"Connection lost. Reason: Service Disconnected");
}
}
}
)
.enableAutoManage(this, 0, new GoogleApiClient.OnConnectionFailedListener() {
#Override
public void onConnectionFailed(ConnectionResult result) {
Log.e(TAG, "!_##ERROR :: Google Play services connection failed. Cause: " + result.toString());
}
})
.build();
}
}
private void findFitnessDataSources() {
Fitness.SensorsApi.findDataSources(
mClient,
new DataSourcesRequest.Builder()
.setDataTypes(DataType.TYPE_STEP_COUNT_DELTA)
.setDataSourceTypes(DataSource.TYPE_DERIVED)
.build())
.setResultCallback(new ResultCallback<DataSourcesResult>() {
#Override
public void onResult(DataSourcesResult dataSourcesResult) {
Log.e(TAG, "Result: " + dataSourcesResult.getStatus().toString());
for (DataSource dataSource : dataSourcesResult.getDataSources()) {
Log.e(TAG, "Data source found: " + dataSource.toString());
Log.e(TAG, "Data Source type: " + dataSource.getDataType().getName());
//Let's register a listener to receive Activity data!
if (dataSource.getDataType().equals(DataType.TYPE_STEP_COUNT_DELTA) && mListener == null) {
Log.i(TAG, "Data source for TYPE_STEP_COUNT_DELTA found! Registering.");
registerFitnessDataListener(dataSource, DataType.TYPE_STEP_COUNT_DELTA);
}
}
}
});
}
private void registerFitnessDataListener(final DataSource dataSource, DataType dataType) {
// [START register_data_listener]
mListener = new OnDataPointListener() {
#Override
public void onDataPoint(DataPoint dataPoint) {
for (Field field : dataPoint.getDataType().getFields()) {
Value val = dataPoint.getValue(field);
Log.e(TAG, "Detected DataPoint field: " + field.getName());
Log.e(TAG, "Detected DataPoint value: " + val);
}
}
};
Fitness.SensorsApi.add(
mClient,
new SensorRequest.Builder()
.setDataSource(dataSource) // Optional but recommended for custom data sets.
.setDataType(dataType) // Can't be omitted.
.setSamplingRate(1, TimeUnit.SECONDS)
.build(),
mListener).setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
if (status.isSuccess()) {
Log.i(TAG, "Listener registered!");
} else {
Log.i(TAG, "Listener not registered.");
}
}
});
}
}
NOTE : :: Sometimes in some device it doestn't detect Step values so whenever you are developing and workling with this code Always Uninstall app and then re-install app. then this works fine.
**Don't forget to add this permission**
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
You can try the StepSensor library from OrangeGangster's.
It contains a custom Service allowing to collect data from the Sensor.TYPE_STEP_COUNTER introduced with Android 4.4 (available only for devices that supports this hardware feature).
This code works for me !
Building the client :
mClient = new GoogleApiClient.Builder(this)
.addApi(Fitness.SENSORS_API)
.addScope(new Scope(Scopes.FITNESS_BODY_READ_WRITE))
.addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE))
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
Invoking Sensors API :
private void invokeSensorsAPI() {
Fitness.SensorsApi.add(
mClient,
new SensorRequest.Builder()
.setDataType(DataType.TYPE_STEP_COUNT_DELTA)
.setSamplingRate(1, TimeUnit.SECONDS)
.build(),
this)
.setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
if (status.isSuccess()) {
Log.i(TAG, "Sensor Listener registered!");
} else {
Log.i(TAG, "Sensor Listener not registered.");
}
}
});
}
Recieving data :
#Override
public void onDataPoint(DataPoint dataPoint) {
for (Field field : dataPoint.getDataType().getFields()) {
Value val = dataPoint.getValue(field);
Log.i(TAG, "Detected DataPoint field: " + field.getName());
Log.i(TAG, "Detected DataPoint value: " + val);
final int value = val.asInt();
if (field.getName().compareToIgnoreCase("steps") == 0) {
runOnUiThread(new Runnable() {
#Override
public void run() {
tv.setText("Value" + value)
}
});
}
}
}
I hope it helps
I think you are making a mistake here
if (resultCode == RESULT_OK) {
// If the user authenticated, try to connect again
mClient.connect()
}
instead it should be
if (resultCode != RESULT_OK) {
// If the user is not authenticated, try to connect again/ resultcode = RESULT_CANCEL
mClient.connect()
} else {
onConnected(null);
}
By your code invokeFitnessApis would never be called because you are reconnecting with googleapiclient after successfull connection.
I am doing a module where i need to make user login through google plus.
I can get name and email of user but struck with getting list of people in his/her circle.
Does any one know how to get list of people in the person circles.??
Here is my code .Some what lengthy..
public class SignIn extends Activity implements ResultCallback<People.LoadPeopleResult>,ConnectionCallbacks, OnConnectionFailedListener ,OnClickListener
{
//declaration of variables
static final String EMAIL_PATTERN =
"^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*#"
+ "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.signin_layout);
//googleapiclient initialization..
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Plus.API, null)
.addScope(Plus.SCOPE_PLUS_LOGIN)
.addScope(Plus.SCOPE_PLUS_PROFILE)
.build();
initializecontrols();
cancel.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
finish();
}
});
login.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0)
{
String em=email.getText().toString();
String ps=pswd.getText().toString();
if(em.length()==0||ps.length()==0)
{
Toast.makeText(getApplicationContext(), "Enter all the above", Toast.LENGTH_SHORT).show();
}
else
{
if(validate(em))
{
Toast.makeText(getApplicationContext(), "Login Sucess", Toast.LENGTH_SHORT).show();
}
else
{
Toast.makeText(getApplicationContext(), "Wrong email format", Toast.LENGTH_SHORT).show();
}
}
}
});
fb.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0)
{
Toast.makeText(getApplicationContext(), "yet to do..", Toast.LENGTH_SHORT).show();
}
});
gp.setOnClickListener(this);
}
public boolean validate(final String hex)
{
matcher = pattern.matcher(hex);
return matcher.matches();
}
public void onConnectionFailed(ConnectionResult result) {
if (!mIntentInProgress) {
// Store the ConnectionResult so that we can use it later when the user clicks
// 'sign-in'.
mConnectionResult = result;
if (mSignInClicked) {
// The user has already clicked 'sign-in' so we attempt to resolve all
// errors until the user is signed in, or they cancel.
resolveSignInError();
}
}
}
#Override
public void onClick(View v)
{
if(v.getId()==R.id.gplogin && !mGoogleApiClient.isConnecting())
{
mSignInClicked = true;
mGoogleApiClient.connect();
}
}
protected void onStart() {
super.onStart();
// mGoogleApiClient.connect();
}
protected void onStop() {
super.onStop();
if (mGoogleApiClient.isConnected()) {
mGoogleApiClient.disconnect();
}
}
#Override
public void onConnected(Bundle arg0) {
mSignInClicked = false;
Toast.makeText(this, "User is connected!", Toast.LENGTH_LONG).show();
getProfileInformation();
Plus.PeopleApi.loadVisible(mGoogleApiClient, null)
.setResultCallback(this);
mGoogleApiClient.disconnect();
}
#Override
public void onConnectionSuspended(int arg0) {
mGoogleApiClient.connect();
}
#Override
protected void onActivityResult(int requestCode, int responseCode,
Intent intent) {
if (requestCode == RC_SIGN_IN) {
if (responseCode != RESULT_OK) {
mSignInClicked = false;
}
mIntentInProgress = false;
if (!mGoogleApiClient.isConnecting()) {
mGoogleApiClient.connect();
}
}
}
private void signInWithGplus() {
if (!mGoogleApiClient.isConnecting()) {
mSignInClicked = true;
resolveSignInError();
}
}
private void resolveSignInError() {
if (mConnectionResult.hasResolution()) {
try {
mIntentInProgress = true;
mConnectionResult.startResolutionForResult(this, RC_SIGN_IN);
} catch (SendIntentException e) {
mIntentInProgress = false;
mGoogleApiClient.connect();
}
}
}
private void getProfileInformation() {
try {
if (Plus.PeopleApi.getCurrentPerson(mGoogleApiClient) != null)
{
Person currentPerson = Plus.PeopleApi.getCurrentPerson(mGoogleApiClient);
String personName = currentPerson.getDisplayName();
String personPhotoUrl = currentPerson.getImage().getUrl();
String personGooglePlusProfile = currentPerson.getUrl();
String nickname = currentPerson.getNickname();
String email = Plus.AccountApi.getAccountName(mGoogleApiClient);
Log.e("Output :", "Name: " + personName + ", plusProfile: "
+ personGooglePlusProfile + ", email: " + email
+ ", Image: " + personPhotoUrl);
Toast.makeText(getApplicationContext(),"Name : "+personName+"\n Email :"+email, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getApplicationContext(),
"Person information is null", Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onResult(LoadPeopleResult peopleData)
{
Log.e("function","onResult");
if (peopleData.getStatus().getStatusCode() == CommonStatusCodes.SUCCESS) {
PersonBuffer personBuffer = peopleData.getPersonBuffer();
try {
int count = personBuffer.getCount();
for (int i = 0; i < count; i++) {
Log.d("Person", "Display name: " + personBuffer.get(i).getDisplayName());
}
} finally {
personBuffer.close();
}
} else {
Log.e("person", "Error requesting visible circles: " + peopleData.getStatus());
}
}
}
According to the tutorial onResult callback method will be called when
Plus.PeopleApi.loadVisible(mGoogleApiClient, null)
.setResultCallback(this);
this particular code is executed in OnConnected method
You are disconnecting immediately after you make the request.
Google Play services uses a client-service architecture with asynchronous callbacks. Disconnecting actually closes the service connection, preventing the response from being delivered back to you. This is akin to calling up your buddy, saying "hey, how's it going" and then immediately hanging up your phone.
As an aside, there are a couple other non-standard things going on here. In general you should call connect in onStart() (you have this commented out). If the user has already given your app permission then you can immediately start getting data and doing what you want with it. If the user hasn't granted permission or there's another issue (like it's a Kindle Fire, where Google Play services isn't available), then you'll get onConnectionFailed. You can then hold on to that Status and call the resolution when the user clicks on the button. This will result in a snappier UI and make it easier for you to match your connect() and disconnect() calls.
In general disconnect() should be called in onStop(). This way you won't disconnect until your activity is being torn down anyway.