Google Fit Historical API does not give a response - google-fit

I am trying to retrieve Heart Bit data for a certain time range. I am breaking up a time range into several smaller time ranges for example each one day long. So, if I requested data for the last 5 days it would provide 5-time ranges.
All that data about time ranges is stored in the GoogleFitReadRequestData List.
public class GoogleFitReadRequestData {
private DataType dataType;
private long startTime;
private long endTime;
public GoogleFitReadRequestData(DataType dataType, long startTime, long endTime) {
this.dataType = dataType;
this.startTime = startTime;
this.endTime = endTime;
}
}
I then create requests in a loop for those data ranges. This is a simplified example of what my application does.
public void stackOverflowRequestExample() {
initContext();
HistoricalClientHelper clientHelper = new HistoricalClientHelper();
List<GoogleFitReadRequestData> readRequestData = new ArrayList<>();
readRequestData.addAll(clientHelper.GetTimeBuckets(DataType.TYPE_HEART_RATE_BPM));
for (GoogleFitReadRequestData requestData : readRequestData) {
DataReadRequest.Builder readRequestBuilder = new DataReadRequest.Builder();
readRequestBuilder.read(requestData.getDataType());
readRequestBuilder.enableServerQueries();
readRequestBuilder.setTimeRange(requestData.getStartTime(), requestData.getEndTime(), TimeUnit.MILLISECONDS);
GoogleSignInAccount googleSignInAccount =
GoogleSignIn.getAccountForExtension(this.appContext, getFitnessOptions(requestData.getDataType()));
Fitness.getHistoryClient(this.appContext, googleSignInAccount)
.readData(readRequestBuilder.build())
.addOnSuccessListener(response -> {
clientHelper.ProcessDataSetList(response.getDataSets());
})
.addOnCanceledListener(() -> Log.w("Google Fit: ", "Reading request from Google Fit was cancelled"))
.addOnFailureListener(e -> Log.w("Google Fit: ", "There was an error reading data from Google Fit", e));
}
}
public static GoogleSignInOptionsExtension getFitnessOptions(DataType type) {
FitnessOptions.Builder optionsBuilder = FitnessOptions.builder();
optionsBuilder.addDataType(type, FitnessOptions.ACCESS_READ);
return optionsBuilder.build();
}
The problem is that some of them return a response (success), while others do not return any response. Not even canceled or failed. Does anyone know what I am doing wrong?
PS: I noticed this issue only with the TYPE_HEART_RATE_BPM data type. Others, like TYPE_STEP_COUNT_DELTA work just fine.

Ok. Turns out the problem was with how the permissions for reading were set up in the Google Cloud Console. In the OAuth consent screen, scopes had to be manually found in Google Fitbit documentation and added as complete links and only then they appeared in the sensitive scopes section.

Related

Web Drop-in integration "advanced use case" redirect result

