I am trying to learn how to implement safetynet with the reference of a fellow member in stackover. SafetyNet: package name always return null
The first section of the code is a complete code of SafetyNetVerifier
package com.example.stack;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Base64;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.safetynet.SafetyNet;
import com.google.android.gms.safetynet.SafetyNetApi;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Random;
public class SafetyNetVerifier implements GoogleApiClient.OnConnectionFailedListener {
private final Random mRandom = new SecureRandom();
private String mResult;
private GoogleApiClient mGoogleApiClient;
private FragmentActivity activity;
public SafetyNetVerifier(FragmentActivity activity) {
this.activity = activity;
buildGoogleApiClient();
sendSafetyNetRequest();
}
private byte[] getRequestNonce(String data) {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
byte[] bytes = new byte[24];
mRandom.nextBytes(bytes);
try {
byteStream.write(bytes);
byteStream.write(data.getBytes());
} catch (IOException e) {
return null;
}
return byteStream.toByteArray();
}
protected synchronized void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(activity)
.addApi(SafetyNet.API)
.enableAutoManage(activity, this)
.build();
}
private void sendSafetyNetRequest() {
Log.e("hqthao", "Sending SafetyNet API request.");
String nonceData = "Safety Net Sample: " + System.currentTimeMillis();
byte[] nonce = getRequestNonce(nonceData);
SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce)
.setResultCallback(new ResultCallback<SafetyNetApi.AttestationResult>() {
#Override
public void onResult(SafetyNetApi.AttestationResult result) {
Status status = result.getStatus();
if (status.isSuccess()) {
mResult = result.getJwsResult();
Log.e("hqthao", "Success! SafetyNet result:\n" + mResult + "\n");
SafetyNetResponse response = parseJsonWebSignature(mResult);
Log.e("hqthao", response.toString());
}
}
});
}
#Nullable
private SafetyNetResponse parseJsonWebSignature(String jwsResult) {
if (jwsResult == null) {
return null;
}
//the JWT (JSON WEB TOKEN) is just a 3 base64 encoded parts concatenated by a . character
final String[] jwtParts = jwsResult.split("\\.");
if (jwtParts.length == 3) {
//we're only really interested in the body/payload
String decodedPayload = new String(Base64.decode(jwtParts[1], Base64.DEFAULT));
return SafetyNetResponse.parse(decodedPayload);
} else {
return null;
}
}
#Override
public void onConnectionFailed(#NonNull ConnectionResult connectionResult) {
Log.e("hqthao", "Error connecting to Google Play Services." + connectionResult.getErrorMessage());
}
}
When i tried to debug, it will always stop at
SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce)
May I know why is it so? I look at the Safetynet example provide by google and they will usually pair the API Key with the nonce. How can i change mGoogleApiClient to a API KEY?
private void sendSafetyNetRequest() {
Log.e("hqthao", "Sending SafetyNet API request.");
String nonceData = "Safety Net Sample: " + System.currentTimeMillis();
byte[] nonce = getRequestNonce(nonceData);
SafetyNet.SafetyNetApi.attest(mGoogleApiClient, nonce)
.setResultCallback(new ResultCallback<SafetyNetApi.AttestationResult>() {
#Override
public void onResult(SafetyNetApi.AttestationResult result) {
Status status = result.getStatus();
if (status.isSuccess()) {
mResult = result.getJwsResult();
Log.e("hqthao", "Success! SafetyNet result:\n" + mResult + "\n");
SafetyNetResponse response = parseJsonWebSignature(mResult);
Log.e("hqthao", response.toString());
}
}
});
}
You should use the SafetyNetClient interface to test. The sample code is as follows:
SafetyNetClient client = SafetyNet.getClient(getActivity());
Task<SafetyNetApi.AttestationResponse> task = client.attest(nonce, BuildConfig.API_KEY);
You can refer to the latest code on GitHub:
https://github.com/googlesamples/android-play-safetynet/blob/master/client/java/SafetyNetSample/Application/src/main/java/com/example/android/safetynetsample/SafetyNetSampleFragment.java
From my understanding, you will need an instance of SafetyNetClient to execute the attest method and not SafetyNetApi.attest, which is deprecated and now disabled
In order to get the Client, use:
SafetyNet.getClient(this).attest()
See here and the example here for more details
Related
I'm doing Android Things project.
I want to publish a String message to Google Cloud IoT Core, but there are errors shown.
I'm using Raspberry Pi 3 with Android Things OS and and programming it using Android Studio.
Errors screenshot:
This is the whole code:
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cacaosd.com.sample1">
<!-- PAHO Permissions -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- PAHO Permissions -->
<application>
<uses-library android:name="com.google.android.things"/>
<!-- Mqtt Service -->
<service android:name="org.eclipse.paho.android.service.MqttService" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.IOT_LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
</manifest>
IotCoreCommunicator Class
package cacaosd.com.sample1;
import android.content.Context;
import android.util.Log;
import java.util.concurrent.TimeUnit;
import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
public class IotCoreCommunicator {
private static final String SERVER_URI = "ssl://mqtt.googleapis.com:8883";
public static class Builder {
private Context context;
private String projectId;
private String cloudRegion;
private String registryId;
private String deviceId;
private int privateKeyRawFileId;
public Builder withContext(Context context) {
this.context = context;
return this;
}
public Builder withProjectId(String projectId) {
this.projectId = projectId;
return this;
}
public Builder withCloudRegion(String cloudRegion) {
this.cloudRegion = cloudRegion;
return this;
}
public Builder withRegistryId(String registryId) {
this.registryId = registryId;
return this;
}
public Builder withDeviceId(String deviceId) {
this.deviceId = deviceId;
return this;
}
public Builder withPrivateKeyRawFileId(int privateKeyRawFileId) {
this.privateKeyRawFileId = privateKeyRawFileId;
return this;
}
public IotCoreCommunicator build() {
if (context == null) {
throw new IllegalStateException("context must not be null");
}
if (projectId == null) {
throw new IllegalStateException("projectId must not be null");
}
if (cloudRegion == null) {
throw new IllegalStateException("cloudRegion must not be null");
}
if (registryId == null) {
throw new IllegalStateException("registryId must not be null");
}
if (deviceId == null) {
throw new IllegalStateException("deviceId must not be null");
}
String clientId = "projects/" + projectId + "/locations/" + cloudRegion + "/registries/" + registryId + "/devices/" + deviceId;
if (privateKeyRawFileId == 0) {
throw new IllegalStateException("privateKeyRawFileId must not be 0");
}
MqttAndroidClient client = new MqttAndroidClient(context, SERVER_URI, clientId);
IotCorePasswordGenerator passwordGenerator = new IotCorePasswordGenerator(projectId, context.getResources(), privateKeyRawFileId);
return new IotCoreCommunicator(client, deviceId, passwordGenerator);
}
}
private final MqttAndroidClient client;
private final String deviceId;
private final IotCorePasswordGenerator passwordGenerator;
IotCoreCommunicator(MqttAndroidClient client, String deviceId, IotCorePasswordGenerator passwordGenerator) {
this.client = client;
this.deviceId = deviceId;
this.passwordGenerator = passwordGenerator;
}
public void connect() {
monitorConnection();
clientConnect();
subscribeToConfigChanges();
}
private void monitorConnection() {
client.setCallback(new MqttCallback() {
#Override
public void connectionLost(Throwable cause) {
Log.e("TUT", "connection lost", cause);
}
#Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
Log.d("TUT", "message arrived " + topic + " MSG " + message);
// You need to do something with messages when they arrive
}
#Override
public void deliveryComplete(IMqttDeliveryToken token) {
Log.d("TUT", "delivery complete " + token);
}
});
}
private void clientConnect() {
try {
MqttConnectOptions connectOptions = new MqttConnectOptions();
// Note that the the Google Cloud IoT Core only supports MQTT 3.1.1, and Paho requires that we explicitly set this.
// If you don't, the server will immediately close its connection to your device.
connectOptions.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);
// With Google Cloud IoT Core, the username field is ignored, however it must be set for the
// Paho client library to send the password field. The password field is used to transmit a JWT to authorize the device.
connectOptions.setUserName("unused-but-necessary");
connectOptions.setPassword(passwordGenerator.createJwtRsaPassword());
IMqttToken iMqttToken = client.connect(connectOptions);
iMqttToken.setActionCallback(new IMqttActionListener() {
#Override
public void onSuccess(IMqttToken asyncActionToken) {
Log.d("TUT", "success, connected");
}
#Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
Log.e("TUT", "failure, not connected", exception);
}
});
iMqttToken.waitForCompletion(TimeUnit.SECONDS.toMillis(30));
Log.d("TUT", "IoT Core connection established.");
} catch (MqttException e) {
throw new IllegalStateException(e);
}
}
/**
* Configuration is managed and sent from the IoT Core Platform
*/
private void subscribeToConfigChanges() {
try {
client.subscribe("/devices/" + deviceId + "/config", 1);
} catch (MqttException e) {
throw new IllegalStateException(e);
}
}
public void publishMessage(String subtopic, String message) {
String topic = "/devices/" + deviceId + "/" + subtopic;
String payload = "{msg:\"" + message + "\"}";
MqttMessage mqttMessage = new MqttMessage(payload.getBytes());
mqttMessage.setQos(1);
try {
client.publish(topic, mqttMessage);
Log.d("TUT", "IoT Core message published. To topic: " + topic);
} catch (MqttException e) {
throw new IllegalStateException(e);
}
}
public void disconnect() {
try {
Log.d("TUT", "IoT Core connection disconnected.");
client.disconnect();
} catch (MqttException e) {
throw new IllegalStateException(e);
}
}
}
IotCorePasswordGenerator Class
package cacaosd.com.sample1;
import android.content.res.Resources;
import android.util.Base64;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
class IotCorePasswordGenerator {
private final String projectId;
private final Resources resources;
private final int privateKeyRawFileId;
IotCorePasswordGenerator(String projectId, Resources resources, int privateKeyRawFileId) {
this.projectId = projectId;
this.resources = resources;
this.privateKeyRawFileId = privateKeyRawFileId;
}
char[] createJwtRsaPassword() {
try {
byte[] privateKeyBytes = decodePrivateKey(resources, privateKeyRawFileId);
return createJwtRsaPassword(projectId, privateKeyBytes).toCharArray();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Algorithm not supported. (developer error)", e);
} catch (InvalidKeySpecException e) {
throw new IllegalStateException("Invalid Key spec. (developer error)", e);
} catch (IOException e) {
throw new IllegalStateException("Cannot read private key file.", e);
}
}
private static byte[] decodePrivateKey(Resources resources, int privateKeyRawFileId) throws IOException {
try(InputStream inStream = resources.openRawResource(privateKeyRawFileId)) {
return Base64.decode(inputToString(inStream), Base64.DEFAULT);
}
}
private static String inputToString(InputStream is) {
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
private static String createJwtRsaPassword(String projectId, byte[] privateKeyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
return createPassword(projectId, privateKeyBytes, "RSA", SignatureAlgorithm.RS256);
}
private static String createPassword(String projectId, byte[] privateKeyBytes, String algorithmName, SignatureAlgorithm signatureAlgorithm) throws NoSuchAlgorithmException, InvalidKeySpecException {
Instant now = Instant.now();
// Create a JWT to authenticate this device. The device will be disconnected after the token
// expires, and will have to reconnect with a new token. The audience field should always be set
// to the GCP project id.
JwtBuilder jwtBuilder =
Jwts.builder()
.setIssuedAt(Date.from(now))
.setExpiration(Date.from(now.plus(Duration.ofMinutes(20))))
.setAudience(projectId);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory kf = KeyFactory.getInstance(algorithmName);
return jwtBuilder.signWith(signatureAlgorithm, kf.generatePrivate(spec)).compact();
}
}
MainActivity Class:
package cacaosd.com.sample1;
import android.app.Activity;
import android.hardware.SensorEvent;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Handler;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import cacaosd.com.sample1.R;
import cacaosd.com.sample1.IotCoreCommunicator;
import com.google.android.things.pio.Gpio;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class MainActivity extends Activity {
private IotCoreCommunicator communicator;
private Handler handler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Setup the communication with your Google IoT Core details
communicator = new IotCoreCommunicator.Builder()
.withContext(this)
.withCloudRegion("us-central1") // ex: europe-west1
.withProjectId("my-first-project-198704") // ex: supercoolproject23236
.withRegistryId("vibration") // ex: my-devices
.withDeviceId("my-device") // ex: my-test-raspberry-pi
.withPrivateKeyRawFileId(R.raw.rsa_private)
.build();
HandlerThread thread = new HandlerThread("MyBackgroundThread");
thread.start();
handler = new Handler(thread.getLooper());
handler.post(connectOffTheMainThread); // Use whatever threading mechanism you want
}
private final Runnable connectOffTheMainThread = new Runnable() {
#Override
public void run() {
communicator.connect();
handler.post(sendMqttMessage);
}
};
private final Runnable sendMqttMessage = new Runnable() {
private int i;
/**
* We post 100 messages as an example, 1 a second
*/
#Override
public void run() {
if (i == 100) {
return;
}
// events is the default topic for MQTT communication
String subtopic = "events";
// Your message you want to send
String message = "Hello World " + i++;
communicator.publishMessage(subtopic, message);
handler.postDelayed(this, TimeUnit.SECONDS.toMillis(1));
}
};
#Override
protected void onDestroy() {
communicator.disconnect();
super.onDestroy();
}
}
Update:
I converted the private key from "pem" format to "pkcs8" format by following this documentation and this demo, then the error "Invalid key spec" is gone, but still there is a "FATAL EXCEPTION" and "java.lang.IllegalArgumentException: bad base-64" as shown in the image below:
[![enter image description here][4]][4]
It says that these are the related codes that caused the error (which shown in blue color in the previous image:
IotCorePasswordGenerator.java:47
return Base64.decode(inputToString(inStream), Base64.DEFAULT);
IotCorePasswordGenerator.java:34
byte[] privateKeyBytes = decodePrivateKey(resources, privateKeyRawFileId);
IotCoreCommunicator.java:135
connectOptions.setPassword(passwordGenerator.createJwtRsaPassword());
IotCoreCommunicator.java:101
clientConnect();
MainActivity.java:58
communicator.connect();
Update 2
I deleted the statement "-----BEGIN PRIVATE KEY-----" and the statement "------END PRIVATE KEY-----" and the error "bad base 64" is gone, Now there is another error which is "broken pipe" as shown in image below, when I reopen Android Studio and rebuild the project this error "broken pipe" removed, and when I run the project again it comes back again.
The error (first image)
Private key with start and end statements(second image)
Private key without start and end statements(third image)
From the error, it looks like you registered the device with the wrong type of SSL key. Verify that you created an SSL key that matches the format you specified in IoT Core. I.e. if you created an RSA key with the x509 certificate wrapper, be sure that your device is registered with that type and not just the RSA key.
Also be sure that the private key is actually on the device, and that it's not corrupted.
Edit: Problem may have been the roots.pem wasn't on device to handle the TLS handshake with IoT Core. We shall see... to get it, run: wget https://pki.google.com/roots.pem and put the roots.pem in the same directory as the private key on device.
I strongly recommend that you take a look at the Android Things connector for Cloud IoT core. This project makes it much easier to access Cloud IoT core from Android Things and handles various best practices such as token refresh.
I have the following code and basically I am wondering how I can use this location method distanceBetween() to find the objects in my .JSON file that are under 500km away. I tried to craft together an if statement and a distanceBetween usage but it doesn't recognize it and I know my syntax has some real problems. Is this the right way to be trying to do this? Any help or tips or code is greatly greatly appreciated!
package com.dredaydesigns.radiostationfinder;
import android.R;
import android.app.Activity;
import android.location.Location;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.LocationServices;
import com.google.gson.JsonParser;
import com.squareup.okhttp.Call;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Text;
import java.io.IOException;
import butterknife.Bind;
import butterknife.ButterKnife;
public class MainActivity extends Activity implements GoogleApiClient.ConnectionCallbacks {
// String HTTPRadioURL = "https://transition.fcc.gov/fcc-bin/fmq?state=&call=&city=&arn=&serv=FC&vac=3&freq=0.0&fre2=107.9&facid=&asrn=&class=&dkt=&list=0&dist=100&dlat2="
// + latitude + "&mlat2=&slat2=&NS=N&dlon2="
// + longitude +"&mlon2=&slon2=&EW=W&size=9";
public static final String TAG = MainActivity.class.getSimpleName();
private RadioData mRadioData;
private GoogleApiClient mGoogleApiClient;
private Location mLastLocation;
#Bind(R.id.longitudeLabel) TextView mLongitudeLabel;
#Bind(R.id.latitudeLabel) TextView mLatitudeLabel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list_content);
ButterKnife.bind(this);
double latitude = 32;
double longitude = -96;
double latitudeStations;
double longitudeStations;
final RadioData[] mRadioData = new RadioData[1];
//String radioFinderURL = "http://data.fcc.gov/lpfmapi/rest/v1/lat/" + latitude + "/long/" + longitude + "?format=json&secondchannel=true";
//String HTTPRadioURL = "https://transition.fcc.gov/fcc-bin/fmq?state=&call=&city=&arn=&serv=FC&vac=3&freq=0.0&fre2=107.9&facid=&asrn=&class=&dkt=&list=0&dist=100&dlat2="
// + latitude + "&mlat2=&slat2=&NS=N&dlon2="
// + longitude +"&mlon2=&slon2=&EW=W&size=9";
String radioFinderURL = "http://dredaycreative.com/json/radioData.json";
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(radioFinderURL)
.build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
#Override
public void onFailure(Request request, IOException e) {
}
#Override
public void onResponse(Response response) throws IOException {
try {
String jsonData = response.body().string();
Log.v(TAG,jsonData);
if (response.isSuccessful()) {
mRadioData[0] = getCurrentDetails(jsonData);
}
} catch (IOException e) {
Log.e(TAG, "Exception Caught: ", e);
}
catch(JSONException e){
Log.e(TAG, "Exception Caught:", e);
}
}
});
}
private RadioData getCurrentDetails(String jsonData) throws JSONException {
JSONObject radioData = new JSONObject(jsonData);
String callSign;
double latitudeStations;
double longitudeStations;
int frequency;
JSONArray jsonArray = radioData.getJSONArray("array");
for(int i =0; i<jsonArray.length(); i++){
callSign = radioData.getJSONObject(i+"")
.getJSONArray("array").getJSONObject(i).getString("FIELD1");
frequency = Integer.parseInt(radioData.getJSONObject(i + "")
.getJSONArray("array").getJSONObject(i).getString("FIELD2"));
longitudeStations = Double.parseDouble(radioData.getJSONObject(i+"")
.getJSONArray("array").getJSONObject(i).getString("FIELD20"));
latitudeStations = Double.parseDouble(radioData.getJSONObject(i+"")
.getJSONArray("array").getJSONObject(i).getString("FIELD24"));
Log.i(TAG, "From JSON: " + callSign + frequency + latitudeStations + longitudeStations);
if {
distanceBetween(double latitude, double longitude, double latitudeStations, double longitudeStations, float[] results) <100km
RadioData currently = new RadioData();
}
}
return new RadioData();
}
protected synchronized void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener((GoogleApiClient.OnConnectionFailedListener) this)
.addApi(LocationServices.API)
.build();
}
#Override
public void onConnected(Bundle bundle) {
mLastLocation = LocationServices.FusedLocationApi.getLastLocation(
mGoogleApiClient);
if (mLastLocation != null) {
mLatitudeLabel.setText(String.valueOf(mLastLocation.getLatitude()));
mLongitudeLabel.setText(String.valueOf(mLastLocation.getLongitude()));
}
}
#Override
public void onConnectionSuspended(int i) {
}
}
UPDATED CODER AFTER COMMENT AT 7:55PMCMT 8/17/15
Okay thank you for your help! I switched it up in this way am I getting warmer, ha?
private RadioData getCurrentDetails(String jsonData) throws JSONException {
JSONObject radioData = new JSONObject(jsonData);
static void distanceBetween;
String callSign;
double latitudeStations;
double longitudeStations;
int frequency;
private int mRadius = distanceBetween(double latitude, double longitude, double latitudeStations, double longitudeStations, float[] results)
callSign = radioData.getJSONObject(i + "")
.getJSONObject(i).getString("FIELD1");
frequency = Integer.parseInt(radioData.getJSONObject(i + "")
.getJSONObject(i).getString("FIELD2"));
longitudeStations = Double.parseDouble(radioData.getJSONObject(i + "")
.getJSONObject(i).getString("FIELD20"));
latitudeStations = Double.parseDouble(radioData.getJSONObject(i + "")
.getJSONObject(i).getString("FIELD24"));
Log.i(TAG, "From JSON: " + callSign + frequency + latitudeStations + longitudeStations);
if{
500 >= mRadius;
RadioData radioData = new RadioData();
}
}
return new RadioData();
}
I'm unable to get an access token to provide an email address. I'm using GoogleAuthUtil. (the result says the token is invalid)
I use this:
mGoogleApiClient = new GoogleApiClient.Builder (cordova.getActivity().getApplicationContext())
.addConnectionCallbacks (this)
.addOnConnectionFailedListener (this)
.addApi (Games.API)
.addScope (Games.SCOPE_GAMES)
.addApi(Plus.API)
.addScope(Plus.SCOPE_PLUS_PROFILE)
.addScope(Plus.SCOPE_PLUS_LOGIN)
.build ();
And this:
scope = "oauth2:" + Scopes.PROFILE + " " + "email";
Dropping " email" works to give me a valid access token -- but there's no email address in the response.
Here is my code.
package com.flyingsoftgames.googleplayservices;
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.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.games.Players;
import com.google.android.gms.games.Games;
import com.google.android.gms.auth.GoogleAuthException;
import com.google.android.gms.auth.GoogleAuthUtil;
import com.google.android.gms.auth.UserRecoverableAuthException;
import com.google.android.gms.auth.GooglePlayServicesAvailabilityException;
import com.google.android.gms.plus.Account;
import com.google.android.gms.plus.Plus;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.PluginResult;
import org.apache.cordova.PluginResult.Status;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import org.apache.cordova.*;
import java.io.IOException;
import android.util.Log;
public class GooglePlayServices extends CordovaPlugin implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
private static final String LOG_TAG = "GooglePlayServices";
private static final int REQ_SIGN_IN_REQUIRED = 55664;
public static GoogleApiClient mGoogleApiClient = null;
public CallbackContext tryConnectCallback = null;
public String accessToken = "";
private int connectionAttempts = 0;
#Override public void onConnectionFailed (ConnectionResult result) {
String errormessage = result.toString();
Log.w (LOG_TAG, errormessage);
connectionAttempts += 1;
if (!result.hasResolution() || connectionAttempts >= 2) {
Log.w (LOG_TAG, "Error: no resolution. Google Play Services connection failed.");
tryConnectCallback.error ("Error: " + errormessage + "."); tryConnectCallback = null;
return;
}
try {
result.startResolutionForResult (cordova.getActivity(), result.getErrorCode());
} catch (SendIntentException e) {
// There was an error with the resolution intent. Try again.
mGoogleApiClient.connect ();
}
}
#Override public void onConnected (Bundle connectionHint) {
String mAccountName = Plus.AccountApi.getAccountName(mGoogleApiClient);
new RetrieveTokenTask().execute (mAccountName);
Games.setViewForPopups (mGoogleApiClient, webView);
}
public void onActivityResult (int requestCode, int responseCode, Intent intent) {
if (!mGoogleApiClient.isConnecting()) mGoogleApiClient.connect ();
}
#Override public void onConnectionSuspended (int cause) {
mGoogleApiClient.connect ();
}
public boolean execute (String action, JSONArray inputs, CallbackContext callbackContext) throws JSONException {
if ("getPlayerId".equals(action)) {
String playerId = Games.Players.getCurrentPlayerId (mGoogleApiClient);
callbackContext.sendPluginResult (new PluginResult (PluginResult.Status.OK, playerId));
} else if ("tryConnect".equals(action)) {
tryConnect (callbackContext);
} else if ("getAccessToken".equals(action)) {
callbackContext.sendPluginResult (new PluginResult (PluginResult.Status.OK, accessToken));
}
return true;
}
// tryConnect runs the callback with a value of false if Google Play Services isn't available.
public void tryConnect (CallbackContext callbackContext) {
boolean isGpsAvailable = (GooglePlayServicesUtil.isGooglePlayServicesAvailable(cordova.getActivity()) == ConnectionResult.SUCCESS);
if (!isGpsAvailable) {
callbackContext.sendPluginResult (new PluginResult (PluginResult.Status.OK, false));
return;
}
tryConnectCallback = callbackContext;
mGoogleApiClient = new GoogleApiClient.Builder (cordova.getActivity().getApplicationContext())
.addConnectionCallbacks (this)
.addOnConnectionFailedListener (this)
.addApi (Games.API)
.addScope (Games.SCOPE_GAMES)
.addApi(Plus.API)
.addScope(Plus.SCOPE_PLUS_PROFILE)
.addScope(Plus.SCOPE_PLUS_LOGIN)
.build ();
mGoogleApiClient.connect ();
}
private class RetrieveTokenTask extends AsyncTask<String, Void, String> {
#Override protected String doInBackground (String... params) {
String accountName = params[0];
String scope = "oauth2:" + Scopes.PROFILE + " " + "email";
Context context = cordova.getActivity().getApplicationContext();
try {
accessToken = GoogleAuthUtil.getToken (context, accountName, scope);
} catch (IOException e) {
String errormessage = e.getMessage();
Log.e (LOG_TAG, errormessage);
if (tryConnectCallback != null) tryConnectCallback.error ("Error: " + errormessage + "."); tryConnectCallback = null;
} catch (UserRecoverableAuthException e) {
cordova.getActivity().startActivityForResult (e.getIntent(), REQ_SIGN_IN_REQUIRED);
} catch (GoogleAuthException e) {
String errormessage = e.getMessage();
Log.e (LOG_TAG, errormessage);
if (tryConnectCallback != null) tryConnectCallback.error ("Error: " + errormessage + "."); tryConnectCallback = null;
}
return accessToken;
}
#Override protected void onPostExecute (String newAccessToken) {
super.onPostExecute (newAccessToken);
accessToken = newAccessToken;
if (tryConnectCallback != null) {
String playerId = Games.Players.getCurrentPlayerId (mGoogleApiClient);
tryConnectCallback.sendPluginResult (new PluginResult (PluginResult.Status.OK, playerId));
tryConnectCallback = null;
}
}
}
}
It seems that clearing an app's permission in https://security.google.com/settings/security/permissions?hl=en does trigger a re-authorization screen, but the access token that is received from .getToken is stale. GoogleAuthUtil.clearToken (context, accessToken); needs to be used to clear the old token, or the old token must time out. Thus, we can do:
accessToken = GoogleAuthUtil.getToken(context, accountName, scope);
GoogleAuthUtil.clearToken (context, accessToken);
accessToken = GoogleAuthUtil.getToken(context, accountName, scope);
However:
It seems that this only resolves after a few iterations of re-launching the app, because GoogleAuthUtil.getToken is asynchronous when new permissions are required, and two SEPARATE permission screens pop up: one from Google (triggered from GoogleAuthUtil.getToken and one from Google+ (triggered from GoogleApiClient.Builder).
Edit:
Using the AccountPicker class eliminates the need to use GoogleApiClient.Builder!
See:
https://github.com/agamemnus/googleplaytoken
The user is being presented with multiple permission screens for the same permissions in my app/game.
First, I use mGoogleApiClient = new GoogleApiClient.Builder ( .. to get the user's email address. A "Google+" permissions screen pops up.
Then, I use GoogleAuthUtil.getToken(context, accountName, scope); to get a token, where accountName is the email address. A "Google" permissions screen pops up.
Is there any way I can avoid two permission screens?
Full code is below.
package com.flyingsoftgames.googleplayservices;
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.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.games.Players;
import com.google.android.gms.games.Games;
import com.google.android.gms.auth.GoogleAuthException;
import com.google.android.gms.auth.GoogleAuthUtil;
import com.google.android.gms.auth.UserRecoverableAuthException;
import com.google.android.gms.auth.GooglePlayServicesAvailabilityException;
import com.google.android.gms.plus.Account;
import com.google.android.gms.plus.Plus;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.PluginResult;
import org.apache.cordova.PluginResult.Status;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import org.apache.cordova.*;
import java.io.IOException;
import android.util.Log;
public class GooglePlayServices extends CordovaPlugin implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
private static final String LOG_TAG = "GooglePlayServices";
private static final int REQ_SIGN_IN_REQUIRED = 55664;
public static GoogleApiClient mGoogleApiClient = null;
public CallbackContext tryConnectCallback = null;
public String accessToken = "";
private int connectionAttempts = 0;
#Override public void onConnectionFailed (ConnectionResult result) {
String errormessage = result.toString();
Log.w (LOG_TAG, errormessage);
connectionAttempts += 1;
if (!result.hasResolution() || connectionAttempts >= 2) {
Log.w (LOG_TAG, "Error: no resolution. Google Play Services connection failed.");
tryConnectCallback.error ("Error: " + errormessage + "."); tryConnectCallback = null;
return;
}
try {
result.startResolutionForResult (cordova.getActivity(), result.getErrorCode());
} catch (SendIntentException e) {
// There was an error with the resolution intent. Try again.
mGoogleApiClient.connect ();
}
}
#Override public void onConnected (Bundle connectionHint) {
String mAccountName = Plus.AccountApi.getAccountName(mGoogleApiClient);
new RetrieveTokenTask().execute (mAccountName);
Games.setViewForPopups (mGoogleApiClient, webView);
}
public void onActivityResult (int requestCode, int responseCode, Intent intent) {
if (!mGoogleApiClient.isConnecting()) mGoogleApiClient.connect ();
}
#Override public void onConnectionSuspended (int cause) {
mGoogleApiClient.connect ();
}
public boolean execute (String action, JSONArray inputs, CallbackContext callbackContext) throws JSONException {
if ("getPlayerId".equals(action)) {
String playerId = Games.Players.getCurrentPlayerId (mGoogleApiClient);
callbackContext.sendPluginResult (new PluginResult (PluginResult.Status.OK, playerId));
} else if ("tryConnect".equals(action)) {
tryConnect (callbackContext);
} else if ("getAccessToken".equals(action)) {
callbackContext.sendPluginResult (new PluginResult (PluginResult.Status.OK, accessToken));
}
return true;
}
// tryConnect runs the callback with a value of false if Google Play Services isn't available.
public void tryConnect (CallbackContext callbackContext) {
boolean isGpsAvailable = (GooglePlayServicesUtil.isGooglePlayServicesAvailable(cordova.getActivity()) == ConnectionResult.SUCCESS);
if (!isGpsAvailable) {
callbackContext.sendPluginResult (new PluginResult (PluginResult.Status.OK, false));
return;
}
tryConnectCallback = callbackContext;
mGoogleApiClient = new GoogleApiClient.Builder (cordova.getActivity().getApplicationContext())
.addConnectionCallbacks (this)
.addOnConnectionFailedListener (this)
.addApi (Games.API)
.addScope (Games.SCOPE_GAMES)
.addApi(Plus.API)
.addScope(Plus.SCOPE_PLUS_PROFILE)
.addScope(Plus.SCOPE_PLUS_LOGIN)
.build ();
mGoogleApiClient.connect ();
}
private class RetrieveTokenTask extends AsyncTask<String, Void, String> {
#Override protected String doInBackground (String... params) {
String accountName = params[0];
String scope = "oauth2:https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile";
Context context = cordova.getActivity().getApplicationContext();
try {
accessToken = GoogleAuthUtil.getToken(context, accountName, scope);
Log.e (LOG_TAG, "127: " + accessToken);
} catch (IOException e) {
String errormessage = e.getMessage();
Log.e (LOG_TAG, errormessage);
if (tryConnectCallback != null) tryConnectCallback.error ("Error: " + errormessage + "."); tryConnectCallback = null;
} catch (UserRecoverableAuthException e) {
cordova.getActivity().startActivityForResult (e.getIntent(), REQ_SIGN_IN_REQUIRED);
} catch (GoogleAuthException e) {
String errormessage = e.getMessage();
Log.e (LOG_TAG, errormessage);
if (tryConnectCallback != null) tryConnectCallback.error ("Error: " + errormessage + "."); tryConnectCallback = null;
}
return accessToken;
}
#Override protected void onPostExecute (String newAccessToken) {
super.onPostExecute (newAccessToken);
accessToken = newAccessToken;
if (tryConnectCallback != null) {
String playerId = Games.Players.getCurrentPlayerId (mGoogleApiClient);
tryConnectCallback.sendPluginResult (new PluginResult (PluginResult.Status.OK, playerId));
tryConnectCallback = null;
}
}
}
}
I figured out that if I use just AccountPicker.newChooseAccountIntent, I do not need to do mGoogleApiClient = new GoogleApiClient.Builder: https://github.com/agamemnus/googleplaytoken.
I need to retrieve the email addresses that the user has stored in his gmail account. In my app, the user can now decide to invite a friend of him. I want that the application (if the user tell me "ok") presents a list of the user's contacts email addresses stored in gmail, among which he can choose one or more...
I know that exists Authentication and Authorization for Google APIs". Is it the right way? And, how to use them in Android?
I hope this will help for someone like me, because I have searched a lot for this and finally done with the below.
I have used GData java client library for Google Contacts API v3.
package com.example.cand;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.MalformedURLException;
import java.net.URL;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import com.google.gdata.client.Query;
import com.google.gdata.client.Service;
import com.google.gdata.client.contacts.ContactsService;
import com.google.gdata.data.Link;
import com.google.gdata.data.contacts.ContactEntry;
import com.google.gdata.data.contacts.ContactFeed;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.NoLongerAvailableException;
import com.google.gdata.util.ServiceException;
public class MainActivity extends Activity {
private URL feedUrl;
private static final String username="yourUsername";
private static final String pwd="yourPassword";
private ContactsService service;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String url = "https://www.google.com/m8/feeds/contacts/default/full";
try {
this.feedUrl = new URL(url);
} catch (MalformedURLException e) {
e.printStackTrace();
}
new GetTask().execute();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private class GetTask extends AsyncTask<Void, Void, Void>{
#Override
protected Void doInBackground(Void... params) {
service = new ContactsService("ContactsSample");
try {
service.setUserCredentials(username, pwd);
} catch (AuthenticationException e) {
e.printStackTrace();
}
try {
queryEntries();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
private void queryEntries() throws IOException, ServiceException{
Query myQuery = new Query(feedUrl);
myQuery.setMaxResults(50);
myQuery.setStartIndex(1);
myQuery.setStringCustomParameter("showdeleted", "false");
myQuery.setStringCustomParameter("requirealldeleted", "false");
// myQuery.setStringCustomParameter("sortorder", "ascending");
// myQuery.setStringCustomParameter("orderby", "");
try{
ContactFeed resultFeed = (ContactFeed)this.service.query(myQuery, ContactFeed.class);
for (ContactEntry entry : resultFeed.getEntries()) {
printContact(entry);
}
System.err.println("Total: " + resultFeed.getEntries().size() + " entries found");
}
catch (NoLongerAvailableException ex) {
System.err.println("Not all placehorders of deleted entries are available");
}
}
private void printContact(ContactEntry contact) throws IOException, ServiceException{
System.err.println("Id: " + contact.getId());
if (contact.getTitle() != null)
System.err.println("Contact name: " + contact.getTitle().getPlainText());
else {
System.err.println("Contact has no name");
}
System.err.println("Last updated: " + contact.getUpdated().toUiString());
if (contact.hasDeleted()) {
System.err.println("Deleted:");
}
// ElementHelper.printContact(System.err, contact);
Link photoLink = contact.getLink("http://schemas.google.com/contacts/2008/rel#photo", "image/*");
if (photoLink.getEtag() != null) {
Service.GDataRequest request = service.createLinkQueryRequest(photoLink);
request.execute();
InputStream in = request.getResponseStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
RandomAccessFile file = new RandomAccessFile("/tmp/" + contact.getSelfLink().getHref().substring(contact.getSelfLink().getHref().lastIndexOf('/') + 1), "rw");
byte[] buffer = new byte[4096];
for (int read = 0; (read = in.read(buffer)) != -1; )
out.write(buffer, 0, read);
file.write(out.toByteArray());
file.close();
in.close();
request.end();
}
System.err.println("Photo link: " + photoLink.getHref());
String photoEtag = photoLink.getEtag();
System.err.println(" Photo ETag: " + (photoEtag != null ? photoEtag : "(No contact photo uploaded)"));
System.err.println("Self link: " + contact.getSelfLink().getHref());
System.err.println("Edit link: " + contact.getEditLink().getHref());
System.err.println("ETag: " + contact.getEtag());
System.err.println("-------------------------------------------\n");
}
}
Required library files: you can get these jars from here
gdata-client-1.0.jar
gdata-client-meta-1.0.jar
gdata-contacts-3.0.jar
gdata-contacts-meta-3.0.jar
gdata-core-1.0.jar
guava-11.0.2.jar
Note: Add internet permission in AndroidManifest file.
<uses-permission android:name="android.permission.INTERNET"/>