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 import android.app.Notification;
20 import android.app.NotificationManager;
21 import android.app.PendingIntent;
22 import android.content.Intent;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapFactory;
25 import android.net.Uri;
26 import android.text.TextUtils;
27 import android.text.format.DateFormat;
28 import android.util.Log;
29 
30 import static com.example.android.wearable.agendadata.Constants.ALL_DAY;
31 import static com.example.android.wearable.agendadata.Constants.BEGIN;
32 import static com.example.android.wearable.agendadata.Constants.DESCRIPTION;
33 import static com.example.android.wearable.agendadata.Constants.EXTRA_SILENT;
34 import static com.example.android.wearable.agendadata.Constants.END;
35 import static com.example.android.wearable.agendadata.Constants.PROFILE_PIC;
36 import static com.example.android.wearable.agendadata.Constants.TAG;
37 import static com.example.android.wearable.agendadata.Constants.TITLE;
38 
39 import com.google.android.gms.common.api.GoogleApiClient;
40 import com.google.android.gms.wearable.Asset;
41 import com.google.android.gms.wearable.DataApi;
42 import com.google.android.gms.wearable.DataEvent;
43 import com.google.android.gms.wearable.DataEventBuffer;
44 import com.google.android.gms.wearable.DataItem;
45 import com.google.android.gms.wearable.DataMap;
46 import com.google.android.gms.wearable.DataMapItem;
47 import com.google.android.gms.wearable.Wearable;
48 import com.google.android.gms.wearable.WearableListenerService;
49 
50 import java.util.Date;
51 import java.util.HashMap;
52 import java.util.Map;
53 
54 /**
55  * Listens to DataItem events on the home device.
56  */
57 public class HomeListenerService extends WearableListenerService {
58 
59     private static final Map<Uri, Integer> sNotificationIdByDataItemUri =
60             new HashMap<Uri, Integer>();
61     private static int sNotificationId = 1;
62     private GoogleApiClient mGoogleApiClient;
63 
64     @Override
onCreate()65     public void onCreate() {
66         super.onCreate();
67         mGoogleApiClient = new GoogleApiClient.Builder(this.getApplicationContext())
68                 .addApi(Wearable.API)
69                 .build();
70         mGoogleApiClient.connect();
71     }
72 
73     @Override
onDataChanged(DataEventBuffer dataEvents)74     public void onDataChanged(DataEventBuffer dataEvents) {
75         if (Log.isLoggable(TAG, Log.DEBUG)) {
76             Log.d(TAG, "onDataChanged: " + dataEvents + " for " + getPackageName());
77         }
78         for (DataEvent event : dataEvents) {
79 
80             if (event.getType() == DataEvent.TYPE_DELETED) {
81                 deleteDataItem(event.getDataItem());
82             } else if (event.getType() == DataEvent.TYPE_CHANGED) {
83                 updateNotificationForDataItem(event.getDataItem());
84             }
85         }
86     }
87 
88     /**
89      * Deletes the calendar card associated with the data item.
90      */
deleteDataItem(DataItem dataItem)91     private void deleteDataItem(DataItem dataItem) {
92         if (Log.isLoggable(TAG, Log.VERBOSE)) {
93             Log.v(TAG, "onDataItemDeleted:DataItem=" + dataItem.getUri());
94         }
95         Integer notificationId = sNotificationIdByDataItemUri.remove(dataItem.getUri());
96         if (notificationId != null) {
97             ((NotificationManager) getSystemService(NOTIFICATION_SERVICE)).cancel(notificationId);
98         }
99     }
100 
101     /**
102      * Posts a local notification to show calendar card.
103      */
updateNotificationForDataItem(DataItem dataItem)104     private void updateNotificationForDataItem(DataItem dataItem) {
105         DataMapItem mapDataItem = DataMapItem.fromDataItem(dataItem);
106         DataMap data = mapDataItem.getDataMap();
107 
108         String description = data.getString(DESCRIPTION);
109         if (TextUtils.isEmpty(description)) {
110             description = "";
111         } else {
112             // Add a space between the description and the time of the event.
113             description += " ";
114         }
115         String contentText;
116         if (data.getBoolean(ALL_DAY)) {
117             contentText = getString(R.string.desc_all_day, description);
118         } else {
119             String startTime = DateFormat.getTimeFormat(this).format(new Date(data.getLong(BEGIN)));
120             String endTime = DateFormat.getTimeFormat(this).format(new Date(data.getLong(END)));
121             contentText = getString(R.string.desc_time_period, description, startTime, endTime);
122         }
123 
124         Intent deleteOperation = new Intent(this, DeleteService.class);
125         // Use a unique identifier for the delete action.
126         String deleteAction = "action_delete" + dataItem.getUri().toString() + sNotificationId;
127         deleteOperation.setAction(deleteAction);
128         deleteOperation.setData(dataItem.getUri());
129         PendingIntent deleteIntent = PendingIntent.getService(this, 0, deleteOperation,
130                 PendingIntent.FLAG_ONE_SHOT);
131         PendingIntent silentDeleteIntent = PendingIntent.getService(this, 1,
132                 deleteOperation.putExtra(EXTRA_SILENT, true), PendingIntent.FLAG_ONE_SHOT);
133 
134         Notification.Builder notificationBuilder = new Notification.Builder(this)
135                 .setContentTitle(data.getString(TITLE))
136                 .setContentText(contentText)
137                 .setSmallIcon(R.drawable.ic_launcher)
138                 .addAction(R.drawable.ic_menu_delete, getText(R.string.delete), deleteIntent)
139                 .setDeleteIntent(silentDeleteIntent)  // Delete DataItem if notification dismissed.
140                 .setLocalOnly(true)
141                 .setPriority(Notification.PRIORITY_MIN);
142 
143         // Set the event owner's profile picture as the notification background.
144         Asset asset = data.getAsset(PROFILE_PIC);
145         if (null != asset) {
146             if (mGoogleApiClient.isConnected()) {
147                 DataApi.GetFdForAssetResult assetFdResult =
148                         Wearable.DataApi.getFdForAsset(mGoogleApiClient, asset).await();
149                 if (assetFdResult.getStatus().isSuccess()) {
150                     Bitmap profilePic = BitmapFactory.decodeStream(assetFdResult.getInputStream());
151                     notificationBuilder.extend(new Notification.WearableExtender()
152                             .setBackground(profilePic));
153                 } else if (Log.isLoggable(TAG, Log.DEBUG)) {
154                     Log.d(TAG, "asset fetch failed with statusCode: "
155                             + assetFdResult.getStatus().getStatusCode());
156                 }
157             } else {
158                 Log.e(TAG, "Failed to set notification background"
159                          + " - Client disconnected from Google Play Services");
160             }
161         }
162         Notification card = notificationBuilder.build();
163 
164         ((NotificationManager) getSystemService(NOTIFICATION_SERVICE))
165                 .notify(sNotificationId, card);
166 
167         sNotificationIdByDataItemUri.put(dataItem.getUri(), sNotificationId++);
168     }
169 }
170