I'm trying to understand if it's possible to retrieve the redirectResult when the user is redirected to our redirectUrl to invoke the /payment/details API endpoint to speed up delivering virtual goods instead of waiting for the web hook (I'm aware it will not work for "async" payment methods).
Looking at https://docs.adyen.com/online-payments/web-drop-in/advanced-use-cases/redirect-result it should be possible, however, the returnUrl in CreateCheckoutSessionResponse returned from com.adyen.service.Checkout#sessions (adyen-java-api-library 17.2.0 - checkout api version v68) does not contain the aforementioned redirectResult param so the configuration we pass into the drop-in template is missing this data and does not seem to be available in the onPaymentCompleted callback either (only resultCode and sessionData).
#Override
public RedirectResponse handleRedirectToPartner(PaymentContext paymentContext) throws PartnerIntegrationException {
final Payment payment = paymentContext.getPayment();
final Amount amount = new Amount();
amount.setCurrency(payment.getCurrency().toUpperCase());
amount.setValue((long) payment.getPriceInCents());
final CreateCheckoutSessionRequest checkoutSessionRequest = new CreateCheckoutSessionRequest();
...
checkoutSessionRequest.setChannel(CreateCheckoutSessionRequest.ChannelEnum.WEB);
checkoutSessionRequest.setReturnUrl(getReturnUrl());
try {
CreateCheckoutSessionResponse checkoutSessionResponse = checkout().sessions(checkoutSessionRequest);
JSONObject params = new JSONObject();
params.put("environment", testMode ? "test" : "live");
params.put("clientKey", adyenClientKey);
JSONObject session = new JSONObject();
session.put("id", checkoutSessionResponse.getId());
session.put("sessionData", checkoutSessionResponse.getSessionData());
params.put("session", session);
params.put("urlKo", getFailureUrl());
params.put("urlOk", checkoutSessionResponse.getReturnUrl());
params.put("urlPending", getUrlPending(checkoutSessionResponse.getReturnUrl()));
return new RedirectResponse(RedirectResponse.Type.REDIRECT_CUSTOM_HTML_ADYEN, null, params);
} catch (ApiException | IOException e) {
throw new PartnerIntegrationException("Failed creating Adyen session", e);
}
}
protected Checkout checkout() {
return new Checkout(new Client(adyenApiKey, testMode ? Environment.TEST : Environment.LIVE,
testMode ? null : liveEndpointUrlPrefix));
}
(async () => {
let configuration = ${partnerJsonParameters?string};
configuration.onPaymentCompleted = function(result, component) {
console.info(result);
if (result.sessionData) {
console.info(result.sessionData);
}
if (result.resultCode) {
console.info(result.resultCode);
}
handleServerResponse(result, configuration);
};
configuration.onError = function(error, component) {
console.error(error, component);
handleServerResponse(result, configuration);
};
let checkout = await AdyenCheckout(configuration);
checkout.create('dropin').mount('#dropin-container');
})();
The sessions request does not perform the payment, but only initiates the payment session with all required parameters and configuration.
The Web drop-in takes care of 'talking' to the Adyen backend and eventually, the payment outcome can be obtained in the frontend using the onPaymentCompleted handler.
onPaymentCompleted: (result, component) => {
console.info("onPaymentCompleted: " + result.resultCode);
...
}
See Use the result code
On the server-side it is possible to get the payment result with a /payments/details call in addition to /sessions if needed.
// get redirectResult appended to the returnUrl
String redirectResult = request.getParameter("redirectResult");
var paymentDetails = new PaymentsDetailsRequest();
paymentDetails.setDetails(Collections.singletonMap("redirectResult", redirectResult));
// use paymentDetails() method
var paymentsDetailsResponse = checkout.paymentsDetails(paymentDetails);
String resultCode = paymentsDetailsResponse.getResultCode();
Note that a synchronous result is not always available, hence relying on the webhook is best.
I'm the (non-developer) colleague testing with PAL enable and sadly the delay is way too long with them as well. It is crucial for us to be able to get trustworthy authorisation and deliver the goods for the customers in (tens of) seconds, not minutes.
Is there any way to achieve fast and trustworthy credit card transactions with your web drop-in without notifications?
This is possible with your soon-to-be-obsoleted HPP integration and I find it unbelievable that you could have impaired, worsen the integration and user experience. So, there must be some kind of misunderstanding and communication breakdown somewhere (I hope). :)
Sorry, could not just comment as I don't have enough reputation...

Google Drive API - Granting Access to Files in a Folder to Service Account

