How to push a video format file to the Google Drive from an Android app?

Viewed 189

I'm trying to make my Android app to upload a Video format file to my Google Drive account. The whole code:

import android.Manifest;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;

import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.ExponentialBackOff;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.FileList;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URLConnection;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static net.fortuna.ical4j.util.ResourceLoader.getResourceAsStream;

public class ManagerUploadFragment extends Fragment {
    private final static int APP_PERMISSION_REQUEST = 100;
    private final static int ACTIVITY_CHOOSE_FILE = 1;

    private static final String APPLICATION_NAME = "Google Drive API Java Quickstart";
    private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
    private static final String TOKENS_DIRECTORY_PATH = "tokens";


    private static final List<String> SCOPES = Collections.singletonList(DriveScopes.DRIVE_METADATA_READONLY);
    private static final String CREDENTIALS_FILE_PATH = "/credentials.json";


    private View fragmentView;

    private Button uploadButton;

    private Drive driveService;

    // Required empty public constructor for fragments
    public ManagerUploadFragment() {}

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        fragmentView = inflater.inflate(R.layout.fragment_manager_upload, container, false);

        uploadButton = (Button) fragmentView.findViewById(R.id.upload_view_browse_button);

        // Set upload button
        uploadButton.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {
                uploadImage();
            }
        });

        // Connect to drive
        try {
            driveService = createService();
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return fragmentView;
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == APP_PERMISSION_REQUEST) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                for (int grantResult : grantResults) {
                    if (grantResult != PackageManager.PERMISSION_GRANTED) {
                        Toast.makeText(getActivity(), R.string.enable_required_permissions, Toast.LENGTH_LONG).show();
                        return; // Permissions were not granted
                    }
                }
            }
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == AppCompatActivity.RESULT_OK) {
            if (requestCode == ACTIVITY_CHOOSE_FILE && getActivity() != null) {
                Uri uri = data.getData();
                System.out.println("URI " + uri.toString());
                if (!(isVideoFile(uri))) {
                    Toast.makeText(getActivity(), "File is not in a valid video format", Toast.LENGTH_LONG).show();
                    return;
                }
                if (getFileSizeInMB(uri.getPath()) > 1) {
                    Toast.makeText(getActivity(), "File size is bigger than 20MB", Toast.LENGTH_LONG).show();
                    return;
                }
            }
        }
    }

    private static long getFileSizeInMB(final String file_path) {
        final File file = new File(file_path);
        long sizeInBytes = file.length();
        return sizeInBytes / (1024 * 1024);
    }

    private boolean isVideoFile(Uri uri) {
        ContentResolver cR = getContext().getContentResolver();
        String type = cR.getType(uri);
        return type != null && (type.equals("video/mp4") || type.equals("video/avi"));
    }

    private void uploadImage() {
        if (getActivity() != null) {
            final int writeStoragePermission = ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
            final int readStoragePermission = ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE);
            if (writeStoragePermission != PackageManager.PERMISSION_GRANTED || readStoragePermission != PackageManager.PERMISSION_GRANTED) {
                final String[] permissions = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE};
                ActivityCompat.requestPermissions(getActivity(), permissions, APP_PERMISSION_REQUEST);
                return;
            }

            Intent chooseFile;
            Intent intent;
            chooseFile = new Intent(Intent.ACTION_GET_CONTENT);
            chooseFile.addCategory(Intent.CATEGORY_OPENABLE);
            chooseFile.setType("*/*");
            intent = Intent.createChooser(chooseFile, "Choose a file");
            startActivityForResult(intent, ACTIVITY_CHOOSE_FILE);
        }
    }

    private Drive createService() throws GeneralSecurityException, IOException {
        final NetHttpTransport HTTP_TRANSPORT = new com.google.api.client.http.javanet.NetHttpTransport();
        Drive mService = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(getContext(), HTTP_TRANSPORT))
                .setApplicationName(APPLICATION_NAME)
                .build();

        // Print the names and IDs for up to 10 files.
        FileList result = mService.files().list()
                .setPageSize(10)
                .setFields("nextPageToken, files(id, name)")
                .execute();
        List<com.google.api.services.drive.model.File> files = result.getFiles();
        if (files == null || files.isEmpty()) {
            System.out.println("No files found.");
        } else {
            System.out.println("Files:");
            for (com.google.api.services.drive.model.File file : files) {
                System.out.printf("%s (%s)\n", file.getName(), file.getId());
            }
        }
        return mService;
    }

    private static Credential getCredentials(final Context context, final NetHttpTransport HTTP_TRANSPORT) throws IOException {
        // Load client secrets.
        InputStream in = context.getClass().getResourceAsStream(CREDENTIALS_FILE_PATH);
        if (in == null) {
            throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH);
        }
        GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));

        // Build flow and trigger user authorization request.
        GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
                HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
                .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
                .setAccessType("offline")
                .build();
        LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
        return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
    }
}

In order to upload the video to the Dirve, I need to connect to the Drive first. I followed the steps on how to create to use the API (docs). Then I followed this example, which should show how to connect to the Google Drive and list the content.

But, before even pressing the "upload" button, I get an error to connect to the Google Drive:

java.io.IOException: unable to create directory: /tokens

On line:

GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
                HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
                .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
                .setAccessType("offline")
                .build();

Please correct me if I'm wrong, but as I understand, it tries to create the token directory locally and not in the remote - but why does it need it? I just want to be able to push the video I got from the user to the Drive. How can I connect to the Google Drive and push the video to the Drive (if you will take a look into a code, I have the uri of the file, that should be pushed to the Drive).

1 Answers

I fell victim to this as well. Unfortunately, the tutorial you're following is for a "simple Java command line application" (stated on the first sentence of the quickstart example). So you are correct, the issue is that it tries create the token directory in order to store the access token, which you need in order to make requests to the Google Drive API.

If you are ok with using GoogleSignInClient to initiate authentication, I believe this should help. Start at the "The Future: Using GoogleApi" section.

If you want to follow the low-level protocol instead you can follow this doc on OAuth2.0 for mobile.

I hope this helps or, at the very least, saves some time.

Related