Invalid parent folder error for AppFolder in Drive API - android

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.

Related

Google drive android API unable to retrieve file content

Hi guys i plan at my android side to retrieve some documents file from my google drive,but always failed mention:
Drive item not found, or you are not authorized to access it.
Here is my code the problem part is the "EXISTING_FILE_ID "
private static final String EXISTING_FILE_ID = "0BxyOMAFcmxoYTkt6cHZwcUtaNlU";
private static final int REQUEST_CODE = 102;
private GoogleApiClient googleApiClient;
private static final String TAG = "retrieve_contents";
First:i tried to get my shared documents link at google drive and pick the key values example,0BxyOMAFcmxoYTkt6cHZwcUtaNlU https://drive.google.com/file/d/0BxyOMAFcmxoYTkt6cHZwcUtaNlU/view?usp=sharing
Second:i use my android side application to create a file,and retrieved a DriveId:CAESABiiHyCcwq2SpVMoAA== but still unable to retrieve file content
this problem confused me Several days does some one help me ?
From the fact that you mention 'EXISTING_FILE_ID', I assume you are referring to the demo code here.
So, to answer the point called 'First' above: The error tells you that you're trying to access a file, not created by your Android app. GDAA supports only SCOPE_FILE scope, e.g. only files created by the Android app can by found, opened, modified,...
The point called 'Second': You are (probably) correctly trying to access a file created by your Android app, but using incorrect ID. The ID I'm seeing is a 'DriveId', different from 'ResourceId' you are supposed to use (see SO 29030110 here).
The most immediate solution would be to turn your DriveId into ResourceId by means of
DriveId driveId = DriveId.decodeFromString("DriveId:CAESABii.....");
String EXISTING_FILE_ID = driveId.getResourceId();
And try that ID.
Good Luck
#user2953788
Did you solve this issue? I have the same example working and I can see 2 potential issues that could give you the error you have mentioned (you have not pasted the actual error log)
1) You have not given access to the app to Google drive (less likely since you followed tutorial)
2) The variable "public static final String EXISTING_FILE_ID" in the BaseDemoActivity.java in the sample code is not updated with the latest EXISTING_FILE_ID that you have in your Google drive. Every time you delete or add the file the ID changes even if the file name is not changed. This is more likely cause of your error. I assume you know how to find the EXISTING_FILE_ID of any file in Google drive as given in the Google tutorials.
All the best!

Cannot access Google Drive file

I am making a notepad app using Google Drive. Whenever user creates a file in the app, it also creates a file in the Google Drive. And user can enter the text and save the file, the unsaved text will get committed whenever the internet is available. I am managing the update and create processes within the app using the DriveId.
If the user wants to use the files with the alternative device using my app, for that I also have the option called DriveId import. By clicking the option DriveId import user will be prompted with the input box for entering the existing DriveId. Using the DriveId I thought of opening the files, But it was giving an error.
Then I saw an answer given in this SO which clearly says DriveId can be used only inside the app and device which created the file.
I also found a similar question like mine in here SO But I can’t get my problem solved. I have taken ResourceId using result.getDriveFolder().getDriveId().getResourceId()
How to read the data’s programmatically using the ResourceID? As said in the above answer here I don’t want to change the track and go into Drive REST API. Is there a way that I can read the data using Google Drive Android API ? I have done all the development process, but in the ending when I try to access from other device it is giving the error. Totally struck.
If I can only read the data using REST API any simple code will be appreciated. Thanks in advance.
Finally Solved the DriveId Issue without REST API.
To get DriveId on the alternative device. You will need resourceId. You can use following code:-
String resourseId = "xxxxxxxxxxxxx"
Drive.DriveApi.fetchDriveId(mGoogleApiClient,resourseId).setResultCallback(idCallBack);
private ResultCallBack<DriveApi.DriveResult> idCallBack = new ResultCallback<DriveApi.DriveIdResult>() {
#Override
public void onResult(DriveApi.DriveIdResult driveIdResult) {
msg.Log("onResult");
DriveId id = driveIdResult.getDriveId(); //Here you go :)
}
}

Google Drive Android API - Access app-created files and folders across devices

