Java tutorial
// 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(); } }