/*
 * Copyright (C) 2022 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.location;

import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;

import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.location.ILocationListener;
import android.location.LocationManagerInternal;
import android.location.LocationRequest;
import android.location.provider.ProviderRequest;
import android.os.IBinder;
import android.os.PowerManager;
import android.platform.test.annotations.Presubmit;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;

import com.android.server.LocalServices;
import com.android.server.location.injector.FakeUserInfoHelper;
import com.android.server.location.injector.TestInjector;
import com.android.server.location.provider.AbstractLocationProvider;
import com.android.server.location.provider.LocationProviderManager;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;

import com.google.common.util.concurrent.MoreExecutors;

import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;

import java.util.Collections;

@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class LocationManagerServiceTest {
    private static final String PROVIDER_WITH_PERMISSION = "provider_with_permission";
    private static final String PROVIDER_WITHOUT_PERMISSION = "provider_without_permission";
    private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
    private static final String CALLER_PACKAGE = "caller_package";
    private static final String MISSING_PERMISSION = "missing_permission";
    private static final String ATTRIBUTION_TAG = "test_tag";

    private TestInjector mInjector;
    private LocationManagerService mLocationManagerService;

    @Spy private FakeAbstractLocationProvider mProviderWithPermission;
    @Spy private FakeAbstractLocationProvider mProviderWithoutPermission;
    @Mock private ILocationListener mLocationListener;
    @Mock private IBinder mBinder;
    @Mock private Context mContext;
    @Mock private Resources mResources;
    @Mock private PackageManager mPackageManager;
    @Mock private AppOpsManager mAppOpsManager;
    @Mock private PowerManager mPowerManager;
    @Mock private PowerManager.WakeLock mWakeLock;
    @Mock private LegacyPermissionManagerInternal mPermissionManagerInternal;

    @Before
    public void setUp() {
        initMocks(this);

        doReturn(mContext).when(mContext).createAttributionContext(any());
        doReturn("android").when(mContext).getPackageName();
        doReturn(mResources).when(mContext).getResources();
        doReturn(mPackageManager).when(mContext).getPackageManager();
        doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class);
        doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString());
        doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
        String[] packages = {CALLER_PACKAGE};
        doReturn(InstrumentationRegistry.getInstrumentation().getContext().getContentResolver())
                .when(mContext)
                .getContentResolver();
        doReturn(packages).when(mPackageManager).getPackagesForUid(anyInt());
        doReturn(mBinder).when(mLocationListener).asBinder();
        doReturn(PackageManager.PERMISSION_DENIED)
                .when(mContext)
                .checkCallingOrSelfPermission(MISSING_PERMISSION);

        mInjector = new TestInjector(mContext);
        mInjector.getUserInfoHelper().setUserVisible(CURRENT_USER, true);

        LocalServices.addService(LegacyPermissionManagerInternal.class, mPermissionManagerInternal);

        mLocationManagerService = new LocationManagerService(mContext, mInjector);

        LocationProviderManager managerWithPermission =
                new LocationProviderManager(
                        mContext, mInjector, PROVIDER_WITH_PERMISSION, /* passiveManager= */ null);
        mLocationManagerService.addLocationProviderManager(
                managerWithPermission, mProviderWithPermission);
        LocationProviderManager managerWithoutPermission =
                new LocationProviderManager(
                        mContext,
                        mInjector,
                        PROVIDER_WITHOUT_PERMISSION,
                        /* passiveManager= */ null,
                        Collections.singletonList(MISSING_PERMISSION));
        mLocationManagerService.addLocationProviderManager(
                managerWithoutPermission, mProviderWithoutPermission);
    }

    @After
    public void tearDown() throws Exception {
        LocalServices.removeServiceForTest(LegacyPermissionManagerInternal.class);
        LocalServices.removeServiceForTest(LocationManagerInternal.class);
    }

    @Test
    @Ignore("b/274432939") // Test is flaky for as of yet unknown reasons
    public void testRequestLocationUpdates() {
        LocationRequest request = new LocationRequest.Builder(0).build();
        mLocationManagerService.registerLocationListener(
                PROVIDER_WITH_PERMISSION,
                request,
                mLocationListener,
                CALLER_PACKAGE,
                ATTRIBUTION_TAG,
                "any_listener_id");
        verify(mProviderWithPermission).onSetRequestPublic(any());
    }

    @Test
    public void testRequestLocationUpdates_noPermission() {
        LocationRequest request = new LocationRequest.Builder(0).build();
        assertThrows(
                IllegalArgumentException.class,
                () ->
                        mLocationManagerService.registerLocationListener(
                                PROVIDER_WITHOUT_PERMISSION,
                                request,
                                mLocationListener,
                                CALLER_PACKAGE,
                                ATTRIBUTION_TAG,
                                "any_listener_id"));
    }

    @Test
    public void testHasProvider() {
        assertThat(mLocationManagerService.hasProvider(PROVIDER_WITH_PERMISSION)).isTrue();
    }

    @Test
    public void testHasProvider_noPermission() {
        assertThat(mLocationManagerService.hasProvider(PROVIDER_WITHOUT_PERMISSION)).isFalse();
    }

    @Test
    public void testGetAllProviders() {
        assertThat(mLocationManagerService.getAllProviders()).contains(PROVIDER_WITH_PERMISSION);
        assertThat(mLocationManagerService.getAllProviders())
                .doesNotContain(PROVIDER_WITHOUT_PERMISSION);
    }

    abstract static class FakeAbstractLocationProvider extends AbstractLocationProvider {
        FakeAbstractLocationProvider() {
            super(
                    MoreExecutors.directExecutor(),
                    /* identity= */ null,
                    /* properties= */ null,
                    /* extraAttributionTags= */ Collections.emptySet());
            setAllowed(true);
        }

        @Override
        protected void onSetRequest(ProviderRequest request) {
            // Call a public version of this method so mockito can verify.
            onSetRequestPublic(request);
        }

        public abstract void onSetRequestPublic(ProviderRequest request);
    }
}
