// Copyright 2015 The Chromium 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 org.chromium.chrome.browser.externalnav; import android.annotation.SuppressLint; import android.content.Intent; import android.os.AsyncTask; import org.chromium.base.Log; import org.chromium.base.SecureRandomInitializer; import org.chromium.chrome.browser.IntentHandler; import org.chromium.chrome.browser.util.IntentUtils; import java.io.IOException; import java.security.SecureRandom; import java.util.Arrays; import java.util.concurrent.ExecutionException; /** * This class hold a token for the most recent launched external intent that has * user gesture. If the external intent resolves to chrome itself, the user * gesture will be applied to the new url request. Since there could be at most * one intent chooser at a time, this class only stores the most recent intent * that has user gesture. A previously launched intent with user gesture will * have its token invalidated if a new one comes. */ public class IntentWithGesturesHandler { /** * Extra to record whether the intent is launched by user gesture. */ public static final String EXTRA_USER_GESTURE_TOKEN = "org.chromium.chrome.browser.user_gesture_token"; private static final String TAG = "IntentGestureHandler"; private static final Object INSTANCE_LOCK = new Object(); private static IntentWithGesturesHandler sIntentWithGesturesHandler; private SecureRandom mSecureRandom; private AsyncTask<Void, Void, SecureRandom> mSecureRandomInitializer; private byte[] mIntentToken; private String mUri; /** * Get the singleton instance of this object. */ public static IntentWithGesturesHandler getInstance() { synchronized (INSTANCE_LOCK) { if (sIntentWithGesturesHandler == null) { sIntentWithGesturesHandler = new IntentWithGesturesHandler(); } } return sIntentWithGesturesHandler; } private IntentWithGesturesHandler() { mSecureRandomInitializer = new AsyncTask<Void, Void, SecureRandom>() { // SecureRandomInitializer addresses the bug in SecureRandom that "TrulyRandom" // warns about, so this lint warning can safely be suppressed. @SuppressLint("TrulyRandom") @Override protected SecureRandom doInBackground(Void... params) { SecureRandom secureRandom = null; try { secureRandom = new SecureRandom(); SecureRandomInitializer.initialize(secureRandom); } catch (IOException ioe) { Log.e(TAG, "Cannot initialize SecureRandom", ioe); } return secureRandom; } }.execute(); } /** * Generate a new token for the intent that has user gesture. This will * invalidate the token on the previously launched intent with user gesture. * * @param intent Intent with user gesture. */ public void onNewIntentWithGesture(Intent intent) { if (mSecureRandomInitializer != null) { try { mSecureRandom = mSecureRandomInitializer.get(); } catch (InterruptedException | ExecutionException e) { Log.e(TAG, "Error fetching SecureRandom", e); } mSecureRandomInitializer = null; } if (mSecureRandom == null) return; mIntentToken = new byte[32]; mSecureRandom.nextBytes(mIntentToken); intent.putExtra(EXTRA_USER_GESTURE_TOKEN, mIntentToken); mUri = IntentHandler.getUrlFromIntent(intent); } /** * Get the user gesture from the intent and clear the stored token. * * @param intent Intent that is used to launch chrome. * @return true if the intent has user gesture, or false otherwise. */ public boolean getUserGestureAndClear(Intent intent) { if (mIntentToken == null || mUri == null) return false; byte[] bytes = IntentUtils.safeGetByteArrayExtra(intent, EXTRA_USER_GESTURE_TOKEN); boolean ret = (bytes != null) && Arrays.equals(bytes, mIntentToken) && mUri.equals(IntentHandler.getUrlFromIntent(intent)); clear(); return ret; } /** * Clear the gesture token. */ public void clear() { mIntentToken = null; mUri = null; } }