| /* |
| * 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; |
| } |
| } |
| } |