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

Commit 155d1a75 authored by Chinmay Dhodapkar's avatar Chinmay Dhodapkar
Browse files

Fix recording of permission grants

While granting permissions, one of the grants could fail (with exception)
This results in EmergencyCallHelper not recording the permission grant.
So in scenarios where FINE_LOCATION was granted, but BACKGROUND location
was not granted (due to exception) --- we never end up revoking FINE_LOCATION perm.

Added UT for EmergencyCallHelper

See: b/245279980#comment5 for issue logs
Bug: 209812164
Test: UT plus manual verification with dialer. on b/209812164#18
Change-Id: I02a1a19336c6ed6ead0cd60414a00ea671bf7f98
parent 6d746939
Loading
Loading
Loading
Loading
+62 −29
Original line number Diff line number Diff line
@@ -34,8 +34,17 @@ public class EmergencyCallHelper {
    private final DefaultDialerCache mDefaultDialerCache;
    private final Timeouts.Adapter mTimeoutsAdapter;
    private UserHandle mLocationPermissionGrantedToUser;

    //stores the original state of permissions that dialer had
    private boolean mHadFineLocation = false;
    private boolean mHadBackgroundLocation = false;

    //stores whether we successfully granted the runtime permission
    //This is stored so we don't unnecessarily revoke if the grant had failed with an exception.
    //Else we will get an exception
    private boolean mFineLocationGranted= false;
    private boolean mBackgroundLocationGranted = false;

    private long mLastEmergencyCallTimestampMillis;

    @VisibleForTesting
@@ -48,7 +57,8 @@ public class EmergencyCallHelper {
        mTimeoutsAdapter = timeoutsAdapter;
    }

    void maybeGrantTemporaryLocationPermission(Call call, UserHandle userHandle) {
    @VisibleForTesting
    public void maybeGrantTemporaryLocationPermission(Call call, UserHandle userHandle) {
        if (shouldGrantTemporaryLocationPermission(call)) {
            grantLocationPermission(userHandle);
        }
@@ -57,7 +67,8 @@ public class EmergencyCallHelper {
        }
    }

    void maybeRevokeTemporaryLocationPermission() {
    @VisibleForTesting
    public void maybeRevokeTemporaryLocationPermission() {
        if (wasGrantedTemporaryLocationPermission()) {
            revokeLocationPermission();
        }
@@ -95,9 +106,9 @@ public class EmergencyCallHelper {

    private void grantLocationPermission(UserHandle userHandle) {
        String systemDialerPackage = mDefaultDialerCache.getSystemDialerApplication();
        Log.i(this, "Granting temporary location permission to " + systemDialerPackage
        Log.i(this, "Attempting to grant temporary location permission to " + systemDialerPackage
            + ", user: " + userHandle);
        try {

        boolean hadBackgroundLocation = hasBackgroundLocationPermission();
        boolean hadFineLocation = hasFineLocationPermission();
        if (hadBackgroundLocation && hadFineLocation) {
@@ -105,20 +116,26 @@ public class EmergencyCallHelper {
                + " holds sufficient permissions");
            return;
        }
        mHadFineLocation = hadFineLocation;
        mHadBackgroundLocation = hadBackgroundLocation;

        if (!hadFineLocation) {
            try {
                mContext.getPackageManager().grantRuntimePermission(systemDialerPackage,
                    Manifest.permission.ACCESS_FINE_LOCATION, userHandle);
                recordFineLocationPermissionGrant(userHandle);
            } catch (Exception e) {
                Log.i(this, "Failed to grant ACCESS_FINE_LOCATION");
            }
        }
        if (!hadBackgroundLocation) {
            try {
                mContext.getPackageManager().grantRuntimePermission(systemDialerPackage,
                    Manifest.permission.ACCESS_BACKGROUND_LOCATION, userHandle);
            }
            mHadFineLocation = hadFineLocation;
            mHadBackgroundLocation = hadBackgroundLocation;
            recordPermissionGrant(userHandle);
                recordBackgroundLocationPermissionGrant(userHandle);
            } catch (Exception e) {
            Log.e(this, e, "Failed to grant location permissions to " + systemDialerPackage
                  + ", user: " + userHandle);
                Log.i(this, "Failed to grant ACCESS_BACKGROUND_LOCATION");
            }
        }
    }

@@ -127,12 +144,19 @@ public class EmergencyCallHelper {
        Log.i(this, "Revoking temporary location permission from " + systemDialerPackage
            + ", user: " + mLocationPermissionGrantedToUser);
        UserHandle userHandle = mLocationPermissionGrantedToUser;

        try {
            if (!mHadFineLocation) {
            if (!mHadFineLocation && mFineLocationGranted) {
                mContext.getPackageManager().revokeRuntimePermission(systemDialerPackage,
                    Manifest.permission.ACCESS_FINE_LOCATION, userHandle);
            }
            if (!mHadBackgroundLocation) {
        } catch (Exception e) {
            Log.e(this, e, "Failed to revoke location permission from " + systemDialerPackage
                + ", user: " + userHandle);
        }

        try {
            if (!mHadBackgroundLocation && mBackgroundLocationGranted) {
                mContext.getPackageManager().revokeRuntimePermission(systemDialerPackage,
                    Manifest.permission.ACCESS_BACKGROUND_LOCATION, userHandle);
            }
@@ -140,6 +164,7 @@ public class EmergencyCallHelper {
            Log.e(this, e, "Failed to revoke location permission from " + systemDialerPackage
                + ", user: " + userHandle);
        }

        clearPermissionGrant();
    }

@@ -157,8 +182,14 @@ public class EmergencyCallHelper {
                == PackageManager.PERMISSION_GRANTED;
    }

    private void recordPermissionGrant(UserHandle userHandle) {
    private void recordBackgroundLocationPermissionGrant(UserHandle userHandle) {
        mLocationPermissionGrantedToUser = userHandle;
        mBackgroundLocationGranted = true;
    }

    private void recordFineLocationPermissionGrant(UserHandle userHandle) {
        mLocationPermissionGrantedToUser = userHandle;
        mFineLocationGranted = true;
    }

    private boolean wasGrantedTemporaryLocationPermission() {
@@ -169,5 +200,7 @@ public class EmergencyCallHelper {
        mLocationPermissionGrantedToUser = null;
        mHadBackgroundLocation = false;
        mHadFineLocation = false;
        mBackgroundLocationGranted = false;
        mFineLocationGranted = false;
    }
}
+248 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 Tc
 *
 * 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.telecom.tests;

import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;

import android.content.Context;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.test.suitebuilder.annotation.SmallTest;

import com.android.server.telecom.Call;
import com.android.server.telecom.DefaultDialerCache;
import com.android.server.telecom.EmergencyCallHelper;
import com.android.server.telecom.Timeouts;

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

import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(JUnit4.class)
public class EmergencyCallHelperTest extends TelecomTestCase {
  private static final String SYSTEM_DIALER_PACKAGE = "abc.xyz";
  private EmergencyCallHelper mEmergencyCallHelper;
  @Mock
  private PackageManager mPackageManager;
  @Mock
  private DefaultDialerCache mDefaultDialerCache;
  @Mock
  private Timeouts.Adapter mTimeoutsAdapter;
  @Mock
  private UserHandle mUserHandle;
  @Mock
  private Call mCall;

  @Override
  @Before
  public void setUp() throws Exception {
    super.setUp();
    MockitoAnnotations.initMocks(this);
    mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
    when(mContext.getPackageManager()).thenReturn(mPackageManager);
    mEmergencyCallHelper = new EmergencyCallHelper(mContext, mDefaultDialerCache,
        mTimeoutsAdapter);
    when(mDefaultDialerCache.getSystemDialerApplication()).thenReturn(SYSTEM_DIALER_PACKAGE);

    //start with no perms
    when(mPackageManager.checkPermission(eq(ACCESS_BACKGROUND_LOCATION),
        eq(SYSTEM_DIALER_PACKAGE))).thenReturn(
        PackageManager.PERMISSION_DENIED);

    when(mPackageManager.checkPermission(eq(ACCESS_FINE_LOCATION),
        eq(SYSTEM_DIALER_PACKAGE))).thenReturn(
        PackageManager.PERMISSION_DENIED);

    when(mCall.isEmergencyCall()).thenReturn(true);
    when(mContext.getResources().getBoolean(R.bool.grant_location_permission_enabled)).thenReturn(
        true);
  }

  @Override
  @After
  public void tearDown() throws Exception {
    super.tearDown();
  }

  private void verifyRevokeInvokedFor(String perm) {
    verify(mPackageManager, times(1)).revokeRuntimePermission(eq(SYSTEM_DIALER_PACKAGE),
        eq(perm), eq(mUserHandle));
  }

  private void verifyRevokeNotInvokedFor(String perm) {
    verify(mPackageManager, never()).revokeRuntimePermission(eq(SYSTEM_DIALER_PACKAGE),
        eq(perm), eq(mUserHandle));
  }

  private void verifyGrantInvokedFor(String perm) {
    verify(mPackageManager, times(1)).grantRuntimePermission(
        nullable(String.class),
        eq(perm), eq(mUserHandle));
  }

  private void verifyGrantNotInvokedFor(String perm) {
    verify(mPackageManager, never()).grantRuntimePermission(
        nullable(String.class),
        eq(perm), eq(mUserHandle));
  }

  @SmallTest
  @Test
  public void testEmergencyCallHelperRevokesOnlyFinePermAfterBackgroundPermGrantException() {

    //granting of background location perm fails
    doThrow(new SecurityException()).when(mPackageManager).grantRuntimePermission(
        nullable(String.class),
        eq(ACCESS_BACKGROUND_LOCATION), eq(mUserHandle));

    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
    mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();

    verifyGrantInvokedFor(ACCESS_BACKGROUND_LOCATION);
    verifyGrantInvokedFor(ACCESS_FINE_LOCATION);
    //only fine perm should be revoked
    verifyRevokeNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
    verifyRevokeInvokedFor(ACCESS_FINE_LOCATION);
  }

  @SmallTest
  @Test
  public void testEmergencyCallHelperRevokesOnlyBackgroundPermAfterFinePermGrantException() {

    //granting of fine location perm fails
    doThrow(new SecurityException()).when(mPackageManager).grantRuntimePermission(
        nullable(String.class),
        eq(ACCESS_FINE_LOCATION), eq(mUserHandle));

    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
    mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();

    //only background perm should be revoked
    verifyGrantInvokedFor(ACCESS_BACKGROUND_LOCATION);
    verifyGrantInvokedFor(ACCESS_FINE_LOCATION);
    //only fine perm should be revoked
    verifyRevokeNotInvokedFor(ACCESS_FINE_LOCATION);
    verifyRevokeInvokedFor(ACCESS_BACKGROUND_LOCATION);
  }

  @SmallTest
  @Test
  public void testNoPermGrantWhenPackageHasAllPerms() {

    when(mPackageManager.checkPermission(eq(ACCESS_BACKGROUND_LOCATION),
        eq(SYSTEM_DIALER_PACKAGE))).thenReturn(
        PackageManager.PERMISSION_GRANTED);

    when(mPackageManager.checkPermission(eq(ACCESS_FINE_LOCATION),
        eq(SYSTEM_DIALER_PACKAGE))).thenReturn(
        PackageManager.PERMISSION_GRANTED);

    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
    mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();

    //permissions should neither be granted or revoked
    verifyGrantNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
    verifyGrantNotInvokedFor(ACCESS_FINE_LOCATION);
    verifyRevokeNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
    verifyRevokeNotInvokedFor(ACCESS_FINE_LOCATION);
  }

  @SmallTest
  @Test
  public void testNoPermGrantForNonEmergencyCall() {

    when(mCall.isEmergencyCall()).thenReturn(false);

    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
    mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();

    //permissions should neither be granted or revoked
    verifyGrantNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
    verifyGrantNotInvokedFor(ACCESS_FINE_LOCATION);
    verifyRevokeNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
    verifyRevokeNotInvokedFor(ACCESS_FINE_LOCATION);
  }

  @SmallTest
  @Test
  public void testNoPermGrantWhenGrantLocationPermissionIsFalse() {

    when(mContext.getResources().getBoolean(R.bool.grant_location_permission_enabled)).thenReturn(
        false);

    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
    mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();

    //permissions should neither be granted or revoked
    verifyGrantNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
    verifyGrantNotInvokedFor(ACCESS_FINE_LOCATION);
    verifyRevokeNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
    verifyRevokeNotInvokedFor(ACCESS_FINE_LOCATION);
  }

  @SmallTest
  @Test
  public void testOnlyFineLocationPermIsGrantedAndRevoked() {

    when(mPackageManager.checkPermission(eq(ACCESS_BACKGROUND_LOCATION),
        eq(SYSTEM_DIALER_PACKAGE))).thenReturn(
        PackageManager.PERMISSION_GRANTED);

    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
    mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();

    //permissions should neither be granted or revoked
    verifyGrantNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
    verifyGrantInvokedFor(ACCESS_FINE_LOCATION);
    verifyRevokeNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
    verifyRevokeInvokedFor(ACCESS_FINE_LOCATION);
  }

  @SmallTest
  @Test
  public void testOnlyBackgroundLocationPermIsGrantedAndRevoked() {

    when(mPackageManager.checkPermission(eq(ACCESS_FINE_LOCATION),
        eq(SYSTEM_DIALER_PACKAGE))).thenReturn(
        PackageManager.PERMISSION_GRANTED);

    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
    mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();

    //permissions should neither be granted or revoked
    verifyGrantNotInvokedFor(ACCESS_FINE_LOCATION);
    verifyGrantInvokedFor(ACCESS_BACKGROUND_LOCATION);
    verifyRevokeNotInvokedFor(ACCESS_FINE_LOCATION);
    verifyRevokeInvokedFor(ACCESS_BACKGROUND_LOCATION);
  }
}