/* * Copyright (C) 2018 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.dx.mockito.tests; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.provider.Settings; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.view.View; import android.widget.FrameLayout; import org.junit.Test; import org.junit.runner.RunWith; /** * Simplified versions of bugs seen in the past */ @RunWith(AndroidJUnit4.class) public class BlacklistedApis { /** * Check if the application is marked as {@code android:debuggable} in the manifest * * @return {@code true} iff it is marked as such */ private boolean isDebuggable() throws PackageManager.NameNotFoundException { Context context = InstrumentationRegistry.getTargetContext(); PackageInfo packageInfo = context.getPackageManager().getPackageInfo( context.getPackageName(), 0); return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; } @Test public void callBlacklistedPublicMethodRealMethod() throws Exception { Context targetContext = InstrumentationRegistry.getTargetContext(); FrameLayout child = new FrameLayout(targetContext); FrameLayout parent = spy(new FrameLayout(targetContext)); if (isDebuggable()) { // This calls a blacklisted public method. // Since Android P these methods are not callable from outside of the Android framework // anymore: // // https://android-developers.googleblog.com/2018/02/ // improving-stability-by-reducing-usage.html // // Hence if we use a subclass mock this will fail. Inline mocking does not have this // problem as the mock class is the same as the mocked class. parent.addView(child); } else { try { parent.addView(child); fail(); } catch (NoSuchMethodError expected) { // expected } } } @Test public void copyBlacklistedFields() throws Exception { assumeTrue(isDebuggable()); Context targetContext = InstrumentationRegistry.getTargetContext(); FrameLayout child = new FrameLayout(targetContext); FrameLayout parent = spy(new FrameLayout(targetContext)); parent.addView(child); // During cloning of the parent spy, all fields are copied. This accesses a blacklisted // fields. Since Android P these fields are not visible from outside of the Android // framework anymore: // // https://android-developers.googleblog.com/2018/02/ // improving-stability-by-reducing-usage.html // // As 'measure' requires the fields to be initialized, this fails if the fields are not // copied. parent.measure(100, 100); } @Test public void cannotCallBlackListedAfterSpying() { // 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(); } catch (NoSuchMethodException expected) { // expected } } public class CallBlackListedMethod { public boolean callingBlacklistedMethodCausesException() { // Settings.Global#isValidZenMode is a blacklisted method. Resolving it should fail try { Settings.Global.class.getDeclaredMethod("isValidZenMode", Integer.TYPE); return false; } catch (NoSuchMethodException expected) { return true; } } } @Test public void spiesCannotBeUsedToCallHiddenMethods() { CallBlackListedMethod t = spy(new CallBlackListedMethod()); assertTrue(t.callingBlacklistedMethodCausesException()); } public abstract class CallBlacklistedMethodAbstract { public boolean callingBlacklistedMethodCausesException() { // Settings.Global#isValidZenMode is a blacklisted method. Resolving it should fail try { Settings.Global.class.getDeclaredMethod("isValidZenMode", Integer.TYPE); return false; } catch (NoSuchMethodException expected) { return true; } } public abstract void unused(); } @Test public void mocksOfAbstractClassesCannotBeUsedToCallHiddenMethods() { CallBlacklistedMethodAbstract t = mock(CallBlacklistedMethodAbstract.class); doCallRealMethod().when(t).callingBlacklistedMethodCausesException(); assertTrue(t.callingBlacklistedMethodCausesException()); } }