/* * 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.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Handler; import android.os.Process; import android.os.UserHandle; import android.util.Log; import androidx.annotation.CheckResult; import androidx.annotation.Keep; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.Size; import com.oasisfeng.condom.util.Lazy; import java.util.concurrent.Executor; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.O; import static android.os.Build.VERSION_CODES.Q; /** * The condom-style {@link ContextWrapper} to prevent unwanted behaviors going through. * * Created by Oasis on 2017/3/25. */ @Keep public class CondomContext extends ContextWrapper { public static @CheckResult CondomContext wrap(final Context base, final @Nullable @Size(max=13) String tag) { return wrap(base, tag, new CondomOptions()); } /** * This is the very first (probably only) API you need to wrap the naked {@link Context} under protection of <code>CondomContext</code> * * @param base the original context used before <code>CondomContext</code> is introduced. * @param tag the optional tag to distinguish between multiple instances of <code>CondomContext</code> used parallel. */ public static @CheckResult CondomContext wrap(final Context base, final @Nullable @Size(max=13) String tag, final CondomOptions options) { if (base instanceof CondomContext) { final CondomContext condom = ((CondomContext) base); Log.w("Condom", "The wrapped context is already a CondomContext (tag: " + condom.TAG + "), tag and options specified here will be ignore."); return condom; } final Context app_context = base.getApplicationContext(); final CondomCore condom = new CondomCore(base, options, CondomCore.buildLogTag("Condom", "Condom.", tag)); if (app_context instanceof Application) { // The application context is indeed an Application, this should be preserved semantically. final Application app = (Application) app_context; final CondomApplication condom_app = new CondomApplication(condom, app, tag); // TODO: Application instance should be unique across CondomContext. final CondomContext condom_context = new CondomContext(condom, condom_app, tag); condom_app.attachBaseContext(base == app_context ? condom_context : new CondomContext(condom, app, tag)); return condom_context; } else return new CondomContext(condom, base == app_context ? null : new CondomContext(condom, app_context, tag), tag); } /** @deprecated Use {@link CondomOptions} instead */ public CondomContext setDryRun(final boolean dry_run) { if (dry_run == mCondom.mDryRun) return this; mCondom.mDryRun = dry_run; if (dry_run) Log.w(TAG, "Start dry-run mode, no outbound requests will be blocked actually, despite later stated in log."); else Log.w(TAG, "Stop dry-run mode."); return this; } /** @deprecated Use {@link CondomOptions} instead */ @Deprecated public CondomContext preventWakingUpStoppedPackages(final boolean prevent_or_not) { mCondom.mExcludeStoppedPackages = prevent_or_not; return this; } /** @deprecated Use {@link CondomOptions} instead */ @Deprecated public CondomContext preventBroadcastToBackgroundPackages(final boolean prevent_or_not) { mCondom.mExcludeBackgroundReceivers = prevent_or_not; return this; } /** @deprecated Use {@link CondomOptions} instead */ @Deprecated public CondomContext preventServiceInBackgroundPackages(final boolean prevent_or_not) { if (SDK_INT < O) mCondom.mExcludeBackgroundServices = prevent_or_not; return this; } /* ****** Hooked Context APIs ****** */ @Override public boolean bindService(final Intent intent, final ServiceConnection conn, final int flags) { return doBindService(intent, () -> CondomContext.super.bindService(intent, conn, flags)); } @RequiresApi(Q) @Override public boolean bindService(final Intent intent, final int flags, final Executor executor, final ServiceConnection conn) { return doBindService(intent, () -> CondomContext.super.bindService(intent, flags, executor, conn)); } @RequiresApi(Q) @Override public boolean bindIsolatedService(final Intent intent, final int flags, final String instanceName, final Executor executor, final ServiceConnection conn) { return doBindService(intent, () -> CondomContext.super.bindIsolatedService(intent, flags, instanceName, executor, conn)); } private boolean doBindService(final Intent intent, final CondomCore.WrappedValueProcedure<Boolean> procedure) { final boolean result = mCondom.proceed(OutboundType.BIND_SERVICE, intent, Boolean.FALSE, procedure); if (result) mCondom.logIfOutboundPass(TAG, intent, CondomCore.getTargetPackage(intent), CondomCore.CondomEvent.BIND_PASS); return result; } @Override public ComponentName startService(final Intent intent) { final ComponentName component = mCondom.proceed(OutboundType.START_SERVICE, intent, null, () -> CondomContext.super.startService(intent)); if (component != null) mCondom.logIfOutboundPass(TAG, intent, component.getPackageName(), CondomCore.CondomEvent.START_PASS); return component; } @Override public void sendBroadcast(final Intent intent) { mCondom.proceedBroadcast(this, intent, new CondomCore.WrappedProcedure() { @Override public void run() { CondomContext.super.sendBroadcast(intent); }}, null); } @Override public void sendBroadcast(final Intent intent, final String receiverPermission) { mCondom.proceedBroadcast(this, intent, new CondomCore.WrappedProcedure() { @Override public void run() { CondomContext.super.sendBroadcast(intent, receiverPermission); }}, null); } @RequiresApi(JELLY_BEAN_MR1) @SuppressLint("MissingPermission") @Override public void sendBroadcastAsUser(final Intent intent, final UserHandle user) { mCondom.proceedBroadcast(this, intent, new CondomCore.WrappedProcedure() { @Override public void run() { CondomContext.super.sendBroadcastAsUser(intent, user); }}, null); } @RequiresApi(JELLY_BEAN_MR1) @SuppressLint("MissingPermission") @Override public void sendBroadcastAsUser(final Intent intent, final UserHandle user, final String receiverPermission) { mCondom.proceedBroadcast(this, intent, new CondomCore.WrappedProcedure() { @Override public void run() { CondomContext.super.sendBroadcastAsUser(intent, user, receiverPermission); }}, null); } @Override public void sendOrderedBroadcast(final Intent intent, final String receiverPermission) { mCondom.proceedBroadcast(this, intent, new CondomCore.WrappedProcedure() { @Override public void run() { CondomContext.super.sendOrderedBroadcast(intent, receiverPermission); }}, null); } @Override public void sendOrderedBroadcast(final Intent intent, final String receiverPermission, final BroadcastReceiver resultReceiver, final Handler scheduler, final int initialCode, final String initialData, final Bundle initialExtras) { mCondom.proceedBroadcast(this, intent, new CondomCore.WrappedProcedure() { @Override public void run() { CondomContext.super.sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras); }}, resultReceiver); } @RequiresApi(JELLY_BEAN_MR1) @SuppressLint("MissingPermission") @Override public void sendOrderedBroadcastAsUser(final Intent intent, final UserHandle user, final String receiverPermission, final BroadcastReceiver resultReceiver, final Handler scheduler, final int initialCode, final String initialData, final Bundle initialExtras) { mCondom.proceedBroadcast(this, intent, new CondomCore.WrappedProcedure() { @Override public void run() { CondomContext.super.sendOrderedBroadcastAsUser(intent, user, receiverPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras); }}, resultReceiver); } @Override @SuppressLint("MissingPermission") public void sendStickyBroadcast(final Intent intent) { mCondom.proceedBroadcast(this, intent, new CondomCore.WrappedProcedure() { @Override public void run() { CondomContext.super.sendStickyBroadcast(intent); }}, null); } @RequiresApi(JELLY_BEAN_MR1) @SuppressLint("MissingPermission") @Override public void sendStickyBroadcastAsUser(final Intent intent, final UserHandle user) { mCondom.proceedBroadcast(this, intent, new CondomCore.WrappedProcedure() { @Override public void run() { CondomContext.super.sendStickyBroadcastAsUser(intent, user); }}, null); } @Override @SuppressLint("MissingPermission") public void sendStickyOrderedBroadcast(final Intent intent, final BroadcastReceiver resultReceiver, final Handler scheduler, final int initialCode, final String initialData, final Bundle initialExtras) { mCondom.proceedBroadcast(this, intent, new CondomCore.WrappedProcedure() { @Override public void run() { CondomContext.super.sendStickyOrderedBroadcast(intent, resultReceiver, scheduler, initialCode, initialData, initialExtras); }}, resultReceiver); } @RequiresApi(JELLY_BEAN_MR1) @SuppressLint("MissingPermission") @Override public void sendStickyOrderedBroadcastAsUser(final Intent intent, final UserHandle user, final BroadcastReceiver resultReceiver, final Handler scheduler, final int initialCode, final String initialData, final Bundle initialExtras) { mCondom.proceedBroadcast(this, intent, new CondomCore.WrappedProcedure() { @Override public void run() { CondomContext.super.sendStickyOrderedBroadcastAsUser(intent, user, resultReceiver, scheduler, initialCode, initialData, initialExtras); }}, resultReceiver); } @Override public Object getSystemService(final String name) { final Object service = mCondom.getSystemService(name); return service != null ? service : super.getSystemService(name); } @RequiresApi(M) @Override public int checkSelfPermission(final String permission) { return mCondom.shouldSpoofPermission(permission) ? PERMISSION_GRANTED : super.checkSelfPermission(permission); } @Override public int checkPermission(final String permission, final int pid, final int uid) { return pid == Process.myPid() && uid == Process.myUid() && mCondom.shouldSpoofPermission(permission) ? PERMISSION_GRANTED : super.checkPermission(permission, pid, uid); } @Override public ContentResolver getContentResolver() { return mCondom.getContentResolver(); } @Override public PackageManager getPackageManager() { return mCondom.getPackageManager(); } @Override public Context getApplicationContext() { return mApplicationContext; } @Override public Context getBaseContext() { mCondom.logConcern(TAG, "getBaseContext"); return mBaseContext.get(); } /* ********************************* */ private CondomContext(final CondomCore condom, final @Nullable Context app_context, final @Nullable @Size(max=16) String tag) { super(condom.mBase); mCondom = condom; mApplicationContext = app_context != null ? app_context : this; mBaseContext = new Lazy<Context>() { @Override protected Context create() { return new PseudoContextImpl(CondomContext.this); }}; TAG = CondomCore.buildLogTag("Condom", "Condom.", tag); } CondomCore mCondom; private final Context mApplicationContext; private final Lazy<Context> mBaseContext; final String TAG; /* ****** Internal branch functionality ****** */ // This should act as what ContextImpl stands for in the naked Context structure. private static class PseudoContextImpl extends PseudoContextWrapper { public PseudoContextImpl(final CondomContext condom) { super(condom); } } }