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.app.Activity; 23 import android.net.Uri; 24 import android.os.Bundle; 25 import android.support.v4.app.NotificationCompat; 26 import android.support.v4.app.NotificationManagerCompat; 27 import android.util.Log; 28 import android.view.LayoutInflater; 29 import android.view.MenuItem; 30 import android.view.View; 31 import android.view.ViewGroup; 32 import android.widget.Toast; 33 34 import com.example.android.wearable.synchronizednotifications.common.Constants; 35 import com.google.android.gms.common.ConnectionResult; 36 import com.google.android.gms.common.api.GoogleApiClient; 37 import com.google.android.gms.common.api.ResultCallback; 38 import com.google.android.gms.wearable.DataApi; 39 import com.google.android.gms.wearable.PutDataMapRequest; 40 import com.google.android.gms.wearable.PutDataRequest; 41 import com.google.android.gms.wearable.Wearable; 42 43 import java.text.SimpleDateFormat; 44 import java.util.Date; 45 import java.util.Locale; 46 47 48 /** 49 * A simple fragment that presents three buttons that would trigger three different combinations of 50 * notifications on the handset and the watch: 51 * <ul> 52 * <li>The first button builds a simple local-only notification on the handset.</li> 53 * <li>The second one creates a wearable-only notification by putting a data item in the shared data 54 * store and having a {@link com.google.android.gms.wearable.WearableListenerService} listen for 55 * that on the wearable</li> 56 * <li>The third one creates a local notification and a wearable notification by combining the above 57 * two. It, however, demonstrates how one can set things up so that the dismissal of one 58 * notification results in the dismissal of the other one.</li> 59 * </ul> 60 */ 61 public class SynchronizedNotificationsFragment extends Fragment 62 implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { 63 64 private static final String TAG = "SynchronizedNotificationsFragment"; 65 private GoogleApiClient mGoogleApiClient; 66 67 @Override 68 public void onCreate(Bundle savedInstanceState) { 69 super.onCreate(savedInstanceState); 70 mGoogleApiClient = new GoogleApiClient.Builder(this.getActivity()) 71 .addApi(Wearable.API) 72 .addConnectionCallbacks(this) 73 .addOnConnectionFailedListener(this) 74 .build(); 75 setHasOptionsMenu(true); 76 } 77 78 @Override 79 public boolean onOptionsItemSelected(MenuItem item) { 80 switch (item.getItemId()) { 81 case R.id.btn_phone_only: 82 buildLocalOnlyNotification(getString(R.string.phone_only), now(), 83 Constants.PHONE_ONLY_ID, false); 84 return true; 85 case R.id.btn_wear_only: 86 buildWearableOnlyNotification(getString(R.string.wear_only), now(), 87 Constants.WATCH_ONLY_PATH); 88 return true; 89 case R.id.btn_different: 90 buildMirroredNotifications(getString(R.string.phone_both), getString(R.string.watch_both), now()); 91 return true; 92 } 93 return false; 94 } 95 96 /** 97 * Builds a local-only notification for the handset. This is achieved by using 98 * <code>setLocalOnly(true)</code>. If <code>withDismissal</code> is set to <code>true</code>, a 99 * {@link android.app.PendingIntent} will be added to handle the dismissal of notification to 100 * be able to remove the mirrored notification on the wearable. 101 */ 102 private void buildLocalOnlyNotification(String title, String content, int notificationId, 103 boolean withDismissal) { 104 NotificationCompat.Builder builder = new NotificationCompat.Builder(this.getActivity()); 105 builder.setContentTitle(title) 106 .setContentText(content) 107 .setLocalOnly(true) 108 .setSmallIcon(R.drawable.ic_launcher); 109 110 if (withDismissal) { 111 Intent dismissIntent = new Intent(Constants.ACTION_DISMISS); 112 dismissIntent.putExtra(Constants.KEY_NOTIFICATION_ID, Constants.BOTH_ID); 113 PendingIntent pendingIntent = PendingIntent 114 .getService(this.getActivity(), 0, dismissIntent, PendingIntent.FLAG_UPDATE_CURRENT); 115 builder.setDeleteIntent(pendingIntent); 116 } 117 NotificationManagerCompat.from(this.getActivity()).notify(notificationId, builder.build()); 118 } 119 120 /** 121 * Builds a DataItem that on the wearable will be interpreted as a request to show a 122 * notification. The result will be a notification that only shows up on the wearable. 123 */ 124 private void buildWearableOnlyNotification(String title, String content, String path) { 125 if (mGoogleApiClient.isConnected()) { 126 PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(path); 127 putDataMapRequest.getDataMap().putString(Constants.KEY_CONTENT, content); 128 putDataMapRequest.getDataMap().putString(Constants.KEY_TITLE, title); 129 PutDataRequest request = putDataMapRequest.asPutDataRequest(); 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