/*
 * Copyright (C) 2020 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.devicepolicy;

import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;

import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.IBinder;
import android.os.ServiceManager;
import android.provider.Settings;
import android.provider.Telephony;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Slog;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
import android.view.inputmethod.InputMethodInfo;

import com.android.internal.R;
import com.android.server.inputmethod.InputMethodManagerInternal;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

/**
 * Utility class to find what personal apps should be suspended to limit personal device use.
 */
public class PersonalAppsSuspensionHelper {
    private static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;

    // Flags to get all packages even if the user is still locked.
    private static final int PACKAGE_QUERY_FLAGS =
            PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;

    private final Context mContext;
    private final PackageManager mPackageManager;

    /**
     * @param context Context for the user whose apps should to be suspended.
     */
    public PersonalAppsSuspensionHelper(Context context) {
        mContext = context;
        mPackageManager = context.getPackageManager();
    }

    /**
     * @return List of packages that should be suspended to limit personal use.
     */
    String[] getPersonalAppsForSuspension() {
        final List<PackageInfo> installedPackageInfos =
                mPackageManager.getInstalledPackages(PACKAGE_QUERY_FLAGS);
        final Set<String> result = new ArraySet<>();
        for (final PackageInfo packageInfo : installedPackageInfos) {
            final ApplicationInfo info = packageInfo.applicationInfo;
            if ((!info.isSystemApp() && !info.isUpdatedSystemApp())
                    || hasLauncherIntent(packageInfo.packageName)) {
                result.add(packageInfo.packageName);
            }
        }
        result.removeAll(getCriticalPackages());
        result.removeAll(getSystemLauncherPackages());
        result.removeAll(getAccessibilityServices());
        result.removeAll(getInputMethodPackages());
        result.remove(Telephony.Sms.getDefaultSmsPackage(mContext));
        result.remove(getSettingsPackageName());

        final String[] unsuspendablePackages =
                mPackageManager.getUnsuspendablePackages(result.toArray(new String[0]));
        for (final String pkg : unsuspendablePackages) {
            result.remove(pkg);
        }

        Slog.i(LOG_TAG, "Packages subject to suspension: " + String.join(",", result));
        return result.toArray(new String[0]);
    }

    private List<String> getSystemLauncherPackages() {
        final List<String> result = new ArrayList<>();
        final Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        final List<ResolveInfo> matchingActivities =
                mPackageManager.queryIntentActivities(intent, PACKAGE_QUERY_FLAGS);
        for (final ResolveInfo resolveInfo : matchingActivities) {
            if (resolveInfo.activityInfo == null
                    || TextUtils.isEmpty(resolveInfo.activityInfo.packageName)) {
                Slog.wtf(LOG_TAG, "Could not find package name for launcher app" + resolveInfo);
                continue;
            }
            final String packageName = resolveInfo.activityInfo.packageName;
            try {
                final ApplicationInfo applicationInfo =
                        mPackageManager.getApplicationInfo(packageName, PACKAGE_QUERY_FLAGS);
                if (applicationInfo.isSystemApp() || applicationInfo.isUpdatedSystemApp()) {
                    result.add(packageName);
                }
            } catch (PackageManager.NameNotFoundException e) {
                Slog.e(LOG_TAG, "Could not find application info for launcher app: " + packageName);
            }
        }
        return result;
    }

    private List<String> getAccessibilityServices() {
        final List<AccessibilityServiceInfo> accessibilityServiceInfos =
                getAccessibilityManagerForUser(mContext.getUserId())
                        .getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK);
        final List<String> result = new ArrayList<>();
        for (final AccessibilityServiceInfo serviceInfo : accessibilityServiceInfos) {
            final ComponentName componentName =
                    ComponentName.unflattenFromString(serviceInfo.getId());
            if (componentName != null) {
                result.add(componentName.getPackageName());
            }
        }
        return result;
    }

    private List<String> getInputMethodPackages() {
        final List<InputMethodInfo> enabledImes = InputMethodManagerInternal.get()
                .getEnabledInputMethodListAsUser(mContext.getUserId());
        final List<String> result = new ArrayList<>();
        for (final InputMethodInfo info : enabledImes) {
            result.add(info.getPackageName());
        }
        return result;
    }

    @Nullable
    private String getSettingsPackageName() {
        final Intent intent = new Intent(Settings.ACTION_SETTINGS);
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        final ResolveInfo resolveInfo =
                mPackageManager.resolveActivity(intent, PACKAGE_QUERY_FLAGS);
        if (resolveInfo != null) {
            return resolveInfo.activityInfo.packageName;
        }
        return null;
    }

    private List<String> getCriticalPackages() {
        return Arrays.asList(mContext.getResources()
                .getStringArray(R.array.config_packagesExemptFromSuspension));
    }

    private boolean hasLauncherIntent(String packageName) {
        final Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
        intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
        intentToResolve.setPackage(packageName);
        final List<ResolveInfo> resolveInfos =
                mPackageManager.queryIntentActivities(intentToResolve, PACKAGE_QUERY_FLAGS);
        return resolveInfos != null && !resolveInfos.isEmpty();
    }

    private AccessibilityManager getAccessibilityManagerForUser(int userId) {
        final IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
        final IAccessibilityManager service =
                iBinder == null ? null : IAccessibilityManager.Stub.asInterface(iBinder);
        return new AccessibilityManager(mContext, service, userId);
    }
}
