chromium/components/policy/android/junit/src/org/chromium/components/policy/AbstractAppRestrictionsProviderTest.java

// Copyright 2015 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.components.policy;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;

import androidx.test.core.app.ApplicationProvider;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;

import org.chromium.base.ContextUtils;
import org.chromium.base.test.BaseRobolectricTestRunner;

import java.util.concurrent.atomic.AtomicInteger;

/** Robolectric test for AbstractAppRestrictionsProvider. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@LooperMode(LooperMode.Mode.LEGACY)
public class AbstractAppRestrictionsProviderTest {
    /** Minimal concrete class implementing AbstractAppRestrictionsProvider. */
    private class DummyAppRestrictionsProvider extends AbstractAppRestrictionsProvider {
        public DummyAppRestrictionsProvider(Context context) {
            super(context);
        }

        @Override
        protected Bundle getApplicationRestrictions(String packageName) {
            return null;
        }

        @Override
        protected String getRestrictionChangeIntentAction() {
            return null;
        }
    }

    private class DummyContext extends ContextWrapper {
        public DummyContext(Context baseContext) {
            super(baseContext);
            mReceiverCount = new AtomicInteger(0);
            mLastRegisteredReceiverFlags = new AtomicInteger(0);
        }

        @Override
        public Intent registerReceiver(
                BroadcastReceiver receiver,
                IntentFilter filter,
                String broadcastPermission,
                Handler scheduler,
                int flags) {
            Intent intent =
                    super.registerReceiver(receiver, filter, broadcastPermission, scheduler, flags);
            mReceiverCount.getAndIncrement();
            mLastRegisteredReceiverFlags.set(flags);
            return intent;
        }

        @Override
        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
            Intent intent = super.registerReceiver(receiver, filter);
            mReceiverCount.getAndIncrement();
            return intent;
        }

        @Override
        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {
            Intent intent = super.registerReceiver(receiver, filter, flags);
            mReceiverCount.getAndIncrement();
            mLastRegisteredReceiverFlags.set(flags);
            return intent;
        }

        @Override
        public Intent registerReceiver(
                BroadcastReceiver receiver,
                IntentFilter filter,
                String broadcastPermission,
                Handler scheduler) {
            Intent intent =
                    super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
            mReceiverCount.getAndIncrement();
            return intent;
        }

        @Override
        public void unregisterReceiver(BroadcastReceiver receiver) {
            // Not to unregisterReceiver in Android o+,  otherwise roboletric throws exception
            // because it doesn't override registerReceiver() with flag paramenter.
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
                super.unregisterReceiver(receiver);
            }
            mReceiverCount.getAndDecrement();
        }

        public int getReceiverCount() {
            return mReceiverCount.get();
        }

        public int getLastRegisteredReceiverFlags() {
            return mLastRegisteredReceiverFlags.get();
        }

        private AtomicInteger mReceiverCount;
        private AtomicInteger mLastRegisteredReceiverFlags;
    }

    /** Test method for {@link AbstractAppRestrictionsProvider#refresh()}. */
    @Test
    public void testRefresh() {
        // We want to control precisely when background tasks run
        Robolectric.getBackgroundThreadScheduler().pause();

        Context context = RuntimeEnvironment.application;

        // Clear the preferences
        ContextUtils.getAppSharedPreferences().edit().clear();

        // Set up a bundle for testing.
        Bundle b1 = new Bundle();
        b1.putString("Key1", "value1");
        b1.putInt("Key2", 42);

        // Mock out the histogram functions, since they call statics.
        AbstractAppRestrictionsProvider provider = spy(new DummyAppRestrictionsProvider(context));

        // Set up the buffer to be returned by getApplicationRestrictions.
        when(provider.getApplicationRestrictions(anyString())).thenReturn(b1);

        // Prepare the provider
        CombinedPolicyProvider combinedProvider = mock(CombinedPolicyProvider.class);
        provider.setManagerAndSource(combinedProvider, 0);

        provider.refresh();
        verify(provider).getApplicationRestrictions(anyString());
        verify(combinedProvider).onSettingsAvailable(0, b1);
    }

    /** Test method for {@link AbstractAppRestrictionsProvider#startListeningForPolicyChanges()}. */
    @Test
    public void testStartListeningForPolicyChanges() {
        DummyContext dummyContext = new DummyContext(ApplicationProvider.getApplicationContext());
        AbstractAppRestrictionsProvider provider =
                spy(new DummyAppRestrictionsProvider(dummyContext));
        Intent intent = new Intent("org.chromium.test.policy.Hello");

        // If getRestrictionsChangeIntentAction returns null then we should not start a broadcast
        // receiver.
        provider.startListeningForPolicyChanges();
        Assert.assertEquals(0, dummyContext.getReceiverCount());

        // If it returns a string then we should.
        when(provider.getRestrictionChangeIntentAction())
                .thenReturn("org.chromium.test.policy.Hello");
        provider.startListeningForPolicyChanges();
        Assert.assertEquals(1, dummyContext.getReceiverCount());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // Ensure that neither RECEIVER_EXPORTED nor RECEIVER_NOT_EXPORTED flags are set,
            // asserting that the receiver was only registered for protected broadcasts.
            final int badMask = ContextUtils.RECEIVER_EXPORTED | ContextUtils.RECEIVER_NOT_EXPORTED;
            Assert.assertEquals(0, dummyContext.getLastRegisteredReceiverFlags() & badMask);
        }
    }

    /** Test method for {@link AbstractAppRestrictionsProvider#stopListening()}. */
    @Test
    public void testStopListening() {
        DummyContext dummyContext = new DummyContext(ApplicationProvider.getApplicationContext());
        AbstractAppRestrictionsProvider provider =
                spy(new DummyAppRestrictionsProvider(dummyContext));
        Intent intent = new Intent("org.chromium.test.policy.Hello");

        // First try with null result from getRestrictionsChangeIntentAction, only test here is no
        // crash.
        provider.stopListening();

        // Now try starting and stopping listening properly.
        when(provider.getRestrictionChangeIntentAction())
                .thenReturn("org.chromium.test.policy.Hello");
        provider.startListeningForPolicyChanges();
        Assert.assertEquals(1, dummyContext.getReceiverCount());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // Ensure that neither RECEIVER_EXPORTED nor RECEIVER_NOT_EXPORTED flags are set,
            // asserting that the receiver was only registered for protected broadcasts.
            final int badMask = ContextUtils.RECEIVER_EXPORTED | ContextUtils.RECEIVER_NOT_EXPORTED;
            Assert.assertEquals(0, dummyContext.getLastRegisteredReceiverFlags() & badMask);
        }
        provider.stopListening();
        Assert.assertEquals(0, dummyContext.getReceiverCount());
    }
}