Java tutorial
/* * Copyright (c) 2016-2017, Michael A. Updike All rights reserved. * Licensed under Apache 2.0 * https://opensource.org/licenses/Apache-2.0 * https://github.com/Pushy-Clipboard/pushy-android/blob/master/LICENSE.md */ package com.weebly.opus1269.clipman.backup; import android.accounts.Account; import android.annotation.SuppressLint; import android.content.Context; import android.text.TextUtils; import com.google.android.gms.common.api.Scope; import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential; import com.google.api.client.http.ByteArrayContent; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.services.drive.Drive; import com.google.api.services.drive.DriveScopes; import com.google.api.services.drive.model.File; import com.google.android.gms.auth.api.signin.GoogleSignIn; import com.google.android.gms.auth.api.signin.GoogleSignInAccount; import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.Tasks; import com.google.api.services.drive.model.FileList; import com.weebly.opus1269.clipman.R; import com.weebly.opus1269.clipman.app.App; import com.weebly.opus1269.clipman.app.Log; import com.weebly.opus1269.clipman.db.entity.Backup; import com.weebly.opus1269.clipman.model.BackupContents; import com.weebly.opus1269.clipman.model.User; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import androidx.annotation.NonNull; /** Singleton to manage interactions with Google Drive */ public class DriveServiceHelper { // OK, because mAppCtxt is the global Application context /** Static instance */ @SuppressLint("StaticFieldLeak") private static DriveServiceHelper sInstance; /** Global Application Context */ private final Context mAppCtxt; /** Timeout for task completion */ private final int WAIT_TIME_SECS = 20; /** Mime type of backups */ private final String MIME_TYPE = "application/zip"; /** No permissions for Drive */ private final String ERR_PERMISSION; /** Error accessing Drive API */ private final String ERR_INTERNAL; /** Class Indentifier */ private final String TAG = this.getClass().getSimpleName(); /** App folder on Drive */ private final String APP_FOLDER = "appDataFolder"; private DriveServiceHelper(@NonNull Context context) { mAppCtxt = context.getApplicationContext(); ERR_INTERNAL = mAppCtxt.getString(R.string.err_internal_drive); ERR_PERMISSION = mAppCtxt.getString(R.string.err_drive_scope_denied); } /** * Lazily create our instance * @param context any old context */ public static DriveServiceHelper INST(@NonNull Context context) { synchronized (DriveServiceHelper.class) { if (sInstance == null) { sInstance = new DriveServiceHelper(context); } return sInstance; } } /** No permission for our appFolder */ public boolean noAppFolderPermission() { final GoogleSignInAccount account = User.INST(mAppCtxt).getGoogleAccount(); return ((account == null) || !GoogleSignIn.hasPermissions(account, new Scope(DriveScopes.DRIVE_APPDATA))); } /** * Retrieve all the backups in our appFolder * @return list of backups */ @NonNull public List<Backup> getBackups() throws ExecutionException, InterruptedException, TimeoutException, DriveException { final List<Backup> backups = new ArrayList<>(); final Drive service = getDriveService(); final FileList fileList = Tasks.await(listFiles(service), WAIT_TIME_SECS, TimeUnit.SECONDS); Log.logD(TAG, "got list of backups"); final List<File> files = fileList.getFiles(); for (File file : files) { final Backup backup = new Backup(mAppCtxt, file); backups.add(backup); } return backups; } /** * Create a new backup * @param filename name of file * @param data contents of file * @return new backup */ @NonNull Backup createBackup(final String filename, final byte[] data) throws ExecutionException, InterruptedException, TimeoutException, DriveException { Backup backup; final Drive service = getDriveService(); // create a new file File driveFile = Tasks.await(createFile(service, filename, Backup.getAppProperties(mAppCtxt)), WAIT_TIME_SECS, TimeUnit.SECONDS); if (driveFile == null) { final String err = mAppCtxt.getString(R.string.err_create_backup); throw new DriveException(err); } final String fileId = driveFile.getId(); // add the contents driveFile = Tasks.await(saveContents(service, fileId, filename, data), WAIT_TIME_SECS, TimeUnit.SECONDS); backup = new Backup(mAppCtxt, driveFile); Log.logD(TAG, "created backup"); return backup; } /** * Update a backup * @param fileId file to update * @param filename file to update * @param data update data */ void updateBackup(@NonNull final String fileId, String filename, @NonNull final byte[] data) throws ExecutionException, InterruptedException, TimeoutException, DriveException { final Drive service = getDriveService(); // add the contents Tasks.await(saveContents(service, fileId, filename, data), WAIT_TIME_SECS, TimeUnit.SECONDS); Log.logD(TAG, "updated backup"); } /** * Delete a backup * @param fileId name of file */ void deleteBackup(final String fileId) throws ExecutionException, InterruptedException, TimeoutException, DriveException { final Drive service = getDriveService(); Tasks.await(deleteFile(service, fileId), WAIT_TIME_SECS, TimeUnit.SECONDS); Log.logD(TAG, "deleted backup"); } /** * Get the contents of a backup * @param fileId file to retrieve * @return contents of a backup */ @NonNull BackupContents getBackupContents(final String fileId) throws ExecutionException, InterruptedException, TimeoutException, DriveException { final Drive service = getDriveService(); final BackupContents backupContents = Tasks.await(getContents(service, fileId), WAIT_TIME_SECS, TimeUnit.SECONDS); Log.logD(TAG, "got contents of file"); return backupContents; } /** * Create a zip file in the user's App directory. */ private Task<File> createFile(Drive service, String fileName, Map<String, String> appProperties) { return Tasks.call(App.getExecutors().networkIO(), () -> { File metadata = new File().setParents(Collections.singletonList(APP_FOLDER)).setMimeType(MIME_TYPE) .setName(fileName).setAppProperties(appProperties); final File driveFile = service.files().create(metadata) .setFields("id, name, modifiedByMeTime, appProperties").execute(); if (driveFile == null) { throw new IOException("Null result when requesting file creation."); } return driveFile; }); } /** * Get the files in the user's App Data folder */ private Task<FileList> listFiles(Drive service) { return Tasks.call(App.getExecutors().networkIO(), () -> { final FileList files = service.files().list().setSpaces("appDataFolder") .setFields("files(id, name, modifiedByMeTime, appProperties)").execute(); if (files == null) { throw new IOException("Null result when requesting file list."); } return files; }); } /** * Get the contents of a backup */ private Task<BackupContents> getContents(Drive service, String fileId) { return Tasks.call(App.getExecutors().networkIO(), () -> { final BackupContents backupContents = new BackupContents(); BufferedInputStream bis = null; try { InputStream is = service.files().get(fileId).executeMediaAsInputStream(); bis = new BufferedInputStream(is); BackupHelper.INST(mAppCtxt).extractFromZipFile(bis, backupContents); } finally { if (bis != null) { bis.close(); } } return backupContents; }); } /** * Seve a file and its contents */ private Task<File> saveContents(Drive service, String fileId, String filename, byte[] contents) { return Tasks.call(App.getExecutors().networkIO(), () -> { // Create a File containing any metadata changes final File metadata = new File().setName(filename); // Convert content to an AbstractInputStreamContent instance final ByteArrayContent contentStream = new ByteArrayContent(MIME_TYPE, contents); // Update the metadata and contents return service.files().update(fileId, metadata, contentStream) .setFields("id, name, createdTime, modifiedByMeTime, appProperties").execute(); }); } /** * Delete a file on Drive */ private Task<Void> deleteFile(Drive service, String fileId) { return Tasks.call(App.getExecutors().networkIO(), () -> { return service.files().delete(fileId).execute(); }); } /** Check permissions for our appFolder */ private void checkAppFolderPermission() throws DriveException { if (noAppFolderPermission()) { throw new DriveException(ERR_PERMISSION); } } /** get the DriveService */ @NonNull private Drive getDriveService() throws DriveException { checkAppFolderPermission(); Drive service = null; final HttpTransport httpTransport = new NetHttpTransport(); final JacksonFactory jsonFactory = new JacksonFactory(); final String email = User.INST(mAppCtxt).getEmail(); final Collection<String> scopes = new ArrayList<>(Collections.singletonList(DriveScopes.DRIVE_APPDATA)); if (!TextUtils.isEmpty(email)) { // Use the authenticated account to sign in to the Drive service. GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(mAppCtxt, scopes); credential.setSelectedAccount(new Account(email, "com.google")); service = new Drive.Builder(httpTransport, jsonFactory, credential) .setApplicationName(mAppCtxt.getString(R.string.app_name)).build(); } if (service == null) { throw new DriveException(ERR_INTERNAL); } return service; } }