blob: c28655655765ad5c0553ce0ca5a0004bea7fb238 [file] [log] [blame]
/*
* Copyright (C) 2021 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.server.am;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import android.annotation.Nullable;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import java.util.function.BiConsumer;
/**
* List of keys that have expiration time.
* If the expiration time is less than current elapsedRealtime, the key has expired.
* Otherwise it is valid (or allowed).
*
* <p>This is used for both FGS-BG-start restriction, and FGS-while-in-use permissions check.</p>
*
* <p>Note: the underlying data structure is an {@link SparseArray}, for performance reason,
* it is only suitable to hold up to hundreds of entries.</p>
* @param <E> type of the additional optional info.
*/
public class FgsTempAllowList<E> {
private static final int DEFAULT_MAX_SIZE = 100;
/**
* The value is Pair type, Pair.first is the expirationTime(an elapsedRealtime),
* Pair.second is the optional information entry about this key.
*/
private final SparseArray<Pair<Long, E>> mTempAllowList = new SparseArray<>();
private int mMaxSize = DEFAULT_MAX_SIZE;
private final Object mLock = new Object();
public FgsTempAllowList() {
}
/**
*
* @param maxSize The max size of the list. It is only a suggestion. If the list size is
* larger than max size, a warning message is printed in logcat, new entry can
* still be added to the list. The default max size is {@link #DEFAULT_MAX_SIZE}.
*/
public FgsTempAllowList(int maxSize) {
if (maxSize <= 0) {
Slog.e(TAG_AM, "Invalid FgsTempAllowList maxSize:" + maxSize
+ ", force default maxSize:" + DEFAULT_MAX_SIZE);
mMaxSize = DEFAULT_MAX_SIZE;
} else {
mMaxSize = maxSize;
}
}
/**
* Add a key and its duration with optional info into the temp allowlist.
* @param durationMs temp-allowlisted duration in milliseconds.
* @param entry additional optional information of this key, could be null.
*/
public void add(int uid, long durationMs, @Nullable E entry) {
synchronized (mLock) {
if (durationMs <= 0) {
Slog.e(TAG_AM, "FgsTempAllowList bad duration:" + durationMs + " key: "
+ uid);
return;
}
// The temp allowlist should be a short list with only a few entries in it.
// for a very large list, HashMap structure should be used.
final long now = SystemClock.elapsedRealtime();
final int size = mTempAllowList.size();
if (size > mMaxSize) {
Slog.w(TAG_AM, "FgsTempAllowList length:" + size + " exceeds maxSize"
+ mMaxSize);
for (int index = size - 1; index >= 0; index--) {
if (mTempAllowList.valueAt(index).first < now) {
mTempAllowList.removeAt(index);
}
}
}
final Pair<Long, E> existing = mTempAllowList.get(uid);
final long expirationTime = now + durationMs;
if (existing == null || existing.first < expirationTime) {
mTempAllowList.put(uid, new Pair(expirationTime, entry));
}
}
}
/**
* If the key has not expired (AKA allowed), return its non-null value.
* If the key has expired, return null.
* @return
*/
@Nullable
public Pair<Long, E> get(int uid) {
synchronized (mLock) {
final int index = mTempAllowList.indexOfKey(uid);
if (index < 0) {
return null;
} else if (mTempAllowList.valueAt(index).first < SystemClock.elapsedRealtime()) {
mTempAllowList.removeAt(index);
return null;
} else {
return mTempAllowList.valueAt(index);
}
}
}
/**
* If the key has not expired (AKA allowed), return true.
* If the key has expired, return false.
*/
public boolean isAllowed(int uid) {
Pair<Long, E> entry = get(uid);
return entry != null;
}
/**
* Remove a given UID.
*/
public void removeUid(int uid) {
synchronized (mLock) {
mTempAllowList.remove(uid);
}
}
/**
* Remove by appId.
*/
public void removeAppId(int appId) {
synchronized (mLock) {
// Find all UIDs matching the appId.
for (int i = mTempAllowList.size() - 1; i >= 0; i--) {
final int uid = mTempAllowList.keyAt(i);
if (UserHandle.getAppId(uid) == appId) {
mTempAllowList.removeAt(i);
}
}
}
}
/**
* Iterate over the entries.
*/
public void forEach(BiConsumer<Integer, Pair<Long, E>> callback) {
synchronized (mLock) {
for (int i = 0; i < mTempAllowList.size(); i++) {
final int uid = mTempAllowList.keyAt(i);
final Pair<Long, E> entry = mTempAllowList.valueAt(i);
if (entry != null) {
callback.accept(uid, entry);
}
}
}
}
}