/*
 * 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.ComponentCallbacks;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
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 androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.Size;

import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.M;

/**
 * Application wrapper for {@link CondomContext#getApplicationContext()}
 *
 * Created by Oasis on 2018/1/6.
 */
class CondomApplication extends Application {

	@Override public void registerComponentCallbacks(final ComponentCallbacks callback) {
		if (SDK_INT >= ICE_CREAM_SANDWICH) mApplication.registerComponentCallbacks(callback);
	}

	@Override public void unregisterComponentCallbacks(final ComponentCallbacks callback) {
		if (SDK_INT >= ICE_CREAM_SANDWICH) mApplication.unregisterComponentCallbacks(callback);
	}

	@Override public void registerActivityLifecycleCallbacks(final ActivityLifecycleCallbacks callback) {
		if (SDK_INT >= ICE_CREAM_SANDWICH) mApplication.registerActivityLifecycleCallbacks(callback);
	}

	@Override public void unregisterActivityLifecycleCallbacks(final ActivityLifecycleCallbacks callback) {
		if (SDK_INT >= ICE_CREAM_SANDWICH) mApplication.unregisterActivityLifecycleCallbacks(callback);
	}

	@Override public void registerOnProvideAssistDataListener(final OnProvideAssistDataListener callback) {
		if (SDK_INT >= JELLY_BEAN_MR2) mApplication.registerOnProvideAssistDataListener(callback);
	}

	@Override public void unregisterOnProvideAssistDataListener(final OnProvideAssistDataListener callback) {
		if (SDK_INT >= JELLY_BEAN_MR2) mApplication.unregisterOnProvideAssistDataListener(callback);
	}

	// The actual context returned may not be semantically consistent. Let's keep an eye for it in the wild.
	@Override public Context getBaseContext() {
		mCondom.logConcern(TAG, "Application.getBaseContext");
		return super.getBaseContext();
	}

	@Override protected void attachBaseContext(final Context base) { super.attachBaseContext(base); }

	/* ****** Hooked Context APIs ****** */

	@Override public boolean bindService(final Intent intent, final ServiceConnection conn, final int flags) {
		final boolean result = mCondom.proceed(OutboundType.BIND_SERVICE, intent, Boolean.FALSE, new CondomCore.WrappedValueProcedure<Boolean>() { @Override public Boolean proceed() {
			return mApplication.bindService(intent, conn, flags);
		}});
		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, new CondomCore.WrappedValueProcedure<ComponentName>() { @Override public ComponentName proceed() {
			return mApplication.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() {
			mApplication.sendBroadcast(intent);
		}}, null);
	}

	@Override public void sendBroadcast(final Intent intent, final String receiverPermission) {
		mCondom.proceedBroadcast(this, intent, new CondomCore.WrappedProcedure() { @Override public void run() {
			mApplication.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() {
			mApplication.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() {
			mApplication.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() {
			mApplication.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() {
			mApplication.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() {
			mApplication.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() {
			mApplication.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() {
			mApplication.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() {
			mApplication.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() {
			mApplication.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 this; }

	CondomApplication(final CondomCore condom, final Application app, final @Nullable @Size(max = 13) String tag) {
		mCondom = condom;
		mApplication = app;
		TAG = CondomCore.buildLogTag("CondomApp", "CondomApp.", tag);
	}

	private final CondomCore mCondom;
	private final Application mApplication;
	private final String TAG;
}