7

I think the answer is YES but I'm not sure how to do it. I've been using the API to get events from calendars stored on the device. e.g.

 public static ArrayList<MINCalendarEvent> queryEvents(long startMillis, long endMillis) throws  MINPermissionException
 {
    if ( ContextCompat.checkSelfPermission(MINMainActivity.getSharedInstance(), Manifest.permission.WRITE_CALENDAR) != PackageManager.PERMISSION_GRANTED )
    {
        throw new MINPermissionException(NO_PERMISSION);
    }
    else
    {
        ArrayList<MINCalendarEvent> eventArray = new ArrayList<MINCalendarEvent>();

        Cursor cur = CalendarContract.Instances.query(MINMainActivity.getSharedInstance().getContentResolver(), EVENT_PROJECTION, startMillis, endMillis);
        int numItems = cur.getCount();
        Log.d("MINCalendarUtils.queryEvents", "Number of events retrieved: " + numItems);
        while (cur.moveToNext())
        {
            MINCalendarEvent event = new MINCalendarEvent();
            event.calendarID = cur.getLong(EVENT_PROJECTION_CALENDAR_ID);
            event.organizer = cur.getString(EVENT_PROJECTION_ORGANIZER);
            event.title = cur.getString(EVENT_PROJECTION_TITLE);
            event.eventLocation = cur.getString(EVENT_PROJECTION_EVENT_LOCATION);
            event.description = cur.getString(EVENT_PROJECTION_DESCRIPTION);
            event.dtStart = cur.getLong(EVENT_PROJECTION_DTSTART);
            event.dtEnd = cur.getLong(EVENT_PROJECTION_DTEND);
            event.eventTimeZone = cur.getString(EVENT_PROJECTION_EVENT_TIMEZONE);
            event.eventEndTimeZone = cur.getString(EVENT_PROJECTION_EVENT_END_TIMEZONE);
            event.duration = cur.getString(EVENT_PROJECTION_DURATION);
            event.allDay = (cur.getInt(EVENT_PROJECTION_ALL_DAY) != 0);
            event.rRule = cur.getString(EVENT_PROJECTION_RRULE);
            event.rDate = cur.getString(EVENT_PROJECTION_RDATE);
            event.availability = cur.getInt(EVENT_PROJECTION_AVAILABILITY);
            event.guestsCanModify = (cur.getInt(EVENT_PROJECTION_GUESTS_CAN_MODIFY) != 0);
            event.guestsCanInviteOthers = (cur.getInt(EVENT_PROJECTION_GUESTS_CAN_INVITE_OTHERS) != 0);
            event.guestsCanSeeGuests = (cur.getInt(EVENT_PROJECTION_GUESTS_CAN_SEE_GUESTS) != 0);
            eventArray.add(event);
        }
        return eventArray;
    }
}

This works great. The problem is that I need to query a shared calendar stored on the server which the user DOES NOT have rights too. My app creates a LOCAL calendar. I need to access a shared that the user WILL NOT have rights too and synchronize the events stored in the shared calendar with the LOCAL calendar (non-synchronized). I'm assuming that I can use a service account to access the shared calendar.

I've successfully created a service account and added the account to the shared calendar. Now what????

There seems to be several ways of accessing calendar events using a service account but I'm totally confused. Obviously, I'd like to use the API I've been using but I think it only works with calendars that are synchronized with the device.

I've investigated using "GoogleCredentials" but I need some sample source code to make this work. 1st of all, when I created the service account, I exported it using JSON and not p12. I don't see how to use it. The API seems to require p12. I'm also totally confused on how to use the credentials once I have them. Here's the sample source I started with:

    //String emailAddress = "123456789000-abc123def456@developer.gserviceaccount.com";
    GoogleCredential credential = null;
    JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
    HttpTransport httpTransport = null;
    try
    {
        httpTransport = GoogleNetHttpTransport.newTrustedTransport();
        credential = new GoogleCredential.Builder()
                .setTransport(httpTransport)
                .setJsonFactory(JSON_FACTORY)
                .setServiceAccountId(emailAddress)
                .setServiceAccountPrivateKeyFromP12File(new File("MyProject.p12"))
                .setServiceAccountScopes(Collections.singleton(SQLAdminScopes.SQLSERVICE_ADMIN))
                .build();
    }
    catch (GeneralSecurityException e)
    {
        e.printStackTrace();
    }
    catch (IOException e)
    {
        e.printStackTrace();
    }

There are several problems. 1) I can't event get this to compile. I can't seem to get the imports to work:

 import com.google.api.services.sqladmin.SQLAdminScopes;

Assuming I can get past that, now what. I'm not sure how to use the credentials to access the remote calendar. What I need is to get a list of events from the shared calendar.

I've also been looking through the source code at the following link for directions, but it doesn't use a service account: https://developers.google.com/google-apps/calendar/quickstart/android

Also, is there a way to hook changes to the server based shared calendar so that I get pinged when changes occur to the shared calendar?

Any help????

EDIT - Working Code

Based on the answer from Andres, I've put together the following code:

