feat: support refresh callable on google.oauth2.credentials.Credentials (#812)

This is an optional parameter that can be set via the constructor.
It is used to provide the credentials with new tokens and their
expiration time on `refresh()` call.

```
def refresh_handler(request, scopes):
    # Generate a new token for the requested scopes by calling
    # an external process.
    return (
        "ACCESS_TOKEN",
        _helpers.utcnow() + datetime.timedelta(seconds=3600))

creds = google.oauth2.credentials.Credentials(
    scopes=scopes,
    refresh_handler=refresh_handler)
creds.refresh(request)
```

It is useful in the following cases:
- Useful in general when tokens are obtained by calling some
  external process on demand.
- Useful in particular for retrieving downscoped tokens from a
  token broker.

This should have no impact on existing behavior. Refresh tokens
will still have higher priority over refresh handlers.

A getter and setter is exposed to make it easy to set the callable
on unpickled credentials as the callable may not be easily serialized.

```
unpickled = pickle.loads(pickle.dumps(oauth_creds))
unpickled.refresh_handler = refresh_handler
```
diff --git a/google/oauth2/credentials.py b/google/oauth2/credentials.py
index dcfa5f9..158249e 100644
--- a/google/oauth2/credentials.py
+++ b/google/oauth2/credentials.py
@@ -74,6 +74,7 @@
         quota_project_id=None,
         expiry=None,
         rapt_token=None,
+        refresh_handler=None,
     ):
         """
         Args:
@@ -103,6 +104,13 @@
                 This project may be different from the project used to
                 create the credentials.
             rapt_token (Optional[str]): The reauth Proof Token.
+            refresh_handler (Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]):
+                A callable which takes in the HTTP request callable and the list of
+                OAuth scopes and when called returns an access token string for the
+                requested scopes and its expiry datetime. This is useful when no
+                refresh tokens are provided and tokens are obtained by calling
+                some external process on demand. It is particularly useful for
+                retrieving downscoped tokens from a token broker.
         """
         super(Credentials, self).__init__()
         self.token = token
@@ -116,13 +124,20 @@
         self._client_secret = client_secret
         self._quota_project_id = quota_project_id
         self._rapt_token = rapt_token
+        self.refresh_handler = refresh_handler
 
     def __getstate__(self):
         """A __getstate__ method must exist for the __setstate__ to be called
         This is identical to the default implementation.
         See https://docs.python.org/3.7/library/pickle.html#object.__setstate__
         """
-        return self.__dict__
+        state_dict = self.__dict__.copy()
+        # Remove _refresh_handler function as there are limitations pickling and
+        # unpickling certain callables (lambda, functools.partial instances)
+        # because they need to be importable.
+        # Instead, the refresh_handler setter should be used to repopulate this.
+        del state_dict["_refresh_handler"]
+        return state_dict
 
     def __setstate__(self, d):
         """Credentials pickled with older versions of the class do not have
@@ -138,6 +153,8 @@
         self._client_secret = d.get("_client_secret")
         self._quota_project_id = d.get("_quota_project_id")
         self._rapt_token = d.get("_rapt_token")
+        # The refresh_handler setter should be used to repopulate this.
+        self._refresh_handler = None
 
     @property
     def refresh_token(self):
@@ -187,6 +204,31 @@
         """Optional[str]: The reauth Proof Token."""
         return self._rapt_token
 
+    @property
+    def refresh_handler(self):
+        """Returns the refresh handler if available.
+
+        Returns:
+           Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]:
+               The current refresh handler.
+        """
+        return self._refresh_handler
+
+    @refresh_handler.setter
+    def refresh_handler(self, value):
+        """Updates the current refresh handler.
+
+        Args:
+            value (Optional[Callable[[google.auth.transport.Request, Sequence[str]], [str, datetime]]]):
+                The updated value of the refresh handler.
+
+        Raises:
+            TypeError: If the value is not a callable or None.
+        """
+        if not callable(value) and value is not None:
+            raise TypeError("The provided refresh_handler is not a callable or None.")
+        self._refresh_handler = value
+
     @_helpers.copy_docstring(credentials.CredentialsWithQuotaProject)
     def with_quota_project(self, quota_project_id):
 
@@ -205,6 +247,31 @@
 
     @_helpers.copy_docstring(credentials.Credentials)
     def refresh(self, request):
+        scopes = self._scopes if self._scopes is not None else self._default_scopes
+        # Use refresh handler if available and no refresh token is
+        # available. This is useful in general when tokens are obtained by calling
+        # some external process on demand. It is particularly useful for retrieving
+        # downscoped tokens from a token broker.
+        if self._refresh_token is None and self.refresh_handler:
+            token, expiry = self.refresh_handler(request, scopes=scopes)
+            # Validate returned data.
+            if not isinstance(token, str):
+                raise exceptions.RefreshError(
+                    "The refresh_handler returned token is not a string."
+                )
+            if not isinstance(expiry, datetime):
+                raise exceptions.RefreshError(
+                    "The refresh_handler returned expiry is not a datetime object."
+                )
+            if _helpers.utcnow() >= expiry - _helpers.CLOCK_SKEW:
+                raise exceptions.RefreshError(
+                    "The credentials returned by the refresh_handler are "
+                    "already expired."
+                )
+            self.token = token
+            self.expiry = expiry
+            return
+
         if (
             self._refresh_token is None
             or self._token_uri is None
@@ -217,8 +284,6 @@
                 "token_uri, client_id, and client_secret."
             )
 
-        scopes = self._scopes if self._scopes is not None else self._default_scopes
-
         (
             access_token,
             refresh_token,