1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.nfc; 17 18 import android.app.ActivityManager; 19 import android.sysprop.NfcProperties; 20 import android.util.Log; 21 import android.util.SparseArray; 22 import android.util.SparseBooleanArray; 23 24 import java.util.ArrayList; 25 import java.util.List; 26 import androidx.annotation.VisibleForTesting; 27 28 public class ForegroundUtils implements ActivityManager.OnUidImportanceListener { 29 static final boolean DBG = NfcProperties.debug_enabled().orElse(true); 30 private final String TAG = "ForegroundUtils"; 31 private final ActivityManager mActivityManager; 32 33 private final Object mLock = new Object(); 34 // We need to keep track of the individual PIDs per UID, 35 // since a single UID may have multiple processes running 36 // that transition into foreground/background state. 37 private final SparseArray<SparseBooleanArray> mForegroundUidPids = 38 new SparseArray<SparseBooleanArray>(); 39 private final SparseArray<List<Callback>> mBackgroundCallbacks = 40 new SparseArray<List<Callback>>(); 41 42 private final SparseBooleanArray mForegroundUids = new SparseBooleanArray(); 43 44 private static class Singleton { 45 private static ForegroundUtils sInstance = null; 46 } 47 48 @VisibleForTesting ForegroundUtils(ActivityManager am)49 public ForegroundUtils(ActivityManager am) { 50 mActivityManager = am; 51 try { 52 mActivityManager.addOnUidImportanceListener(this, 53 ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND); 54 } catch (Exception e) { 55 // Should not happen! 56 Log.e(TAG, "ForegroundUtils: could not register UidImportanceListener"); 57 } 58 } 59 60 public interface Callback { onUidToBackground(int uid)61 void onUidToBackground(int uid); 62 } 63 64 /** 65 * Get an instance of the ForegroundUtils sinleton 66 * 67 * @param am The ActivityManager instance for initialization 68 * @return the instance 69 */ getInstance(ActivityManager am)70 public static ForegroundUtils getInstance(ActivityManager am) { 71 if (Singleton.sInstance == null) { 72 Singleton.sInstance = new ForegroundUtils(am); 73 } 74 return Singleton.sInstance; 75 } 76 77 /** 78 * Checks whether the specified UID has any activities running in the foreground, 79 * and if it does, registers a callback for when that UID no longer has any foreground 80 * activities. This is done atomically, so callers can be ensured that they will 81 * get a callback if this method returns true. 82 * 83 * @param callback Callback to be called 84 * @param uid The UID to be checked 85 * @return true when the UID has an Activity in the foreground and the callback 86 * , false otherwise 87 */ registerUidToBackgroundCallback(Callback callback, int uid)88 public boolean registerUidToBackgroundCallback(Callback callback, int uid) { 89 synchronized (mLock) { 90 if (!isInForegroundLocked(uid)) { 91 return false; 92 } 93 // This uid is in the foreground; register callback for when it moves 94 // into the background. 95 List<Callback> callbacks = mBackgroundCallbacks.get(uid, new ArrayList<Callback>()); 96 callbacks.add(callback); 97 mBackgroundCallbacks.put(uid, callbacks); 98 return true; 99 } 100 } 101 102 /** 103 * @param uid The UID to be checked 104 * @return whether the UID has any activities running in the foreground 105 */ isInForeground(int uid)106 public boolean isInForeground(int uid) { 107 synchronized (mLock) { 108 return isInForegroundLocked(uid); 109 } 110 } 111 112 /** 113 * @return a list of UIDs currently in the foreground, or an empty list 114 * if none are found. 115 */ getForegroundUids()116 public List<Integer> getForegroundUids() { 117 ArrayList<Integer> uids = new ArrayList<Integer>(mForegroundUids.size()); 118 synchronized (mLock) { 119 for (int i = 0; i < mForegroundUids.size(); i++) { 120 if (mForegroundUids.valueAt(i)) { 121 uids.add(mForegroundUids.keyAt(i)); 122 } 123 } 124 } 125 return uids; 126 } 127 isInForegroundLocked(int uid)128 private boolean isInForegroundLocked(int uid) { 129 if (mForegroundUids.get(uid)) { 130 return true; 131 } 132 if (DBG) Log.d(TAG, "Checking UID:" + Integer.toString(uid)); 133 // If the onForegroundActivitiesChanged() has not yet been called, 134 // check whether the UID is in an active state to use the NFC. 135 return (mActivityManager.getUidImportance(uid) 136 == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND); 137 } 138 handleUidToBackground(int uid)139 private void handleUidToBackground(int uid) { 140 ArrayList<Callback> pendingCallbacks = null; 141 synchronized (mLock) { 142 List<Callback> callbacks = mBackgroundCallbacks.get(uid); 143 if (callbacks != null) { 144 pendingCallbacks = new ArrayList<Callback>(callbacks); 145 // Only call them once 146 mBackgroundCallbacks.remove(uid); 147 } 148 } 149 // Release lock for callbacks 150 if (pendingCallbacks != null) { 151 for (Callback callback : pendingCallbacks) { 152 callback.onUidToBackground(uid); 153 } 154 } 155 } 156 157 @Override onUidImportance(int uid, int importance)158 public void onUidImportance(int uid, int importance) { 159 boolean uidToBackground = false; 160 synchronized (mLock) { 161 if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE) { 162 mForegroundUids.delete(uid); 163 mBackgroundCallbacks.remove(uid); 164 if (DBG) Log.d(TAG, "UID: " + Integer.toString(uid) + " deleted."); 165 return; 166 } 167 if (importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { 168 mForegroundUids.put(uid, true); 169 } else { 170 if (mForegroundUids.get(uid)) { 171 uidToBackground = true; 172 mForegroundUids.put(uid, false); 173 } 174 } 175 } 176 if (uidToBackground) { 177 handleUidToBackground(uid); 178 } 179 if (DBG) { 180 Log.d(TAG, "Foreground UID status:"); 181 synchronized (mLock) { 182 for (int j = 0; j < mForegroundUids.size(); j++) { 183 Log.d(TAG, "UID: " + Integer.toString(mForegroundUids.keyAt(j)) 184 + " is in foreground: " + Boolean.toString(mForegroundUids.valueAt(j))); 185 } 186 } 187 } 188 } 189 @VisibleForTesting getBackgroundCallbacks()190 public SparseArray<List<Callback>> getBackgroundCallbacks() { 191 return mBackgroundCallbacks; 192 } 193 194 @VisibleForTesting clearForegroundlist()195 public void clearForegroundlist() { 196 mForegroundUids.clear(); 197 } 198 } 199