/* * 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.app.Application; import android.content.ComponentName; import android.content.ContextWrapper; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.IBinder; import android.os.Process; import android.os.UserHandle; import android.provider.Settings; import androidx.annotation.NonNull; import androidx.test.core.app.ApplicationProvider; import android.util.DisplayMetrics; import android.util.EventLog; import org.junit.Test; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.O; import static java.util.Objects.requireNonNull; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; /** * Miscellaneous test cases * * Created by Oasis on 2017/4/10. */ public class CondomMiscTest { @SuppressWarnings("JavaReflectionMemberAccess") @Test public void testHiddenApi() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { final Object uid = PackageManager.class.getMethod("getUidForSharedUser", String.class).invoke(condom.getPackageManager(),"android.uid.system"); assertEquals(1000, (int) uid); // This hidden API is used by some 3rd-party libraries, as reported in issue #9 on GitHub. if (SDK_INT >= LOLLIPOP) PackageManager.class.getMethod("getUserBadgeForDensity", UserHandle.class, int.class) .invoke(condom.getPackageManager(), Process.myUserHandle(), DisplayMetrics.DENSITY_DEFAULT); } @Test public void testEventLog() throws IOException { readNewEvents(CondomCore.CondomEvent.CONCERN); condom.getBaseContext(); Object[] data = readLastEvent(CondomCore.CondomEvent.CONCERN); assertEquals(condom.getPackageName(), data[0]); assertEquals(condom.TAG, data[1]); assertEquals("getBaseContext", data[2]); assertCallerMatch(data); ((Application) condom.getApplicationContext()).getBaseContext(); data = readLastEvent(CondomCore.CondomEvent.CONCERN); assertEquals("Application.getBaseContext", data[2]); assertCallerMatch(data); condom.getPackageManager().getInstalledApplications(0); data = readLastEvent(CondomCore.CondomEvent.CONCERN); assertEquals("PackageManager.getInstalledApplications", data[2]); assertCallerMatch(data); condom.getPackageManager().getInstalledPackages(0); data = readLastEvent(CondomCore.CondomEvent.CONCERN); assertEquals("PackageManager.getInstalledPackages", data[2]); assertCallerMatch(data); final Intent intent = new Intent().setPackage("a.b.c"); condom.bindService(intent, SERVICE_CONNECTION, 0); data = readLastEvent(CondomCore.CondomEvent.BIND_PASS); assertEquals(condom.getPackageName(), data[0]); assertEquals("Condom." + TAG, data[1]); assertEquals(intent.getPackage(), data[2]); assertEquals(intent.toString(), data[3]); condom.startService(intent); data = readLastEvent(CondomCore.CondomEvent.START_PASS); assertEquals(condom.getPackageName(), data[0]); assertEquals("Condom." + TAG, data[1]); assertEquals(intent.getPackage(), data[2]); assertEquals(intent.toString(), data[3]); if (SDK_INT < O) { final List<ResolveInfo> result = condom.getPackageManager().queryIntentServices(intent.setPackage(null).setComponent(null), 0); assertEquals(1, result.size()); // 1 left: non.bg.service final List<EventLog.Event> events = readNewEvents(CondomCore.CondomEvent.FILTER_BG_SERVICE); assertEquals(2, events.size()); // 2 filtered: bg.service.* data = (Object[]) events.get(0).getData(); assertEquals(condom.getPackageName(), data[0]); assertEquals("Condom." + TAG, data[1]); assertEquals("bg.service.1", data[2]); final String expected_intent = new Intent(intent).toString(); // Flags altered assertEquals(expected_intent, data[3]); data = (Object[]) events.get(1).getData(); assertEquals(condom.getPackageName(), data[0]); assertEquals("Condom." + TAG, data[1]); assertEquals("bg.service.2", data[2]); assertEquals(expected_intent, data[3]); final ResolveInfo resolve = condom.getPackageManager().resolveService(intent, 0); assertEquals("non.bg.service", requireNonNull(resolve).serviceInfo.applicationInfo.packageName); data = readLastEvent(CondomCore.CondomEvent.FILTER_BG_SERVICE); assertEquals(condom.getPackageName(), data[0]); assertEquals("Condom." + TAG, data[1]); assertEquals("bg.service.1", data[2]); assertEquals(expected_intent, data[3]); } final CondomContext condom_wo_tag = CondomContext.wrap(new ContextWrapper(ApplicationProvider.getApplicationContext()) { @Override public boolean bindService(final Intent service, final ServiceConnection conn, final int flags) { return true; } @Override public ComponentName startService(final Intent service) { return service.getComponent() != null ? service.getComponent() : new ComponentName(requireNonNull(service.getPackage()), "A"); } }, null); final ComponentName component = new ComponentName("x.y.z", "O"); intent.setPackage(null).setComponent(component); condom_wo_tag.bindService(intent, SERVICE_CONNECTION, 0); data = readLastEvent(CondomCore.CondomEvent.BIND_PASS); assertEquals(condom_wo_tag.getPackageName(), data[0]); assertEquals("Condom", data[1]); assertEquals(requireNonNull(intent.getComponent()).getPackageName(), data[2]); assertEquals(intent.toString(), data[3]); condom_wo_tag.startService(intent); data = readLastEvent(CondomCore.CondomEvent.START_PASS); assertEquals(condom_wo_tag.getPackageName(), data[0]); assertEquals("Condom", data[1]); assertEquals(intent.getComponent().getPackageName(), data[2]); assertEquals(intent.toString(), data[3]); } private static void assertCallerMatch(final Object[] data) { final String string = data[3].toString(); assertTrue(string, string.startsWith(CondomMiscTest.class.getName() + ".testEventLog:")); } private static Object[] readLastEvent(final CondomCore.CondomEvent type) throws IOException { final List<EventLog.Event> events = readNewEvents(type); assertEquals(1, events.size()); return (Object[]) events.get(0).getData(); } private static List<EventLog.Event> readNewEvents(final CondomCore.CondomEvent type) throws IOException { final List<EventLog.Event> events = new ArrayList<>(); EventLog.readEvents(new int[] { EVENT_TAG_MARK, "Condom".hashCode() + type.ordinal() }, events); if (events.isEmpty()) return Collections.emptyList(); for (int i = events.size() - 1; i >= 0; i --) { final EventLog.Event event = events.get(i); if (event.getTag() == EVENT_TAG_MARK) { EventLog.writeEvent(EVENT_TAG_MARK); return events.subList(i + 1, events.size()); } } EventLog.writeEvent(EVENT_TAG_MARK); return events; } 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 final CondomContext condom = CondomContext.wrap(new ContextWrapper(ApplicationProvider.getApplicationContext()) { @Override public boolean bindService(final Intent service, final ServiceConnection conn, final int flags) { return true; } @Override public ComponentName startService(final Intent service) { return service.getComponent() != null ? service.getComponent() : new ComponentName(requireNonNull(service.getPackage()), "A"); } @Override public PackageManager getPackageManager() { return new PackageManagerWrapper(super.getPackageManager()) { @Override public @NonNull List<ResolveInfo> queryIntentServices(final Intent intent, final int flags) { final List<ResolveInfo> resolves = new ArrayList<>(); final String ime = Settings.Secure.getString(getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); if (ime != null) try { final String ime_pkg = requireNonNull(ComponentName.unflattenFromString(ime)).getPackageName(); final int uid = getPackageManager().getPackageUid(ime_pkg, 0); resolves.add(buildResolveInfo("bg.service.1", 999999999)); // Simulate a background UID. resolves.add(buildResolveInfo("non.bg.service", uid)); resolves.add(buildResolveInfo("bg.service.2", 88888888)); } catch (final NameNotFoundException ignored) {} // Should hardly happen return resolves; } private ResolveInfo buildResolveInfo(final String pkg, final int uid) { final ResolveInfo resolve = new ResolveInfo() { @Override public String toString() { return "ResolveInfo{test}"; } }; resolve.serviceInfo = new ServiceInfo(); resolve.serviceInfo.packageName = pkg; resolve.serviceInfo.applicationInfo = new ApplicationInfo(); resolve.serviceInfo.applicationInfo.packageName = pkg; resolve.serviceInfo.applicationInfo.uid = uid; return resolve; } }; } }, TAG); private static final int EVENT_TAG_MARK = "Condom".hashCode() + 999; private static final String TAG = "Test"; }