/* * Copyright (C) 2017 Oasis Feng. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * 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.oasisfeng.condom; import android.annotation.SuppressLint; import android.app.Application; import android.content.ComponentName; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.IBinder; import android.provider.Settings; import android.support.annotation.Nullable; import android.support.test.InstrumentationRegistry; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Proxy; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.ParametersAreNonnullByDefault; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; @ParametersAreNonnullByDefault public class CondomProcessTest { @Test public void testBindService() { sCondomProcessPackageManager.mCondom.mOutboundJudge = mBlockAllJudge; final Intent intent = new Intent().setPackage("a.b.c"); context().bindService(intent, SERVICE_CONNECTION, Context.BIND_AUTO_CREATE); assertOutboundJudgeCalled(1); assertNotNull(mIntent); // TODO: More cases } @Test public void testStartService() { sCondomProcessPackageManager.mCondom.mOutboundJudge = mBlockAllJudge; final Intent intent = new Intent().setPackage("a.b.c"); context().startService(intent); assertOutboundJudgeCalled(1); assertNotNull(mIntent); // TODO: More cases } @Test public void testBroadcast() { sCondomProcessPackageManager.mCondom.mOutboundJudge = mBlockAllJudge; final Intent intent = new Intent(); mIntent = null; context().sendBroadcast(intent); assertOutboundJudgeCalled(0); assertNull(mIntent); mIntent = null; context().sendBroadcast(new Intent(intent.setPackage("a.b.c"))); assertOutboundJudgeCalled(1); assertNotNull(mIntent); assertTrue(mIntent.filterEquals(intent)); } @Test public void testQuery() { final Context context = context(); final Intent service_intent = new Intent("android.view.InputMethod").addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES/* For consistency */); final ResolveInfo service = context.getPackageManager().resolveService(service_intent, 0); assertNotNull(service); final List<ResolveInfo> services = context.getPackageManager().queryIntentServices(service_intent, 0); assertNotNull(services); assertFalse(services.isEmpty()); final List<ResolveInfo> receivers = context.getPackageManager().queryBroadcastReceivers(new Intent(Intent.ACTION_BOOT_COMPLETED), 0); assertNotNull(receivers); assertFalse(receivers.isEmpty()); sCondomProcessPackageManager.mCondom.mOutboundJudge = mBlockAllJudge; assertNull(context.getPackageManager().resolveService(service_intent, 0)); assertOutboundJudgeCalled(services.size()); // Outbound judge should have been called for each candidates. List<ResolveInfo> result = context.getPackageManager().queryIntentServices(service_intent, 0); assertTrue(result.isEmpty()); assertOutboundJudgeCalled(services.size()); service_intent.setPackage(service.serviceInfo.packageName); assertNull(context.getPackageManager().resolveService(service_intent, 0)); assertOutboundJudgeCalled(1); // Only once for explicitly targeted intent result = context.getPackageManager().queryIntentServices(service_intent, 0); assertTrue(result.isEmpty()); assertOutboundJudgeCalled(1); service_intent.setPackage(null); } @Test public void testProvider() { final ContentResolver resolver = context().getContentResolver(); // Regular provider access final String android_id = Settings.System.getString(resolver, Settings.System.ANDROID_ID); assertNotNull(android_id); final ContentProviderClient client = resolver.acquireContentProviderClient(Settings.AUTHORITY); assertNotNull(client); client.release(); sCondomProcessPackageManager.mCondom.mOutboundJudge = mBlockAllJudge; assertNull(resolver.acquireContentProviderClient("downloads")); } @Before public void reset() { sCondomProcessPackageManager.mCondom.mOutboundJudge = null; mIntent = null; } @BeforeClass public static void checkInstallation() throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException { // Install in default process intentionally, since test cases cannot run in secondary process. CondomProcess.installExcept(((Application) InstrumentationRegistry.getTargetContext().getApplicationContext()), new CondomOptions(), ""); // Check IActivityManager proxy @SuppressLint("PrivateApi") final Object am_proxy = Class.forName("android.app.ActivityManagerNative").getMethod("getDefault").invoke(null); assertTrue(Proxy.isProxyClass(am_proxy.getClass())); sCondomProcessActivityManager = (CondomProcess.CondomProcessActivityManager) Proxy.getInvocationHandler(am_proxy); assertEquals(CondomProcess.CondomProcessActivityManager.class, sCondomProcessActivityManager.getClass()); // Check IPackageManager proxy final PackageManager pm = context().getPackageManager(); assertEquals("android.app.ApplicationPackageManager", pm.getClass().getName()); final Field ApplicationPackageManager_mPm = pm.getClass().getDeclaredField("mPM"); ApplicationPackageManager_mPm.setAccessible(true); final Object pm_proxy = ApplicationPackageManager_mPm.get(pm); assertTrue(Proxy.isProxyClass(pm_proxy.getClass())); sCondomProcessPackageManager = (CondomProcess.CondomProcessPackageManager) Proxy.getInvocationHandler(pm_proxy); assertEquals(CondomProcess.CondomProcessPackageManager.class, sCondomProcessPackageManager.getClass()); } private void assertOutboundJudgeCalled(final int count) { assertEquals(count, mNumOutboundJudgeCalled.getAndSet(0)); } // The sContext returned by getTargetContext() is actually never accessible except in test cases. private static Context context() { return InstrumentationRegistry.getTargetContext().getApplicationContext(); } private final AtomicInteger mNumOutboundJudgeCalled = new AtomicInteger(); private Intent mIntent; private final OutboundJudge mBlockAllJudge = new OutboundJudge() { @Override public boolean shouldAllow(final OutboundType type, final @Nullable Intent intent, final String target_pkg) { mIntent = intent; mNumOutboundJudgeCalled.incrementAndGet(); return false; }}; private static CondomProcess.CondomProcessActivityManager sCondomProcessActivityManager; private static CondomProcess.CondomProcessPackageManager sCondomProcessPackageManager; private static final ServiceConnection SERVICE_CONNECTION = new ServiceConnection() { @Override public void onServiceConnected(final ComponentName name, final IBinder service) {} @Override public void onServiceDisconnected(final ComponentName name) {} }; private static final String TAG = CondomProcessTest.class.getSimpleName(); }