I am using a service account to connect to the Google Drive of a G Suite. I understand that with the available access (scope: https://www.googleapis.com/auth/drive.file) I can only see the files that the service account has created and using the /auth/drive scope (which gives full visibility) requires special Google approval.
I need to be able to expand the service account's visibility to include files in at least 1 folder the user has not created. I can't get this through normal folder sharing as best I can tell.
Does anyone know how to do this?
Edit to include code (written in Apex, which is similar to Java). Still pretty rough, I haven't cleaned it up yet but:
private static String buildAuthBody(Google_Drive_Integration__mdt mdt) {
//Builds and encodes the JWT header
String bodyHeader = '{"alg":"RS256","typ":"JWT"}';
String encodedHeader = encode(bodyHeader);
//Builds and encodes the JWT Claim Set. See googleAuth body
googleAuth ga = new googleAuth(mdt);
String claimSetString = JSON.serialize(ga);
String encodedClaimSet = encode(claimSetString);
//Builds out necessary pieces for Crypt.sign(algorithmName, input, privateKey). Input = body
String signatureBody = encodedHeader + '.' + encodedClaimSet;
signatureBody = signatureBody.replaceAll('=','');
String encodedSignatureBody = EncodingUtil.urlEncode(signatureBody,'UTF-8');
Blob signatureBodyBlob = Blob.valueOf(encodedSignatureBody);
Blob key = EncodingUtil.base64Decode(mdt.Service_Account_Private_Key__c); //Must be decoded to pass into method w/o error
//Builds the signature
Blob signatureBlob = Crypto.sign('RSA-SHA256', signatureBodyBlob , key);
String encodedSignature = encodeBlob(signatureBlob);
//Sets grant type
String grantType = EncodingUtil.urlEncode('urn:ietf:params:oauth:grant-type:jwt-bearer', 'UTF-8');
//Sets body and debugs to rebuild body
System.debug('grant type: grant_type=' + grantType);
System.debug('&assertion=');
System.debug('encoded header: '+encodedHeader);
System.debug('encoded claim set: '+encodedClaimSet);
System.debug('encoded signature: '+encodedSignature);
//Build and return the body
String body = 'grant_type=' + grantType;
body += '&assertion=';
body += signatureBody;
body += '.' + encodedSignature;
return body;
}
class googleAuth {
public String iss; //'test-google-drive#sapient-flare-252622.iam.gserviceaccount.com';
public String scope = 'https://www.googleapis.com/auth/drive';
public String aud = 'https://www.googleapis.com/oauth2/v4/token';
public Long exp;
public Long iat;
googleAuth(Google_Drive_Integration__mdt mdt) {
DateTime dt = DateTime.now();
iat = dt.getTime() / 1000;
exp = iat + 3600;
iss = mdt.Service_Account_User_Email__c;
}
}
private static String encode(String str) {
Blob b = Blob.valueOf(str);
String ret = EncodingUtil.base64Encode(b);
ret = EncodingUtil.urlEncode(ret, 'UTF-8');
return ret;
}
private static String encodeBlob(Blob b) {
String ret = EncodingUtil.base64Encode(b);
ret = EncodingUtil.urlEncode(ret, 'UTF-8');
return ret;
}
I think you are misunderstanding things about service accounts and about scopes.
Scopes define the amount of access a user has granted to your application. In this case the user is the service account. Using scope: https://www.googleapis.com/auth/drive.file with a service account doesn't make much sense really as you the developer own the service account and there by own its drive account. so really just give it full access using scope: https://www.googleapis.com/auth/drive there is no real reason for you to limit it. If you had a normal application using Oauth2 you would only request the access you need to the users drive account.
I need to be able to expand the service account's visibility to include files in at least 1 folder the user has not created. I can't get this through normal folder sharing as best I can tell.
I am not sure i understand this. Assuming that when you say "user" you mean the service account. I am going to assume this folder is part of your gsuite system. In which case you simply need to have your Gsuite admin set up domain wide delegation to the service account and it will have access. This is a way of granting the service account permissions to access data on your gsuite account kind of like adding a new user to the system.

Manipulating a mocked Calendar object to return specific days

I'm using a Calendar object to determine whether or not to increase the workload of a system based on current day/hour values. Given that this object uses static methods, I'm using PowerMock to mock the static methods with the following annotations:
#RunWith(PowerMockRunner.class)
#PrepareForTest({ Calendar.class })
The code under test is pretty simple (though my logic needs work, I know):
public void determineDefaultMaximumScans() throws ParseException{
parseTime();
Calendar cal = Calendar.getInstance();
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
System.out.println(cal.get(Calendar.DAY_OF_WEEK));
if(dayOfWeek == (Calendar.SATURDAY) || dayOfWeek == (Calendar.SUNDAY)){
setDefaultMax(calculateNewDefaultMax(getDefaultMax()));
System.out.println("defaultMax increased by 20%");
} else {
if(currentTime.after(afterHoursBegin) && currentTime.before(afterHoursEnd)){
System.out.println("Not afterhours. Maintaining current maximum.");
setDefaultMax(defaultMax);
System.out.println("Current Maximum number of scans: " + getDefaultMax());
}
}
}
My test case reads as follows:
#SuppressWarnings("static-access")
#Test
public void testDetermineMaximumScans() throws ParseException{
PowerMock.mockStatic(Calendar.class);
String beginningTime = "18:00";
String endingTime = "05:00";
mockAfterHoursBegin = parser.parse(beginningTime);
mockAfterHoursEnd = parser.parse(endingTime);
mockCurrentTime = parser.parse(parser.format(new Date()));
EasyMock.expect(Calendar.getInstance()).andReturn(mockCalendar);
EasyMock.expect(mockCalendar.get(Calendar.DAY_OF_WEEK)).andReturn(6);
EasyMock.replay(mocks);
offHourMaximumCalculator.determineDefaultMaximumScans();
EasyMock.verify(mocks);
}
As of now, all of my attempts to return a specific value result in the following assertion error. Now I vaguely understand why it's returning the default but I do not see why I can't force the value or how to get around this expectation. Mocks in general are still a frustrating mystery to me. What am I missing?
java.lang.AssertionError:
Expectation failure on verify:
Calendar.get(7): expected: 1, actual: 0
Mocks are fairly simple. But wanting to mock static methods is a big running after complexity. I generally do not recommend to mock something like a Calendar. If you do weird and complex thing with it, just encapsulate in something you can test and mock easily.
And in fact, we pretty much never use Calendar.getInstance(). It returns something according to the locale. But it's rare that you don't want a specific calendar i.e. GregorianCalendar. So just do new GregorianCalendar.
But anyway, add a protected method doing
protected Calendar newCalendar() {
return Calendar.getInstance(); // or new GregorianCalendar()
}
will take 2 minutes and then a simple partial mock will do the trick.
Finally, I also don't recommend to use Calendar. You have a much nicer API in java.util.date in Java 8.
All this said, here is how you should do it. Calendar is a system class, so you need to follow a real specific path which is explained here.
#RunWith(PowerMockRunner.class)
#PrepareForTest(Calendar.class)
public class MyTest {
#Test
public void testDetermineMaximumScans() throws ParseException {
PowerMock.mockStatic(Calendar.class);
Calendar calendar = mock(Calendar.class);
EasyMock.expect(Calendar.getInstance()).andReturn(calendar);
EasyMock.expect(calendar.get(Calendar.DAY_OF_WEEK)).andReturn(6);
// really important to replayAll to replay the static expectation
PowerMock.replayAll(calendar);
assertThat(Calendar.getInstance().get(Calendar.DAY_OF_WEEK)).isEqualTo(6);
// and verifyAll is you want to verify that the static call actually happened
PowerMock.verifyAll();
}
}

Real Time Google Fit Sessions

I'm working on a fitness activity tracker app, and I have a couple questions regarding the Google Fit Sessions API.
From the Google Fit Session guide, if we want to create a session with real time data, we need to specify start-time using the method Session.Builder.setStartTime(long,TimeUnit), and then we call the method startSession.
Question 1: Can we set the end time for that session if we're using the real time data? If we've already defined the endTime, do we still need to call the stopSession method?
In the documentation, a sample session is created with a specified start-time and end-time, and then sample data are added to that session.
Question 2: How would you create a SessionInsertRequest on sessions with real time data? How can we get a real time recorded DataSet from the DataSource to use in the SessionInsertRequest.Builder.addDataSet(DataSet) method?
Question 3: I already have this method to find my DataSources; do I still need to create a new DataSource object to get a DataSet object (as answered in this thread: DataSet Object for the Google Fit API)?
The method:
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.i(TAG, "Result: " + dataSourcesResult.getStatus().toString());
for (DataSource dataSource : dataSourcesResult.getDataSources()) {
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_STEP_COUNT_DELTA)
&& mListener == null) {
Log.i(TAG, "Data source for LOCATION_SAMPLE found! Registering.");
registerFitnessDataListener(dataSource,
DataType.TYPE_STEP_COUNT_DELTA);
}
}
}
});
//just temporarily return datasource for testing purpose
}
Question 4: How do I modify my method to return the dataSource(s) to use in the Session Builder?
Thank you.

