Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ dependencies {
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.microsoft.identity.client:msal:8.5.0'
implementation 'com.microsoft.graph:microsoft-graph:5.80.0'
implementation 'com.microsoft.graph:microsoft-graph:6.71.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

import androidx.annotation.NonNull;

import com.microsoft.graph.authentication.BaseAuthenticationProvider;
import com.microsoft.kiota.RequestInformation;
import com.microsoft.kiota.authentication.AllowedHostsValidator;
import com.microsoft.kiota.authentication.AuthenticationProvider;
import com.microsoft.identity.client.AuthenticationCallback;
import com.microsoft.identity.client.IAuthenticationResult;
import com.microsoft.identity.client.IPublicClientApplication;
Expand All @@ -18,14 +20,18 @@
import com.microsoft.identity.client.SignInParameters;
import com.microsoft.identity.client.exception.MsalException;

import java.net.URL;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

// Singleton class - the app only needs a single instance
// of PublicClientApplication
public class AuthenticationHelper extends BaseAuthenticationProvider {
public class AuthenticationHelper implements AuthenticationProvider {
private static AuthenticationHelper INSTANCE = null;
private static final AllowedHostsValidator ALLOWED_HOSTS =
new AllowedHostsValidator("graph.microsoft.com");
private ISingleAccountPublicClientApplication mPCA = null;
private final String[] mScopes = { "User.Read", "MailboxSettings.Read", "Calendars.ReadWrite" };

Expand Down Expand Up @@ -147,15 +153,17 @@ public void onError(MsalException exception) {
};
}

@NonNull
@Override
public CompletableFuture<String> getAuthorizationTokenAsync(@NonNull URL requestUrl) {
if (shouldAuthenticateRequestWithUrl(requestUrl)) {
return acquireTokenSilently()
.thenApply(IAuthenticationResult::getAccessToken);
public void authenticateRequest(@NonNull RequestInformation request,
Map<String, Object> additionalAuthenticationContext) {
try {
URI requestUri = request.getUri();
if (requestUri != null && ALLOWED_HOSTS.isUrlHostValid(requestUri)) {
String accessToken = acquireTokenSilently().join().getAccessToken();
request.headers.tryAdd("Authorization", "Bearer " + accessToken);
}
} catch (URISyntaxException exception) {
throw new IllegalArgumentException("Unable to parse request URL", exception);
}

return CompletableFuture.completedFuture(null);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,12 @@ public View getView(int position, View convertView, ViewGroup parent) {
holder = (ViewHolder) convertView.getTag();
}

holder.subject.setText(event.subject);
Objects.requireNonNull(event.organizer);
holder.organizer.setText(Objects.requireNonNull(event.organizer.emailAddress).name);
holder.start.setText(getLocalDateTimeString(Objects.requireNonNull(event.start)));
holder.end.setText(getLocalDateTimeString(Objects.requireNonNull(event.end)));
holder.subject.setText(event.getSubject());
Objects.requireNonNull(event.getOrganizer());
holder.organizer.setText(
Objects.requireNonNull(event.getOrganizer().getEmailAddress()).getName());
holder.start.setText(getLocalDateTimeString(Objects.requireNonNull(event.getStart())));
holder.end.setText(getLocalDateTimeString(Objects.requireNonNull(event.getEnd())));

return convertView;
}
Expand All @@ -73,8 +74,8 @@ public View getView(int position, View convertView, ViewGroup parent) {
// a LocalDateTime, then return a formatted string
@NonNull
private String getLocalDateTimeString(@NonNull DateTimeTimeZone dateTime) {
ZonedDateTime localDateTime = LocalDateTime.parse(dateTime.dateTime)
.atZone(GraphToIana.getZoneIdFromWindows(dateTime.timeZone));
ZonedDateTime localDateTime = LocalDateTime.parse(dateTime.getDateTime())
.atZone(GraphToIana.getZoneIdFromWindows(dateTime.getTimeZone()));

return String.format("%s %s",
localDateTime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)),
Expand Down
131 changes: 57 additions & 74 deletions src/app/src/main/java/com/example/graphsample/GraphHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,27 @@
import com.microsoft.graph.models.DateTimeTimeZone;
import com.microsoft.graph.models.EmailAddress;
import com.microsoft.graph.models.Event;
import com.microsoft.graph.models.EventCollectionResponse;
import com.microsoft.graph.models.ItemBody;
import com.microsoft.graph.models.User;
import com.microsoft.graph.options.HeaderOption;
import com.microsoft.graph.options.Option;
import com.microsoft.graph.options.QueryOption;
import com.microsoft.graph.requests.EventCollectionPage;
import com.microsoft.graph.requests.EventCollectionRequestBuilder;
import com.microsoft.graph.requests.GraphServiceClient;
import com.microsoft.graph.serviceclient.GraphServiceClient;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import okhttp3.Request;

// Singleton class - the app only needs a single instance
// of the Graph client
public class GraphHelper {
private static GraphHelper INSTANCE = null;
private final GraphServiceClient<Request> mClient;
private final GraphServiceClient mClient;

private GraphHelper() {
AuthenticationHelper authProvider = AuthenticationHelper.getInstance();

mClient = GraphServiceClient.builder()
.authenticationProvider(authProvider).buildClient();
mClient = new GraphServiceClient(authProvider);
}

public static synchronized GraphHelper getInstance() {
Expand All @@ -51,59 +44,50 @@ public static synchronized GraphHelper getInstance() {

public CompletableFuture<User> getUser() {
// GET /me (logged in user)
return mClient.me().buildRequest()
.select("displayName,mail,mailboxSettings,userPrincipalName")
.getAsync();
return CompletableFuture.supplyAsync(() -> mClient.me().get(requestConfiguration ->
requestConfiguration.queryParameters.select = new String[] {
"displayName", "mail", "mailboxSettings", "userPrincipalName"
}));
}

public CompletableFuture<List<Event>> getCalendarView(ZonedDateTime viewStart,
ZonedDateTime viewEnd,
String timeZone) {

final List<Option> options = new LinkedList<>();
options.add(new QueryOption("startDateTime",
viewStart.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)));
options.add(new QueryOption("endDateTime",
viewEnd.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)));

// Start and end times adjusted to user's time zone
options.add(new HeaderOption("Prefer",
"outlook.timezone=\"" + timeZone + "\""));

final List<Event> allEvents = new LinkedList<>();
// Create a separate list of options for the paging requests
// paging request should not include the query parameters from the initial
// request, but should include the headers.
final List<Option> pagingOptions = new LinkedList<>();
pagingOptions.add(new HeaderOption("Prefer",
"outlook.timezone=\"" + timeZone + "\""));

return mClient.me().calendarView()
.buildRequest(options)
.select("subject,organizer,start,end")
.orderBy("start/dateTime")
.top(5)
.getAsync()
.thenCompose(eventPage -> processPage(eventPage, allEvents, pagingOptions));
}
return CompletableFuture.supplyAsync(() -> {
final List<Event> allEvents = new LinkedList<>();
EventCollectionResponse eventPage = mClient.me().calendarView()
.get(requestConfiguration -> {
requestConfiguration.queryParameters.startDateTime =
viewStart.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
requestConfiguration.queryParameters.endDateTime =
viewEnd.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
requestConfiguration.queryParameters.select =
new String[] {"subject", "organizer", "start", "end"};
requestConfiguration.queryParameters.orderby =
new String[] {"start/dateTime"};
requestConfiguration.queryParameters.top = 5;
requestConfiguration.headers.add(
"Prefer", "outlook.timezone=\"" + timeZone + "\"");
});

while (eventPage != null) {
if (eventPage.getValue() != null) {
allEvents.addAll(eventPage.getValue());
}

String nextLink = eventPage.getOdataNextLink();
if (nextLink == null) {
break;
}

eventPage = mClient.me().calendarView().withUrl(nextLink).get(requestConfiguration ->
requestConfiguration.headers.add(
"Prefer", "outlook.timezone=\"" + timeZone + "\""));
}

private CompletableFuture<List<Event>> processPage(EventCollectionPage currentPage,
List<Event> eventList,
List<Option> options) {
eventList.addAll(currentPage.getCurrentPage());

// Check if there is another page of results
EventCollectionRequestBuilder nextPage = currentPage.getNextPage();
if (nextPage != null) {
// Request the next page and repeat
return nextPage.buildRequest(options)
.getAsync()
.thenCompose(eventPage -> processPage(eventPage, eventList, options));
} else {
// No more pages, complete the future
// with the complete list
return CompletableFuture.completedFuture(eventList);
}
return allEvents;
});
}

public CompletableFuture<Event> createEvent(String subject,
Expand All @@ -116,51 +100,50 @@ public CompletableFuture<Event> createEvent(String subject,

// Set properties on the event
// Subject
newEvent.subject = subject;
newEvent.setSubject(subject);

// Start
newEvent.start = new DateTimeTimeZone();
newEvent.setStart(new DateTimeTimeZone());
// DateTimeTimeZone has two parts:
// The date/time expressed as an ISO 8601 Local date/time
// Local meaning there is no UTC or UTC offset designation
// Example: 2020-01-12T09:00:00
newEvent.start.dateTime = start.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
newEvent.getStart().setDateTime(start.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
// The time zone - can be either a Windows time zone name ("Pacific Standard Time")
// or an IANA time zone identifier ("America/Los_Angeles")
newEvent.start.timeZone = timeZone;
newEvent.getStart().setTimeZone(timeZone);

// End
newEvent.end = new DateTimeTimeZone();
newEvent.end.dateTime = end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
newEvent.end.timeZone = timeZone;
newEvent.setEnd(new DateTimeTimeZone());
newEvent.getEnd().setDateTime(end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
newEvent.getEnd().setTimeZone(timeZone);

// Add attendees if any were provided
if (attendees.length > 0) {
newEvent.attendees = new LinkedList<>();
newEvent.setAttendees(new LinkedList<>());

for (String attendeeEmail : attendees) {
Attendee newAttendee = new Attendee();
// Set the attendee type, in this case required
newAttendee.type = AttendeeType.REQUIRED;
newAttendee.setType(AttendeeType.Required);
// Create a new EmailAddress object with the address
// provided
newAttendee.emailAddress = new EmailAddress();
newAttendee.emailAddress.address = attendeeEmail;
newAttendee.setEmailAddress(new EmailAddress());
newAttendee.getEmailAddress().setAddress(attendeeEmail);

newEvent.attendees.add(newAttendee);
newEvent.getAttendees().add(newAttendee);
}
}

// Add body if provided
if (!body.isEmpty()) {
newEvent.body = new ItemBody();
newEvent.setBody(new ItemBody());
// Set the content
newEvent.body.content = body;
newEvent.getBody().setContent(body);
// Specify content is plain text
newEvent.body.contentType = BodyType.TEXT;
newEvent.getBody().setContentType(BodyType.Text);
}

return mClient.me().events().buildRequest()
.postAsync(newEvent);
return CompletableFuture.supplyAsync(() -> mClient.me().events().post(newEvent));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -278,10 +278,11 @@ private void handleSignInSuccess(@NonNull IAuthenticationResult authenticationRe
GraphHelper graphHelper = GraphHelper.getInstance();
graphHelper.getUser()
.thenAccept(user -> {
mUserName = user.displayName;
mUserEmail = user.mail == null ? user.userPrincipalName : user.mail;
mUserTimeZone = (user.mailboxSettings == null || user.mailboxSettings.timeZone == null)
? "UTC": user.mailboxSettings.timeZone;
mUserName = user.getDisplayName();
mUserEmail = user.getMail() == null ? user.getUserPrincipalName() : user.getMail();
mUserTimeZone = (user.getMailboxSettings() == null
|| user.getMailboxSettings().getTimeZone() == null)
? "UTC" : user.getMailboxSettings().getTimeZone();

runOnUiThread(() -> {
hideProgressBar();
Expand Down
Loading