chromium/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClientTest.java

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.browserservices;

import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.ComponentName;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.RemoteException;

import androidx.browser.trusted.Token;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.browserservices.metrics.TrustedWebActivityUmaRecorder;
import org.chromium.chrome.browser.browserservices.permissiondelegation.InstalledWebappPermissionManager;
import org.chromium.chrome.browser.notifications.NotificationBuilderBase;
import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
import org.chromium.components.browser_ui.notifications.NotificationWrapper;
import org.chromium.components.embedder_support.util.Origin;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;

/** Unit tests for {@link TrustedWebActivityClient}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class TrustedWebActivityClientTest {
    private static final int SERVICE_SMALL_ICON_ID = 1;
    private static final String CLIENT_PACKAGE_NAME = "com.example.app";

    @Mock private TrustedWebActivityClientWrappers.ConnectionPool mConnectionPool;
    @Mock private TrustedWebActivityClientWrappers.Connection mService;
    @Mock private NotificationBuilderBase mNotificationBuilder;
    @Mock private TrustedWebActivityUmaRecorder mRecorder;
    @Mock private NotificationUmaTracker mNotificationUmaTracker;

    @Mock private Bitmap mServiceSmallIconBitmap;
    @Mock private NotificationWrapper mNotificationWrapper;
    @Mock private InstalledWebappPermissionManager mPermissionManager;

    private TrustedWebActivityClient mClient;

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

        doAnswer(
                        invocation -> {
                            Origin origin = invocation.getArgument(1);
                            TrustedWebActivityClient.ExecutionCallback callback =
                                    invocation.getArgument(3);

                            callback.onConnected(origin, mService);

                            return null;
                        })
                .when(mConnectionPool)
                .connectAndExecute(any(), any(), any(), any());

        when(mService.getSmallIconId()).thenReturn(SERVICE_SMALL_ICON_ID);
        when(mService.getSmallIconBitmap()).thenReturn(mServiceSmallIconBitmap);
        when(mService.getComponentName()).thenReturn(new ComponentName(CLIENT_PACKAGE_NAME, ""));
        when(mService.areNotificationsEnabled(any())).thenReturn(true);
        when(mNotificationBuilder.build(any())).thenReturn(mNotificationWrapper);

        Set<Token> delegateApps = new HashSet<>();
        delegateApps.add(createDummyToken());
        when(mPermissionManager.getAllDelegateApps(any())).thenReturn(delegateApps);

        mClient = new TrustedWebActivityClient(mConnectionPool, mPermissionManager, mRecorder);
    }

    @Test
    public void usesIconFromService_IfStatusBarIconNotSet() {
        setHasStatusBarBitmap(false);
        postNotification();
        verify(mNotificationBuilder)
                .setStatusBarIconForRemoteApp(SERVICE_SMALL_ICON_ID, mServiceSmallIconBitmap);
    }

    @Test
    public void doesntUseIconFromService_IfContentBarIconSet() {
        setHasStatusBarBitmap(true);
        postNotification();
        verify(mNotificationBuilder, never()).setStatusBarIconForRemoteApp(anyInt(), any());
    }

    @Test
    public void usesIconFromService_IfContentSmallIconNotSet()
            throws ExecutionException, InterruptedException {
        setHasContentBitmap(false);
        postNotification();

        verify(mNotificationBuilder).setContentSmallIconForRemoteApp(mServiceSmallIconBitmap);
    }

    @Test
    public void doesntUseIconFromService_IfContentSmallIconSet() {
        setHasContentBitmap(true);
        postNotification();
        verify(mNotificationBuilder, never()).setContentSmallIconForRemoteApp(any());
    }

    @Test
    public void doesntFetchIconIdFromService_IfBothIconsAreSet() throws RemoteException {
        setHasContentBitmap(true);
        setHasStatusBarBitmap(true);
        postNotification();
        verify(mService, never()).getSmallIconId();
    }

    @Test
    public void doesntFetchIconBitmapFromService_IfIconsIdIs() throws RemoteException {
        setHasContentBitmap(false);
        when(mService.getSmallIconId()).thenReturn(-1);
        postNotification();
        verify(mService, never()).getSmallIconBitmap();
    }

    private void setHasStatusBarBitmap(boolean hasBitmap) {
        when(mNotificationBuilder.hasStatusBarIconBitmap()).thenReturn(hasBitmap);
    }

    private void setHasContentBitmap(boolean hasBitmap) {
        when(mNotificationBuilder.hasSmallIconForContent()).thenReturn(hasBitmap);
    }

    private void postNotification() {
        Uri uri = Uri.parse("https://www.example.com");
        mClient.notifyNotification(uri, "tag", 1, mNotificationBuilder, mNotificationUmaTracker);
    }

    @Test
    public void createLaunchIntentForTwaNonHttpScheme() {
        assertNull(
                TrustedWebActivityClient.createLaunchIntentForTwa(
                        RuntimeEnvironment.application,
                        "mailto:[email protected]",
                        new ArrayList<ResolveInfo>()));
    }

    private static Token createDummyToken() {
        // This code requires understanding how Token's parse (see TokenContents.java inside
        // androidx.browser) and is pretty ugly. The alternative is to set up the Robolectric
        // PackageManager to provide the right data, which probably is a more robust approach.
        // However, ideally androidx.browser will add a way to create a mock Token for testing and
        // we can use that instead.
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream writer = new DataOutputStream(baos);

        String packageName = "token.package.name";
        int numFingerprints = 1;
        byte[] fingerprint = "1234".getBytes();

        try {
            writer.writeUTF(packageName);
            writer.writeInt(numFingerprints);
            writer.writeInt(fingerprint.length);
            writer.write(fingerprint);
            writer.flush();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return Token.deserialize(baos.toByteArray());
    }
}