1 /*
2  * Copyright (C) 2018 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.android.server.notification;
18 
19 import static junit.framework.Assert.assertEquals;
20 import static junit.framework.Assert.assertSame;
21 
22 import static org.hamcrest.Matchers.instanceOf;
23 import static org.junit.Assert.assertThat;
24 import static org.mockito.Mockito.eq;
25 import static org.mockito.Mockito.spy;
26 import static org.mockito.Mockito.verify;
27 import static org.mockito.Mockito.when;
28 
29 // this is a lazy way to do in/out/err but we're not particularly interested in the output
30 import static java.io.FileDescriptor.err;
31 import static java.io.FileDescriptor.in;
32 import static java.io.FileDescriptor.out;
33 
34 import android.app.INotificationManager;
35 import android.app.Notification;
36 import android.graphics.Bitmap;
37 import android.graphics.Canvas;
38 import android.graphics.Color;
39 import android.graphics.drawable.GradientDrawable;
40 import android.graphics.drawable.Icon;
41 import android.os.Binder;
42 import android.os.Handler;
43 import android.os.ResultReceiver;
44 import android.os.ShellCallback;
45 import android.os.UserHandle;
46 import android.test.suitebuilder.annotation.SmallTest;
47 import android.testing.AndroidTestingRunner;
48 import android.testing.TestableContext;
49 import android.testing.TestableLooper;
50 import android.testing.TestableLooper.RunWithLooper;
51 
52 import com.android.server.UiServiceTestCase;
53 
54 import org.junit.Before;
55 import org.junit.Test;
56 import org.junit.runner.RunWith;
57 import org.mockito.ArgumentCaptor;
58 import org.mockito.Mock;
59 import org.mockito.MockitoAnnotations;
60 
61 import java.util.ArrayList;
62 import java.util.List;
63 
64 @SmallTest
65 @RunWith(AndroidTestingRunner.class)
66 @RunWithLooper
67 public class NotificationShellCmdTest extends UiServiceTestCase {
68     private final Binder mBinder = new Binder();
69     private final ShellCallback mCallback = new ShellCallback();
70     @Mock
71     NotificationManagerService mMockService;
72     @Mock
73     INotificationManager mMockBinderService;
74     private TestableLooper mTestableLooper;
75     private ResultReceiver mResultReceiver;
76 
77     @Before
setUp()78     public void setUp() throws Exception {
79         MockitoAnnotations.initMocks(this);
80 
81         mTestableLooper = TestableLooper.get(this);
82         mResultReceiver = new ResultReceiver(new Handler(mTestableLooper.getLooper()));
83 
84         when(mMockService.getContext()).thenReturn(mContext);
85         when(mMockService.getBinderService()).thenReturn(mMockBinderService);
86     }
87 
createTestBitmap()88     private Bitmap createTestBitmap() {
89         final Bitmap bits = Bitmap.createBitmap(512, 512, Bitmap.Config.ARGB_8888);
90         final Canvas canvas = new Canvas(bits);
91         final GradientDrawable grad = new GradientDrawable(GradientDrawable.Orientation.TL_BR,
92                 new int[]{Color.RED, Color.YELLOW, Color.GREEN,
93                         Color.CYAN, Color.BLUE, Color.MAGENTA});
94         grad.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
95         grad.draw(canvas);
96         return bits;
97     }
98 
doCmd(String... args)99     private void doCmd(String... args) {
100         System.out.println("Running command: " + String.join(" ", args));
101         new TestNotificationShellCmd(mMockService)
102                 .exec(mBinder, in, out, err, args, mCallback, mResultReceiver);
103     }
104 
105     @Test
testNoArgs()106     public void testNoArgs() throws Exception {
107         doCmd();
108     }
109 
110     @Test
testHelp()111     public void testHelp() throws Exception {
112         doCmd("--help");
113     }
114 
captureNotification(String aTag)115     Notification captureNotification(String aTag) throws Exception {
116         ArgumentCaptor<Notification> notificationCaptor =
117                 ArgumentCaptor.forClass(Notification.class);
118         final String pkg = getContext().getPackageName();
119         verify(mMockBinderService).enqueueNotificationWithTag(
120                 eq(pkg),
121                 eq(pkg),
122                 eq(aTag),
123                 eq(NotificationShellCmd.NOTIFICATION_ID),
124                 notificationCaptor.capture(),
125                 eq(UserHandle.getCallingUserId()));
126         return notificationCaptor.getValue();
127     }
128 
129     @Test
testBasic()130     public void testBasic() throws Exception {
131         final String aTag = "aTag";
132         final String aText = "someText";
133         final String aTitle = "theTitle";
134         doCmd("notify",
135                 "--title", aTitle,
136                 aTag, aText);
137         final Notification captured = captureNotification(aTag);
138         assertEquals(aText, captured.extras.getString(Notification.EXTRA_TEXT));
139         assertEquals(aTitle, captured.extras.getString(Notification.EXTRA_TITLE));
140     }
141 
142     @Test
testIcon()143     public void testIcon() throws Exception {
144         final String aTag = "aTag";
145         final String aText = "someText";
146         doCmd("notify", "--icon", "@android:drawable/stat_sys_adb", aTag, aText);
147         final Notification captured = captureNotification(aTag);
148         final Icon icon = captured.getSmallIcon();
149         assertEquals("android", icon.getResPackage());
150         assertEquals(com.android.internal.R.drawable.stat_sys_adb, icon.getResId());
151     }
152 
153     @Test
testBigText()154     public void testBigText() throws Exception {
155         final String aTag = "aTag";
156         final String aText = "someText";
157         final String bigText = "someBigText";
158         doCmd("notify",
159                 "--style", "bigtext",
160                 "--big-text", bigText,
161                 aTag, aText);
162         final Notification captured = captureNotification(aTag);
163         assertSame(captured.getNotificationStyle(), Notification.BigTextStyle.class);
164         assertEquals(aText, captured.extras.getString(Notification.EXTRA_TEXT));
165         assertEquals(bigText, captured.extras.getString(Notification.EXTRA_BIG_TEXT));
166     }
167 
168     @Test
testBigPicture()169     public void testBigPicture() throws Exception {
170         final String aTag = "aTag";
171         final String aText = "someText";
172         final String bigPicture = "@android:drawable/default_wallpaper";
173         doCmd("notify",
174                 "--style", "bigpicture",
175                 "--picture", bigPicture,
176                 aTag, aText);
177         final Notification captured = captureNotification(aTag);
178         assertSame(captured.getNotificationStyle(), Notification.BigPictureStyle.class);
179         final Object pic = captured.extras.get(Notification.EXTRA_PICTURE);
180         assertThat(pic, instanceOf(Bitmap.class));
181     }
182 
183     @Test
testInbox()184     public void testInbox() throws Exception {
185         final int n = 25;
186         final String aTag = "inboxTag";
187         final String aText = "inboxText";
188         ArrayList<String> args = new ArrayList<>();
189         args.add("notify");
190         args.add("--style");
191         args.add("inbox");
192         final int startOfLineArgs = args.size();
193         for (int i = 0; i < n; i++) {
194             args.add("--line");
195             args.add(String.format("Line %02d", i));
196         }
197         args.add(aTag);
198         args.add(aText);
199 
200         doCmd(args.toArray(new String[0]));
201         final Notification captured = captureNotification(aTag);
202         assertSame(captured.getNotificationStyle(), Notification.InboxStyle.class);
203         final Notification.Builder builder =
204                 Notification.Builder.recoverBuilder(mContext, captured);
205         final ArrayList<CharSequence> lines =
206                 ((Notification.InboxStyle) (builder.getStyle())).getLines();
207         for (int i = 0; i < n; i++) {
208             assertEquals(lines.get(i), args.get(1 + 2 * i + startOfLineArgs));
209         }
210     }
211 
212     static final String[] PEOPLE = {
213             "Alice",
214             "Bob",
215             "Charlotte"
216     };
217     static final String[] MESSAGES = {
218             "Who has seen the wind?",
219             "Neither I nor you.",
220             "But when the leaves hang trembling,",
221             "The wind is passing through.",
222             "Who has seen the wind?",
223             "Neither you nor I.",
224             "But when the trees bow down their heads,",
225             "The wind is passing by."
226     };
227 
228     @Test
testMessaging()229     public void testMessaging() throws Exception {
230         final String aTag = "messagingTag";
231         final String aText = "messagingText";
232         ArrayList<String> args = new ArrayList<>();
233         args.add("notify");
234         args.add("--style");
235         args.add("messaging");
236         args.add("--conversation");
237         args.add("Sonnet 18");
238         final int startOfLineArgs = args.size();
239         for (int i = 0; i < MESSAGES.length; i++) {
240             args.add("--message");
241             args.add(String.format("%s:%s",
242                     PEOPLE[i % PEOPLE.length],
243                     MESSAGES[i % MESSAGES.length]));
244         }
245         args.add(aTag);
246         args.add(aText);
247 
248         doCmd(args.toArray(new String[0]));
249         final Notification captured = captureNotification(aTag);
250         assertSame(Notification.MessagingStyle.class, captured.getNotificationStyle());
251         final Notification.Builder builder =
252                 Notification.Builder.recoverBuilder(mContext, captured);
253         final Notification.MessagingStyle messagingStyle =
254                 (Notification.MessagingStyle) (builder.getStyle());
255 
256         assertEquals("Sonnet 18", messagingStyle.getConversationTitle());
257         final List<Notification.MessagingStyle.Message> messages = messagingStyle.getMessages();
258         for (int i = 0; i < messages.size(); i++) {
259             final Notification.MessagingStyle.Message m = messages.get(i);
260             assertEquals(MESSAGES[i], m.getText());
261             assertEquals(PEOPLE[i % PEOPLE.length], m.getSenderPerson().getName());
262         }
263 
264     }
265 
266     /**
267      * Version of NotificationShellCmd that allows this atest to work properly despite coming in
268      * from the wrong uid.
269      */
270     private final class TestNotificationShellCmd extends NotificationShellCmd {
TestNotificationShellCmd(NotificationManagerService service)271         TestNotificationShellCmd(NotificationManagerService service) {
272             super(service);
273         }
274 
275         @Override
checkShellCommandPermission(int callingUid)276         protected boolean checkShellCommandPermission(int callingUid) {
277             return true;
278         }
279     }
280 }
281