The time period format should be in format dd,MMM,yyyy,hh,mm

Hi I am sending JSON data and the JSON will have to validated at the backend. The time period should be in this format--- dd,MMM,yyyy,hh,mm.
This is my JSON
{
"equipmentID":"234",
"modality":"healthcaare",
"facilityID":"manipal",
"countryCode":"abc",
"isoCode":"1234",
"problemType":"234",
"problemArea":"priyanka",
"equipmentStatus":"sdsd",
"name":"taneja",
"phoneNumber":"13333344",
"extension":"12123",
"description":"x ray machine error",
"shortDescription":"2",
"timePeriod":"03-12-2011 04-37",
"serviceCode":"sdfdf",
"locale":"werfd",
"requestingApp":"icenter",
"examNumber":"sdd",
"seriesNumber":"dfdf",
"imageNumber":"dfdfd"
}
This is the validation class
public class RequestValidator implements Validator {
#Override
public ValidationResult validate(String objectName, RequestData rqdata) {
// TODO Auto-generated method stub
ValidationResult result = new ValidationResult();
if (rqdata == null) {
result.addError("error.invalidObjectGraph", "Object graph not initialized correctly");
return result;
}
Validation.rule("EquipmentId", rqdata.getEquipmentID()).required().run(result);
Validation.rule("Modality", rqdata.getModality()).required().run(result);
Validation.rule("FacilityID", rqdata.getFacilityID()).required().run(result);
Validation.rule("CountryCode", rqdata.getCountryCode()).required().maxLength(3).matches("^[a-zA-Z]*$")
.run(result);
Validation.rule("ProblemType", rqdata.getProblemType()).required().run(result);
Validation.rule("Name", rqdata.getName()).required().maxLength(20).run(result);
Validation.rule("PhoneNumber", rqdata.getPhoneNumber()).required().maxLength(25).matches("[0-9]+").run(result);
Validation.rule("Extension", rqdata.getExtension()).required().maxLength(10).matches("[0-9]+").run(result);
Validation.rule("Description", rqdata.getDescription()).required().maxLength(300).run(result);
Validation.rule("ShortDescription", rqdata.getShortDescription()).required().maxLength(80).run(result);
Validation.rule("TimePeriod", rqdata.getTimePeriod()).required().matches("dd-MMM-yyyy hh-mm").run(result);
Validation.rule("Locale", rqdata.getLocale()).required().run(result);
System.out.println("value of requesting app is:" + rqdata.getRequestingApp());
Validation.rule("RequestingApp", rqdata.getRequestingApp()).required().matches("icenter").run(result);
System.out.println(result.getErrorDetails());
return result;
}
}
But i am getting an error that the date is not in appropriate format. Please help me out. Thanks
Matches would require a regular expression inside it.
Try this
^(([0-9])|([0-2][0-9])|([3][0-1]))\-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\-\d{4}$
This would fit in to check against the MMM for the month
Time one of the parameters which have different ways. Please consider epoch format of data exchange for dates. This will be a better choice.
[EDIT]
Change this
Validation.rule("TimePeriod", rqdata.getTimePeriod()).required().matches("dd-MMM-yyyy hh-mm").run(result);
To
String regEx = "^(([0-9])|([0-2][0-9])|([3][0-1]))\-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\-\d{4}$";
Validation.rule("TimePeriod", rqdata.getTimePeriod()).required().matches(regEx).run(result);