package permissions.dispatcher; import android.Manifest; import android.annotation.SuppressLint; import android.content.Context; import android.os.Build; import android.support.v4.content.PermissionChecker; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.BDDMockito; import org.mockito.Mockito; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import static android.os.Build.VERSION_CODES.GINGERBREAD; import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH; import static android.os.Build.VERSION_CODES.JELLY_BEAN; import static android.os.Build.VERSION_CODES.KITKAT_WATCH; import static junit.framework.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; /** * Test suite related to permissions that were added to Android at a later point in the platform's lifecycle. */ @RunWith(PowerMockRunner.class) @PrepareForTest({PermissionChecker.class}) @SuppressLint("NewApi") public class ApiLevelTestSuite { private static final int MOST_RECENT_API_LEVEL = Build.VERSION_CODES.M; private static final int NEEDS_PERMISSION_CHECK = 1024; private final Context mockContext; public ApiLevelTestSuite() { // Mock out Context mockContext = Mockito.mock(Context.class); } @Before public void beforeTest() throws Exception { // Reset the API level assumption this.resetApiLevel(); // Mock out PermissionChecker, so that "checkSelfPermission" // always returns NEEDS_PERMISSION_CHECK. // This way, we can distinguish between auto-grants and permission-checks PowerMockito.mockStatic(PermissionChecker.class); BDDMockito.given(PermissionChecker.checkSelfPermission(any(Context.class), anyString())).willReturn(NEEDS_PERMISSION_CHECK); } @Test public void testAssumeApiLevelWorking() throws Exception { // Check that manually setting the API level to a value works assumeApiLevel(ICE_CREAM_SANDWICH); assertEquals(ICE_CREAM_SANDWICH, Build.VERSION.SDK_INT); // Check that resetting the API level works resetApiLevel(); assertEquals(0, Build.VERSION.SDK_INT); } @Test public void testCheckSelfPermissionMockWorking() throws Exception { // Check that mocking out PermissionChecker works assertEquals(NEEDS_PERMISSION_CHECK, PermissionChecker.checkSelfPermission(mockContext, "permission")); } @Test public void testAddVoicemailPermission() throws Exception { // ADD_VOICEMAIL: // Added in API level 14 ("Ice Cream Sandwich") iteratePermissionCheck(Manifest.permission.ADD_VOICEMAIL, ICE_CREAM_SANDWICH); } @Test public void testBodySensorsPermission() throws Exception { // BODY_SENSORS: // Added in API level 20 ("KitKat Watch") iteratePermissionCheck(Manifest.permission.BODY_SENSORS, KITKAT_WATCH); } @Test public void testReadCallLogPermission() throws Exception { // READ_CALL_LOG: // Added in API level 16 ("Jelly Bean") iteratePermissionCheck(Manifest.permission.READ_CALL_LOG, JELLY_BEAN); } @Test public void testReadExternalStoragePermission() throws Exception { // READ_EXTERNAL_STORAGE: // Added in API level 16 ("Jelly Bean") iteratePermissionCheck(Manifest.permission.READ_EXTERNAL_STORAGE, JELLY_BEAN); } @Test public void testUseSipPermission() throws Exception { // USE_SIP: // Added in API level 9 ("Gingerbread") iteratePermissionCheck(Manifest.permission.USE_SIP, GINGERBREAD); } @Test public void testWriteCallLogPermission() throws Exception { // WRITE_CALL_LOG: // Added in API level 16 ("Jelly Bean") iteratePermissionCheck(Manifest.permission.WRITE_CALL_LOG, JELLY_BEAN); } /* Begin private */ private void iteratePermissionCheck(String permission, int permissionMinLevel) throws Exception { for (int apiLevel = 0; apiLevel <= MOST_RECENT_API_LEVEL; apiLevel++) { // Adjust the current API level assumeApiLevel(apiLevel); // Iterate over all API levels and verify that the permission is auto-granted // below the minimum available level. For all other API levels, the permission // shouldn't be auto-granted. boolean shouldAutoGrantPermission = apiLevel < permissionMinLevel; boolean hasPermission = PermissionUtils.hasSelfPermissions(mockContext, permission); if (shouldAutoGrantPermission != hasPermission) { // Mismatch, because the permission shouldn't be granted because the API level requires a check, // OR the permission should be granted because it doesn't exist on the current API level throw new AssertionError(permission + " check on API level " + apiLevel + " shouldn't return auto-grant=" + shouldAutoGrantPermission + " amd has-permission=" + hasPermission); } } } private void assumeApiLevel(int apiLevel) throws Exception { // Adjust the value of Build.VERSION.SDK_INT statically using reflection Field sdkIntField = Build.VERSION.class.getDeclaredField("SDK_INT"); sdkIntField.setAccessible(true); // Temporarily remove the SDK_INT's "final" modifier Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(sdkIntField, sdkIntField.getModifiers() & ~Modifier.FINAL); // Update the SDK_INT value, re-finalize the field, and lock it again sdkIntField.set(null, apiLevel); modifiersField.setInt(sdkIntField, sdkIntField.getModifiers() | Modifier.FINAL); sdkIntField.setAccessible(false); } private void resetApiLevel() throws Exception { this.assumeApiLevel(0); } }