Skip to content

Most visited

Recently visited

navigation
AgendaData / Application / src / com.example.android.wearable.agendadata /

CalendarQueryService.java

1
/*
2
 * Copyright (C) 2014 The Android Open Source Project
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
 
17
package com.example.android.wearable.agendadata;
18
 
19
 
20
import static com.example.android.wearable.agendadata.Constants.TAG;
21
import static com.example.android.wearable.agendadata.Constants.CONNECTION_TIME_OUT_MS;
22
import static com.example.android.wearable.agendadata.Constants.CAL_DATA_ITEM_PATH_PREFIX;
23
import static com.example.android.wearable.agendadata.Constants.ALL_DAY;
24
import static com.example.android.wearable.agendadata.Constants.BEGIN;
25
import static com.example.android.wearable.agendadata.Constants.DATA_ITEM_URI;
26
import static com.example.android.wearable.agendadata.Constants.DESCRIPTION;
27
import static com.example.android.wearable.agendadata.Constants.END;
28
import static com.example.android.wearable.agendadata.Constants.EVENT_ID;
29
import static com.example.android.wearable.agendadata.Constants.ID;
30
import static com.example.android.wearable.agendadata.Constants.PROFILE_PIC;
31
import static com.example.android.wearable.agendadata.Constants.TITLE;
32
 
33
import android.app.IntentService;
34
import android.content.ContentResolver;
35
import android.content.ContentUris;
36
import android.content.Context;
37
import android.content.Intent;
38
import android.content.res.Resources;
39
import android.database.Cursor;
40
import android.graphics.Bitmap;
41
import android.graphics.BitmapFactory;
42
import android.net.Uri;
43
import android.os.Bundle;
44
import android.provider.CalendarContract;
45
import android.provider.ContactsContract.CommonDataKinds.Email;
46
import android.provider.ContactsContract.Contacts;
47
import android.provider.ContactsContract.Data;
48
import android.text.format.Time;
49
import android.util.Log;
50
 
51
import com.google.android.gms.common.ConnectionResult;
52
import com.google.android.gms.common.api.GoogleApiClient;
53
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
54
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
55
import com.google.android.gms.wearable.Asset;
56
import com.google.android.gms.wearable.DataMap;
57
import com.google.android.gms.wearable.PutDataMapRequest;
58
import com.google.android.gms.wearable.Wearable;
59
 
60
import java.io.ByteArrayOutputStream;
61
import java.io.Closeable;
62
import java.io.IOException;
63
import java.io.InputStream;
64
import java.util.ArrayList;
65
import java.util.List;
66
import java.util.concurrent.TimeUnit;
67
 
68
/**
69
 * Queries calendar events using Android Calendar Provider API and creates a data item for each
70
 * event.
71
 */
