/*
 * Copyright (C) 2014 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 com.android.server.restrictions;

import android.app.AppGlobals;
import android.app.admin.IDevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IRestrictionsManager;
import android.content.RestrictionsManager;
import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.IUserManager;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;

import com.android.internal.util.ArrayUtils;
import com.android.server.SystemService;

/**
 * SystemService wrapper for the RestrictionsManager implementation. Publishes the
 * Context.RESTRICTIONS_SERVICE.
 */
public final class RestrictionsManagerService extends SystemService {

    static final String LOG_TAG = "RestrictionsManagerService";
    static final boolean DEBUG = false;

    private final RestrictionsManagerImpl mRestrictionsManagerImpl;

    public RestrictionsManagerService(Context context) {
        super(context);
        mRestrictionsManagerImpl = new RestrictionsManagerImpl(context);
    }

    @Override
    public void onStart() {
        publishBinderService(Context.RESTRICTIONS_SERVICE, mRestrictionsManagerImpl);
    }

    class RestrictionsManagerImpl extends IRestrictionsManager.Stub {
        final Context mContext;
        private final IUserManager mUm;
        private final IDevicePolicyManager mDpm;

        public RestrictionsManagerImpl(Context context) {
            mContext = context;
            mUm = (IUserManager) getBinderService(Context.USER_SERVICE);
            mDpm = (IDevicePolicyManager) getBinderService(Context.DEVICE_POLICY_SERVICE);
        }

        @Override
        public Bundle getApplicationRestrictions(String packageName) throws RemoteException {
            return mUm.getApplicationRestrictions(packageName);
        }

        @Override
        public boolean hasRestrictionsProvider() throws RemoteException {
            int userHandle = UserHandle.getCallingUserId();
            if (mDpm != null) {
                long ident = Binder.clearCallingIdentity();
                try {
                    return mDpm.getRestrictionsProvider(userHandle) != null;
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
            } else {
                return false;
            }
        }

        @Override
        public void requestPermission(final String packageName, final String requestType,
                final String requestId,
                final PersistableBundle requestData) throws RemoteException {
            if (DEBUG) {
                Log.i(LOG_TAG, "requestPermission");
            }
            int callingUid = Binder.getCallingUid();
            int userHandle = UserHandle.getUserId(callingUid);
            if (mDpm != null) {
                long ident = Binder.clearCallingIdentity();
                try {
                    ComponentName restrictionsProvider =
                            mDpm.getRestrictionsProvider(userHandle);
                    // Check if there is a restrictions provider
                    if (restrictionsProvider == null) {
                        throw new IllegalStateException(
                            "Cannot request permission without a restrictions provider registered");
                    }
                    // Check that the packageName matches the caller.
                    enforceCallerMatchesPackage(callingUid, packageName, "Package name does not" +
                            " match caller ");
                    // Prepare and broadcast the intent to the provider
                    Intent intent = new Intent(RestrictionsManager.ACTION_REQUEST_PERMISSION);
                    intent.setComponent(restrictionsProvider);
                    intent.putExtra(RestrictionsManager.EXTRA_PACKAGE_NAME, packageName);
                    intent.putExtra(RestrictionsManager.EXTRA_REQUEST_TYPE, requestType);
                    intent.putExtra(RestrictionsManager.EXTRA_REQUEST_ID, requestId);
                    intent.putExtra(RestrictionsManager.EXTRA_REQUEST_BUNDLE, requestData);
                    mContext.sendBroadcastAsUser(intent, new UserHandle(userHandle));
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
            }
        }

        @Override
        public Intent createLocalApprovalIntent() throws RemoteException {
            if (DEBUG) {
                Log.i(LOG_TAG, "requestPermission");
            }
            final int userHandle = UserHandle.getCallingUserId();
            if (mDpm != null) {
                long ident = Binder.clearCallingIdentity();
                try {
                    ComponentName restrictionsProvider =
                            mDpm.getRestrictionsProvider(userHandle);
                    // Check if there is a restrictions provider
                    if (restrictionsProvider == null) {
                        throw new IllegalStateException(
                            "Cannot request permission without a restrictions provider registered");
                    }
                    String providerPackageName = restrictionsProvider.getPackageName();
                    Intent intent = new Intent(RestrictionsManager.ACTION_REQUEST_LOCAL_APPROVAL);
                    intent.setPackage(providerPackageName);
                    ResolveInfo ri = AppGlobals.getPackageManager().resolveIntent(intent,
                            null /* resolvedType */, 0 /* flags */, userHandle);
                    if (ri != null && ri.activityInfo != null && ri.activityInfo.exported) {
                        intent.setComponent(new ComponentName(ri.activityInfo.packageName,
                                ri.activityInfo.name));
                        return intent;
                    }
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
            }
            return null;
        }

        @Override
        public void notifyPermissionResponse(String packageName, PersistableBundle response)
                throws RemoteException {
            // Check caller
            int callingUid = Binder.getCallingUid();
            int userHandle = UserHandle.getUserId(callingUid);
            if (mDpm != null) {
                long ident = Binder.clearCallingIdentity();
                try {
                    ComponentName permProvider = mDpm.getRestrictionsProvider(userHandle);
                    if (permProvider == null) {
                        throw new SecurityException("No restrictions provider registered for user");
                    }
                    enforceCallerMatchesPackage(callingUid, permProvider.getPackageName(),
                            "Restrictions provider does not match caller ");

                    // Post the response to target package
                    Intent responseIntent = new Intent(
                            RestrictionsManager.ACTION_PERMISSION_RESPONSE_RECEIVED);
                    responseIntent.setPackage(packageName);
                    responseIntent.putExtra(RestrictionsManager.EXTRA_RESPONSE_BUNDLE, response);
                    mContext.sendBroadcastAsUser(responseIntent, new UserHandle(userHandle));
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
            }
        }

        private void enforceCallerMatchesPackage(int callingUid, String packageName,
                String message) {
            try {
                String[] pkgs = AppGlobals.getPackageManager().getPackagesForUid(callingUid);
                if (pkgs != null) {
                    if (!ArrayUtils.contains(pkgs, packageName)) {
                        throw new SecurityException(message + callingUid);
                    }
                }
            } catch (RemoteException re) {
                // Shouldn't happen
            }
        }
    }
}
