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