io.v.android.impl.google.services.blessing.BlessingActivity.java Source code

Java tutorial

Introduction

Here is the source code for io.v.android.impl.google.services.blessing.BlessingActivity.java

Source

// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package io.v.android.impl.google.services.blessing;

import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Base64;
import android.util.Log;

import com.google.common.base.Preconditions;
import com.google.common.io.ByteStreams;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.interfaces.ECPublicKey;

import io.v.android.v23.V;
import io.v.v23.context.VContext;
import io.v.v23.security.Caveat;
import io.v.v23.verror.VException;
import io.v.v23.vom.VomUtil;

/**
 * Mints a new Vanadium blessing given the email address and the public key.
 * <p>
 * The provided email address must be already present on the phone.
 */
public class BlessingActivity extends Activity implements ActivityCompat.OnRequestPermissionsResultCallback {
    public static final String TAG = "BlessingActivity";

    private static final int REQUEST_CODE_USER_APPROVAL = 1000;
    private static final int REQUEST_CODE_PICK_ACCOUNT = 1001;

    private static final String STATE_GOOGLE_ACCOUNT = "STATE_GOOGLE_ACCOUNT";
    private static final String STATE_PUBLIC_KEY = "STATE_PUBLIC_KEY";
    private static final String STATE_PREF_KEY = "STATE_PREF_KEY";

    private static final String OAUTH_PROFILE = "https://www.googleapis.com/auth/userinfo.email";
    private static final String OAUTH_SCOPE = "oauth2:" + OAUTH_PROFILE;

    private static final int BASE64_FLAGS = Base64.URL_SAFE | Base64.NO_WRAP;

    /**
     * Serialized {@link ECPublicKey} of the invoking activity.
     */
    public static final String EXTRA_PUBLIC_KEY = "PUBLIC_KEY";
    /**
     * If non-{@code null} and non-empty in the invoking intent, the activity will generate
     * blessings based on the provided account, instead of prompting the user to choose
     * an account.
     */
    public static final String EXTRA_GOOGLE_ACCOUNT = "GOOGLE_ACCOUNT";

    /**
     * If non-{@code null} and non-empty in the invoking intent, this activity will store the
     * generated blessings in default {@link SharedPreferences} under this key.
     */
    public static final String EXTRA_PREF_KEY = "PREF_KEY";

    /**
     * If successful, VOM-encoded blessings will be stored (in byte array format) in the returned
     * intent under the given key.
     */
    public static final String EXTRA_REPLY = "REPLY";

    /**
     * If an error is encountered, the error string will be stored in the returned intent
     * under the given key.
     */
    public static final String EXTRA_ERROR = "ERROR";

    private VContext mBaseContext;
    private String mGoogleAccount = "";
    private ECPublicKey mPublicKey = null;
    private String mPrefKey = "";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBaseContext = V.init(this);
        setFinishOnTouchOutside(false);
        if (savedInstanceState != null) {
            mGoogleAccount = savedInstanceState.getString(STATE_GOOGLE_ACCOUNT);
            mPublicKey = (ECPublicKey) savedInstanceState.getSerializable(STATE_PUBLIC_KEY);
            mPrefKey = savedInstanceState.getString(STATE_PREF_KEY);
            return;
        }

