Receiving completion events

CompletionEvent events are callbacks you receive when updates made to DriveFile objects, such as file creation or edits to existing files, are propagated to the server. These events are received even when the application is not running.

You can use completion events to take actions upon notification that changes have been propagated to the server or if a change has failed to be applied. For example, you can delete the local version of a file to free up disk space once you know changes to the file have successfully propagated to the server.

To receive a CompletionEvent, you must request it when calling the DriveResourceClient.commitContents method to commit your changes. Also you must create a service extending the DriveEventService and override its onCompletion method, where you handle the CompletionEvent.

Implement completion events

Completion events must be requested and handled once returned.

Request completion events

Create an ExecutionOptions object and call its setNotifyOnCompletion method with the value true. Then pass the ExecutionOptions object to your DriveResourceClient.commitContents method call.

ExecutionOptions executionOptions =
        new ExecutionOptions.Builder()
                .setNotifyOnCompletion(true)
                .setConflictStrategy(
                        ExecutionOptions.CONFLICT_STRATEGY_KEEP_REMOTE)
                .build();
return getDriveResourceClient().commitContents(
        driveContents, null, executionOptions);

Handle completion events

Create a class that extends DriveEventService and overrides the DriveEventService.onCompletion method. This is where you will receive the notification of the action’s completion and handle the result which may be either success, failure or conflict.

public class MyDriveEventService extends DriveEventService {
    private static final String TAG = "MyDriveEventService";
    private ExecutorService mExecutorService;

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

    @Override
    public synchronized void onDestroy() {
        super.onDestroy();
        // ...
    }

    @Override
    public void onCompletion(CompletionEvent event) {
        boolean eventHandled = false;
        switch (event.getStatus()) {
            case CompletionEvent.STATUS_SUCCESS:
                // Commit completed successfully.
                // Can now access the remote resource Id
                // ...
                break;
            case CompletionEvent.STATUS_FAILURE:
                // Handle failure....
                // Modified contents and metadata failed to be applied to the server.
                // They can be retrieved from the CompletionEvent to try to be applied later.
                // ...
                break;
            case CompletionEvent.STATUS_CONFLICT:
                // Handle completion conflict.
                // ...
                break;
        }

        if (eventHandled) {
            event.dismiss();
        }
    }
}

Define your DriveEventService in your AndroidManifest.xml file as a child of the application element.

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <service android:name=".MyDriveEventService"
             android:exported="true" >
        <intent-filter>
            <action android:name="com.google.android.gms.drive.events.HANDLE_EVENT" />
        </intent-filter>
    </service>
</application>

Handling the completion event status

Completion events can return one of three status types:

Success

This status indicates that the action associated with this event has been applied to the server successfully.

Failure

This status indicates that the action associated with this event has permanently failed to be applied to the server. The content or metadata that failed to be applied to the server can be retrieved using the CompletionEvent.getModifiedContentsInputStream or CompletionEvent.getModifiedMetadataChangeSet methods, allowing you to try to apply them to the server at a later time.

Conflict

This status indicates that the action associated with this event has failed because of a conflict. The app must resolve the conflict. The CompletionEvent provides only the base and modified versions of the conflicted file; the app can access the current version on the server in the usual way.

By default, the Drive Android API automatically overwrites the server version with the local version when there is a conflict. To resolve the conflict in your app, however, you can use the ExecutionOptions.Builder.setConflictStrategy method, passing in ExecutionOptions.CONFLICT_STRATEGY_KEEP_REMOTE to store the base and modified versions of the content. When you use ExecutionOptions.CONFLICT_STRATEGY_KEEP_REMOTE, you must also request completion notifications. After Drive detects a conflict, you can use the CompletionEvent to access the locally committed changes that caused the conflict, which then allows you to resolve those conflicts yourself.

Conflict detection requires that you use the DriveResourceClient.reopenContentsForWrite method to commit modifications. You can only call the DriveResourceClient.reopenContentsForWrite method on a DriveContents object that was initially opened in MODE_READ_ONLY mode.

Task<DriveContents> reopenTask =
        getDriveResourceClient().reopenContentsForWrite(mDriveContents);

As mentioned above conflict detection also requires that you set the conflict strategy of the ExecutionOptions object to ExecutionOptions.CONFLICT_STRATEGY_KEEP_REMOTE.

