1 /* 2 * Copyright (C) 2019 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 android.app.stubs; 18 19 import static android.app.stubs.BubbledActivity.EXTRA_LOCUS_ID; 20 21 import android.app.Activity; 22 import android.app.Notification; 23 import android.app.Notification.BubbleMetadata; 24 import android.app.NotificationManager; 25 import android.app.PendingIntent; 26 import android.app.Person; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.LocusId; 30 import android.graphics.drawable.Icon; 31 import android.os.Bundle; 32 import android.os.SystemClock; 33 34 /** 35 * Used by NotificationManagerTest for testing policy around bubbles, this activity is able to 36 * send a bubble. 37 */ 38 public class SendBubbleActivity extends Activity { 39 final String TAG = SendBubbleActivity.class.getSimpleName(); 40 41 // Should be same as what NotificationManagerTest is using 42 private static final String NOTIFICATION_CHANNEL_ID = "NotificationManagerTest"; 43 private static final String SHARE_SHORTCUT_ID = "shareShortcut"; 44 45 public static final String BUBBLE_ACTIVITY_OPENED = 46 "android.app.stubs.BUBBLE_ACTIVITY_OPENED"; 47 public static final int BUBBLE_NOTIF_ID = 1; 48 49 private volatile boolean mIsStopped; 50 51 @Override onCreate(Bundle savedInstanceState)52 public void onCreate(Bundle savedInstanceState) { 53 super.onCreate(savedInstanceState); 54 setContentView(R.layout.main); 55 56 Intent i = new Intent(BUBBLE_ACTIVITY_OPENED); 57 sendBroadcast(i); 58 } 59 startBubbleActivity(int id)60 public void startBubbleActivity(int id) { 61 startBubbleActivity(id, true /* addLocusId */); 62 } 63 64 /** 65 * Starts the same activity that is in the bubble produced by this activity. 66 */ startBubbleActivity(int id, boolean addLocusId)67 public void startBubbleActivity(int id, boolean addLocusId) { 68 final Intent intent = new Intent(getApplicationContext(), BubbledActivity.class); 69 // Clear any previous instance of this activity 70 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); 71 if (addLocusId) { 72 intent.putExtra(EXTRA_LOCUS_ID, String.valueOf(id)); 73 } 74 startActivity(intent); 75 } 76 77 /** 78 * Sends a notification that has bubble metadata but the rest of the notification isn't 79 * configured correctly so the system won't allow it to bubble. 80 */ sendInvalidBubble(int notifId, boolean autoExpand)81 public void sendInvalidBubble(int notifId, boolean autoExpand) { 82 Context context = getApplicationContext(); 83 84 PendingIntent pendingIntent = PendingIntent.getActivity(context, notifId, new Intent(), 85 PendingIntent.FLAG_MUTABLE); 86 Notification n = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 87 .setSmallIcon(R.drawable.black) 88 .setWhen(System.currentTimeMillis()) 89 .setContentTitle("notify#" + notifId) 90 .setContentText("This is #" + notifId + "notification ") 91 .setContentIntent(pendingIntent) 92 .setBubbleMetadata(getBubbleMetadata(notifId, autoExpand, 93 false /* suppressNotification */, 94 false /* suppressBubble */, 95 false /* useShortcut */)) 96 .build(); 97 98 NotificationManager noMan = (NotificationManager) context.getSystemService( 99 Context.NOTIFICATION_SERVICE); 100 noMan.notify(notifId, n); 101 } 102 103 /** Sends a notification that is properly configured to bubble. */ sendBubble(int notifId, boolean autoExpand, boolean suppressNotification)104 public void sendBubble(int notifId, boolean autoExpand, boolean suppressNotification) { 105 sendBubble(notifId, autoExpand, suppressNotification, false /* suppressBubble */, 106 false /* useShortcut */, true /* setLocusId */); 107 } 108 109 /** Sends a notification that is properly configured to bubble. */ sendBubble(int notifId, boolean autoExpand, boolean suppressNotification, boolean suppressBubble)110 public void sendBubble(int notifId, boolean autoExpand, boolean suppressNotification, 111 boolean suppressBubble) { 112 sendBubble(notifId, autoExpand, suppressNotification, suppressBubble, 113 false /* useShortcut */, true /* setLocusId */); 114 } 115 116 /** Sends a notification that is properly configured to bubble. */ sendBubble(int notifId, boolean autoExpand, boolean suppressNotification, boolean suppressBubble, boolean useShortcut, boolean setLocusId)117 public void sendBubble(int notifId, boolean autoExpand, boolean suppressNotification, 118 boolean suppressBubble, boolean useShortcut, boolean setLocusId) { 119 Context context = getApplicationContext(); 120 // Give it a person 121 Person person = new Person.Builder() 122 .setName("bubblebot" + notifId) 123 .build(); 124 // Make it messaging style 125 Notification.Builder nb = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID) 126 .setSmallIcon(R.drawable.black) 127 .setContentTitle("Bubble Chat") 128 .setShortcutId(SHARE_SHORTCUT_ID) 129 .setStyle(new Notification.MessagingStyle(person) 130 .setConversationTitle("Bubble Chat") 131 .addMessage("Hello?", 132 SystemClock.currentThreadTimeMillis() - 300000, person) 133 .addMessage("Is it me you're looking for?", 134 SystemClock.currentThreadTimeMillis(), person) 135 ) 136 .setBubbleMetadata(getBubbleMetadata(notifId, 137 autoExpand, 138 suppressNotification, 139 suppressBubble, 140 useShortcut)); 141 142 if (setLocusId) { 143 nb.setLocusId(new LocusId(String.valueOf(notifId))); 144 } 145 146 NotificationManager noMan = (NotificationManager) context.getSystemService( 147 Context.NOTIFICATION_SERVICE); 148 noMan.notify(notifId, nb.build()); 149 } 150 getBubbleMetadata(int notifId, boolean autoExpand, boolean suppressNotification, boolean suppressBubble, boolean useShortcut)151 private BubbleMetadata getBubbleMetadata(int notifId, boolean autoExpand, 152 boolean suppressNotification, 153 boolean suppressBubble, 154 boolean useShortcut) { 155 if (useShortcut) { 156 return new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID) 157 .setAutoExpandBubble(autoExpand) 158 .setSuppressableBubble(suppressBubble) 159 .setSuppressNotification(suppressNotification) 160 .build(); 161 } else { 162 Context context = getApplicationContext(); 163 final Intent intent = new Intent(context, BubbledActivity.class); 164 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 165 intent.setAction(Intent.ACTION_MAIN); 166 final PendingIntent pendingIntent = 167 PendingIntent.getActivity(context, notifId, intent, 168 PendingIntent.FLAG_MUTABLE); 169 170 return new Notification.BubbleMetadata.Builder(pendingIntent, 171 Icon.createWithResource(context, R.drawable.black)) 172 .setAutoExpandBubble(autoExpand) 173 .setSuppressNotification(suppressNotification) 174 .setSuppressableBubble(suppressBubble) 175 .build(); 176 } 177 } 178 179 /** Waits for the activity to be stopped. Do not call this method on main thread. */ waitForStopped()180 public void waitForStopped() { 181 synchronized (this) { 182 while (!mIsStopped) { 183 try { 184 wait(5000 /* timeout */); 185 } catch (InterruptedException ignored) { 186 } 187 } 188 } 189 } 190 191 @Override onStart()192 protected void onStart() { 193 super.onStart(); 194 mIsStopped = false; 195 } 196 197 @Override onStop()198 protected void onStop() { 199 super.onStop(); 200 synchronized (this) { 201 mIsStopped = true; 202 notifyAll(); 203 } 204 } 205 } 206