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