Search code examples
androidgoogle-apigoogle-calendar-apigoogle-oauthgoogle-api-java-client

com.google.api.client.googleapis.json.GoogleJsonResponseException: 401 Unauthorized


I have the following code:

package com.example.bluelightlite.modules

import android.content.Context
import android.os.AsyncTask
import com.example.bluelightlite.builders.ServiceBuilder
import com.google.api.client.auth.oauth2.Credential
import com.google.api.client.http.javanet.NetHttpTransport
import com.google.api.client.util.DateTime
import com.google.api.services.calendar.Calendar
import com.google.api.services.calendar.model.Event
import com.google.api.services.calendar.model.Events

class GoogleCalendarsServiceModule constructor(context: Context): AsyncTask<Void, Void, List<Event>>() {

    private val googleCredential: Credential
    private val httpTransport: NetHttpTransport = NetHttpTransport()
    private val googleCredentialsUtilityModule = GoogleCredentialsUtilityModule(context)

    init {
        this.googleCredential = this.googleCredentialsUtilityModule.execute(this.httpTransport).get()
    }

    private var googleCalendarService: Calendar =
        ServiceBuilder().buildGoogleCalendarService(this.googleCredential, this.httpTransport)

    /**
     * gets all calendar events
     * @return Events list
     */
    private fun getEvents(): List<Event> {
        val now = DateTime(System.currentTimeMillis())
        val events: Events = this.googleCalendarService
            .events()
            .list("Marc Freeman")
            .setMaxResults(10)
            .setOrderBy("startTime")
            .execute()

        return events.items;
    }

    override fun doInBackground(vararg params: Void?): List<Event> {
        return this.getEvents()
    }
}

The application asks the user to accept permissions to access calendar, I have an auth token, however when I call:

GoogleCalendarsServiceModule.execute() // because it needs to run on another thread

I get this error:

E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
    Process: com.example.bluelightlite, PID: 11410
    java.lang.RuntimeException: An error occurred while executing doInBackground()
        at android.os.AsyncTask$4.done(AsyncTask.java:399)
        at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383)
        at java.util.concurrent.FutureTask.setException(FutureTask.java:252)
        at java.util.concurrent.FutureTask.run(FutureTask.java:271)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:289)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:919)
     Caused by: com.google.api.client.googleapis.json.GoogleJsonResponseException: 401 Unauthorized
    {
      "code" : 401,
      "errors" : [ {
        "domain" : "global",
        "location" : "Authorization",
        "locationType" : "header",
        "message" : "Invalid Credentials",
        "reason" : "authError"
      } ],
      "message" : "Invalid Credentials"
    }
        at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:113)
        at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:40)
        at com.google.api.client.googleapis.services.AbstractGoogleClientRequest$1.interceptResponse(AbstractGoogleClientRequest.java:321)
        at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1065)
        at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:419)
        at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:352)
        at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:469)
        at com.example.bluelightlite.modules.GoogleCalendarsServiceModule.getEvents(GoogleCalendarsServiceModule.kt:38)
        at com.example.bluelightlite.modules.GoogleCalendarsServiceModule.doInBackground(GoogleCalendarsServiceModule.kt:44)
        at com.example.bluelightlite.modules.GoogleCalendarsServiceModule.doInBackground(GoogleCalendarsServiceModule.kt:14)
        at android.os.AsyncTask$3.call(AsyncTask.java:378)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
            ... 4 more

How do I pass my access token into my request? I can't seem to find an API that allows me to do this when I call the GoogleCalendarService, Absolutely dispising Android by the way guys.


