I have an Android app which uses its own folder on my Dropbox account. Every time I start up my app I get the below prompt:
Is it possible to code my app or configure it somehow so that I only have to acknowledge this prompt just once?
I don't mind having to acknowledge each time I change my code but every time the app is restarted seems a bit excessive.
I've reduced my code right down to one line which causes this to appear:
Auth.startOAuth2Authentication( gContext, APP_KEY );
Turns out I don't need to call:
Auth.startOAuth2Authentication( gContext, APP_KEY );
Once I get an access token, I can store that for subsequent program restarts and create a DbxClientV2 by using that saved token. I can also generate a token on my app's Dropbox webpage and use that instead (which makes more sense in my case anyway).
DbxRequestConfig config = new DbxRequestConfig( "dropbox/sample-app" );
DbxClientV2 client = new DbxClientV2( config, savedToken );
No more auth screen and the API calls continue to work!
Related
I get an invalid parent folder error, and I've seen the solutions to use resource ID rather than Drive ID, but it's a different scenario here.
I'm trying to access the AppFolder, and this just uses the GoogleApiClient like so:
this.appFolder = Drive.DriveApi.getAppFolder(mGoogleApiClient);
When I later try to create a file in it, I get the above error.
DriveFolder.DriveFileResult fileResult = appFolder.createFile(mGoogleApiClient, changeSet, driveContentsResult.getDriveContents()).await();
Then fileResult.getStatus() gives me the erros.
This used to work for me before.
What's different is that I've manually emptied my app's data on Google Drive (delete hidden app data).
But I haven't disconnected the app - so I would assume that appFolder will continue to work the same way...
So far the only workaround is uninstalling the app, but this way I lose my data.
This is reproducible. Please help.
Update: This issue, #4483, was fixed in January 2017. The following fix may not apply anymore.
Since this continues to be an outstanding issue, I have taken steps to establish a work-around that can be done with code without resorting to user intervention.
As #seanpj says, this issue does not always occur and seems to be dependent upon the sync status of the Drive service. However, if the problem does occur, the following method works for me to circumvent the problem. I post it here in case it may be helpful to someone else. The Javadoc has more information.
/**
* Works around the Drive API for Android (GDAA) issue where the appDataFolder becomes
* unavailable after hidden app data is deleted through the online Drive interface
* (Settings->Manage Apps->Delete hidden app data) by using the REST v3 API to create an
* empty file in appDataFolder. The file is immediately deleted after creation but leaves
* the appDataFolder in working order.
* See <a href="https://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=4483"
* target="_blank">apps-api-issues #4483</a>
* <p>
* Call this method after a call to the Drive API fails with error code 1502
* (DriveStatusCodes.DRIVE_RESOURCE_NOT_AVAILABLE) when dealing with the appDataFolder then
* try the failed operation again.
* <p>
* This method assumes that all appropriate permissions have been granted and authorizations
* made prior to invocation.
*
* #param context - Context
* #param account The account name that has been authorized, e.g., someone#gmail.com.
* #throws IOException From the REST API.
*/
private void fixAppDataFolder(Context context, String account) throws IOException {
final String[] SCOPES = {DriveScopes.DRIVE_APPDATA};
GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(
context, Arrays.asList(SCOPES)).setBackOff(new ExponentialBackOff());
HttpTransport transport = AndroidHttp.newCompatibleTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
com.google.api.services.drive.Drive driveService;
credential.setSelectedAccountName(account);
driveService = new com.google.api.services.drive.Drive.Builder(
transport, jsonFactory, credential)
.setApplicationName("Your app name here")
.build();
File fileMetadata = new File();
fileMetadata.setName("fixAppDataFolder")
.setMimeType("text/plain")
.setParents(Collections.singletonList("appDataFolder"));
File appDataFile = driveService.files()
.create(fileMetadata)
.setFields("id")
.execute();
driveService.files().delete(appDataFile.getId()).execute();
} // fixAppDataFolder
Although this does not solve your problem, your question got me interested, so I ran a little test using this demo (follow "appfolder" in this code). Here's what I learned:
First, I could reproduce your problem using the following sequence
1/ getAppFolder(mGAC)
2/ create folder DEMOROOT in app folder
3/ create folder 2015-10 in DEMOROOT
4/ create file with content in 151022-075754 in 2015-10
5/ list full tree ... result \DEMOROOT\2015-10\151022-075754
6/ read content of 151022-075754 ... OK
so far so good. Without disconnecting, I go to
drive.google.com > Settings > Manage Apps > Options > Delete hidden app data
Now, there should be no objects in the app folder, I run:
1/ getAppFolder(mGAC)
2/ list full tree ... result \DEMOROOT\2015-10\151022-075754
3/ read content of 151022-075754 ... FAIL
As you can see, in my situation the getAppFolder(mGAC) returns valid DriveFolder (DriveId) that can be used. Even the DriveId string looks the same.
Listing of folders/files returns valid objects. It is not supposed to, but I know there is a latency I have to count on, so the list result may change later to reflect the deletion. Attempt to read the file content fails.
A few minutes later (GDAA probably synchronized), the same attempt to list fails, still understandable result, but another attempt to create any object (folder/file) in app folder fails with 'invalid parent folder' error as you pointed out. Disconnect / re-connect does not help, so the app is toasted.
This points to a serious bug that should be addressed. Again the same as in SO 30172915, an uneducated user's action can cause irreparable damage - loss of data to the Android App with no known remedy.
I am experiencing the same problem. I thought GoogleApiClient.ClearDefaultAccountAndReconnect() might be a workaround but it didn't help. As an alternative to uninstalling/reinstalling the app you can try these steps, they worked for me:
In a browser, go to the page for managing your Google account's security settings.
Select "Connected apps & sites", then "Manage Apps"
Select your app from the list. It will show that it has access to Google Drive and a Remove button.
Click on Remove.
Wait a minute or two for the change to take effect, then run your app.
When your app attempts to connect to Drive, you should get the consent screen prompting the user to allow or deny access. Accepting will reauthorize the app and should clear the problem. It did for me.
This bug was reported to google on May 4th:
https://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=4483
As a workaround you can use the REST API.
I am making a Chrome/Android/iOS app using the cca toolchain. I am using the chrome.identity API to get an access_token to interact with Google APIs.
When I set interactive to false (immediate to true) the app is able to get the token without showing the permissions dialog again, but it still shows the account chooser every time on my Android device. This ruins the app experience because every time it is opened the user gets an annoying dialog. How can I make the app remember the chosen account after the first time, like it does with the permissions?
On Android, you can specify accountHint in the details argument of calls to getAuthToken. For example:
var details = { interactive: true, accountHint: 'email#address.com' };
var callback = function(token, account) { ... };
chrome.identity.getAuthToken(details, callback);
This will bypass the account chooser dialog. Note that the callback has account, which can be stored for this purpose.
Unfortunately, this is Android-only; Chrome on desktop doesn't like it when you add extra stuff to details. You'll need to use chrome.runtime.getPlatformInfo to determine what platform you're running on, so that you can create and pass the right details object. On Android, platformInfo.os will be cordova-android.
I am trying to submit a status from my application to Window's live, the user starts the app, gets asked to give my app permissions to do this, and once granted I have a live session object and I can update their status. This works perfectly.
However, if the user closes the application and then opens it again, they are again asked to approve my app for this action. Every time.
Now the live documentation says you can obtain a refresh token (which I do) to prevent this, problem is the access token and the refresh token are all baked in the LiveConnectSession, so when my application is closed this object is destroyed and the user is asked to give the app permissions again.
So what I'd like to know is if anyone knows a way of recreating that object when the application starts (if I stored the token and refresh token) or a way of saving the object onDestroy()..
Iterable<String> scopes = Arrays.asList("wl.signin", "wl.share", "wl.offline_access" );
this.auth.login(this, scopes, this);
public void onAuthComplete(LiveStatus status, LiveConnectSession session, Object userState) {
if(status == LiveStatus.CONNECTED) {
Log.d("", "Signed in.");
client = new LiveConnectClient(session);
stuck with the same issue using Windows Phone..
I have tried serializing the session, which does not work because the session class has no default constructor.
EDIT:
after two full days searching for the mistake I was making, I finally found out what I was doing wrong: I have to use the wl.offline_access scope to make this work!
Now everything is fun again. Can't believe that this was the problem. Tested & working. Nice!
As I can see, you are using the offline scope, so that's not the problem for you.
But I have found out more:
there are two ways to connect to Live (in C#, I don't know how the methods are called in Java):
use LiveConnectClient.LoginAsync (which comes with GUI)
use LiveConnectClient.InitializeAsync (which is UI less and connects in background)
So if your application is already connected, use the second one to gain access to a new session object.
AFAIK, this object is valid for one year, after that, the user has to sign in again. But don't quote me on that.
Please let me know if this works for you.
I'm in a situation where I need to request access tokens for two scopes (from my android application), https://www.googleapis.com/auth/userinfo.email and https://www.googleapis.com/auth/userinfo.userinfo
I would like to get both permissions on a single call to getAuthToken, but can't figure out the string to pass in the authTokenType parameter. I tried several reasonable combinations with no positive results :(
Has anyone solved this issue? Is it possible?
I was having the same issue. Shah is almost right, but his scope string is wrong. It should be
"oauth2:<scope_url> <scope_url>"
not
"oauth2:<scope_url> oauth2:<scope_url>"
If you need multiple OAuth 2.0 scopes, use a space-separated list.
oauth2:https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.userinfo
You asked for sample code, so have a look at the Google Docs Upload Sample application, and in this application have look at the authentication flow done in this sample Android screen (ignore that it's about Google Docs, it still authorizes first). You can get the whole application and run it in an emulator with Google APIs present or run it on your phone. The authorization workflow starts with the buttonAuthorize click, Authorize() and you are specifically interested in this method:
private void gotAccount(Account account)
{
Bundle options = new Bundle();
accountManager.getAuthToken(
account, // Account retrieved using getAccountsByType()
"oauth2:https://www.googleapis.com/auth/userinfo.email oauth2:https://www.googleapis.com/auth/userinfo.userinfo", // Auth scope
//"writely", // Auth scope, doesn't work :(
options, // Authenticator-specific options
this, // Your activity
new OnTokenAcquired(), // Callback called when a token is successfully acquired
null); // Callback called if an error occurs
}
The user gets this access request screen:
Note that this is using the 'local' OAuth2 mechanism, not opening a web browser, but using the authentication provided when you first activated the Android phone.
Also note that the user sees the full URL of the scope instead of a friendly name, I haven't found a way around this and if you do find out it would be great if you could share the answer.
We recently started making use of the new Google Expansion APK mechanism. Overall it works well, but it seems somewhat flakey for us. Some questions:
Some users get the expansion app downloaded along with the app while others don't and our app has to download it itself. Does anyone know what governs when it works automatically and when not?
Sometimes when we need to download the expansion file ourselves, Google Play returns -1 for the file size and null for the URL, indicating the expansion file doesn't exist. If I run the app again, the second time it will generally return a valid size and URL. Does anyone else see this flakiness?
Here are the basics of the code:
This is how we set up the call to verify licensing via a callback
policy = new APKExpansionPolicy( context, new AESObfuscator( SALT, context.getPackageName(), deviceId ) );
mChecker = new LicenseChecker( context, policy, BASE64_PUBLIC_KEY );
mLicenseCheckerCallback = new MyLicenseCheckerCallback();
mChecker.checkAccess( mLicenseCheckerCallback );
Then in the callback we have this for the allow() method (when the license is valid).
public void allow( int reason )
{
String expansionFileName = policy.getExpansionFileName( APKExpansionPolicy.MAIN_FILE_URL_INDEX );
String expansionURL = policy.getExpansionURL( APKExpansionPolicy.MAIN_FILE_URL_INDEX );
long expansionFileSize = policy.getExpansionFileSize( APKExpansionPolicy.MAIN_FILE_URL_INDEX );
}
We just released the app with this new code, but a significant number of users are getting -1 back as the expansionFileSize and null as the url. This causes the user to not get the expansion file installed. Generally if they run the app again, it will work on the second (or third) time.
Anyone have any thoughts on what could be going on?
You are getting -1 because the APKExpansionPolicy responds with a local cached result if you try to contact the licensing server again - but the URL, filesize and filename are not cached and are lost after the first real response. APKExpansionPolicy does not cache these results, here is a comment from the APKExpansionPolicy source code which explains it:
Expansion URL's are not committed to preferences, but are instead intended to be stored when the license response is processed by the front-end.
So you need to store these values in the preferences right after you get the first successful response (in allow callback method);
The blog post on Android Developers addresses #1:
On most newer devices, when users download your app from Android Market, the expansion files will be downloaded automatically, and the refund period won’t start until the expansion files are downloaded. On older devices, your app will download the expansion files the first time it runs
To add to Daniel Novak's answer, if you reset the policy before the call to checkAccess(), this will force it to make a new license request, and therefore retrieve the URL:
policy.resetPolicy();
You probably only want to do this if you're sure you need the URL (ie, if you've already checked that the expansion file is missing).