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