72
public class CalendarQueryService extends IntentService
73
        implements ConnectionCallbacks, OnConnectionFailedListener {
74
 
75
    private static final String[] INSTANCE_PROJECTION = {
76
            CalendarContract.Instances._ID,
77
            CalendarContract.Instances.EVENT_ID,
78
            CalendarContract.Instances.TITLE,
79
            CalendarContract.Instances.BEGIN,
80
            CalendarContract.Instances.END,
81
            CalendarContract.Instances.ALL_DAY,
82
            CalendarContract.Instances.DESCRIPTION,
83
            CalendarContract.Instances.ORGANIZER
84
    };
85
 
86
    private static final String[] CONTACT_PROJECTION = new String[] { Data._ID, Data.CONTACT_ID };
87
    private static final String CONTACT_SELECTION = Email.ADDRESS + " = ?";
88
 
89
    private GoogleApiClient mGoogleApiClient;
90
 
91
    public CalendarQueryService() {
92
        super(CalendarQueryService.class.getSimpleName());
93
    }
94
 
95
    @Override
96
    public void onCreate() {
97
        super.onCreate();
98
        mGoogleApiClient = new GoogleApiClient.Builder(this)
99
                .addApi(Wearable.API)
100
                .addConnectionCallbacks(this)
101
                .addOnConnectionFailedListener(this)
102
                .build();
103
    }
104
 
105
    @Override
106
    protected void onHandleIntent(Intent intent) {
107
        mGoogleApiClient.blockingConnect(CONNECTION_TIME_OUT_MS, TimeUnit.MILLISECONDS);
108
        // Query calendar events in the next 24 hours.
109
        Time time = new Time();
110
        time.setToNow();
111
        long beginTime = time.toMillis(true);
112
        time.monthDay++;
113
        time.normalize(true);
114
        long endTime = time.normalize(true);
115
 
116
        List<Event> events = queryEvents(this, beginTime, endTime);
117
        for (Event event : events) {
118
            final PutDataMapRequest putDataMapRequest = event.toPutDataMapRequest();
119
            if (mGoogleApiClient.isConnected()) {
120
                Wearable.DataApi.putDataItem(
121
                    mGoogleApiClient, putDataMapRequest.asPutDataRequest()).await();
122
            } else {
123
                Log.e(TAG, "Failed to send data item: " + putDataMapRequest
124
                         + " - Client disconnected from Google Play Services");
125
            }
126
        }
127
        mGoogleApiClient.disconnect();
128
    }
129
 
130
    private static String makeDataItemPath(long eventId, long beginTime) {
131
        return CAL_DATA_ITEM_PATH_PREFIX + eventId + "/" + beginTime;
132
    }
133
 
134
    private static List<Event> queryEvents(Context context, long beginTime, long endTime) {
135
        ContentResolver contentResolver = context.getContentResolver();
136
        Uri.Builder builder = CalendarContract.Instances.CONTENT_URI.buildUpon();
137
        ContentUris.appendId(builder, beginTime);
138
        ContentUris.appendId(builder, endTime);
139
 
140
        Cursor cursor = contentResolver.query(builder.build(), INSTANCE_PROJECTION,
141
                null /* selection */, null /* selectionArgs */, null /* sortOrder */);
142
        try {
143
            int idIdx = cursor.getColumnIndex(CalendarContract.Instances._ID);
144
            int eventIdIdx = cursor.getColumnIndex(CalendarContract.Instances.EVENT_ID);
145
            int titleIdx = cursor.getColumnIndex(CalendarContract.Instances.TITLE);
146
            int beginIdx = cursor.getColumnIndex(CalendarContract.Instances.BEGIN);
147
            int endIdx = cursor.getColumnIndex(CalendarContract.Instances.END);
148
            int allDayIdx = cursor.getColumnIndex(CalendarContract.Instances.ALL_DAY);
149
            int descIdx = cursor.getColumnIndex(CalendarContract.Instances.DESCRIPTION);
150
            int ownerEmailIdx = cursor.getColumnIndex(CalendarContract.Instances.ORGANIZER);
151
 
152
            List<Event> events = new ArrayList<Event>(cursor.getCount());
153
            while (cursor.moveToNext()) {
154
                Event event = new Event();
155
                event.id = cursor.getLong(idIdx);
156
                event.eventId = cursor.getLong(eventIdIdx);
157
                event.title = cursor.getString(titleIdx);
158
                event.begin = cursor.getLong(beginIdx);
159
                event.end = cursor.getLong(endIdx);
160
                event.allDay = cursor.getInt(allDayIdx) != 0;
161
                event.description = cursor.getString(descIdx);
162
                String ownerEmail = cursor.getString(ownerEmailIdx);
163
                Cursor contactCursor = contentResolver.query(Data.CONTENT_URI,
164
                        CONTACT_PROJECTION, CONTACT_SELECTION, new String[] {ownerEmail}, null);
165
                int ownerIdIdx = contactCursor.getColumnIndex(Data.CONTACT_ID);
166
                long ownerId = -1;
167
                if (contactCursor.moveToFirst()) {
168
                    ownerId = contactCursor.getLong(ownerIdIdx);
169
                }
170
                contactCursor.close();
171
                // Use event organizer's profile picture as the notification background.
172
                event.ownerProfilePic = getProfilePicture(contentResolver, context, ownerId);
173
                events.add(event);
174
            }
175
            return events;
176
        } finally {
177
            cursor.close();
178
        }
179
    }
180
 
181
    @Override
182
    public void onConnected(Bundle connectionHint) {
183
    }
184
 
185
    @Override
186
    public void onConnectionSuspended(int cause) {
187
    }
188
 
189
    @Override
190
    public void onConnectionFailed(ConnectionResult result) {
191
    }
192
 
193
    private static Asset getDefaultProfile(Resources res) {
194
        Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.nobody);
195
        return Asset.createFromBytes(toByteArray(bitmap));
196
    }
197
 
198
    private static Asset getProfilePicture(ContentResolver contentResolver, Context context,
199
                                           long contactId) {
200
        if (contactId != -1) {
201
            // Try to retrieve the profile picture for the given contact.
202
            Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
203
            InputStream inputStream = Contacts.openContactPhotoInputStream(contentResolver,
204
                    contactUri, true /*preferHighres*/);
205
 
206
            if (null != inputStream) {
207
                try {
208
                    Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
209
                    if (bitmap != null) {
210
                        return Asset.createFromBytes(toByteArray(bitmap));
211
                    } else {
212
                        Log.e(TAG, "Cannot decode profile picture for contact " + contactId);
213
                    }
214
                } finally {
215
                    closeQuietly(inputStream);
216
                }
217
            }
218
        }
219
        // Use a default background image if the user has no profile picture or there was an error.
220
        return getDefaultProfile(context.getResources());
221
    }
222
 
223
    private static byte[] toByteArray(Bitmap bitmap) {
224
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
225
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
226
        byte[] byteArray = stream.toByteArray();
227
        closeQuietly(stream);
228
        return byteArray;
229
    }
230
 
231
    private static void closeQuietly(Closeable closeable) {
232
        try {
233
            closeable.close();
234
        } catch (IOException e) {
235
            Log.e(TAG, "IOException while closing closeable.", e);
236
        }
237
    }
238
 
239
    private static class Event {
240
 
241
        public long id;
242
        public long eventId;
243
        public String title;
244
        public long begin;
245
        public long end;
246
        public boolean allDay;
247
        public String description;
248
        public Asset ownerProfilePic;
249
 
250
        public PutDataMapRequest toPutDataMapRequest(){
251
            final PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(
252
                    makeDataItemPath(eventId, begin));
253
            /* In most cases (as in this one), you don't need your DataItem appear instantly. By
254
            default, delivery of normal DataItems to the Wear network might be delayed in order to
255
            improve battery life for user devices. However, if you can't tolerate a delay in the
256
            sync of your DataItems, you can mark them as urgent via setUrgent().
257
             */
258
            DataMap data = putDataMapRequest.getDataMap();
259
            data.putString(DATA_ITEM_URI, putDataMapRequest.getUri().toString());
260
            data.putLong(ID, id);
261
            data.putLong(EVENT_ID, eventId);
262
            data.putString(TITLE, title);
263
            data.putLong(BEGIN, begin);
264
            data.putLong(END, end);
265
            data.putBoolean(ALL_DAY, allDay);
266
            data.putString(DESCRIPTION, description);
267
            data.putAsset(PROFILE_PIC, ownerProfilePic);
268
 
269
            return putDataMapRequest;
270
        }
271
    }
272
}