        Intent intent = getIntent();
        if (intent == null) {
            replyWithError("Intent not found.");
            return;
        }
        // Get the public key of the application invoking this activity.
        mPublicKey = (ECPublicKey) intent.getSerializableExtra(EXTRA_PUBLIC_KEY);
        if (mPublicKey == null) {
            replyWithError("Empty blesee public key.");
            return;
        }
        // Get the SharedPreferences key where the blessings are to be stored.  If empty, blessings
        // aren't stored in preferences.
        if (intent.hasExtra(EXTRA_PREF_KEY)) {
            mPrefKey = intent.getStringExtra(EXTRA_PREF_KEY);
        }
        // Get the google email address (if any).
        mGoogleAccount = intent.getStringExtra(EXTRA_GOOGLE_ACCOUNT);
        if (mGoogleAccount == null || mGoogleAccount.isEmpty()) {
            // Prompt the user to choose the google account to use (if more than one).
            Intent accountIntent = AccountManager.newChooseAccountIntent(null, null, new String[] { "com.google" },
                    false, null, null, null, null);
            startActivityForResult(accountIntent, REQUEST_CODE_PICK_ACCOUNT);
            return;
        }
        getBlessing();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mBaseContext.cancel();
    }

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
        savedInstanceState.putString(STATE_GOOGLE_ACCOUNT, mGoogleAccount);
        savedInstanceState.putSerializable(STATE_PUBLIC_KEY, mPublicKey);
        savedInstanceState.putString(STATE_PREF_KEY, mPrefKey);
        super.onSaveInstanceState(savedInstanceState);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
        case REQUEST_CODE_PICK_ACCOUNT:
            if (resultCode != RESULT_OK) {
                replyWithError("User didn't pick account.");
                return;
            }
            mGoogleAccount = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
            if (mGoogleAccount == null || mGoogleAccount.isEmpty()) {
                replyWithError("Empty Google email.");
                return;
            }
            getBlessing();
            break;
        case REQUEST_CODE_USER_APPROVAL:
            if (resultCode != RESULT_OK) {
                replyWithError("User didn't give proposed permissions.");
                return;
            }
            getBlessing();
            break;
        default:
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String permission[], int[] grantResults) {
        switch (requestCode) {
        case REQUEST_CODE_USER_APPROVAL:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                getBlessing();
                return;
            }
            replyWithError("User didn't give proposed permissions.");
            break;
        }
    }

    private void getBlessing() {
        int permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.GET_ACCOUNTS);
        if (permissionCheck == PackageManager.PERMISSION_DENIED) {
            ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.GET_ACCOUNTS },
                    REQUEST_CODE_USER_APPROVAL);
            return;
        }
        Account[] accounts = AccountManager.get(this).getAccountsByType("com.google");
        Account account = null;
        for (int i = 0; i < accounts.length; i++) {
            if (accounts[i].name.equals(mGoogleAccount)) {
                account = accounts[i];
            }
        }
        if (account == null) {
            replyWithError("Couldn't find Google account with name: " + mGoogleAccount);
            return;
        }
        AccountManager.get(this).getAuthToken(account, OAUTH_SCOPE, new Bundle(), false, new OnTokenAcquired(),
                new Handler(new Handler.Callback() {
                    @Override
                    public boolean handleMessage(Message msg) {
                        replyWithError("Error getting auth token: " + msg.toString());
                        return true;
                    }
                }));
    }

    class OnTokenAcquired implements AccountManagerCallback<Bundle> {
        @Override
        public void run(AccountManagerFuture<Bundle> result) {
            try {
                Bundle bundle = result.getResult();
                Intent launch = (Intent) bundle.get(AccountManager.KEY_INTENT);
                if (launch != null) { // Needs user approval.
                    // NOTE(spetrovic): The returned intent has the wrong flag value
                    // FLAG_ACTIVITY_NEW_TASK set, which results in the launched intent replying
                    // immediately with RESULT_CANCELED.  Hence, we clear the flag here.
                    launch.setFlags(0);
                    startActivityForResult(launch, REQUEST_CODE_USER_APPROVAL);
                    return;
                }
                String token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
                (new BlessingFetcher()).execute(token);
            } catch (AuthenticatorException e) {
                replyWithError("Couldn't authorize: " + e.getMessage());
                return;
            } catch (OperationCanceledException e) {
                replyWithError("Authorization cancelled: " + e.getMessage());
                return;
            } catch (IOException e) {
                replyWithError("Unexpected error: " + e.getMessage());
                return;
            }
        }
    }

    private class BlessingFetcher extends AsyncTask<String, Void, byte[]> {
        ProgressDialog progressDialog = new ProgressDialog(BlessingActivity.this);
        String errorMsg = null;

        @Override
        protected void onPreExecute() {
            progressDialog.setMessage("Fetching Vanadium Blessing...");
            progressDialog.show();
        }

        @Override
        protected byte[] doInBackground(String... tokens) {
            if (tokens.length != 1) {
                errorMsg = "Empty OAuth token.";
                return null;
            }
            try {
                String publicKey = Base64.encodeToString(mPublicKey.getEncoded(), BASE64_FLAGS);
                String token = tokens[0];
                String caveats = Base64.encodeToString(VomUtil.encode(new Caveat[] {}, Caveat[].class),
                        BASE64_FLAGS);
                String outputFormat = "base64vom";
                URL url = new URL("https://dev.v.io/auth/google/bless" + "?token=" + token + "&public_key="
                        + publicKey + "&caveats=" + caveats + "&output_format=" + outputFormat);
                byte[] base64EncodedBlessingVom = ByteStreams.toByteArray(url.openConnection().getInputStream());
                return Base64.decode(base64EncodedBlessingVom, BASE64_FLAGS);
            } catch (MalformedURLException e) {
                errorMsg = e.getMessage();
                return null;
            } catch (IOException e) {
                errorMsg = e.getMessage();
                return null;
            } catch (VException e) {
                errorMsg = e.getMessage();
                return null;
            }
        }

        @Override
        protected void onPostExecute(byte[] blessingVom) {
            progressDialog.dismiss();
            if (blessingVom == null || blessingVom.length == 0) {
                replyWithError("Couldn't get identity from Vanadium identity servers: " + errorMsg);
                return;
            }
            replyWithSuccess(blessingVom);
        }
    }

    private void updatePrefs(String key, String value) {
        SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
        editor.putString(key, value);
        editor.commit();
    }

    private void replyWithError(String error) {
        Preconditions.checkArgument(error != null && !error.isEmpty());
        Log.e(TAG, "Error while blessing: " + error);
        Intent intent = new Intent();
        intent.putExtra(EXTRA_ERROR, error);
        setResult(RESULT_CANCELED, intent);
        finish();
    }

    private void replyWithSuccess(byte[] blessingVom) {
        Preconditions.checkArgument(blessingVom != null && blessingVom.length > 0);
        // Store the result in preferences, if the caller asked for it.
        if (!mPrefKey.isEmpty()) {
            String hexBlessingsVom = VomUtil.bytesToHexString(blessingVom);
            updatePrefs(mPrefKey, hexBlessingsVom);
        }
        // Prepare the return intent.
        Intent intent = new Intent();
        intent.putExtra(EXTRA_REPLY, blessingVom);
        setResult(RESULT_OK, intent);
        finish();
    }
}