Solution

  • So I have no idea what happened but I cleaned out my project and the code worked... It goes as follows:

    class GoogleCalendarsServiceModule constructor(context: Context) {
    
        private val googleCredential: Credential
        private val httpTransport: NetHttpTransport = NetHttpTransport()
        private val googleCredentialsUtilityModule = GoogleCredentialsUtilityModule(context)
        private var googleCalendarService: Calendar
    
        init {
            this.googleCredential = this.googleCredentialsUtilityModule.execute(this.httpTransport).get()
            this.googleCalendarService = ServiceBuilder().buildGoogleCalendarService(this.googleCredential, this.httpTransport)
        }
    
        /**
         * gets all calendar events
         * @see - https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-i-o.html
         * @return Events list
         */
        suspend fun getCalendarEvents(): List<Event> = withContext(Dispatchers.IO) {
            val now = DateTime(System.currentTimeMillis())
            val events: Calendar.Events? = googleCalendarService.events()
    
            val allEvents = events!!.list("primary")
            .setMaxResults(10)
                .setTimeMin(now)
                .setOrderBy("startTime")
                .setSingleEvents(true)
                .execute()
    
            allEvents.items
        }
    
        suspend fun getCalendarEventsForRoom(roomName: String): List<Event> = withContext(Dispatchers.IO) {
            val now = DateTime(System.currentTimeMillis())
            val events: Calendar.Events? = googleCalendarService.events()
    
            val allEventsForRoom = events!!.list("primary")
                .setMaxResults(10)
                .setTimeMin(now)
                .setOrderBy("startTime")
                .setSingleEvents(true)
                .execute()
    
            allEventsForRoom.items.distinctBy { x -> x.location == roomName }
        }
    }
    

    Credentials Module that sets up and maintains Google 0Auth Credentials. Including setting up the tokens folder if missing.

    class GoogleCredentialsUtilityModule constructor(private val context: Context): AsyncTask<NetHttpTransport, Void, Credential>() {
    
        /**
         * Starts getting credentials on a different thread by implementing
         * AsyncTask<>()
         * @param params: a list of parameters
         * @return none
         */
        override fun doInBackground(vararg params: NetHttpTransport): Credential {
            return this.getCredentials(params[0])
        }
    
        /**
         * Creates an authorized Credential object.
         * @param HTTP_TRANSPORT The network HTTP Transport.
         * @return An authorized Credential object.
         * @throws java.io.IOException If the credentials.json file cannot be found.
         */
        private fun getCredentials(HTTP_TRANSPORT: NetHttpTransport): Credential {
    
            val inputStream: InputStream = getCredentialsAsInputStream()
            val clientSecrets: GoogleClientSecrets = GoogleClientSecrets.load(JSON_FACTORY, InputStreamReader(inputStream))
    
            createTokenFolderIfMissing()
    
            val authorisationFlow: GoogleAuthorizationCodeFlow = getAuthorisationFlow(HTTP_TRANSPORT, clientSecrets)
            val localServerReceiver: LocalServerReceiver = LocalServerReceiver.Builder().setPort(43783).build()
    
            val ab: AuthorizationCodeInstalledApp =
                object : AuthorizationCodeInstalledApp(authorisationFlow, localServerReceiver) {
                    @Throws(IOException::class)
                    override fun onAuthorization(authorizationUrl: AuthorizationCodeRequestUrl) {
                        val url = authorizationUrl.build()
                        val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
                        context.startActivity(browserIntent)
                    }
                }
    
            return ab.authorize("user")
        }
    
        /**
         * Gets the /credentials.json file
         * @return InputStream
         */
        private fun getCredentialsAsInputStream(): InputStream {
            return this.javaClass.getResourceAsStream(CREDENTIALS_FILE_PATH)
                ?: throw FileNotFoundException("Resource Not found: $CREDENTIALS_FILE_PATH")
        }
    
        /**
         * Creates the Tokens Folder for Google Authentication
         * Uses the current context for the folder path from
         * Context.getExternalFilesDir()
         */
        private fun createTokenFolderIfMissing() {
            val tokenFolder = getTokenFolder()
            if (!tokenFolder.exists()) {
                tokenFolder.mkdir()
            }
        }
    
        /**
         * gets External storage directory from the
         * current context
         * @return File
         */
        private fun getTokenFolder(): File {
            return File(this.context.getExternalFilesDir("")?.absolutePath + TOKENS_DIRECTORY_PATH)
        }
    
        /**
         * Gets authorisation flow so that the application can authenticate into Google Calendars
         * @param HTTP_TRANSPORT allows the app to use HTTP connections
         * @param clientSecrets secrets for authentication into Google
         * @return GoogleAuthorizationCodeFlow
         */
        private fun getAuthorisationFlow(HTTP_TRANSPORT: NetHttpTransport, clientSecrets: GoogleClientSecrets): GoogleAuthorizationCodeFlow {
            return GoogleAuthorizationCodeFlow.Builder(
                HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
                .setDataStoreFactory(FileDataStoreFactory(getTokenFolder()))
                .setAccessType("offline")
                .build()
        }
    }
    

    And finally, calling the service using a co-routine:

    class MainActivity : AppCompatActivity(), CoroutineScope, IMeetingRoomDelegate {
        private var job: Job = Job()
        override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job
    
        @Inject
        lateinit var permissionsUtilityModule: PermissionsUtilityModule
    
        private lateinit var googleCalendarsServiceModule: GoogleCalendarsServiceModule
        private lateinit var meetingRooms: MeetingRooms
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            DaggerUtilityModuleComponent.create().inject(this)
    
            this.permissionsUtilityModule.checkUserPermissions(this)
            this.googleCalendarsServiceModule = GoogleCalendarsServiceModule(this);
    
            launch {
                val events: List<Event> = googleCalendarsServiceModule.getCalendarEvents()
                meetingRooms = setMeetingRooms(events)
                setMeetingRoomFragments(meetingRooms)
            }
    
            setContentView(R.layout.activity_main)
        }
    
        override fun onDestroy() {
            super.onDestroy()
            this.job.cancel()
        }
    
        override fun onMeetingRoomFragmentClicked(fragment: MeetingRoomFragment, meetingRoom: MeetingRoom) {
            val intent = Intent(this, ViewMeetingRoomDataActivity::class.java)
            intent.putExtra("meetingRoomData", meetingRoom)
    
            startActivity(intent)
        }
    
        private fun setMeetingRooms(events: List<Event>): MeetingRooms {
            val mappedEvents: List<MeetingEvent> = mapMeetingEvents(events)
            val meetingEvents = MeetingEvents(mappedEvents)
    
            return MeetingRoomFactory().getRooms(meetingEvents)
        }
    
        private fun setMeetingRoomFragments(mappedMeetingRooms: MeetingRooms) {
            for(meetingRoom: MeetingRoom in mappedMeetingRooms.getMeetingRooms()) {
                val bundle = Bundle()
                val meetingRoomFragment = MeetingRoomFragment()
    
                bundle.putParcelable("meetingRoomData", meetingRoom)
                meetingRoomFragment.arguments = bundle
    
                supportFragmentManager.beginTransaction()
                    .add(R.id.root_container, meetingRoomFragment)
                    .commitAllowingStateLoss()
            }
        }
    }
    

    Thought I'd post in case anyone requires help with the whole 0Auth rigmarole.