• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 
17 package com.android.server.am;
18 
19 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
20 
21 import android.annotation.Nullable;
22 import android.os.SystemClock;
23 import android.os.UserHandle;
24 import android.util.Pair;
25 import android.util.Slog;
26 import android.util.SparseArray;
27 
28 import java.util.function.BiConsumer;
29 
30 /**
31  * List of keys that have expiration time.
32  * If the expiration time is less than current elapsedRealtime, the key has expired.
33  * Otherwise it is valid (or allowed).
34  *
35  * <p>This is used for both FGS-BG-start restriction, and FGS-while-in-use permissions check.</p>
36  *
37  * <p>Note: the underlying data structure is an {@link SparseArray}, for performance reason,
38  * it is only suitable to hold up to hundreds of entries.</p>
39  * @param <E> type of the additional optional info.
40  */
41 public class FgsTempAllowList<E> {
42     private static final int DEFAULT_MAX_SIZE = 100;
43 
44     /**
45      * The value is Pair type, Pair.first is the expirationTime(an elapsedRealtime),
46      * Pair.second is the optional information entry about this key.
47      */
48     private final SparseArray<Pair<Long, E>> mTempAllowList = new SparseArray<>();
49     private int mMaxSize = DEFAULT_MAX_SIZE;
50     private final Object mLock = new Object();
51 
FgsTempAllowList()52     public FgsTempAllowList() {
53     }
54 
55     /**
56      *
57      * @param maxSize The max size of the list. It is only a suggestion. If the list size is
58      *                larger than max size, a warning message is printed in logcat, new entry can
59      *                still be added to the list. The default max size is {@link #DEFAULT_MAX_SIZE}.
60      */
FgsTempAllowList(int maxSize)61     public FgsTempAllowList(int maxSize) {
62         if (maxSize <= 0) {
63             Slog.e(TAG_AM, "Invalid FgsTempAllowList maxSize:" + maxSize
64                     + ", force default maxSize:" + DEFAULT_MAX_SIZE);
65             mMaxSize = DEFAULT_MAX_SIZE;
66         } else {
67             mMaxSize = maxSize;
68         }
69     }
70 
71     /**
72      * Add a key and its duration with optional info into the temp allowlist.
73      * @param durationMs temp-allowlisted duration in milliseconds.
74      * @param entry additional optional information of this key, could be null.
75      */
add(int uid, long durationMs, @Nullable E entry)76     public void add(int uid, long durationMs, @Nullable E entry) {
77         synchronized (mLock) {
78             if (durationMs <= 0) {
79                 Slog.e(TAG_AM, "FgsTempAllowList bad duration:" + durationMs + " key: "
80                         + uid);
81                 return;
82             }
83             // The temp allowlist should be a short list with only a few entries in it.
84             // for a very large list, HashMap structure should be used.
85             final long now = SystemClock.elapsedRealtime();
86             final int size = mTempAllowList.size();
87             if (size > mMaxSize) {
88                 Slog.w(TAG_AM, "FgsTempAllowList length:" + size + " exceeds maxSize"
89                         + mMaxSize);
90                 for (int index = size - 1; index >= 0; index--) {
91                     if (mTempAllowList.valueAt(index).first < now) {
92                         mTempAllowList.removeAt(index);
93                     }
94                 }
95             }
96             final Pair<Long, E> existing = mTempAllowList.get(uid);
97             final long expirationTime = now + durationMs;
98             if (existing == null || existing.first < expirationTime) {
99                 mTempAllowList.put(uid, new Pair(expirationTime, entry));
100             }
101         }
102     }
103 
104     /**
105      * If the key has not expired (AKA allowed), return its non-null value.
106      * If the key has expired, return null.
107      * @return
108      */
109     @Nullable
get(int uid)110     public Pair<Long, E> get(int uid) {
111         synchronized (mLock) {
112             final int index = mTempAllowList.indexOfKey(uid);
113             if (index < 0) {
114                 return null;
115             } else if (mTempAllowList.valueAt(index).first < SystemClock.elapsedRealtime()) {
116                 mTempAllowList.removeAt(index);
117                 return null;
118             } else {
119                 return mTempAllowList.valueAt(index);
120             }
121         }
122     }
123 
124     /**
125      * If the key has not expired (AKA allowed), return true.
126      * If the key has expired, return false.
127      */
isAllowed(int uid)128     public boolean isAllowed(int uid) {
129         Pair<Long, E> entry = get(uid);
130         return entry != null;
131     }
132 
133     /**
134      * Remove a given UID.
135      */
removeUid(int uid)136     public void removeUid(int uid) {
137         synchronized (mLock) {
138             mTempAllowList.remove(uid);
139         }
140     }
141 
142     /**
143      * Remove by appId.
144      */
removeAppId(int appId)145     public void removeAppId(int appId) {
146         synchronized (mLock) {
147             // Find all UIDs matching the appId.
148             for (int i = mTempAllowList.size() - 1; i >= 0; i--) {
149                 final int uid = mTempAllowList.keyAt(i);
150                 if (UserHandle.getAppId(uid) == appId) {
151                     mTempAllowList.removeAt(i);
152                 }
153             }
154         }
155     }
156 
157     /**
158      * Iterate over the entries.
159      */
forEach(BiConsumer<Integer, Pair<Long, E>> callback)160     public void forEach(BiConsumer<Integer, Pair<Long, E>> callback) {
161         synchronized (mLock) {
162             for (int i = 0; i < mTempAllowList.size(); i++) {
163                 final int uid = mTempAllowList.keyAt(i);
164                 final Pair<Long, E> entry = mTempAllowList.valueAt(i);
165                 if (entry != null) {
166                     callback.accept(uid, entry);
167                 }
168             }
169         }
170     }
171 }
172