blob: c39a5ed1b48cbff87270e8d85fb58c11a88816f4 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.wearable.agendadata;
import static com.example.android.wearable.agendadata.Constants.TAG;
import static com.example.android.wearable.agendadata.Constants.CONNECTION_TIME_OUT_MS;
import static com.example.android.wearable.agendadata.Constants.CAL_DATA_ITEM_PATH_PREFIX;
import static com.example.android.wearable.agendadata.Constants.ALL_DAY;
import static com.example.android.wearable.agendadata.Constants.BEGIN;
import static com.example.android.wearable.agendadata.Constants.DATA_ITEM_URI;
import static com.example.android.wearable.agendadata.Constants.DESCRIPTION;
import static com.example.android.wearable.agendadata.Constants.END;
import static com.example.android.wearable.agendadata.Constants.EVENT_ID;
import static com.example.android.wearable.agendadata.Constants.ID;
import static com.example.android.wearable.agendadata.Constants.PROFILE_PIC;
import static com.example.android.wearable.agendadata.Constants.TITLE;
import android.app.IntentService;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.provider.CalendarContract;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.text.format.Time;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.wearable.Asset;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.Wearable;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Queries calendar events using Android Calendar Provider API and creates a data item for each
* event.
*/
public class CalendarQueryService extends IntentService
implements ConnectionCallbacks, OnConnectionFailedListener {
private static final String[] INSTANCE_PROJECTION = {
CalendarContract.Instances._ID,
CalendarContract.Instances.EVENT_ID,
CalendarContract.Instances.TITLE,
CalendarContract.Instances.BEGIN,
CalendarContract.Instances.END,
CalendarContract.Instances.ALL_DAY,
CalendarContract.Instances.DESCRIPTION,
CalendarContract.Instances.ORGANIZER
};
private static final String[] CONTACT_PROJECTION = new String[] { Data._ID, Data.CONTACT_ID };
private static final String CONTACT_SELECTION = Email.ADDRESS + " = ?";
private GoogleApiClient mGoogleApiClient;
public CalendarQueryService() {
super(CalendarQueryService.class.getSimpleName());
}
@Override
public void onCreate() {
super.onCreate();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
@Override
protected void onHandleIntent(Intent intent) {
mGoogleApiClient.blockingConnect(CONNECTION_TIME_OUT_MS, TimeUnit.MILLISECONDS);
// Query calendar events in the next 24 hours.
Time time = new Time();
time.setToNow();
long beginTime = time.toMillis(true);
time.monthDay++;
time.normalize(true);
long endTime = time.normalize(true);
List<Event> events = queryEvents(this, beginTime, endTime);
for (Event event : events) {
final PutDataMapRequest putDataMapRequest = event.toPutDataMapRequest();
if (mGoogleApiClient.isConnected()) {
Wearable.DataApi.putDataItem(
mGoogleApiClient, putDataMapRequest.asPutDataRequest()).await();
} else {
Log.e(TAG, "Failed to send data item: " + putDataMapRequest
+ " - Client disconnected from Google Play Services");
}
}
mGoogleApiClient.disconnect();
}
private static String makeDataItemPath(long eventId, long beginTime) {
return CAL_DATA_ITEM_PATH_PREFIX + eventId + "/" + beginTime;
}
private static List<Event> queryEvents(Context context, long beginTime, long endTime) {
ContentResolver contentResolver = context.getContentResolver();
Uri.Builder builder = CalendarContract.Instances.CONTENT_URI.buildUpon();
ContentUris.appendId(builder, beginTime);
ContentUris.appendId(builder, endTime);
Cursor cursor = contentResolver.query(builder.build(), INSTANCE_PROJECTION,
null /* selection */, null /* selectionArgs */, null /* sortOrder */);
try {
int idIdx = cursor.getColumnIndex(CalendarContract.Instances._ID);
int eventIdIdx = cursor.getColumnIndex(CalendarContract.Instances.EVENT_ID);
int titleIdx = cursor.getColumnIndex(CalendarContract.Instances.TITLE);
int beginIdx = cursor.getColumnIndex(CalendarContract.Instances.BEGIN);
int endIdx = cursor.getColumnIndex(CalendarContract.Instances.END);
int allDayIdx = cursor.getColumnIndex(CalendarContract.Instances.ALL_DAY);
int descIdx = cursor.getColumnIndex(CalendarContract.Instances.DESCRIPTION);
int ownerEmailIdx = cursor.getColumnIndex(CalendarContract.Instances.ORGANIZER);
List<Event> events = new ArrayList<Event>(cursor.getCount());
while (cursor.moveToNext()) {
Event event = new Event();
event.id = cursor.getLong(idIdx);
event.eventId = cursor.getLong(eventIdIdx);
event.title = cursor.getString(titleIdx);
event.begin = cursor.getLong(beginIdx);
event.end = cursor.getLong(endIdx);
event.allDay = cursor.getInt(allDayIdx) != 0;
event.description = cursor.getString(descIdx);
String ownerEmail = cursor.getString(ownerEmailIdx);
Cursor contactCursor = contentResolver.query(Data.CONTENT_URI,
CONTACT_PROJECTION, CONTACT_SELECTION, new String[] {ownerEmail}, null);
int ownerIdIdx = contactCursor.getColumnIndex(Data.CONTACT_ID);
long ownerId = -1;
if (contactCursor.moveToFirst()) {
ownerId = contactCursor.getLong(ownerIdIdx);
}
contactCursor.close();
// Use event organizer's profile picture as the notification background.
event.ownerProfilePic = getProfilePicture(contentResolver, context, ownerId);
events.add(event);
}
return events;
} finally {
cursor.close();
}
}
@Override
public void onConnected(Bundle connectionHint) {
}
@Override
public void onConnectionSuspended(int cause) {
}
@Override
public void onConnectionFailed(ConnectionResult result) {
}
private static Asset getDefaultProfile(Resources res) {
Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.nobody);
return Asset.createFromBytes(toByteArray(bitmap));
}
private static Asset getProfilePicture(ContentResolver contentResolver, Context context,
long contactId) {
if (contactId != -1) {
// Try to retrieve the profile picture for the given contact.
Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
InputStream inputStream = Contacts.openContactPhotoInputStream(contentResolver,
contactUri, true /*preferHighres*/);
if (null != inputStream) {
try {
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
if (bitmap != null) {
return Asset.createFromBytes(toByteArray(bitmap));
} else {
Log.e(TAG, "Cannot decode profile picture for contact " + contactId);
}
} finally {
closeQuietly(inputStream);
}
}
}
// Use a default background image if the user has no profile picture or there was an error.
return getDefaultProfile(context.getResources());
}
private static byte[] toByteArray(Bitmap bitmap) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
closeQuietly(stream);
return byteArray;
}
private static void closeQuietly(Closeable closeable) {
try {
closeable.close();
} catch (IOException e) {
Log.e(TAG, "IOException while closing closeable.", e);
}
}
private static class Event {
public long id;
public long eventId;
public String title;
public long begin;
public long end;
public boolean allDay;
public String description;
public Asset ownerProfilePic;
public PutDataMapRequest toPutDataMapRequest(){
final PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(
makeDataItemPath(eventId, begin));
DataMap data = putDataMapRequest.getDataMap();
data.putString(DATA_ITEM_URI, putDataMapRequest.getUri().toString());
data.putLong(ID, id);
data.putLong(EVENT_ID, eventId);
data.putString(TITLE, title);
data.putLong(BEGIN, begin);
data.putLong(END, end);
data.putBoolean(ALL_DAY, allDay);
data.putString(DESCRIPTION, description);
data.putAsset(PROFILE_PIC, ownerProfilePic);
return putDataMapRequest;
}
}
}