DriveContents driveContents = task.getResult();
OutputStream outputStream = driveContents.getOutputStream();
try (Writer writer = new OutputStreamWriter(outputStream)) {
    writer.write(mEditText.getText().toString());
}
// ExecutionOptions define the conflict strategy to be used.
ExecutionOptions executionOptions =
        new ExecutionOptions.Builder()
                .setNotifyOnCompletion(true)
                .setConflictStrategy(
                        ExecutionOptions.CONFLICT_STRATEGY_KEEP_REMOTE)
                .build();
return getDriveResourceClient().commitContents(
        driveContents, null, executionOptions);

A conflict occurs when the local version of the base file contents do not match the current file contents on the server. In this case, a simple merge is not possible and a Conflict status is returned. Your application can use the base, current, and modified versions to create a new merged version and commit it to the server.

// A new DriveResourceClient should be created to handle each new CompletionEvent since each
// event is tied to a specific user account. Any DriveFile action taken must be done using
// the correct account.
GoogleSignInOptions.Builder signInOptionsBuilder =
        new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestScopes(Drive.SCOPE_FILE)
                .requestScopes(Drive.SCOPE_APPFOLDER);
if (mConflictedCompletionEvent.getAccountName() != null) {
    signInOptionsBuilder.setAccountName(mConflictedCompletionEvent.getAccountName());
}
GoogleSignInClient signInClient =
        GoogleSignIn.getClient(mContext, signInOptionsBuilder.build());
signInClient.silentSignIn()
        .continueWith(mExecutorService,
                (Continuation<GoogleSignInAccount, Void>) signInTask -> {
                    mDriveResourceClient = Drive.getDriveResourceClient(
                            mContext, signInTask.getResult());
                    mBaseContent = ConflictUtil.getStringFromInputStream(
                            mConflictedCompletionEvent.getBaseContentsInputStream());
                    mModifiedContent = ConflictUtil.getStringFromInputStream(
                            mConflictedCompletionEvent
                                    .getModifiedContentsInputStream());
                    return null;
                })
        .continueWithTask(mExecutorService,
                task -> {
                    DriveId driveId = mConflictedCompletionEvent.getDriveId();
                    return mDriveResourceClient.openFile(
                            driveId.asDriveFile(), DriveFile.MODE_READ_ONLY);
                })
        .continueWithTask(mExecutorService,
                task -> {
                    mDriveContents = task.getResult();
                    InputStream serverInputStream = task.getResult().getInputStream();
                    mServerContent =
                            ConflictUtil.getStringFromInputStream(serverInputStream);
                    return mDriveResourceClient.reopenContentsForWrite(mDriveContents);
                })
        .continueWithTask(mExecutorService,
                task -> {
                    DriveContents contentsForWrite = task.getResult();
                    mResolvedContent = ConflictUtil.resolveConflict(
                            mBaseContent, mServerContent, mModifiedContent);

                    OutputStream outputStream = contentsForWrite.getOutputStream();
                    try (Writer writer = new OutputStreamWriter(outputStream)) {
                        writer.write(mResolvedContent);
                    }

                    // It is not likely that resolving a conflict will result in another
                    // conflict, but it can happen if the file changed again while this
                    // conflict was resolved. Since we already implemented conflict
                    // resolution and we never want to miss user data, we commit here
                    // with execution options in conflict-aware mode (otherwise we would
                    // overwrite server content).
                    ExecutionOptions executionOptions =
                            new ExecutionOptions.Builder()
                                    .setNotifyOnCompletion(true)
                                    .setConflictStrategy(
                                            ExecutionOptions
                                                    .CONFLICT_STRATEGY_KEEP_REMOTE)
                                    .build();

                    // Commit resolved contents.
                    MetadataChangeSet modifiedMetadataChangeSet =
                            mConflictedCompletionEvent.getModifiedMetadataChangeSet();
                    return mDriveResourceClient.commitContents(contentsForWrite,
                            modifiedMetadataChangeSet, executionOptions);
        })
        .addOnSuccessListener(aVoid -> {
            mConflictedCompletionEvent.dismiss();
            Log.d(TAG, "resolved list");
            sendResult(mModifiedContent);
        })
        .addOnFailureListener(e -> {
            // The contents cannot be reopened at this point, probably due to
            // connectivity, so by snoozing the event we will get it again later.
            Log.d(TAG, "Unable to write resolved content, snoozing completion event.",
                    e);
            mConflictedCompletionEvent.snooze();
            if (mDriveContents != null) {
                mDriveResourceClient.discardContents(mDriveContents);
            }
        });

Send feedback about...

Drive API for Android
Drive API for Android
Need help? Visit our support page.