/* * 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 mForegroundUidPids = new SparseArray(); private final SparseArray> mBackgroundCallbacks = new SparseArray>(); 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 callbacks = mBackgroundCallbacks.get(uid, new ArrayList()); 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 getForegroundUids() { ArrayList uids = new ArrayList(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 pendingCallbacks = null; synchronized (mLock) { List callbacks = mBackgroundCallbacks.get(uid); if (callbacks != null) { pendingCallbacks = new ArrayList(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> getBackgroundCallbacks() { return mBackgroundCallbacks; } @VisibleForTesting public void clearForegroundlist() { mForegroundUids.clear(); } }