public static void calendarAuthenticate()
{
    Thread thread = new Thread(new Runnable()
    {
        @Override
        public void run()
        {
            try
            {
                MINAppConfiguration appConfig = MINAppConfiguration.getSharedInstance();

                // Application Name
                appName = appConfig.getCurrentAppInfo().appName;

                // Directory to store user credentials for this application;
                //dataStoreDir = new File(appConfig.appDirectoryOnDevice);

                // Instance of the JSON factory
                jsonFactory = JacksonFactory.getDefaultInstance();

                // Instance of the scopes required
                scopes = new ArrayList<String>();
                scopes.add(CalendarScopes.CALENDAR_READONLY);

                // Http transport creation
                httpTransport = AndroidHttp.newCompatibleTransport();

                java.io.File licenseFile = getSecretFile();
                GoogleCredential credential = new GoogleCredential.Builder()
                        .setTransport(httpTransport)
                        .setJsonFactory(jsonFactory)
                        .setServiceAccountId("account-1@handy-contact-762.iam.gserviceaccount.com")
                        .setServiceAccountScopes(scopes)
                        .setServiceAccountPrivateKeyFromP12File(licenseFile)
                        .build();
                com.google.api.services.calendar.Calendar.Builder builder = new com.google.api.services.calendar.Calendar.Builder(httpTransport, jsonFactory, credential);
                builder.setApplicationName(appName);
                com.google.api.services.calendar.Calendar client = builder.build();

                // List the next 10 events from the target calendar.
                DateTime now = new DateTime(System.currentTimeMillis());
                com.google.api.services.calendar.Calendar.Events.List list = client.events().list("mobilityinitiative.com_qfvbdrk368f9la06hacb4bduos@group.calendar.google.com");
                list.setMaxAttendees(10);
                list.setTimeMin(now);
                list.setOrderBy("startTime");
                list.setSingleEvents(true);
                Events events = list.execute();

                List<Event> items = events.getItems();
                if (items.size() == 0)
                {
                    Log.d(TAG, "No upcoming events found.");
                }
                else
                {
                    Log.d(TAG, "Upcoming events");
                    for (Event event : items) {
                        DateTime start = event.getStart().getDateTime();
                        if (start == null) {
                            start = event.getStart().getDate();
                        }
                        Log.d(TAG, event.getSummary() + " (" + start + ")\n");
                    }
                }
            }
            catch (GeneralSecurityException e)
            {
                e.printStackTrace();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    });
    thread.start();
}

public static java.io.File getSecretFile()
{
    File f = new File(MINMainActivity.getSharedInstance().getCacheDir()+ "/" +"google_calendar_secret.p12");
    if (f.exists())
    {
        f.delete();
    }
    try
    {
        InputStream is = MINMainActivity.getSharedInstance().getAssets().open("google_calendar_secret.p12");
        int size = is.available();
        byte[] buffer = new byte[size];
        is.read(buffer);
        is.close();


        FileOutputStream fos = new FileOutputStream(f);
        fos.write(buffer);
        fos.close();
    }
    catch (Exception e)
    {
        throw new RuntimeException(e);
    }
    return f;
}

A couple of notes:

JustLearningAgain
  • 2,227
  • 4
  • 34
  • 50

1 Answers1

7

I'm not an Android expert, but either of the following approach should work for your use case.

  1. Your approach: Using Service Account, will avoid having the user authenticating to your application. You may want to read about 'Delegating domain-wide authority', this will allow your application to make API calls as the user in your domain (also known as 'impersonate' users). I also found this this SO to be helpful.

  2. Another approach: Using the Acl.Insert resource. This will require user authentication every time they log-in to your application. Here is an example on how to implement this.

From your sample source above, set your scopes to Calendar scope instead of SQL Admin, looks something like this:

GoogleCredential credential = new GoogleCredential.Builder()
    ...    
      .setServiceAccountScopes(CalendarScopes.CALENDAR)
      .setServiceAccountPrivateKeyFromP12File(xxxx)  
      .build();

Hope this helps and good luck!

Community
  • 1
  • 1
Andres
  • 671
  • 4
  • 9
  • I've added an UPDATE section above with the source I've been able to piece together. It still has problems. Any ideas? – JustLearningAgain Dec 02 '15 at 17:21
  • For Service account, make sure you have properly granted access to the Google Apps domain's user data that you want to access. Did you follow the steps in the 'Delegating domain-wide authority' section? There are series of steps an administrator would need to complete to give authority for the app to make API calls. Please review the steps and try again. https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority – Andres Dec 02 '15 at 20:15
  • I retried the domain access sequence. It didn't help. It was correct. The problem was that I was using setServiceAccountId & setServiceAccountUser. Once I got rid of the .setServiceAccountUser line, I was in business. Yahooo!!!!! I have updated the code above. – JustLearningAgain Dec 02 '15 at 21:49
  • Awesome!! Just a note, if you ever want to impersonate a user you would need to use the .setServiceAccountUser. Anyways, cheers! – Andres Dec 02 '15 at 21:57