Using the new GDAA, as I understand it, in order to access a folder and its contents you have to have the folder's DriveId - because this insures that an app can only access the content it has created itself.
Now, my app uploads files to the user's Google Drive account (pictures) in a custom folder. When this folder is first created I save the DriveId of the folder to Shared Preferences so I can access the folder later for more uploads. My problem is that I would like the user to be able to access the pictures from multiple devices (so he/she e.g. can look at pictures uploaded from his/her phone while being on a tablet and vice versa), but this I cannot do without having the folder's DriveId on both devices. The only solution I can think of is sharing the DriveId between user's devices via some cloud service, but this seems awfully inconvenient for the purpose.
Any thoughts?
Shamelessly promoting myself to Cheryl's sidekick, I can give you some specific points, since I've run through this gauntlet before. This is what I did:
Create a unique 'root' for my app in the system root - "MyStupidAppRoot'. Here you'll hit the main challenge since you are creating it by name and if you base the creation on it's non-existence, you may not reliably be able to check it. But it is getting better, see SO 22382099 and SO 22515028.
Once you have a reliable anchor 'MyStupidAppRoot', you can create AppFolder mentioned by Cheryl (not available yet) or create you own visible file (again, the unique creation challenge) that can keep anything you want. Like for instance all your PREFERENCE strings. I even got so brave as to store full SQLite DB file there. It is possible since you write a byte[] buffer to a file.
Than, any other device with you app can find 'MyStupidAppRoot', get the resource file from there and read it.
It should be noted that the main difference between you own folder/file and the AppFolder is, that user's can't read the contents of an AppFolder, but can still delete it.
Here's how you can write byte[] buffer to a file. It is the 'await' version to make it simple, but there is an async version 'createFileAsync()' here.
public DriveFile createFileWait(DriveFolder fldr, String name, String mime, byte[] buff) {
DriveFile drvFile = null;
if (isConnected()) try {
ContentsResult rslt = Drive.DriveApi.newContents(_gac).await();
if (rslt.getStatus().isSuccess()) {
Contents cont = rslt.getContents();
cont.getOutputStream().write(buff);
MetadataChangeSet meta = (mime == null) ?
new MetadataChangeSet.Builder().setTitle(name).build() :
new MetadataChangeSet.Builder().setTitle(name).setMimeType(mime).build();
drvFile = fldr.createFile(_gac, meta, cont).await().getDriveFile();
}
} catch (Exception e) {}
return drvFile;
}
About the IDs:
The DriveId you mention above is an object, that can be turned into 2 different strings as discussed in SO 21800257. It is up to you which one you choose. The long one from 'encodeToString()' is easier to turn back into DriveId by 'decodeFromString()', the shorter one can be easily recognized in the http address, but takes async / await method to get back the DriveId - fetchDriveId().
You don't necessarily have to have the DriveId, its just the most sure-fire way to do it since it uniquely identifies the folder. You can also query based on the title to try to find the same folder. Assuming that the web and Android app share an app id, both should be able to access the same files.
One easy option for sharing state between apps is to make use of the newly launched App Folders (called App Data Folders on the web.) This is a hidden folder where you can store files specific to your app. Its not yet in the Android docs, but it should show up there as soon as the rollout of Google Play Services 4.3 is released. See http://android-developers.blogspot.com/2014/03/google-play-services-43.html

Files.List is empty but only when signed with release certificate

I am experiencing an infuriating bug with the Google Drive API for Android. Specifically, in presenting an "Open File from Google Drive" UI to the user, I am querying for the files in a folder using the following code:
String query = "'" + folderId + "' in parents and trashed = false";
Files.List request = service.files().list().setQ(query);
FileList files = request.execute();
java.util.List<File> items = files.getItems(); // Returns null!
When I run this code directly from Eclipse (i.e. signed with the debug certificate), there is no problem. However, when I build and run a signed APK using our release certificate, the call to files.getItems() returns null.
Oddly, while getItems() returns null, there is definitely data returned from the Drive server, because calling files.toString() shows a mess of Json, amongst which I have identified the filenames of a few of my files, so I don't think the problem is an authentication issue.
Also, using different folderIds in the query string does not seem to make any difference to the Json returned by toString(). Based on aggressive GC activity in logcat, it looks like the server might be returning ALL files in my drive, especially odd considering getItems() returns null.
Note that the code works absolutely fine when signed with the debug certificate, as I am able to browse my Google Drive perfectly from within my app.
As per earlier comment:
Any chance Proguard kicks in when exporting your signed APK? If you rely on i.e. variable names to map the JSON onto POJOs, this is likely to brake without the appropriate Proguard exclusions/rules. Have a look in your project.properties file and comment out any lines in the form of proguard.config=<file_name>. After that, export another signed APK and retest.

Android expansion apk problems

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).

Categories

Resources