How to properly decouple the service layer from the user interface layer, if the service layer needs to interact with the user?

In my program, I have a long-running function from which I would like to interact with the user for various reasons:

  1. Giving status updates (“Downloading file /foo/bar.png)
  2. Displaying warnings (“File upload failed; will retry later”)
  3. Asking for input (“Please enter your google drive password”)

The problem is, of course, that this function doesn’t know how it should interact with the user. For example, it would be nonsensical to display messages on stdout in a GUI program.

In case of errors, the solution is obvious – just throw an exception that’ll be caught in the user interface layer. Errors are easy because the service layer’s job is done as soon as the exception is thrown. But what about things like warnings and input? How do I bridge the gap between the service layer and the user?