blob: 58536675519194323e32bee36d3322f897ca852b [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.tare;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.IUidObserver;
import android.app.UidObserver;
import android.os.RemoteException;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArrayMap;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** Modifier that makes things more cheaper based on an app's process state. */
class ProcessStateModifier extends Modifier {
private static final String TAG = "TARE-" + ProcessStateModifier.class.getSimpleName();
private static final int PROC_STATE_BUCKET_NONE = 0;
private static final int PROC_STATE_BUCKET_TOP = 1;
private static final int PROC_STATE_BUCKET_FGS = 2;
private static final int PROC_STATE_BUCKET_BFGS = 3;
private static final int PROC_STATE_BUCKET_BG = 4;
@IntDef(prefix = {"PROC_STATE_BUCKET_"}, value = {
PROC_STATE_BUCKET_NONE,
PROC_STATE_BUCKET_TOP,
PROC_STATE_BUCKET_FGS,
PROC_STATE_BUCKET_BFGS,
PROC_STATE_BUCKET_BG
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProcStateBucket {
}
private final Object mLock = new Object();
private final InternalResourceService mIrs;
/** Cached mapping of userId+package to their UIDs (for all users) */
private final SparseArrayMap<String, Integer> mPackageToUidCache = new SparseArrayMap<>();
@GuardedBy("mLock")
private final SparseIntArray mUidProcStateBucketCache = new SparseIntArray();
private final IUidObserver mUidObserver = new UidObserver() {
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) {
final int newBucket = getProcStateBucket(procState);
synchronized (mLock) {
final int curBucket = mUidProcStateBucketCache.get(uid);
if (curBucket != newBucket) {
mUidProcStateBucketCache.put(uid, newBucket);
}
notifyStateChangedLocked(uid);
}
}
@Override
public void onUidGone(int uid, boolean disabled) {
synchronized (mLock) {
if (mUidProcStateBucketCache.indexOfKey(uid) < 0) {
Slog.e(TAG, "UID " + uid + " marked gone but wasn't in cache.");
return;
}
mUidProcStateBucketCache.delete(uid);
notifyStateChangedLocked(uid);
}
}
};
ProcessStateModifier(@NonNull InternalResourceService irs) {
super();
mIrs = irs;
}
@Override
@GuardedBy("mLock")
void setup() {
try {
ActivityManager.getService().registerUidObserver(mUidObserver,
ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE,
ActivityManager.PROCESS_STATE_UNKNOWN, null);
} catch (RemoteException e) {
// ignored; both services live in system_server
}
}
@Override
@GuardedBy("mLock")
void tearDown() {
try {
ActivityManager.getService().unregisterUidObserver(mUidObserver);
} catch (RemoteException e) {
// ignored; both services live in system_server
}
mPackageToUidCache.clear();
mUidProcStateBucketCache.clear();
}
/**
* Get the final modified price based on an app's process state.
*
* @param ctp Cost to produce. @see EconomicPolicy.Action#costToProduce
* @param price Current price
*/
long getModifiedPrice(final int userId, @NonNull final String pkgName,
final long ctp, final long price) {
final int procState;
synchronized (mLock) {
procState = mUidProcStateBucketCache.get(
mIrs.getUid(userId, pkgName), PROC_STATE_BUCKET_NONE);
}
switch (procState) {
case PROC_STATE_BUCKET_TOP:
return 0;
case PROC_STATE_BUCKET_FGS:
// Can't get notification priority. Just use CTP for now.
return Math.min(ctp, price);
case PROC_STATE_BUCKET_BFGS:
if (price <= ctp) {
return price;
}
return (long) (ctp + .5 * (price - ctp));
case PROC_STATE_BUCKET_BG:
default:
return price;
}
}
@Override
@GuardedBy("mLock")
void dump(IndentingPrintWriter pw) {
pw.print("Proc state bucket cache = ");
pw.println(mUidProcStateBucketCache);
}
@ProcStateBucket
private int getProcStateBucket(int procState) {
if (procState <= ActivityManager.PROCESS_STATE_TOP) {
return PROC_STATE_BUCKET_TOP;
}
if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
return PROC_STATE_BUCKET_FGS;
}
if (procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
return PROC_STATE_BUCKET_BFGS;
}
return PROC_STATE_BUCKET_BG;
}
@GuardedBy("mLock")
private void notifyStateChangedLocked(final int uid) {
// Never call out to the IRS with the local lock held.
TareHandlerThread.getHandler().post(() -> mIrs.onUidStateChanged(uid));
}
}