I have published a pretty successful app about 2 weeks ago. But starting from yesterday, users keep sending me emails about Drive not being accessable anymore. After a quick debug, I found that requests to the Drive API now return "403 Forbidden" -> "Access Not Configured".
I think this might be an issue with the refresh token not being handled properly.
I'm using the following code (from the Android Drive SDK samples):
mCredentials = GoogleAccountCredential.usingOAuth2(this, DriveScopes.DRIVE);
String accountName = PreferenceManager.getDefaultSharedPreferences(this).getString(PREF_DRIVE_NAME, null);
if (accountName != null) {
setupDrive(accountName);
} else {
startActivityForResult(mCredentials.newChooseAccountIntent(), 0);
}
setupDrive(...) looks like this:
mCredentials.setSelectedAccountName(accountName);
try {
mCredentials.getToken();
} catch (Exception e) {
Log.w(AbstractDriveActivity.class.getSimpleName(), "Error getting auth token", e);
if (e instanceof UserRecoverableAuthException) {
Intent intent = ((UserRecoverableAuthException) e).getIntent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).addFlags(Intent.FLAG_FROM_BACKGROUND);
startActivity(intent);
} else {
Toast.makeText(AbstractDriveActivity.this, getString(R.string.toast_drive_setup_error),
Toast.LENGTH_SHORT).show();
finish();
}
}
drive = new Drive.Builder(AndroidHttp.newCompatibleTransport(), new GsonFactory(),
mCredentials).build();
Any idea what might be wrong here?
From my understanding, GoogleAccountCredential uses the Google Play Services to manage the OAuth2 flow and all you need to provide is the username. Am I wrong? Did I miss something?
Btw: After clearing app data, selecting the Google Account again, everything works fine. That's why I think that it has something to do with the refresh token.
Goddchen
No guaranty, but the problem might come from here:
mCredentials = GoogleAccountCredential.usingOAuth2(this, DriveScopes.DRIVE);
This method appears as deprecated to me. You should upgrade your SDK and environment and change it to:
mCredentials = GoogleAccountCredential.usingOAuth2(this, Arrays.asList(DriveScopes.DRIVE));
Related
According to my boss, some of our applications have been invested on advertising of the app via Google Ads. In order for them to parse the data and analyze them correctly, they are using the UTM auto-tagging approach. It is my job from the client (Android Device) to send the UTM using Firebase Analytics and also a custom event to Firebase depending on this UTM.
However, our data shows that both Firebase SDK and our events are transferred incorrectly. The click numbers and the download numbers do not match. Since both of them are incorrect, I'm guessing the received UTM on the device itself is wrong, and this needs to be received correctly and I am unable to find an answer for this.
I'm using Install Referrer Library to track down what the UTM is after the app is downloaded to the device. I am guessing Firebase SDK also uses somewhat similar approach. On our end, the UTM is recorded to SharedPreferences and it is not queried again if the query was successful.
Here is the related code for it (the processReferrer method basically parses the UTM according to our needs):
/**
* Checks if the referrer information is recorded before, if not, creates
* a connection to Google Play and saves the data to shared preferences.
*/
private static void fetchReferrerInformation(Context context)
{
SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(context);
String utmData = preferences.getString(UTM_DATA, "");
// Only connect if utm is not recorded before.
if (TextUtils.isEmpty(utmData))
{
InstallReferrerClient client;
try
{
client = InstallReferrerClient.newBuilder(context).build();
client.startConnection(new InstallReferrerStateListener()
{
#Override
public void onInstallReferrerSetupFinished(int responseCode)
{
switch (responseCode)
{
case InstallReferrerClient.InstallReferrerResponse.OK:
{
ReferrerDetails response;
try
{
response = client.getInstallReferrer();
}
catch (Exception e)
{
Log.e(TAG, "Error while fetching referrer information.", e);
if (Fabric.isInitialized())
Crashlytics.logException(new IllegalStateException("Exception while fetching UTM information.", e));
return;
}
if (response != null)
{
processReferrer(context, response.getInstallReferrer());
}
break;
}
case InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED:
{
Log.w(TAG, "Install referrer client: Feature is not supported.");
break;
}
case InstallReferrerClient.InstallReferrerResponse.SERVICE_UNAVAILABLE:
{
Log.w(TAG, "Install referrer client: Service is unavailable.");
break;
}
}
try
{
client.endConnection();
}
catch (Exception ignored){}
}
#Override
public void onInstallReferrerServiceDisconnected()
{
// Do nothing, we need to fetch the information once and
// it is not really necessary to try to reconnect.
// If app is opened once more, the attempt will be made anyway.
}
});
}
catch (Exception e)
{
if (Fabric.isInitialized())
Crashlytics.logException(new IllegalStateException("Exception while fetching UTM information.", e));
}
}
else
Log.i(TAG, "UTM is already recorded, skipping connection initialization. Value: " +
utmData);
}
The approach is pretty simple, however the data seems to be wrong. So, does it seem that the implementation is somewhat incorrect? If not, why is the data received from Google Ads is wrong? Any help is appreciated, thank you very much.
Edit: Upon some testing, here is what I've found:
Works:
An API 19 real device (GM Discovery II Mini) and in between API 25-29 emulators with Play Store installed. Edit: UTM can also be fetched with API 23 and 24 Genymotion Emulators, where Play Store is installed.
Doesn't work:
An API 24 Android Studio emulator with latest Google Play Services and Play Store installed (device is also logged in to my account), and a real device (General Mobile 4G Dual, API 23) cannot query the UTM information. The code below lands on the case of InstallReferrerResponse.FEATURE_NOT_SUPPORTED. So I am almost sure that the install referrer client is bugged on some API levels.
Edit: Opened an issue to the Google: https://issuetracker.google.com/issues/149342702
As I don't know how you are processing the resonse, I can show you the way we did it in our implementation.
ReferrerDetails response = referrerClient.getInstallReferrer();
if (response == null) {
break;
}
String[] installReferrer = response.getInstallReferrer().split("&");
if (installReferrer.length >= 1) {
utmSource = installReferrer[0].split("=")[1];
}
if (installReferrer.length >= 2) {
utmMedium = installReferrer[1].split("=")[1];
}
Compare this snippet with yours and check if anything differs.
I am trying to get expiry time or status of subscription to ensure if user is paying regularly for my item or not . When i query using
Purchase monthlySubscription = inv.getPurchase("itemName");
or
ArrayList<String> ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
It returns following data
{
"packageName":"com.abcPackage",
"productId":"auto1week",
"purchaseTime":1453369299644,
"purchaseState":0,
"developerPayload":"PAY_LOAD",
"purchaseToken":"TOKEN",
"autoRenewing":true
}
The problem is , purchaseTime remains same after several weeks which is supposed to be change after every purchase.
I tried google Play developers API
https://developers.google.com/android-publisher/#subscriptions
but i am having a hard time implementing it on my android device .
I will be grateful if someone can guide me step by step process to get this data on android device.
https://developers.google.com/android-publisher/api-ref/purchases/subscriptions
Any help in this regard will be highly appreciated.
Not sure if this will help, but here is my server side code (in java) that connects to the developer API and returns the expiration of the subscription.
I created a Service Account in the Google Developer Console, and followed the somewhat obtuse instructions to create a key file in src/resources/keys.json. APPLICATION_NAME is the package name of my app, and PRODUCT_ID is the subscription ID from the Google PLAY developer console.
Sorry it's not really 'step by step' as you asked for, but I also am doing verification on the server side instead of on the client. I suppose on the client you could do some sort of soft-verification by checking purchaseState == 0 (1=cancelled, 2=refunded), and autoRenewing==true. You may get stuck there if they cancel though, since you are still supposed to provide service through the duration of the subscription.
public static Long doSomeWork(String token){
log.debug("Google Validation: Doing some work:" + token);
try{
// Creating new Trusted Transport
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
// Getting Auth Creds
Credential cred = getAuthCredential();
// Building Android Publisher API call
AndroidPublisher ap = new AndroidPublisher.Builder(httpTransport, JSON_FACTORY, cred)
.setApplicationName(APPLICATION_NAME).build();
// Get Subscription
AndroidPublisher.Purchases.Subscriptions.Get get = ap.purchases().subscriptions().get(
APPLICATION_NAME,
PRODUCT_ID,
token);
SubscriptionPurchase subscription = get.execute();
log.debug(subscription.toPrettyString());
log.debug("DONE (not null)");
return subscription.getExpiryTimeMillis();
} catch(IOException ex) {
ex.printStackTrace();
} catch (GeneralSecurityException ex2) {
ex2.printStackTrace();
}
log.debug("DONE (failure) (0)");
return 0L;
}
private static Credential getAuthCredential(){
log.debug("getAuthCredential");
try{
//Read the credentials from the keys file. This file is obtained from the
// Google Developer Console (not the Play Developer Console
InputStream is = GoogleReceiptValidation.class.getClassLoader().getResourceAsStream("keys.json");
String str = IOUtils.toString(is);
is.close();
JSONObject obj = new JSONObject(str);
InputStream stream = new ByteArrayInputStream(obj.toString().getBytes());
//This is apparently "beta functionality".
GoogleCredential creds = GoogleCredential.fromStream(stream);
creds = creds.createScoped(Collections.singleton(AndroidPublisherScopes.ANDROIDPUBLISHER));
return creds;
} catch (IOException ex){
ex.printStackTrace();
} catch (JSONException ex2){
ex2.printStackTrace();
}
log.debug("No Creds found - returning null");
return null;
}
I want to restrict my API endpoints access only to my android app, but without google_account/password.
I've the choice of those methods : https://developers.google.com/accounts/docs/OAuth2
For test, I succeeded to authenticate my android app to my API with this method: https://cloud.google.com/appengine/docs/python/endpoints/consume_android
==> This method allow you to authenticate your app with combo:
Login/password (Google account)
SHA1 and package name of your android APP
So if someone get my code (Decompiling apk) and modify my android code, they can't access to my API because SHA1 fingerprint of my app will change. (I tested it, and it works =) )
This method works fine, but I don't want Google login/password for authentication..
So I tried this method: https://developers.google.com/accounts/docs/OAuth2ServiceAccount
I successfully authenticate my android app, BUT, if my android code is modified by someone else(So the SHA1 changed), my android app can still connect to my API !! So if someone get my package and decompile it, he'll changed freely code and successfully access to my API..
Here is my API Code:
#ApiMethod( name = "ListCampagnes", httpMethod = ApiMethod.HttpMethod.GET, path="list", clientIds = {CONSTANTES.ANDROID_CLIENT_ID, CONSTANTES.WEB_CLIENT_ID, CONSTANTES.SERVICE_CLIENT_ID, com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID}, audiences = {CONSTANTES.ANDROID_AUDIENCE})
public Collection<Campagne> getCampagnes(#Named("NumPortable")String NumPortable, User user) throws UnauthorizedException {
if (user == null) throw new UnauthorizedException("User is Not Valid");
return CampagneCRUD.getInstance().findCampagne(NumPortable);
}
Here is my android code:
GoogleCredential credentialToAppengine;
try {
String p12Password = "notasecret";
KeyStore keystore = KeyStore.getInstance("PKCS12");
InputStream keyFileStream = getAssets().open("59ce5a08e110.p12");
keystore.load(keyFileStream, p12Password.toCharArray());
PrivateKey key = (PrivateKey)keystore.getKey("privatekey", p12Password.toCharArray());
credentialToAppengine = new GoogleCredential.Builder().setTransport(AndroidHttp.newCompatibleTransport()).setJsonFactory(new JacksonFactory()).setServiceAccountId("301991144702-3v9ikfp4lsmokee1utkucj35847eddvg#developer.gserviceaccount.com").setServiceAccountPrivateKey(key).setServiceAccountScopes(Collections.singleton("https://www.googleapis.com/auth/userinfo.email")).build();
} catch (GeneralSecurityException e) {
e.printStackTrace();
return null;
} catch (Exception e) {
e.printStackTrace();
return null;
}
Do I try an other method for authenticate my android App ? Or did I missing something in my API code ?
Thanks a looot in advance,
Authenticate Android End point without Google User Account is just impossible ! I tried every ways but still doesn't works !
So here is my way to resolv this problem, without any user interaction (Maybe not the right but that works, and you've got strong authentication (SHA1 + Google Account)):
HERE IS MY ANDROID CODE
Get and Build Valid Credential
//Get all accounts from my Android Phone
String validGoogleAccount = null;
Pattern emailPattern = Patterns.EMAIL_ADDRESS; // API level 8+
Account[] accounts = AccountManager.get(context).getAccounts();
for (Account account : accounts) {
if (emailPattern.matcher(account.name).matches()) {
//Just store mail if countain gmail.com
if (account.name.toString().contains("gmail.com")&&account.type.toString().contains("com.google")){
validGoogleAccount=account.name.toString();
}
}
}
//Build Credential with valid google account
GoogleAccountCredential credential = GoogleAccountCredential.usingAudience(this,"server:client_id:301991144702-5qkqclsogd0b4fnkhrja7hppshrvp4kh.apps.googleusercontent.com");
credential.setSelectedAccountName(validGoogleAccount);
Use this credential for secure calls
Campagneendpoint.Builder endpointBuilder = new Campagneendpoint.Builder(AndroidHttp.newCompatibleTransport(), new JacksonFactory(), credential);
HERE IS MY API BACKEND CODE:
API Annotation
#Api(
scopes=CONSTANTES.EMAIL_SCOPE,
clientIds = {CONSTANTES.ANDROID_CLIENT_ID,
CONSTANTES.WEB_CLIENT_ID,
com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID},
audiences = {CONSTANTES.ANDROID_AUDIENCE},
name = "campagneendpoint",
version = "v1"
)
Method code:
public Collection<Campagne> getCampagnes(#Named("NumPortable")String NumPortable, User user) throws UnauthorizedException {
if (user == null) throw new UnauthorizedException("User is Not Valid");
return CampagneCRUD.getInstance().findCampagne(NumPortable);
}
For the moment, it only works on Android (I don't know how we gonna do on IOS..)..
Hope It will help you !
I tried this example from google to authenticate with Google Drive and it can work. But the problem is that I don't know how to restore the previous success login from users. For example, when users login success, the next time they go to my app, they don't need to login again. I look in the GoogleAccountCredential class, it only have getToken method and don't have 'setToken` so I don't know how to do. And the document doesn't say anything about it. Here is my try:
credential = GoogleAccountCredential.usingOAuth2(context, DriveScopes.DRIVE);
// try to add login account into credential
String accountName = SharePreferenceHelper.getDriveAccount(context);
if (accountName != null) {
credential.setSelectedAccountName(accountName);
service = getDriveService(credential);
}
// try to get token again
try {
String token = credential.getToken();
Log.d(TAG,"token = " + token);
} catch (UserRecoverableAuthException ex) {
startActivityForResult(ex.getIntent(), requestCode);
}
Does anybody know how to do it?
You need not do this manually. It is already taken care of by GoogleAccountCredential
This call
credential = GoogleAccountCredential.usingOAuth2(this, scopes);
will automatically restore the saved token if its previously saved.
You can check out a sample source code of my app here:
https://github.com/madhur/GAnalytics/blob/develop/src/in/co/madhur/ganalyticsdashclock/MainActivity.java
the difference it uses Google Analytics instead of Google Drive.
I need to obtain OAuth2 authentication token to pass it to the server so it can fetch list of Google Reader feeds for the user. Server is .NET - I have no access to it or to it's code but most likely it is using unofficial Reader API
I was able to use Android Account manager to obtain valid token for this purpose with the following code (notice that authTokenType="reader")
Account account = accounts[0];
manager.getAuthToken(account, "reader", null, this, new AccountManagerCallback<Bundle>() {
public void run(AccountManagerFuture<Bundle> future) {
try {
// If the user has authorized your application to use the tasks API
// a token is available.
String token = future.getResult().getString(AccountManager.KEY_AUTHTOKEN);
// Now you can send the token to API...
cacheManager.putString(GOOGLE_AUTH, token);
GoogleReaderManager.startAddFeedActivity(AddGoogleReaderSourcesActivity.this);
finish();
} catch (OperationCanceledException e) {
Log.e(TAG, "User cancelled", e);
finish();
} catch (Exception e) {
Log.e(TAG, "Failed to obtain Google reader API_KEY", e);
}
}
}, null);
The code above works fine when I send token to the server side .Net app: the app is able to retrieve the list of Reader feeds.
The problem is that this only works for "Google inside" devices. On Nook I have no such luck since there's no way that I was able to find to add Google account to the account manager. So I'm trying to it using OAuth 2 protocol as described here
It works fine as far as obtaining the token: User approves the app from the mobile page which returns the code token which then mobile app exchanges for the Auth token. However this token will not work with the server process. I have a feeling that perhaps I'm using the wrong scope in this URL:
https://accounts.google.com/o/oauth2/auth?response_type=code&scope=https://www.google.com/reader/api/0/subscription/list&redirect_uri=http://localhost&approval_prompt=force&state=/ok&client_id={apps.client.id}
Scopes that I did try in various combinations:
https://www.google.com/reader/api
https://www.google.com/reader/api/0
https://www.google.com/reader/api/0/subscription/list
https://www.google.com/reader/api+https://www.google.com/reader/atom
Here's example of JSON that is returned from get token POST
{"expires_in":3600,
"token_type":"Bearer",
"access_token":"ya29.AHES6ZSEvuUb6Bvd2DNoMnnN_UnfxirZmf_RQjn7LptFLfI",
"refresh_token":"1\/bUwa5MyOtP6VyWqaIEKgfPh08LNdawJ5Qxz6-qZrHg0"}
Am I messing up scope or token type? Not sure how to change a token type. Any other ideas?
P.S. Google account login page asks: Manage your data in Google Reader, that's why I suspect that the scope is wrong
I got it working for https://www.google.com/reader/api/0/subscription/list. So thought of sharing with you.
I have valid access_token:
This is what i tried to resolve it (partially) :
Google provides OAuth 2.o playgound; where they actually simulate all aspects of OAuth 2.0 as well as final API call to fetch data.
I found this very helpful as it clearly shows what is being sent to request.
Here is the URL : https://developers.google.com/oauthplayground/
Using this, i tweaked my api call below and it works :)
public static String getReaderContent(String accessToken){
String url = "https://www.google.com/reader/api/0/subscription/list" ;
HttpClient client = new HttpClient();
GetMethod method = new GetMethod(url);
String response="";
method.setRequestHeader("Authorization", "OAuth "+accessToken);
try {
int statusCode = client.executeMethod(method);
String response= method.getResponseBodyAsString();
System.out.println("response " + responseStr);
} catch (HttpException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return response;
}
So this works properly fine for getting subscription list; but have not been able to make it work for reader api which you have mentioned in your question.
Let me know if you have got way around google reader API.