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 */
16package com.android.nfc;
17
18import java.util.ArrayList;
19import java.util.List;
20
21import android.app.ActivityManagerNative;
22import android.app.IActivityManager;
23import android.app.IProcessObserver;
24import android.os.RemoteException;
25import android.util.Log;
26import android.util.SparseArray;
27import android.util.SparseBooleanArray;
28
29public class ForegroundUtils extends IProcessObserver.Stub {
30    static final boolean DBG = false;
31    private final String TAG = "ForegroundUtils";
32    private final IActivityManager mIActivityManager;
33
34    private final Object mLock = new Object();
35    // We need to keep track of the individual PIDs per UID,
36    // since a single UID may have multiple processes running
37    // that transition into foreground/background state.
38    private final SparseArray<SparseBooleanArray> mForegroundUidPids =
39            new SparseArray<SparseBooleanArray>();
40    private final SparseArray<List<Callback>> mBackgroundCallbacks =
41            new SparseArray<List<Callback>>();
42
43    private static class Singleton {
44        private static final ForegroundUtils INSTANCE = new ForegroundUtils();
45    }
46
47    private ForegroundUtils() {
48        mIActivityManager = ActivityManagerNative.getDefault();
49        try {
50            mIActivityManager.registerProcessObserver(this);
51        } catch (RemoteException e) {
52            // Should not happen!
53            Log.e(TAG, "ForegroundUtils: could not get IActivityManager");
54        }
55    }
56
57    public interface Callback {
58        void onUidToBackground(int uid);
59    }
60
61    public static ForegroundUtils getInstance() {
62        return Singleton.INSTANCE;
63    }
64
65    /**
66     * Checks whether the specified UID has any activities running in the foreground,
67     * and if it does, registers a callback for when that UID no longer has any foreground
68     * activities. This is done atomically, so callers can be ensured that they will
69     * get a callback if this method returns true.
70     *
71     * @param callback Callback to be called
72     * @param uid The UID to be checked
73     * @return true when the UID has an Activity in the foreground and the callback
74     * , false otherwise
75     */
76    public boolean registerUidToBackgroundCallback(Callback callback, int uid) {
77        synchronized (mLock) {
78            if (!isInForegroundLocked(uid)) {
79                return false;
80            }
81            // This uid is in the foreground; register callback for when it moves
82            // into the background.
83            List<Callback> callbacks = mBackgroundCallbacks.get(uid, new ArrayList<Callback>());
84            callbacks.add(callback);
85            mBackgroundCallbacks.put(uid, callbacks);
86            return true;
87        }
88    }
89
90    /**
91     * @param uid The UID to be checked
92     * @return whether the UID has any activities running in the foreground
93     */
94    public boolean isInForeground(int uid) {
95        synchronized (mLock) {
96            return isInForegroundLocked(uid);
97        }
98    }
99
100    /**
101     * @return a list of UIDs currently in the foreground, or an empty list
102     *         if none are found.
103     */
104    public List<Integer> getForegroundUids() {
105        ArrayList<Integer> uids = new ArrayList<Integer>(mForegroundUidPids.size());
106        synchronized (mLock) {
107            for (int i = 0; i < mForegroundUidPids.size(); i++) {
108                uids.add(mForegroundUidPids.keyAt(i));
109            }
110        }
111        return uids;
112    }
113
114    private boolean isInForegroundLocked(int uid) {
115        return mForegroundUidPids.get(uid) != null;
116    }
117
118    private void handleUidToBackground(int uid) {
119        ArrayList<Callback> pendingCallbacks = null;
120        synchronized (mLock) {
121            List<Callback> callbacks = mBackgroundCallbacks.get(uid);
122            if (callbacks != null) {
123                pendingCallbacks = new ArrayList<Callback>(callbacks);
124                // Only call them once
125                mBackgroundCallbacks.remove(uid);
126            }
127        }
128        // Release lock for callbacks
129        if (pendingCallbacks != null) {
130            for (Callback callback : pendingCallbacks) {
131                callback.onUidToBackground(uid);
132            }
133        }
134    }
135
136    @Override
137    public void onForegroundActivitiesChanged(int pid, int uid,
138            boolean hasForegroundActivities) throws RemoteException {
139        boolean uidToBackground = false;
140        synchronized (mLock) {
141            SparseBooleanArray foregroundPids = mForegroundUidPids.get(uid,
142                    new SparseBooleanArray());
143            if (hasForegroundActivities) {
144               foregroundPids.put(pid, true);
145            } else {
146               foregroundPids.delete(pid);
147            }
148            if (foregroundPids.size() == 0) {
149                mForegroundUidPids.remove(uid);
150                uidToBackground = true;
151            } else {
152                mForegroundUidPids.put(uid, foregroundPids);
153            }
154        }
155        if (uidToBackground) {
156            handleUidToBackground(uid);
157        }
158        if (DBG) {
159            if (DBG) Log.d(TAG, "Foreground changed, PID: " + Integer.toString(pid) + " UID: " +
160                                    Integer.toString(uid) + " foreground: " +
161                                    hasForegroundActivities);
162            synchronized (mLock) {
163                Log.d(TAG, "Foreground UID/PID combinations:");
164                for (int i = 0; i < mForegroundUidPids.size(); i++) {
165                    int foregroundUid = mForegroundUidPids.keyAt(i);
166                    SparseBooleanArray foregroundPids = mForegroundUidPids.get(foregroundUid);
167                    if (foregroundPids.size() == 0) {
168                        Log.e(TAG, "No PIDS associated with foreground UID!");
169                    }
170                    for (int j = 0; j < foregroundPids.size(); j++)
171                        Log.d(TAG, "UID: " + Integer.toString(foregroundUid) + " PID: " +
172                                Integer.toString(foregroundPids.keyAt(j)));
173                }
174            }
175        }
176    }
177
178
179    @Override
180    public void onProcessDied(int pid, int uid) throws RemoteException {
181        if (DBG) Log.d(TAG, "Process died; UID " + Integer.toString(uid) + " PID " +
182                Integer.toString(pid));
183        onForegroundActivitiesChanged(pid, uid, false);
184    }
185
186    @Override
187    public void onProcessStateChanged(int pid, int uid, int procState)
188            throws RemoteException {
189        // Don't care
190    }
191}
192