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