/*
 * 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.nfc;

import android.app.ActivityManager;
import android.sysprop.NfcProperties;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;

import java.util.ArrayList;
import java.util.List;
import androidx.annotation.VisibleForTesting;

public class ForegroundUtils implements ActivityManager.OnUidImportanceListener {
    static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
    private final String TAG = "ForegroundUtils";
    private final ActivityManager mActivityManager;

    private final Object mLock = new Object();
    // We need to keep track of the individual PIDs per UID,
    // since a single UID may have multiple processes running
    // that transition into foreground/background state.
    private final SparseArray<SparseBooleanArray> mForegroundUidPids =
            new SparseArray<SparseBooleanArray>();
    private final SparseArray<List<Callback>> mBackgroundCallbacks =
            new SparseArray<List<Callback>>();

    private final SparseBooleanArray mForegroundUids = new SparseBooleanArray();

    private static class Singleton {
        private static ForegroundUtils sInstance = null;
    }

    @VisibleForTesting
    public ForegroundUtils(ActivityManager am) {
        mActivityManager = am;
        try {
            mActivityManager.addOnUidImportanceListener(this,
                    ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
        } catch (Exception e) {
            // Should not happen!
            Log.e(TAG, "ForegroundUtils: could not register UidImportanceListener");
        }
    }

    public interface Callback {
        void onUidToBackground(int uid);
    }

    /**
     * Get an instance of the ForegroundUtils sinleton
     *
     * @param am The ActivityManager instance for initialization
     * @return the instance
     */
    public static ForegroundUtils getInstance(ActivityManager am) {
        if (Singleton.sInstance == null) {
            Singleton.sInstance = new ForegroundUtils(am);
        }
        return Singleton.sInstance;
    }

    /**
     * Checks whether the specified UID has any activities running in the foreground,
     * and if it does, registers a callback for when that UID no longer has any foreground
     * activities. This is done atomically, so callers can be ensured that they will
     * get a callback if this method returns true.
     *
     * @param callback Callback to be called
     * @param uid The UID to be checked
     * @return true when the UID has an Activity in the foreground and the callback
     * , false otherwise
     */
    public boolean registerUidToBackgroundCallback(Callback callback, int uid) {
        synchronized (mLock) {
            if (!isInForegroundLocked(uid)) {
                return false;
            }
            // This uid is in the foreground; register callback for when it moves
            // into the background.
            List<Callback> callbacks = mBackgroundCallbacks.get(uid, new ArrayList<Callback>());
            callbacks.add(callback);
            mBackgroundCallbacks.put(uid, callbacks);
            return true;
        }
    }

    /**
     * @param uid The UID to be checked
     * @return whether the UID has any activities running in the foreground
     */
    public boolean isInForeground(int uid) {
        synchronized (mLock) {
            return isInForegroundLocked(uid);
        }
    }

    /**
     * @return a list of UIDs currently in the foreground, or an empty list
     *         if none are found.
     */
    public List<Integer> getForegroundUids() {
        ArrayList<Integer> uids = new ArrayList<Integer>(mForegroundUids.size());
        synchronized (mLock) {
            for (int i = 0; i < mForegroundUids.size(); i++) {
                if (mForegroundUids.valueAt(i)) {
                    uids.add(mForegroundUids.keyAt(i));
                }
            }
        }
        return uids;
    }

    private boolean isInForegroundLocked(int uid) {
        if (mForegroundUids.get(uid)) {
            return true;
        }
        if (DBG) Log.d(TAG, "Checking UID:" + Integer.toString(uid));
        // If the onForegroundActivitiesChanged() has not yet been called,
        // check whether the UID is in an active state to use the NFC.
        return (mActivityManager.getUidImportance(uid)
                == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
    }

    private void handleUidToBackground(int uid) {
        ArrayList<Callback> pendingCallbacks = null;
        synchronized (mLock) {
            List<Callback> callbacks = mBackgroundCallbacks.get(uid);
            if (callbacks != null) {
                pendingCallbacks = new ArrayList<Callback>(callbacks);
                // Only call them once
                mBackgroundCallbacks.remove(uid);
            }
        }
        // Release lock for callbacks
        if (pendingCallbacks != null) {
            for (Callback callback : pendingCallbacks) {
                callback.onUidToBackground(uid);
            }
        }
    }

    @Override
    public void onUidImportance(int uid, int importance) {
        boolean uidToBackground = false;
        synchronized (mLock) {
            if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE) {
                mForegroundUids.delete(uid);
                mBackgroundCallbacks.remove(uid);
                if (DBG) Log.d(TAG, "UID: " + Integer.toString(uid) + " deleted.");
                return;
            }
            if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                mForegroundUids.put(uid, true);
            } else {
                if (mForegroundUids.get(uid)) {
                    uidToBackground = true;
                    mForegroundUids.put(uid, false);
                }
            }
        }
        if (uidToBackground) {
            handleUidToBackground(uid);
        }
        if (DBG) {
            Log.d(TAG, "Foreground UID status:");
            synchronized (mLock) {
                for (int j = 0; j < mForegroundUids.size(); j++) {
                    Log.d(TAG, "UID: " + Integer.toString(mForegroundUids.keyAt(j))
                            + " is in foreground: " + Boolean.toString(mForegroundUids.valueAt(j)));
                }
            }
        }
    }
    @VisibleForTesting
    public SparseArray<List<Callback>> getBackgroundCallbacks() {
        return mBackgroundCallbacks;
    }

    @VisibleForTesting
    public void clearForegroundlist() {
        mForegroundUids.clear();
    }
}
