Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
public class BlacklistedApis {
@Before
public void onlyRunOnPlatformsThatSupportBlacklisting() {
assumeTrue(Build.VERSION.SDK_INT >= 28);
assumeTrue("Requires API level 28 or higher", Build.VERSION.SDK_INT >= 28);
}

/**
Expand All @@ -63,14 +63,23 @@ private boolean isDebuggable() throws PackageManager.NameNotFoundException {
return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}

/**
* Check if blacklisting of non-SDK APIs is currently enforced in this process.
*
* @return {@code true} iff blacklisting is enforced
*/
private boolean isBlacklistingEnforced() {
return new CallBlackListedMethod().callingBlacklistedMethodCausesException();
}

@Test
public void callBlacklistedPublicMethodRealMethod() throws Exception {
Context targetContext = InstrumentationRegistry.getTargetContext();

FrameLayout child = new FrameLayout(targetContext);
FrameLayout parent = spy(new FrameLayout(targetContext));

if (isDebuggable()) {
if (isDebuggable() || !isBlacklistingEnforced()) {
// This calls a blacklisted public method.
// Since Android P these methods are not callable from outside of the Android framework
// anymore:
Expand All @@ -82,19 +91,25 @@ public void callBlacklistedPublicMethodRealMethod() throws Exception {
// problem as the mock class is the same as the mocked class.
parent.addView(child);
} else {
boolean threw = false;
try {
parent.addView(child);
fail();
} catch (NoSuchMethodError expected) {
// expected
} catch (NoSuchMethodError | NullPointerException expected) {
// expected: on early Android P, accessing hidden ViewGroup.mChildren threw
// NoSuchMethodError; on modern Android, mChildren cannot be copied into the
// subclass spy because reflection on hidden fields is blocked, leaving mChildren
// null and causing addView to throw NullPointerException.
threw = true;
}
assertTrue("Calling blacklisted method parent.addView() should throw an exception or error",
threw);
}
}

@Test
public void copyBlacklistedFields() throws Exception {
// Can only copy blacklisted fields when debuggable
assumeTrue(isDebuggable());
assumeTrue("Application must be debuggable to copy blacklisted fields", isDebuggable());

Context targetContext = InstrumentationRegistry.getTargetContext();

Expand All @@ -116,23 +131,27 @@ public void copyBlacklistedFields() throws Exception {
}

@SuppressLint({"PrivateApi", "CheckReturnValue", "SoonBlockedPrivateApi"})
@SuppressWarnings("ReturnValueIgnored") // Used for side effect of checking reflection resolution
@Test
public void cannotCallBlackListedAfterSpying() {
public void cannotCallBlackListedAfterSpying() throws Exception {
assumeTrue("Blacklisting is not enforced in this process", isBlacklistingEnforced());

// Spying and mocking might change the View class's byte code
spy(new View(InstrumentationRegistry.getTargetContext(), null));
mock(View.class);

// View#setNotifyAutofillManagerOnClick is a blacklisted method. Resolving it should fail
try {
View.class.getDeclaredMethod("setNotifyAutofillManagerOnClick", Boolean.TYPE);
fail();
fail("Resolving blacklisted method should throw NoSuchMethodException");
} catch (NoSuchMethodException expected) {
// expected
}
}

public static class CallBlackListedMethod {
@SuppressLint({"PrivateApi", "SoonBlockedPrivateApi"})
@SuppressWarnings("ReturnValueIgnored") // Used for side effect of checking reflection resolution
boolean callingBlacklistedMethodCausesException() {
// Settings.Global#isValidZenMode is a blacklisted method. Resolving it should fail
try {
Expand All @@ -145,13 +164,17 @@ boolean callingBlacklistedMethodCausesException() {
}

@Test
public void spiesCannotBeUsedToCallHiddenMethods() {
public void spiesCannotBeUsedToCallHiddenMethods() throws Exception {
assumeTrue("Blacklisting is not enforced in this process", isBlacklistingEnforced());

CallBlackListedMethod t = spy(new CallBlackListedMethod());
assertTrue(t.callingBlacklistedMethodCausesException());
assertTrue("Calling blacklisted method on spy should cause an exception",
t.callingBlacklistedMethodCausesException());
}

public static abstract class CallBlacklistedMethodAbstract {
@SuppressLint({"PrivateApi", "SoonBlockedPrivateApi"})
@SuppressWarnings("ReturnValueIgnored") // Used for side effect of checking reflection resolution
public boolean callingBlacklistedMethodCausesException() {
// Settings.Global#isValidZenMode is a blacklisted method. Resolving it should fail
try {
Expand All @@ -166,9 +189,12 @@ public boolean callingBlacklistedMethodCausesException() {
}

@Test
public void mocksOfAbstractClassesCannotBeUsedToCallHiddenMethods() {
public void mocksOfAbstractClassesCannotBeUsedToCallHiddenMethods() throws Exception {
assumeTrue("Blacklisting is not enforced in this process", isBlacklistingEnforced());

CallBlacklistedMethodAbstract t = mock(CallBlacklistedMethodAbstract.class);
doCallRealMethod().when(t).callingBlacklistedMethodCausesException();
assertTrue(t.callingBlacklistedMethodCausesException());
assertTrue("Calling blacklisted method on mock of abstract class should cause an exception",
t.callingBlacklistedMethodCausesException());
}
}