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.synchronizednotifications; 18 19 import android.app.PendingIntent; 20 import android.content.Intent; 21 import android.support.v4.app.Fragment; 22 import android.os.Bundle; 23 import android.support.v4.app.NotificationCompat; 24 import android.support.v4.app.NotificationManagerCompat; 25 import android.util.Log; 26 import android.view.MenuItem; 27 28 import com.example.android.wearable.synchronizednotifications.common.Constants; 29 import com.google.android.gms.common.ConnectionResult; 30 import com.google.android.gms.common.api.GoogleApiClient; 31 import com.google.android.gms.common.api.ResultCallback; 32 import com.google.android.gms.wearable.DataApi; 33 import com.google.android.gms.wearable.PutDataMapRequest; 34 import com.google.android.gms.wearable.PutDataRequest; 35 import com.google.android.gms.wearable.Wearable; 36 37 import java.text.SimpleDateFormat; 38 import java.util.Date; 39 import java.util.Locale; 40 41 42 /** 43 * A simple fragment that presents three buttons that would trigger three different combinations of 44 * notifications on the handset and the watch: 45 * <ul> 46 * <li>The first button builds a simple local-only notification on the handset.</li> 47 * <li>The second one creates a wearable-only notification by putting a data item in the shared data 48 * store and having a {@link com.google.android.gms.wearable.WearableListenerService} listen for 49 * that on the wearable</li> 50 * <li>The third one creates a local notification and a wearable notification by combining the above 51 * two. It, however, demonstrates how one can set things up so that the dismissal of one 52 * notification results in the dismissal of the other one.</li> 53 * </ul> 54 */ 55 public class SynchronizedNotificationsFragment extends Fragment 56 implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { 57 58 private static final String TAG = "SynchronizedFragment"; 59 private GoogleApiClient mGoogleApiClient; 60 61 @Override 62 public void onCreate(Bundle savedInstanceState) { 63 super.onCreate(savedInstanceState); 64 mGoogleApiClient = new GoogleApiClient.Builder(this.getActivity()) 65 .addApi(Wearable.API) 66 .addConnectionCallbacks(this) 67 .addOnConnectionFailedListener(this) 68 .build(); 69 setHasOptionsMenu(true); 70 } 71 72 @Override 73 public boolean onOptionsItemSelected(MenuItem item) { 74 switch (item.getItemId()) { 75 case R.id.btn_phone_only: 76 buildLocalOnlyNotification(getString(R.string.phone_only), now(), 77 Constants.PHONE_ONLY_ID, false); 78 return true; 79 case R.id.btn_wear_only: 80 buildWearableOnlyNotification(getString(R.string.wear_only), now(), 81 Constants.WATCH_ONLY_PATH); 82 return true; 83 case R.id.btn_different: 84 buildMirroredNotifications( 85 getString(R.string.phone_both), getString(R.string.watch_both), now()); 86 return true; 87 } 88 return false; 89 } 90 91 /** 92 * Builds a local-only notification for the handset. This is achieved by using 93 * <code>setLocalOnly(true)</code>. If <code>withDismissal</code> is set to <code>true</code>, a 94 * {@link android.app.PendingIntent} will be added to handle the dismissal of notification to 95 * be able to remove the mirrored notification on the wearable. 96 */ 97 private void buildLocalOnlyNotification(String title, String content, int notificationId, 98 boolean withDismissal) { 99 NotificationCompat.Builder builder = new NotificationCompat.Builder(this.getActivity()); 100 builder.setContentTitle(title) 101 .setContentText(content) 102 .setLocalOnly(true) 103 .setSmallIcon(R.drawable.ic_launcher); 104 105 if (withDismissal) { 106 Intent dismissIntent = new Intent(Constants.ACTION_DISMISS); 107 dismissIntent.putExtra(Constants.KEY_NOTIFICATION_ID, Constants.BOTH_ID); 108 PendingIntent pendingIntent = 109 PendingIntent.getService( 110 this.getActivity(), 111 0, 112 dismissIntent, 113 PendingIntent.FLAG_UPDATE_CURRENT); 114 builder.setDeleteIntent(pendingIntent); 115 } 116 NotificationManagerCompat.from(this.getActivity()).notify(notificationId, builder.build()); 117 } 118 119 /** 120 * Builds a DataItem that on the wearable will be interpreted as a request to show a 121 * notification. The result will be a notification that only shows up on the wearable. 122 */ 123 private void buildWearableOnlyNotification(String title, String content, String path) { 124 if (mGoogleApiClient.isConnected()) { 125 PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(path); 126 putDataMapRequest.getDataMap().putString(Constants.KEY_CONTENT, content); 127 putDataMapRequest.getDataMap().putString(Constants.KEY_TITLE, title); 128 PutDataRequest request = putDataMapRequest.asPutDataRequest(); 129 request.setUrgent(); 130 Wearable.DataApi.putDataItem(mGoogleApiClient, request) 131 .setResultCallback(new ResultCallback<DataApi.DataItemResult>() { 132 @Override 133 public void onResult(DataApi.DataItemResult dataItemResult) { 134 if (!dataItemResult.getStatus().isSuccess()) { 135 Log.e(TAG, "buildWatchOnlyNotification(): Failed to set the data, " 136 + "status: " + dataItemResult.getStatus().getStatusCode()); 137 } 138 } 139 }); 140 } else { 141 Log.e(TAG, "buildWearableOnlyNotification(): no Google API Client connection"); 142 } 143 } 144 145 /** 146 * Builds a local notification and sets a DataItem that will be interpreted by the wearable as 147 * a request to build a notification on the wearable as as well. The two notifications show 148 * different messages. 149 * Dismissing either of the notifications will result in dismissal of the other; this is 150 * achieved by creating a {@link android.app.PendingIntent} that results in removal of 151 * the DataItem that created the watch notification. The deletion of the DataItem is observed on 152 * both sides, using WearableListenerService callbacks, and is interpreted on each side as a 153 * request to dismiss the corresponding notification. 154 */ 155 private void buildMirroredNotifications(String phoneTitle, String watchTitle, String content) { 156 if (mGoogleApiClient.isConnected()) { 157 // Wearable notification 158 buildWearableOnlyNotification(watchTitle, content, Constants.BOTH_PATH); 159 160 // Local notification, with a pending intent for dismissal 161 buildLocalOnlyNotification(phoneTitle, content, Constants.BOTH_ID, true); 162 } 163 } 164 165 @Override 166 public void onStart() { 167 super.onStart(); 168 mGoogleApiClient.connect(); 169 } 170 171 @Override 172 public void onStop() { 173 mGoogleApiClient.disconnect(); 174 super.onStop(); 175 } 176 177 @Override 178 public void onConnected(Bundle bundle) { 179 } 180 181 @Override 182 public void onConnectionSuspended(int i) { 183 } 184 185 @Override 186 public void onConnectionFailed(ConnectionResult connectionResult) { 187 Log.e(TAG, "Failed to connect to Google API Client"); 188 } 189 190 /** 191 * Returns a string built from the current time 192 */ 193 private String now() { 194 SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); 195 return sdf.format(new Date()); 196 } 197 198 } 199