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.tare; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.app.ActivityManager; 22 import android.app.IUidObserver; 23 import android.app.UidObserver; 24 import android.os.RemoteException; 25 import android.util.IndentingPrintWriter; 26 import android.util.Slog; 27 import android.util.SparseArrayMap; 28 import android.util.SparseIntArray; 29 30 import com.android.internal.annotations.GuardedBy; 31 32 import java.lang.annotation.Retention; 33 import java.lang.annotation.RetentionPolicy; 34 35 /** Modifier that makes things more cheaper based on an app's process state. */ 36 class ProcessStateModifier extends Modifier { 37 private static final String TAG = "TARE-" + ProcessStateModifier.class.getSimpleName(); 38 39 private static final int PROC_STATE_BUCKET_NONE = 0; 40 private static final int PROC_STATE_BUCKET_TOP = 1; 41 private static final int PROC_STATE_BUCKET_FGS = 2; 42 private static final int PROC_STATE_BUCKET_BFGS = 3; 43 private static final int PROC_STATE_BUCKET_BG = 4; 44 45 @IntDef(prefix = {"PROC_STATE_BUCKET_"}, value = { 46 PROC_STATE_BUCKET_NONE, 47 PROC_STATE_BUCKET_TOP, 48 PROC_STATE_BUCKET_FGS, 49 PROC_STATE_BUCKET_BFGS, 50 PROC_STATE_BUCKET_BG 51 }) 52 @Retention(RetentionPolicy.SOURCE) 53 public @interface ProcStateBucket { 54 } 55 56 private final Object mLock = new Object(); 57 private final InternalResourceService mIrs; 58 59 /** Cached mapping of userId+package to their UIDs (for all users) */ 60 private final SparseArrayMap<String, Integer> mPackageToUidCache = new SparseArrayMap<>(); 61 62 @GuardedBy("mLock") 63 private final SparseIntArray mUidProcStateBucketCache = new SparseIntArray(); 64 65 private final IUidObserver mUidObserver = new UidObserver() { 66 @Override 67 public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { 68 final int newBucket = getProcStateBucket(procState); 69 synchronized (mLock) { 70 final int curBucket = mUidProcStateBucketCache.get(uid); 71 if (curBucket != newBucket) { 72 mUidProcStateBucketCache.put(uid, newBucket); 73 } 74 notifyStateChangedLocked(uid); 75 } 76 } 77 78 @Override 79 public void onUidGone(int uid, boolean disabled) { 80 synchronized (mLock) { 81 if (mUidProcStateBucketCache.indexOfKey(uid) < 0) { 82 Slog.e(TAG, "UID " + uid + " marked gone but wasn't in cache."); 83 return; 84 } 85 mUidProcStateBucketCache.delete(uid); 86 notifyStateChangedLocked(uid); 87 } 88 } 89 }; 90 ProcessStateModifier(@onNull InternalResourceService irs)91 ProcessStateModifier(@NonNull InternalResourceService irs) { 92 super(); 93 mIrs = irs; 94 } 95 96 @Override 97 @GuardedBy("mLock") setup()98 void setup() { 99 try { 100 ActivityManager.getService().registerUidObserver(mUidObserver, 101 ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE, 102 ActivityManager.PROCESS_STATE_UNKNOWN, null); 103 } catch (RemoteException e) { 104 // ignored; both services live in system_server 105 } 106 } 107 108 @Override 109 @GuardedBy("mLock") tearDown()110 void tearDown() { 111 try { 112 ActivityManager.getService().unregisterUidObserver(mUidObserver); 113 } catch (RemoteException e) { 114 // ignored; both services live in system_server 115 } 116 mPackageToUidCache.clear(); 117 mUidProcStateBucketCache.clear(); 118 } 119 120 /** 121 * Get the final modified price based on an app's process state. 122 * 123 * @param ctp Cost to produce. @see EconomicPolicy.Action#costToProduce 124 * @param price Current price 125 */ getModifiedPrice(final int userId, @NonNull final String pkgName, final long ctp, final long price)126 long getModifiedPrice(final int userId, @NonNull final String pkgName, 127 final long ctp, final long price) { 128 final int procState; 129 synchronized (mLock) { 130 procState = mUidProcStateBucketCache.get( 131 mIrs.getUid(userId, pkgName), PROC_STATE_BUCKET_NONE); 132 } 133 switch (procState) { 134 case PROC_STATE_BUCKET_TOP: 135 return 0; 136 case PROC_STATE_BUCKET_FGS: 137 // Can't get notification priority. Just use CTP for now. 138 return Math.min(ctp, price); 139 case PROC_STATE_BUCKET_BFGS: 140 if (price <= ctp) { 141 return price; 142 } 143 return (long) (ctp + .5 * (price - ctp)); 144 case PROC_STATE_BUCKET_BG: 145 default: 146 return price; 147 } 148 } 149 150 @Override 151 @GuardedBy("mLock") dump(IndentingPrintWriter pw)152 void dump(IndentingPrintWriter pw) { 153 pw.print("Proc state bucket cache = "); 154 pw.println(mUidProcStateBucketCache); 155 } 156 157 @ProcStateBucket getProcStateBucket(int procState)158 private int getProcStateBucket(int procState) { 159 if (procState <= ActivityManager.PROCESS_STATE_TOP) { 160 return PROC_STATE_BUCKET_TOP; 161 } 162 if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { 163 return PROC_STATE_BUCKET_FGS; 164 } 165 if (procState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { 166 return PROC_STATE_BUCKET_BFGS; 167 } 168 return PROC_STATE_BUCKET_BG; 169 } 170 171 @GuardedBy("mLock") notifyStateChangedLocked(final int uid)172 private void notifyStateChangedLocked(final int uid) { 173 // Never call out to the IRS with the local lock held. 174 TareHandlerThread.getHandler().post(() -> mIrs.onUidStateChanged(uid)); 175 } 176 } 177