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!
Related
My key is restricted using package name and SHA1, still Google Play store shows this warning.
Any idea why it is showing like this. I defined my API key in build.gradle file and using it from there.
As per google's recommendation putting restrictions such as specifying the package name and also the SHA-1 key is the way to go.
It has been explained here: https://cloud.google.com/docs/authentication/api-keys#securing_an_api_key
Now, the problem here is that whatever you do your API key will end up in the codebase i.e if you specify it outside your codebase (via some properties file) but pass it in via the BuildConfig field during the build phase (the whole key is visible to someone decompiling your code as it is now part of BuildConfig class file) or you split it up and concatenate in the codebase (the split keys are still visible and anyone can concatenate them by seeing the usage to get the final key from a decompiled apk).
The split key version will get rid of the warning in the Play Console, but the key is still exposed.
My suggested solution thus would be to encode your API key and pass that around your codebase. Just before using it you decode it back.
A very simple example can be:
Please use a better encoding algo and not this, this is for demonstration purpose only. Here we are using Base64 encoding.
import android.util.Base64
fun main() {
// API Key = "123456ABC"
val myEncodedApiKey = "MTIzNDU2QUJD" // Should be passed via BuildConfig
val decodedApiKey = Base64.decode(myEncodedApiKey, Base64.DEFAULT)
// Now use `decodedApiKey` in your codebase.
val decodedApiKeyString = String(decodedApiKey)
}
Why is this better?
Your key is not exactly the same as in your GCP project.
The play console when it scans your codebase, cannot match it back to your GCP project API keys. Thus no warnings.
Update (clarification on using the google-services.json file for API key):
The solution to use the API key from google-services.json isn't quite valid. google-services.json is generated file usually if you connect your firebase account. The API key defined there has a different restriction model. The one you define in your GCP project is different, allowing you to pass in package name and an SHA-1 key as well as restricted to a specific kind of API access such as Youtube only access. So if one was to use the API keys from google-services.json then you are essentially not using the restrictions you set up in your GCP account. GCP accounts do not generate google-services.json file.
To bring into perspective here is an official doc from Google for setting up Youtube API which uses GCP project defined API keys and in the docs, it mentions to directly put the keys in the code. (which is anyways wrong as it is exposed, but that's Google for you).
https://developers.google.com/youtube/android/player/setup
Nowhere in any docs, it is referred to use google-services.json file for retrieving API keys.
Got Google Play Ads? It would appear that, at least as of 8-1-19, an api key that triggers this is embedded within the full google play services ads library. That is--
implementation 'com.google.android.gms:play-services-ads:18.1.1'
triggers the alert, but
implementation 'com.google.android.gms:play-services-ads-lite:18.1.1'
which removes some code that already exists in the play store, does not. The error itself cites an obfuscated method, which I tracked down in the apk to:
com/google/android/gms/internal/ads/[obfuscatedstring]/<clinit>()
This appears to set a bunch of constants. One of which is called gads:safe_browsing:api_key
Just in case I'm wrong and this isn't in everyone's code I won't reproduce it here, but it sure looks to me like a GCP key that might trigger the issue. It is 40 characters and this identical key can be found elsewhere on the Internet.
My guess is, if you're using the play-services-ads library (as opposed to the Firebase library), it is probably seeing this string and sounding an alarm.
You can remove this warning by split your keys into 4 parts like this
public static final String API_KEY_PART_1 = "Asdsdfdd-";
public static final String API_KEY_PART_2 = "dfsFdsdsFdd";
public static final String API_KEY_PART_3 = "Pgdhs_SfSfs";
public static final String API_KEY_PART_4 = "fdfDDSD";
and use it by concatenate Strings
.attest(nonce.getBytes(), Constants.API_KEY_PART_1+Constants.API_KEY_PART_2+Constants.API_KEY_PART_3+Constants.API_KEY_PART_4)
NOTE: Make sure you restrict your API key to Applications Access only. otherwise if someone decompile your apk and use your api key then it may be increase your billing.
Restric your API's Key with SHA & Package name click here for details
Sorry, a bit late to the game here...
Follow the below steps:
The Firebase console will help you download the google-services.json. In addition, the Quickstart guides for most APIs have instructions for generating this file. Once you have downloaded the google-services.json file,copy it into the app/ folder of your Android Studio project, or into the app/src/{build_type} folder if you are using multiple build types.
The Google Cloud Console will help you to enable the GCP(ex: Places API,Map SDK for android etc.) in API Library section by choosing the project which was created in the Firebase console.
you could get the api key with:
activity.getResources().getString(R.string.google_api_key);
String key name is "google_api_key" for getting the API Key.
FYI, The google-services.json file gets parsed and its values get added to an xml (path: app/build/generated/res/google-services/{build_type}/values/values.xml) which you have access to: https://developers.google.com/android/guides/google-services-plugin
Sorry, a bit late as my previous answer was just bypass the warning after some research i found an appropriate fix for this problem
you can get api key from google json file.
The google-services.json file gets parsed and its values get added to an xml which you have access to all the elements of json as Android resources in your Java code click here for details
Below is an example to access the google api key from json file :
activity.getResources().getString(R.string.google_api_key);
better to use firbase firestore database to store your api key and access it. Firestore is safe and highly secure as its a product of Google.
and access it using this code
mDatabase.collection(COLLECTION_NAME)
.document("DOCUMENT_NAME")
.get()
.addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
Log.d(TAG, "google_places_api_key: " + documentSnapshot.get("google_places_api_key") + "");
}
});
This method is safe and secure and your api key will not access by any Man in the middle attack and of course play store will allow this method.
it may be late, but will help some of the guys,
There is no link between Firebase JSON configuration file and google
maps integration API key
Firebase Clarification
If you integrate Firebase in your app, it requires the JSON configuration to be placed in your code, and this is the only way to connect the app with Firebase.
but in Google maps, it is totally different. It will provide the key - to place in our code but not hard-coded or as it is,
Solution:
I have combined both solutions to work around, from Nishant and Sahil Arora, to encode the key into something else and split it into pieces to make it somehow difficult to decode back, I am splitting into the four-part, you can as much as you feel good:
Decode your ApiKey
// getEncodedApiKey result encoded key,
// splitted into four the part
String thePartOne = "encodedPartOneMaybe";
String thePartTwo = "encodedPartNextMaybe";
String thePartThree = "encodedPartNextMaybe";
String thePartFour = "encodedPartLast";
public String getSplitedDecodedApiKey(){
return new String(
Base64.decode(
Base64.decode(
thePartOne +
thePartTwo +
thePartThree +
thePartFour ,
Base64.DEFAULT),
Base64.DEFAULT));
}
Encode your ApiKey
//just to encode the string before splitting it into the pieces
//and remove this method from the source code
public void getEncodedApiKey(){
String plainApiKey = "xiosdflsghdfkj"; //
String encodedApiKey = new String(
Base64.encode(
Base64.encode(plainApiKey.getBytes(),
Base64.DEFAULT),
Base64.DEFAULT));
Log.i(TAG, "getEncodedApiKey: " + encodedApiKey);
}
Had the same issue for a long time, in my case while initializing the Youtube Player with google api key.
It didn't go away even after restricting the API Key with SHA & Package name.
However, after migrating to androidX the Security alert is gone in Google Play Console.
This error comes when developers write the API keys exposed as hardcoded strings in Java or XML files. Use it inside a strings.xml. And reference it as mentioned below. It will get resolved automatically.
geoApiContext = new GeoApiContext.Builder()
.apiKey(getResources().getString(R.string.maps_api_key))
.build();
Add API_KEY in gradle.properties
API_KEY = **ADD_KEY_HERE**
Add the below line in the build.gradle(app) within the defaultConfig.
buildConfigField('String','GCP_API_KEY',API_KEY)
In code, we can use BuildConfig.GCP_API_KEY
I also encounter your app contains exposed gcp api key error issue on google play console and i tried following Steps :
Splitting the api key
moving the api key to string.xml file
but nothing worked as expected.
To Resolve your app contains exposed gcp api key error move your API key to android manifest file.
I move the API key to Android Manifest file and used that meta data to retrieve the API key from manifest file for initialisation.
Steps to use the api key from manifest file.
Add the API key in Manifest file
<meta-data
android:name="api_key"
android:value="api_key_value" />
Now it's time to use the api key from manifest file.
ApplicationInfo info = getJavaActivity().getPackageManager().getApplicationInfo(getJavaActivity().getPackageName(),getJavaActivity().getPackageManager().GET_META_DATA);
String apiKeyValue = info.metaData.getString("api_key"); // use the apiKeyValue to initialise your SDK
happy Coding :)
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 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 :)
}
}
As per the given link: https://developers.google.com/wallet/objects/savetowalletnative
I am trying to run the sample application, but in this below method, I am not able to find CreateWalletObjectsRequest. It says:
"CreateWalletObjectsRequest cannot be resolved to a type"
public void saveToWallet(View view){
LoyaltyWalletObject wob = generateObject();
CreateWalletObjectsRequest request = new CreateWalletObjectsRequest(wob);
Wallet.createWalletObjects(googleApiClient, request, SAVE_TO_WALLET);
}
Google hasn't opened Save to Wallet api for everyone yet. We had to get a special Google Play Services AAR directly from Google, along with a lot of help from a Googler to get it functioning (the sample is out of date).
As of now, you'll need to work with your Google contact to get the second-party library to integrate Android Pay. Once you get the aar file, you can import it into your project and add it as a dependency.
Now, the 'createWalletObjects' method call in your question has shifted in its location and signature; its now as follows:
Wallet.WalletObjects.createWalletObjects(googleApiClient, request, SAVE_TO_WALLET);
The SAVE_TO_WALLET is the integer request code that you'll identify the request in your onActivityResult(). Creating the request as such is described in the "Create an object" section of https://developers.google.com/save-to-android-pay/guides/android/add-button-to-your-app
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