1 /* 2 * Copyright (C) 2017 Google Inc. All Rights Reserved. 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 package com.example.android.wearable.wear.wearaccessibilityapp; 17 18 import android.app.Activity; 19 import android.app.Notification; 20 import android.app.NotificationChannel; 21 import android.app.NotificationManager; 22 import android.app.PendingIntent; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.graphics.BitmapFactory; 26 import android.os.Build; 27 import android.os.Bundle; 28 import android.preference.Preference; 29 import android.preference.Preference.OnPreferenceChangeListener; 30 import android.preference.Preference.OnPreferenceClickListener; 31 import android.preference.PreferenceFragment; 32 import android.preference.SwitchPreference; 33 import android.support.v4.app.NotificationCompat; 34 import android.support.v4.app.NotificationCompat.MessagingStyle; 35 import android.support.v4.app.NotificationManagerCompat; 36 import android.support.v4.app.RemoteInput; 37 import android.support.v4.content.ContextCompat; 38 import android.support.wear.ambient.AmbientMode; 39 import android.util.Log; 40 41 public class NotificationsActivity extends Activity implements AmbientMode.AmbientCallbackProvider { 42 43 public static final int NOTIFICATION_ID = 888; 44 45 @Override 46 protected void onCreate(Bundle savedInstanceState) { 47 super.onCreate(savedInstanceState); 48 49 AmbientMode.attachAmbientSupport(this); 50 51 // Display the fragment as the main content. 52 getFragmentManager() 53 .beginTransaction() 54 .replace(android.R.id.content, new NotificationsPrefsFragment()) 55 .commit(); 56 } 57 58 public static class NotificationsPrefsFragment extends PreferenceFragment { 59 60 private static final String TAG = "NotificationsActivity"; 61 private NotificationManagerCompat mNotificationManagerCompat; 62 private boolean mActionOn; // if true, displays in-line action 63 private boolean mAvatarOn; // if true, displays avatar of messenger 64 65 @Override 66 public void onCreate(Bundle savedInstanceState) { 67 super.onCreate(savedInstanceState); 68 69 // Load the preferences from an XML resource 70 addPreferencesFromResource(R.xml.prefs_notifications); 71 72 mNotificationManagerCompat = NotificationManagerCompat.from(getActivity()); 73 74 final SwitchPreference mActionSwitchPref = 75 (SwitchPreference) findPreference(getString(R.string.key_pref_action)); 76 final SwitchPreference mAvatarSwitchPref = 77 (SwitchPreference) findPreference(getString(R.string.key_pref_avatar)); 78 Preference mPushNotificationPref = 79 findPreference(getString(R.string.key_pref_push_notification)); 80 81 initInLineAction(mActionSwitchPref); 82 initAvatar(mAvatarSwitchPref); 83 initPushNotification(mPushNotificationPref); 84 } 85 86 public void initInLineAction(SwitchPreference switchPref) { 87 switchPref.setChecked(true); 88 mActionOn = switchPref.isChecked(); 89 switchPref.setOnPreferenceChangeListener( 90 new OnPreferenceChangeListener() { 91 @Override 92 public boolean onPreferenceChange(Preference preference, Object newValue) { 93 mActionOn = (Boolean) newValue; 94 return true; 95 } 96 }); 97 } 98 99 public void initAvatar(SwitchPreference switchPref) { 100 switchPref.setChecked(true); 101 mAvatarOn = switchPref.isChecked(); 102 switchPref.setOnPreferenceChangeListener( 103 new OnPreferenceChangeListener() { 104 @Override 105 public boolean onPreferenceChange(Preference preference, Object newValue) { 106 mAvatarOn = (Boolean) newValue; 107 return true; 108 } 109 }); 110 } 111 112 public void initPushNotification(Preference pref) { 113 pref.setOnPreferenceClickListener( 114 new OnPreferenceClickListener() { 115 @Override 116 public boolean onPreferenceClick(Preference preference) { 117 generateMessagingStyleNotification(getContext()); 118 return true; 119 } 120 }); 121 } 122 123 /* 124 * Generates a MESSAGING_STYLE Notification that supports both Wear 1.+ and Wear 2.0. For 125 * devices on API level 24 (Wear 2.0) and after, displays MESSAGING_STYLE. Otherwise, 126 * displays a basic BIG_TEXT_STYLE. 127 * 128 * IMPORTANT NOTE: 129 * Notification Styles behave slightly different on Wear 2.0 when they are launched by a 130 * native/local Wear app, i.e., they will NOT expand when the user taps them but will 131 * instead take the user directly into the local app for the richest experience. In 132 * contrast, a bridged Notification launched from the phone will expand with the style 133 * details (whether there is a local app or not). 134 * 135 * If you want to enable an action on your Notification without launching the app, you can 136 * do so with the setHintDisplayActionInline() feature (shown below), but this only allows 137 * one action. 138 * 139 * If you wish to replicate the original experience of a bridged notification, please 140 * review the generateBigTextStyleNotification() method above to see how. 141 */ 142 private void generateMessagingStyleNotification(Context context) { 143 Log.d(TAG, "generateMessagingStyleNotification()"); 144 145 // Main steps for building a MESSAGING_STYLE notification: 146 // 0. Get your data 147 // 1. Retrieve Notification Channel for O and beyond devices (26+) 148 // 2. Build the MESSAGING_STYLE 149 // 3. Set up main Intent for notification 150 // 4. Set up RemoteInput (users can input directly from notification) 151 // 5. Build and issue the notification 152 153 // 0. Get your data (everything unique per Notification). 154 MockDatabase.MessagingStyleCommsAppData messagingStyleCommsAppData = 155 MockDatabase.getMessagingStyleData(); 156 157 // 1. Create/Retrieve Notification Channel for O and beyond devices (26+). 158 String notificationChannelId = 159 createNotificationChannel(context, messagingStyleCommsAppData); 160 161 // 2. Build the Notification.Style (MESSAGING_STYLE) 162 String contentTitle = messagingStyleCommsAppData.getContentTitle(); 163 164 MessagingStyle messagingStyle = 165 new NotificationCompat.MessagingStyle( 166 messagingStyleCommsAppData.getReplayName()) 167 // You could set a different title to appear when the messaging style 168 // is supported on device (24+) if you wish. In our case, we use the 169 // same 170 // title. 171 .setConversationTitle(contentTitle); 172 173 // Adds all Messages 174 // Note: Messages include the text, timestamp, and sender 175 for (MessagingStyle.Message message : messagingStyleCommsAppData.getMessages()) { 176 messagingStyle.addMessage(message); 177 } 178 179 // 3. Set up main Intent for notification 180 Intent notifyIntent = new Intent(getActivity(), MessagingMainActivity.class); 181 182 PendingIntent mainPendingIntent = 183 PendingIntent.getActivity( 184 getActivity(), 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT); 185 186 // 4. Set up a RemoteInput Action, so users can input (keyboard, drawing, voice) 187 // directly from the notification without entering the app. 188 189 // Create the RemoteInput specifying this key. 190 String replyLabel = getString(R.string.reply_label); 191 RemoteInput remoteInput = 192 new RemoteInput.Builder(MessagingIntentService.EXTRA_REPLY) 193 .setLabel(replyLabel) 194 .build(); 195 196 // Create PendingIntent for service that handles input. 197 Intent replyIntent = new Intent(getActivity(), MessagingIntentService.class); 198 replyIntent.setAction(MessagingIntentService.ACTION_REPLY); 199 PendingIntent replyActionPendingIntent = 200 PendingIntent.getService(getActivity(), 0, replyIntent, 0); 201 202 // Enable action to appear inline on Wear 2.0 (24+). This means it will appear over the 203 // lower portion of the Notification for easy action (only possible for one action). 204 final NotificationCompat.Action.WearableExtender inlineActionForWear2 = 205 new NotificationCompat.Action.WearableExtender() 206 .setHintDisplayActionInline(mActionOn) 207 .setHintLaunchesActivity(false); 208 209 NotificationCompat.Action replyAction = 210 new NotificationCompat.Action.Builder( 211 R.drawable.reply, replyLabel, replyActionPendingIntent) 212 .addRemoteInput(remoteInput) 213 // Allows system to generate replies by context of conversation 214 .setAllowGeneratedReplies(true) 215 // Add WearableExtender to enable inline actions 216 .extend(inlineActionForWear2) 217 .build(); 218 219 // 5. Build and issue the notification 220 221 // Because we want this to be a new notification (not updating current notification), 222 // we create a new Builder. Later, we update this same notification, so we need to save 223 // this Builder globally (as outlined earlier). 224 225 // Notification Channel Id is ignored for Android pre O (26). 226 NotificationCompat.Builder notificationCompatBuilder = 227 new NotificationCompat.Builder(context, notificationChannelId); 228 229 GlobalNotificationBuilder.setNotificationCompatBuilderInstance( 230 notificationCompatBuilder); 231 232 // Builds and issues notification 233 notificationCompatBuilder 234 // MESSAGING_STYLE sets title and content for Wear 1.+ and Wear 2.0 devices. 235 .setStyle(messagingStyle) 236 .setContentTitle(contentTitle) 237 .setContentText(messagingStyleCommsAppData.getContentText()) 238 .setSmallIcon(R.drawable.watch) 239 .setContentIntent(mainPendingIntent) 240 .setColor(ContextCompat.getColor(context, R.color.background)) 241 .setDefaults(NotificationCompat.DEFAULT_ALL) 242 243 // Number of new notifications for API <24 (Wear 1.+) devices 244 .setSubText( 245 Integer.toString(messagingStyleCommsAppData.getNumberOfNewMessages())) 246 .addAction(replyAction) 247 .setCategory(Notification.CATEGORY_MESSAGE) 248 249 // Sets priority for 25 and below. For 26 and above, 'priority' is deprecated 250 // for 'importance' which is set in the NotificationChannel. The integers 251 // representing 'priority' are different from 'importance', so make sure you 252 // don't mix them. 253 .setPriority(messagingStyleCommsAppData.getPriority()) 254 255 // Sets lock-screen visibility for 25 and below. For 26 and above, lock screen 256 // visibility is set in the NotificationChannel. 257 .setVisibility(messagingStyleCommsAppData.getChannelLockscreenVisibility()); 258 259 notificationCompatBuilder.setLargeIcon( 260 BitmapFactory.decodeResource( 261 getResources(), mAvatarOn ? R.drawable.avatar : R.drawable.watch)); 262 263 // If the phone is in "Do not disturb mode, the user will still be notified if 264 // the sender(s) is starred as a favorite. 265 for (String name : messagingStyleCommsAppData.getParticipants()) { 266 notificationCompatBuilder.addPerson(name); 267 } 268 269 Notification notification = notificationCompatBuilder.build(); 270 mNotificationManagerCompat.notify(NOTIFICATION_ID, notification); 271 272 // Close app to demonstrate notification in steam. 273 getActivity().finish(); 274 } 275 276 private String createNotificationChannel( 277 Context context, MockDatabase.MessagingStyleCommsAppData mockNotificationData) { 278 279 // NotificationChannels are required for Notifications on O (API 26) and above. 280 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 281 282 // The id of the channel. 283 String channelId = mockNotificationData.getChannelId(); 284 285 // The user-visible name of the channel. 286 CharSequence channelName = mockNotificationData.getChannelName(); 287 // The user-visible description of the channel. 288 String channelDescription = mockNotificationData.getChannelDescription(); 289 int channelImportance = mockNotificationData.getChannelImportance(); 290 boolean channelEnableVibrate = mockNotificationData.isChannelEnableVibrate(); 291 int channelLockscreenVisibility = 292 mockNotificationData.getChannelLockscreenVisibility(); 293 294 // Initializes NotificationChannel. 295 NotificationChannel notificationChannel = 296 new NotificationChannel(channelId, channelName, channelImportance); 297 notificationChannel.setDescription(channelDescription); 298 notificationChannel.enableVibration(channelEnableVibrate); 299 notificationChannel.setLockscreenVisibility(channelLockscreenVisibility); 300 301 // Adds NotificationChannel to system. Attempting to create an existing notification 302 // channel with its original values performs no operation, so it's safe to perform 303 // the below sequence. 304 NotificationManager notificationManager = 305 (NotificationManager) 306 context.getSystemService(Context.NOTIFICATION_SERVICE); 307 notificationManager.createNotificationChannel(notificationChannel); 308 309 return channelId; 310 } else { 311 // Returns null for pre-O (26) devices. 312 return null; 313 } 314 } 315 } 316 317 @Override 318 public AmbientMode.AmbientCallback getAmbientCallback() { 319 return new MyAmbientCallback(); 320 } 321 322 private class MyAmbientCallback extends AmbientMode.AmbientCallback {} 323 } 324