Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7e2a7339 authored by Daniel Sandler's avatar Daniel Sandler Committed by Android (Google) Code Review
Browse files

Merge "Post a notification from the shell."

parents 12cd0e44 7647f1d1
Loading
Loading
Loading
Loading
+2 −106
Original line number Diff line number Diff line
@@ -144,7 +144,6 @@ import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -3852,7 +3851,8 @@ public class NotificationManagerService extends SystemService {
        public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
                String[] args, ShellCallback callback, ResultReceiver resultReceiver)
                throws RemoteException {
            new ShellCmd().exec(this, in, out, err, args, callback, resultReceiver);
            new NotificationShellCmd(NotificationManagerService.this)
                    .exec(this, in, out, err, args, callback, resultReceiver);
        }
    };

@@ -7715,110 +7715,6 @@ public class NotificationManagerService extends SystemService {
        }
    }

    private class ShellCmd extends ShellCommand {
        public static final String USAGE = "help\n"
                + "allow_listener COMPONENT [user_id]\n"
                + "disallow_listener COMPONENT [user_id]\n"
                + "allow_assistant COMPONENT\n"
                + "remove_assistant COMPONENT\n"
                + "allow_dnd PACKAGE\n"
                + "disallow_dnd PACKAGE\n"
                + "suspend_package PACKAGE\n"
                + "unsuspend_package PACKAGE";

        @Override
        public int onCommand(String cmd) {
            if (cmd == null) {
                return handleDefaultCommands(cmd);
            }
            final PrintWriter pw = getOutPrintWriter();
            try {
                switch (cmd) {
                    case "allow_dnd": {
                        getBinderService().setNotificationPolicyAccessGranted(
                                getNextArgRequired(), true);
                    }
                    break;

                    case "disallow_dnd": {
                        getBinderService().setNotificationPolicyAccessGranted(
                                getNextArgRequired(), false);
                    }
                    break;
                    case "allow_listener": {
                        ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired());
                        if (cn == null) {
                            pw.println("Invalid listener - must be a ComponentName");
                            return -1;
                        }
                        String userId = getNextArg();
                        if (userId == null) {
                            getBinderService().setNotificationListenerAccessGranted(cn, true);
                        } else {
                            getBinderService().setNotificationListenerAccessGrantedForUser(
                                    cn, Integer.parseInt(userId), true);
                        }
                    }
                    break;
                    case "disallow_listener": {
                        ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired());
                        if (cn == null) {
                            pw.println("Invalid listener - must be a ComponentName");
                            return -1;
                        }
                        String userId = getNextArg();
                        if (userId == null) {
                            getBinderService().setNotificationListenerAccessGranted(cn, false);
                        } else {
                            getBinderService().setNotificationListenerAccessGrantedForUser(
                                    cn, Integer.parseInt(userId), false);
                        }
                    }
                    break;
                    case "allow_assistant": {
                        ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired());
                        if (cn == null) {
                            pw.println("Invalid assistant - must be a ComponentName");
                            return -1;
                        }
                        getBinderService().setNotificationAssistantAccessGranted(cn, true);
                    }
                    break;
                    case "disallow_assistant": {
                        ComponentName cn = ComponentName.unflattenFromString(getNextArgRequired());
                        if (cn == null) {
                            pw.println("Invalid assistant - must be a ComponentName");
                            return -1;
                        }
                        getBinderService().setNotificationAssistantAccessGranted(cn, false);
                    }
                    break;
                    case "suspend_package": {
                        // only use for testing
                        simulatePackageSuspendBroadcast(true, getNextArgRequired());
                    }
                    break;
                    case "unsuspend_package": {
                        // only use for testing
                        simulatePackageSuspendBroadcast(false, getNextArgRequired());
                    }
                    break;
                    default:
                        return handleDefaultCommands(cmd);
                }
            } catch (Exception e) {
                pw.println("Error occurred. Check logcat for details. " + e.getMessage());
                Slog.e(TAG, "Error running shell command", e);
            }
            return 0;
        }

        @Override
        public void onHelp() {
            getOutPrintWriter().println(USAGE);
        }
    }

    private void writeSecureNotificationsPolicy(XmlSerializer out) throws IOException {
        out.startTag(null, LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG);
        out.attribute(null, LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_VALUE,
+483 −0

File added.

Preview size limit exceeded, changes collapsed.

+1 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ android_test {
        "android-support-test",
        "mockito-target-inline-minus-junit4",
        "platform-test-annotations",
        "hamcrest-library",
        "testables",
    ],

+270 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.notification;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertSame;

import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

// this is a lazy way to do in/out/err but we're not particularly interested in the output
import static java.io.FileDescriptor.err;
import static java.io.FileDescriptor.in;
import static java.io.FileDescriptor.out;

import android.app.INotificationManager;
import android.app.Notification;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
import android.os.Binder;
import android.os.Handler;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.UserHandle;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;

import com.android.server.UiServiceTestCase;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;
import java.util.List;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class NotificationShellCmdTest extends UiServiceTestCase {
    private final Binder mBinder = new Binder();
    private final ShellCallback mCallback = new ShellCallback();
    private final TestableContext mTestableContext = spy(getContext());
    @Mock
    NotificationManagerService mMockService;
    @Mock
    INotificationManager mMockBinderService;
    private TestableLooper mTestableLooper;
    private ResultReceiver mResultReceiver;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        mTestableLooper = TestableLooper.get(this);
        mResultReceiver = new ResultReceiver(new Handler(mTestableLooper.getLooper()));

        when(mMockService.getContext()).thenReturn(mTestableContext);
        when(mMockService.getBinderService()).thenReturn(mMockBinderService);
    }

    private Bitmap createTestBitmap() {
        final Bitmap bits = Bitmap.createBitmap(512, 512, Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(bits);
        final GradientDrawable grad = new GradientDrawable(GradientDrawable.Orientation.TL_BR,
                new int[]{Color.RED, Color.YELLOW, Color.GREEN,
                        Color.CYAN, Color.BLUE, Color.MAGENTA});
        grad.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        grad.draw(canvas);
        return bits;
    }

    private void doCmd(String... args) {
        new NotificationShellCmd(mMockService)
                .exec(mBinder, in, out, err, args, mCallback, mResultReceiver);
    }

    @Test
    public void testNoArgs() throws Exception {
        doCmd();
    }

    @Test
    public void testHelp() throws Exception {
        doCmd("--help");
    }

    Notification captureNotification(String aTag) throws Exception {
        ArgumentCaptor<Notification> notificationCaptor =
                ArgumentCaptor.forClass(Notification.class);
        verify(mMockBinderService).enqueueNotificationWithTag(
                eq(NotificationShellCmd.NOTIFICATION_PACKAGE),
                eq("android"),
                eq(aTag),
                eq(NotificationShellCmd.NOTIFICATION_ID),
                notificationCaptor.capture(),
                eq(UserHandle.getCallingUserId()));
        return notificationCaptor.getValue();
    }

    @Test
    public void testBasic() throws Exception {
        final String aTag = "aTag";
        final String aText = "someText";
        final String aTitle = "theTitle";
        doCmd("notify",
                "--title", aTitle,
                aTag, aText);
        final Notification captured = captureNotification(aTag);
        assertEquals(aText, captured.extras.getString(Notification.EXTRA_TEXT));
        assertEquals(aTitle, captured.extras.getString(Notification.EXTRA_TITLE));
    }

    @Test
    public void testIcon() throws Exception {
        final String aTag = "aTag";
        final String aText = "someText";
        doCmd("notify", "--icon", "@android:drawable/stat_sys_adb", aTag, aText);
        final Notification captured = captureNotification(aTag);
        final Icon icon = captured.getSmallIcon();
        assertEquals("android", icon.getResPackage());
        assertEquals(com.android.internal.R.drawable.stat_sys_adb, icon.getResId());
    }

    @Test
    public void testBigText() throws Exception {
        final String aTag = "aTag";
        final String aText = "someText";
        final String bigText = "someBigText";
        doCmd("notify",
                "--style", "bigtext",
                "--big-text", bigText,
                aTag, aText);
        final Notification captured = captureNotification(aTag);
        assertSame(captured.getNotificationStyle(), Notification.BigTextStyle.class);
        assertEquals(aText, captured.extras.getString(Notification.EXTRA_TEXT));
        assertEquals(bigText, captured.extras.getString(Notification.EXTRA_BIG_TEXT));
    }

    @Test
    public void testBigPicture() throws Exception {
        final String aTag = "aTag";
        final String aText = "someText";
        final String bigPicture = "@android:drawable/default_wallpaper";
        doCmd("notify",
                "--style", "bigpicture",
                "--picture", bigPicture,
                aTag, aText);
        final Notification captured = captureNotification(aTag);
        assertSame(captured.getNotificationStyle(), Notification.BigPictureStyle.class);
        final Object pic = captured.extras.get(Notification.EXTRA_PICTURE);
        assertThat(pic, instanceOf(Bitmap.class));
    }

    @Test
    public void testInbox() throws Exception {
        final int n = 25;
        final String aTag = "inboxTag";
        final String aText = "inboxText";
        ArrayList<String> args = new ArrayList<>();
        args.add("notify");
        args.add("--style");
        args.add("inbox");
        final int startOfLineArgs = args.size();
        for (int i = 0; i < n; i++) {
            args.add("--line");
            args.add(String.format("Line %02d", i));
        }
        args.add(aTag);
        args.add(aText);

        doCmd(args.toArray(new String[0]));
        final Notification captured = captureNotification(aTag);
        assertSame(captured.getNotificationStyle(), Notification.InboxStyle.class);
        final Notification.Builder builder =
                Notification.Builder.recoverBuilder(mContext, captured);
        final ArrayList<CharSequence> lines =
                ((Notification.InboxStyle) (builder.getStyle())).getLines();
        for (int i = 0; i < n; i++) {
            assertEquals(lines.get(i), args.get(1 + 2 * i + startOfLineArgs));
        }
    }

    static final String[] PEOPLE = {
            "Alice",
            "Bob",
            "Charlotte"
    };
    static final String[] MESSAGES = {
            "Shall I compare thee to a summer's day?",
            "Thou art more lovely and more temperate:",
            "Rough winds do shake the darling buds of May,",
            "And summer's lease hath all too short a date;",
            "Sometime too hot the eye of heaven shines,",
            "And often is his gold complexion dimm'd;",
            "And every fair from fair sometime declines,",
            "By chance or nature's changing course untrimm'd;",
            "But thy eternal summer shall not fade,",
            "Nor lose possession of that fair thou ow'st;",
            "Nor shall death brag thou wander'st in his shade,",
            "When in eternal lines to time thou grow'st:",
            "   So long as men can breathe or eyes can see,",
            "   So long lives this, and this gives life to thee.",
    };

    @Test
    public void testMessaging() throws Exception {
        final String aTag = "messagingTag";
        final String aText = "messagingText";
        ArrayList<String> args = new ArrayList<>();
        args.add("notify");
        args.add("--style");
        args.add("messaging");
        args.add("--conversation");
        args.add("Sonnet 18");
        final int startOfLineArgs = args.size();
        for (int i = 0; i < MESSAGES.length; i++) {
            args.add("--message");
            args.add(String.format("%s:%s",
                    PEOPLE[i % PEOPLE.length],
                    MESSAGES[i % MESSAGES.length]));
        }
        args.add(aTag);
        args.add(aText);

        doCmd(args.toArray(new String[0]));
        final Notification captured = captureNotification(aTag);
        assertSame(Notification.MessagingStyle.class, captured.getNotificationStyle());
        final Notification.Builder builder =
                Notification.Builder.recoverBuilder(mContext, captured);
        final Notification.MessagingStyle messagingStyle =
                (Notification.MessagingStyle) (builder.getStyle());

        assertEquals("Sonnet 18", messagingStyle.getConversationTitle());
        final List<Notification.MessagingStyle.Message> messages = messagingStyle.getMessages();
        for (int i = 0; i < messages.size(); i++) {
            final Notification.MessagingStyle.Message m = messages.get(i);
            assertEquals(MESSAGES[i], m.getText());
            assertEquals(PEOPLE[i % PEOPLE.length], m.getSenderPerson().getName());
        }

    }
}