blob: 6cdb35abce5a36e17959e7bb0620539f5bd9e0bf [file] [log] [blame]
/*
* Copyright (C) 2017 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 android.os;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.UiDevice;
import android.util.Log;
import com.android.frameworks.coretests.aidl.IBpcCallbackObserver;
import com.android.frameworks.coretests.aidl.IBpcTestAppCmdService;
import com.android.frameworks.coretests.aidl.IBpcTestServiceCmdService;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* Tests for verifying the Binder Proxy Counting and Limiting.
*
* To manually build and install relevant test apps
*
* Build:
* mmma frameworks/base/core/tests/coretests/BinderProxyCountingTestApp
* mmma frameworks/base/core/tests/coretests/BinderProxyCountingTestService
* Install:
* adb install -r \
* ${ANDROID_PRODUCT_OUT}/data/app/BinderProxyCountingTestApp/BinderProxyCountingTestApp.apk
* adb install -r \
* ${ANDROID_PRODUCT_OUT}/data/app/BinderProxyCountingTestService/BinderProxyCountingTestService.apk
*
* To run the tests, use
*
* Build: m FrameworksCoreTests
* Install: adb install -r \
* ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
* Run: adb shell am instrument -e class android.os.BinderProxyCountingTest -w \
* com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
*
* or
*
* bit FrameworksCoreTests:android.os.BinderProxyCountingTest
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
public class BinderProxyCountingTest {
private static final String TAG = BinderProxyCountingTest.class.getSimpleName();
private static final String TEST_APP_PKG =
"com.android.frameworks.coretests.binderproxycountingtestapp";
private static final String TEST_APP_CMD_SERVICE = TEST_APP_PKG + ".BpcTestAppCmdService";
private static final String TEST_SERVICE_PKG =
"com.android.frameworks.coretests.binderproxycountingtestservice";
private static final String TEST_SERVICE_CMD_SERVICE =
TEST_SERVICE_PKG + ".BpcTestServiceCmdService";
private static final int BIND_SERVICE_TIMEOUT_SEC = 5;
private static final int TOO_MANY_BINDERS_TIMEOUT_SEC = 2;
// Keep in sync with sBinderProxyCountLimit in BpBinder.cpp
private static final int BINDER_PROXY_LIMIT = 2500;
private static Context sContext;
private static UiDevice sUiDevice;
private static ServiceConnection sTestAppConnection;
private static ServiceConnection sTestServiceConnection;
private static IBpcTestAppCmdService sBpcTestAppCmdService;
private static IBpcTestServiceCmdService sBpcTestServiceCmdService;
private static final Intent sTestAppIntent = new Intent()
.setComponent(new ComponentName(TEST_APP_PKG, TEST_APP_CMD_SERVICE));
private static final Intent sTestServiceIntent = new Intent()
.setComponent(new ComponentName(TEST_SERVICE_PKG, TEST_SERVICE_CMD_SERVICE));
private static final Consumer<IBinder> sTestAppConsumer = (service) -> {
sBpcTestAppCmdService = IBpcTestAppCmdService.Stub.asInterface(service);
};
private static final Consumer<IBinder> sTestServiceConsumer = (service) -> {
sBpcTestServiceCmdService = IBpcTestServiceCmdService.Stub.asInterface(service);
};
private static int sTestPkgUid;
/**
* Setup any common data for the upcoming tests.
*/
@BeforeClass
public static void setUpOnce() throws Exception {
sContext = InstrumentationRegistry.getContext();
sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_APP_PKG, 0);
((ActivityManager) sContext.getSystemService(Context.ACTIVITY_SERVICE)).killUid(sTestPkgUid,
"Wiping Test Package");
sUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
}
private ServiceConnection bindService(final Consumer<IBinder> consumer, Intent intent)
throws Exception {
final CountDownLatch bindLatch = new CountDownLatch(1);
ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "Service connected");
consumer.accept(service);
bindLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "Service disconnected");
}
};
sContext.bindService(intent, connection,
Context.BIND_AUTO_CREATE
| Context.BIND_ALLOW_OOM_MANAGEMENT
| Context.BIND_NOT_FOREGROUND);
if (!bindLatch.await(BIND_SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
fail("Timed out waiting for the service to bind in " + sTestPkgUid);
}
return connection;
}
private void unbindService(ServiceConnection service) {
if (service != null) {
sContext.unbindService(service);
}
}
private void bindTestAppToTestService() throws Exception {
if (sBpcTestAppCmdService != null) {
String errorMessage = sBpcTestAppCmdService.bindToTestService();
if (errorMessage != null) {
fail(errorMessage);
}
}
}
private void unbindTestAppFromTestService() throws Exception {
if (sBpcTestAppCmdService != null) {
sBpcTestAppCmdService.unbindFromTestService();
}
}
private CountDownLatch createBinderLimitLatch() throws RemoteException {
final CountDownLatch latch = new CountDownLatch(1);
sBpcTestServiceCmdService.setBinderProxyCountCallback(
new IBpcCallbackObserver.Stub() {
@Override
public void onCallback(int uid) {
if (uid == sTestPkgUid) {
latch.countDown();
}
}
});
return latch;
}
/**
* Get the Binder Proxy count held by SYSTEM for a given uid
*/
private int getSystemBinderCount(int uid) throws Exception {
return Integer.parseInt(sUiDevice.executeShellCommand(
"dumpsys activity binder-proxies " + uid).trim());
}
@Test
public void testBinderProxyCount() throws Exception {
// Arbitrary list of Binder create and release
// Should cumulatively equal 0 and must never add up past the binder limit at any point
int[] testValues = {223, -103, -13, 25, 90, -222};
try {
sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
// Get the baseline of binders naturally held by the test Package
int expectedBinderCount = getSystemBinderCount(sTestPkgUid);
for (int testValue : testValues) {
if (testValue > 0) {
sBpcTestAppCmdService.createSystemBinders(testValue);
} else {
sBpcTestAppCmdService.releaseSystemBinders(-testValue);
}
expectedBinderCount += testValue;
int currentBinderCount = getSystemBinderCount(sTestPkgUid);
assertEquals("Current Binder Count (" + currentBinderCount
+ ") does not equal expected Binder Count (" + expectedBinderCount
+ ")", expectedBinderCount, currentBinderCount);
}
} finally {
unbindService(sTestAppConnection);
}
}
@Test
public void testBinderProxyLimitBoundary() throws Exception {
final int binderProxyLimit = 2000;
final int rearmThreshold = 1800;
try {
sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
sTestServiceConnection = bindService(sTestServiceConsumer, sTestServiceIntent);
bindTestAppToTestService();
sBpcTestServiceCmdService.enableBinderProxyLimit(true);
sBpcTestServiceCmdService.forceGc();
// Get the baseline of binders naturally held by the test Package
int baseBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
final CountDownLatch binderLimitLatch = createBinderLimitLatch();
sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold);
// Create Binder Proxies up to the limit
sBpcTestAppCmdService.createTestBinders(binderProxyLimit - baseBinderCount);
if (binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid
+ " when proxy limit should not have been reached");
}
// Create one more Binder to cross the limit
sBpcTestAppCmdService.createTestBinders(1);
if (!binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
}
sBpcTestAppCmdService.releaseAllBinders();
} finally {
unbindTestAppFromTestService();
unbindService(sTestAppConnection);
unbindService(sTestServiceConnection);
}
}
@Test
public void testSetBinderProxyLimit() throws Exception {
int[] testLimits = {1000, 222, 800};
try {
sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
sTestServiceConnection = bindService(sTestServiceConsumer, sTestServiceIntent);
bindTestAppToTestService();
sBpcTestServiceCmdService.enableBinderProxyLimit(true);
sBpcTestServiceCmdService.forceGc();
int baseBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
for (int testLimit : testLimits) {
final CountDownLatch binderLimitLatch = createBinderLimitLatch();
// Change the BinderProxyLimit
sBpcTestServiceCmdService.setBinderProxyWatermarks(testLimit, baseBinderCount + 10);
// Exceed the new Binder Proxy Limit
sBpcTestAppCmdService.createTestBinders(testLimit + 1);
if (!binderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
}
sBpcTestAppCmdService.releaseTestBinders(testLimit + 1);
sBpcTestServiceCmdService.forceGc();
}
} finally {
unbindTestAppFromTestService();
unbindService(sTestAppConnection);
unbindService(sTestServiceConnection);
}
}
@Test
public void testRearmCallbackThreshold() throws Exception {
final int binderProxyLimit = 2000;
final int exceedBinderProxyLimit = binderProxyLimit + 10;
final int rearmThreshold = 1800;
try {
sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
sTestServiceConnection = bindService(sTestServiceConsumer, sTestServiceIntent);
bindTestAppToTestService();
sBpcTestServiceCmdService.enableBinderProxyLimit(true);
sBpcTestServiceCmdService.forceGc();
final CountDownLatch firstBinderLimitLatch = createBinderLimitLatch();
sBpcTestServiceCmdService.setBinderProxyWatermarks(binderProxyLimit, rearmThreshold);
// Exceed the Binder Proxy Limit
sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit);
if (!firstBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
}
sBpcTestServiceCmdService.forceGc();
int currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
// Drop to the threshold, this should not rearm the callback
sBpcTestAppCmdService.releaseTestBinders(currentBinderCount - rearmThreshold);
sBpcTestServiceCmdService.forceGc();
currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
final CountDownLatch secondBinderLimitLatch = createBinderLimitLatch();
// Exceed the Binder Proxy limit which should not cause a callback since there has
// been no rearm
sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - currentBinderCount);
if (secondBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
fail("Received BinderProxyLimitCallback for uid " + sTestPkgUid
+ " when the callback has not been rearmed yet");
}
sBpcTestServiceCmdService.forceGc();
currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
// Drop below the rearmThreshold to rearm the BinderProxyLimitCallback
sBpcTestAppCmdService.releaseTestBinders(currentBinderCount - rearmThreshold + 1);
sBpcTestServiceCmdService.forceGc();
currentBinderCount = sBpcTestServiceCmdService.getBinderProxyCount(sTestPkgUid);
// Exceed the Binder Proxy limit for the last time
sBpcTestAppCmdService.createTestBinders(exceedBinderProxyLimit - currentBinderCount);
if (!secondBinderLimitLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
fail("Timed out waiting for uid " + sTestPkgUid + " to hit limit");
}
sBpcTestAppCmdService.releaseTestBinders(currentBinderCount);
} finally {
unbindTestAppFromTestService();
unbindService(sTestAppConnection);
unbindService(sTestServiceConnection);
}
}
@Test
public void testKillBadBehavingApp() throws Exception {
final CountDownLatch binderDeathLatch = new CountDownLatch(1);
final int exceedBinderProxyLimit = BINDER_PROXY_LIMIT + 1;
try {
sTestAppConnection = bindService(sTestAppConsumer, sTestAppIntent);
sBpcTestAppCmdService.asBinder().linkToDeath(new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.v(TAG, "BpcTestAppCmdService died!");
binderDeathLatch.countDown();
}
}, 0);
try {
// Exceed the Binder Proxy Limit emulating a bad behaving app
sBpcTestAppCmdService.createSystemBinders(exceedBinderProxyLimit);
} catch (DeadObjectException doe) {
// We are expecting the service to get killed mid call, so a DeadObjectException
// is not unexpected
}
if (!binderDeathLatch.await(TOO_MANY_BINDERS_TIMEOUT_SEC, TimeUnit.SECONDS)) {
sBpcTestAppCmdService.releaseSystemBinders(exceedBinderProxyLimit);
fail("Timed out waiting for uid " + sTestPkgUid + " to die.");
}
} finally {
unbindService(sTestAppConnection);
}
}
}