• 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.tare;
18 
19 import static android.app.tare.EconomyManager.parseCreditValue;
20 
21 import static com.android.server.tare.Modifier.COST_MODIFIER_CHARGING;
22 import static com.android.server.tare.Modifier.COST_MODIFIER_DEVICE_IDLE;
23 import static com.android.server.tare.Modifier.COST_MODIFIER_POWER_SAVE_MODE;
24 import static com.android.server.tare.Modifier.COST_MODIFIER_PROCESS_STATE;
25 import static com.android.server.tare.Modifier.NUM_COST_MODIFIERS;
26 import static com.android.server.tare.TareUtils.cakeToString;
27 
28 import android.annotation.CallSuper;
29 import android.annotation.IntDef;
30 import android.annotation.NonNull;
31 import android.annotation.Nullable;
32 import android.provider.DeviceConfig;
33 import android.util.IndentingPrintWriter;
34 import android.util.KeyValueListParser;
35 
36 import java.lang.annotation.Retention;
37 import java.lang.annotation.RetentionPolicy;
38 
39 /**
40  * An EconomicPolicy includes pricing information and daily ARC requirements and suggestions.
41  * Policies are defined per participating system service. This allows each service’s EconomicPolicy
42  * to be isolated while allowing the core economic system to scale across policies to achieve a
43  * logical system-wide value system.
44  */
45 public abstract class EconomicPolicy {
46     private static final String TAG = "TARE-" + EconomicPolicy.class.getSimpleName();
47 
48     private static final int SHIFT_TYPE = 30;
49     static final int MASK_TYPE = 0b11 << SHIFT_TYPE;
50     static final int TYPE_REGULATION = 0 << SHIFT_TYPE;
51     static final int TYPE_ACTION = 1 << SHIFT_TYPE;
52     static final int TYPE_REWARD = 2 << SHIFT_TYPE;
53 
54     private static final int SHIFT_POLICY = 29;
55     static final int MASK_POLICY = 0b1 << SHIFT_POLICY;
56     static final int POLICY_AM = 0 << SHIFT_POLICY;
57     static final int POLICY_JS = 1 << SHIFT_POLICY;
58 
59     static final int MASK_EVENT = ~0 - (0b111 << SHIFT_POLICY);
60 
61     static final int REGULATION_BASIC_INCOME = TYPE_REGULATION | 0;
62     static final int REGULATION_BIRTHRIGHT = TYPE_REGULATION | 1;
63     static final int REGULATION_WEALTH_RECLAMATION = TYPE_REGULATION | 2;
64     static final int REGULATION_PROMOTION = TYPE_REGULATION | 3;
65     static final int REGULATION_DEMOTION = TYPE_REGULATION | 4;
66 
67     static final int REWARD_NOTIFICATION_SEEN = TYPE_REWARD | 0;
68     static final int REWARD_NOTIFICATION_INTERACTION = TYPE_REWARD | 1;
69     static final int REWARD_TOP_ACTIVITY = TYPE_REWARD | 2;
70     static final int REWARD_WIDGET_INTERACTION = TYPE_REWARD | 3;
71     static final int REWARD_OTHER_USER_INTERACTION = TYPE_REWARD | 4;
72 
73     @IntDef({
74             AlarmManagerEconomicPolicy.ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE,
75             AlarmManagerEconomicPolicy.ACTION_ALARM_WAKEUP_EXACT,
76             AlarmManagerEconomicPolicy.ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE,
77             AlarmManagerEconomicPolicy.ACTION_ALARM_WAKEUP_INEXACT,
78             AlarmManagerEconomicPolicy.ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE,
79             AlarmManagerEconomicPolicy.ACTION_ALARM_NONWAKEUP_EXACT,
80             AlarmManagerEconomicPolicy.ACTION_ALARM_NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE,
81             AlarmManagerEconomicPolicy.ACTION_ALARM_NONWAKEUP_INEXACT,
82             AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK,
83             JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START,
84             JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING,
85             JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START,
86             JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING,
87             JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_START,
88             JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING,
89             JobSchedulerEconomicPolicy.ACTION_JOB_LOW_START,
90             JobSchedulerEconomicPolicy.ACTION_JOB_LOW_RUNNING,
91             JobSchedulerEconomicPolicy.ACTION_JOB_MIN_START,
92             JobSchedulerEconomicPolicy.ACTION_JOB_MIN_RUNNING,
93             JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT,
94     })
95     @Retention(RetentionPolicy.SOURCE)
96     public @interface AppAction {
97     }
98 
99     @IntDef({
100             TYPE_ACTION,
101             TYPE_REGULATION,
102             TYPE_REWARD,
103     })
104     @Retention(RetentionPolicy.SOURCE)
105     public @interface EventType {
106     }
107 
108     @IntDef({
109             REWARD_TOP_ACTIVITY,
110             REWARD_NOTIFICATION_SEEN,
111             REWARD_NOTIFICATION_INTERACTION,
112             REWARD_WIDGET_INTERACTION,
113             REWARD_OTHER_USER_INTERACTION,
114     })
115     @Retention(RetentionPolicy.SOURCE)
116     public @interface UtilityReward {
117     }
118 
119     static class Action {
120         /** Unique id (including across policies) for this action. */
121         public final int id;
122         /**
123          * How many ARCs the system says it takes to perform this action.
124          */
125         public final long costToProduce;
126         /**
127          * The base price to perform this action. If this is
128          * less than the {@link #costToProduce}, then the system should not perform
129          * the action unless a modifier lowers the cost to produce.
130          */
131         public final long basePrice;
132 
Action(int id, long costToProduce, long basePrice)133         Action(int id, long costToProduce, long basePrice) {
134             this.id = id;
135             this.costToProduce = costToProduce;
136             this.basePrice = basePrice;
137         }
138     }
139 
140     static class Reward {
141         /** Unique id (including across policies) for this reward. */
142         @UtilityReward
143         public final int id;
144         public final long instantReward;
145         /** Reward credited per second of ongoing activity. */
146         public final long ongoingRewardPerSecond;
147         /** The maximum amount an app can earn from this reward within a 24 hour period. */
148         public final long maxDailyReward;
149 
Reward(int id, long instantReward, long ongoingReward, long maxDailyReward)150         Reward(int id, long instantReward, long ongoingReward, long maxDailyReward) {
151             this.id = id;
152             this.instantReward = instantReward;
153             this.ongoingRewardPerSecond = ongoingReward;
154             this.maxDailyReward = maxDailyReward;
155         }
156     }
157 
158     static class Cost {
159         public final long costToProduce;
160         public final long price;
161 
Cost(long costToProduce, long price)162         Cost(long costToProduce, long price) {
163             this.costToProduce = costToProduce;
164             this.price = price;
165         }
166     }
167 
168     private static final Modifier[] COST_MODIFIER_BY_INDEX = new Modifier[NUM_COST_MODIFIERS];
169 
EconomicPolicy(@onNull InternalResourceService irs)170     EconomicPolicy(@NonNull InternalResourceService irs) {
171         for (int mId : getCostModifiers()) {
172             initModifier(mId, irs);
173         }
174     }
175 
176     @CallSuper
setup(@onNull DeviceConfig.Properties properties)177     void setup(@NonNull DeviceConfig.Properties properties) {
178         for (int i = 0; i < NUM_COST_MODIFIERS; ++i) {
179             final Modifier modifier = COST_MODIFIER_BY_INDEX[i];
180             if (modifier != null) {
181                 modifier.setup();
182             }
183         }
184     }
185 
186     @CallSuper
tearDown()187     void tearDown() {
188         for (int i = 0; i < NUM_COST_MODIFIERS; ++i) {
189             final Modifier modifier = COST_MODIFIER_BY_INDEX[i];
190             if (modifier != null) {
191                 modifier.tearDown();
192             }
193         }
194     }
195 
196     /**
197      * Returns the minimum suggested balance an app should have when the device is at 100% battery.
198      * This takes into account any exemptions the app may have.
199      */
getMinSatiatedBalance(int userId, @NonNull String pkgName)200     abstract long getMinSatiatedBalance(int userId, @NonNull String pkgName);
201 
202     /**
203      * Returns the maximum balance an app should have when the device is at 100% battery. This
204      * exists to ensure that no single app accumulate all available resources and increases fairness
205      * for all apps.
206      */
getMaxSatiatedBalance()207     abstract long getMaxSatiatedBalance();
208 
209     /**
210      * Returns the maximum number of cakes that should be consumed during a full 100% discharge
211      * cycle. This is the initial limit. The system may choose to increase the limit over time,
212      * but the increased limit should never exceed the value returned from
213      * {@link #getHardSatiatedConsumptionLimit()}.
214      */
getInitialSatiatedConsumptionLimit()215     abstract long getInitialSatiatedConsumptionLimit();
216 
217     /**
218      * Returns the maximum number of cakes that should be consumed during a full 100% discharge
219      * cycle. This is the hard limit that should never be exceeded.
220      */
getHardSatiatedConsumptionLimit()221     abstract long getHardSatiatedConsumptionLimit();
222 
223     /** Return the set of modifiers that should apply to this policy's costs. */
224     @NonNull
getCostModifiers()225     abstract int[] getCostModifiers();
226 
227     @Nullable
getAction(@ppAction int actionId)228     abstract Action getAction(@AppAction int actionId);
229 
230     @Nullable
getReward(@tilityReward int rewardId)231     abstract Reward getReward(@UtilityReward int rewardId);
232 
dump(IndentingPrintWriter pw)233     void dump(IndentingPrintWriter pw) {
234     }
235 
236     @NonNull
getCostOfAction(int actionId, int userId, @NonNull String pkgName)237     final Cost getCostOfAction(int actionId, int userId, @NonNull String pkgName) {
238         final Action action = getAction(actionId);
239         if (action == null) {
240             return new Cost(0, 0);
241         }
242         long ctp = action.costToProduce;
243         long price = action.basePrice;
244         final int[] costModifiers = getCostModifiers();
245         boolean useProcessStatePriceDeterminant = false;
246         for (int costModifier : costModifiers) {
247             if (costModifier == COST_MODIFIER_PROCESS_STATE) {
248                 useProcessStatePriceDeterminant = true;
249             } else {
250                 final Modifier modifier = getModifier(costModifier);
251                 ctp = modifier.getModifiedCostToProduce(ctp);
252                 price = modifier.getModifiedPrice(price);
253             }
254         }
255         // ProcessStateModifier needs to be done last.
256         if (useProcessStatePriceDeterminant) {
257             ProcessStateModifier processStateModifier =
258                     (ProcessStateModifier) getModifier(COST_MODIFIER_PROCESS_STATE);
259             price = processStateModifier.getModifiedPrice(userId, pkgName, ctp, price);
260         }
261         return new Cost(ctp, price);
262     }
263 
initModifier(@odifier.CostModifier final int modifierId, @NonNull InternalResourceService irs)264     private static void initModifier(@Modifier.CostModifier final int modifierId,
265             @NonNull InternalResourceService irs) {
266         if (modifierId < 0 || modifierId >= COST_MODIFIER_BY_INDEX.length) {
267             throw new IllegalArgumentException("Invalid modifier id " + modifierId);
268         }
269         Modifier modifier = COST_MODIFIER_BY_INDEX[modifierId];
270         if (modifier == null) {
271             switch (modifierId) {
272                 case COST_MODIFIER_CHARGING:
273                     modifier = new ChargingModifier(irs);
274                     break;
275                 case COST_MODIFIER_DEVICE_IDLE:
276                     modifier = new DeviceIdleModifier(irs);
277                     break;
278                 case COST_MODIFIER_POWER_SAVE_MODE:
279                     modifier = new PowerSaveModeModifier(irs);
280                     break;
281                 case COST_MODIFIER_PROCESS_STATE:
282                     modifier = new ProcessStateModifier(irs);
283                     break;
284                 default:
285                     throw new IllegalArgumentException("Invalid modifier id " + modifierId);
286             }
287             COST_MODIFIER_BY_INDEX[modifierId] = modifier;
288         }
289     }
290 
291     @NonNull
getModifier(@odifier.CostModifier final int modifierId)292     private static Modifier getModifier(@Modifier.CostModifier final int modifierId) {
293         if (modifierId < 0 || modifierId >= COST_MODIFIER_BY_INDEX.length) {
294             throw new IllegalArgumentException("Invalid modifier id " + modifierId);
295         }
296         final Modifier modifier = COST_MODIFIER_BY_INDEX[modifierId];
297         if (modifier == null) {
298             throw new IllegalStateException(
299                     "Modifier #" + modifierId + " was never initialized");
300         }
301         return modifier;
302     }
303 
304     @EventType
getEventType(int eventId)305     static int getEventType(int eventId) {
306         return eventId & MASK_TYPE;
307     }
308 
309     @NonNull
eventToString(int eventId)310     static String eventToString(int eventId) {
311         switch (eventId & MASK_TYPE) {
312             case TYPE_ACTION:
313                 return actionToString(eventId);
314 
315             case TYPE_REGULATION:
316                 return regulationToString(eventId);
317 
318             case TYPE_REWARD:
319                 return rewardToString(eventId);
320 
321             default:
322                 return "UNKNOWN_EVENT:" + Integer.toHexString(eventId);
323         }
324     }
325 
326     @NonNull
actionToString(int eventId)327     static String actionToString(int eventId) {
328         switch (eventId & MASK_POLICY) {
329             case POLICY_AM:
330                 switch (eventId) {
331                     case AlarmManagerEconomicPolicy.ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE:
332                         return "ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE";
333                     case AlarmManagerEconomicPolicy.ACTION_ALARM_WAKEUP_EXACT:
334                         return "ALARM_WAKEUP_EXACT";
335                     case AlarmManagerEconomicPolicy.ACTION_ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE:
336                         return "ALARM_WAKEUP_INEXACT_ALLOW_WHILE_IDLE";
337                     case AlarmManagerEconomicPolicy.ACTION_ALARM_WAKEUP_INEXACT:
338                         return "ALARM_WAKEUP_INEXACT";
339                     case AlarmManagerEconomicPolicy.ACTION_ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE:
340                         return "ALARM_NONWAKEUP_EXACT_ALLOW_WHILE_IDLE";
341                     case AlarmManagerEconomicPolicy.ACTION_ALARM_NONWAKEUP_EXACT:
342                         return "ALARM_NONWAKEUP_EXACT";
343                     case AlarmManagerEconomicPolicy.ACTION_ALARM_NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE:
344                         return "ALARM_NONWAKEUP_INEXACT_ALLOW_WHILE_IDLE";
345                     case AlarmManagerEconomicPolicy.ACTION_ALARM_NONWAKEUP_INEXACT:
346                         return "ALARM_NONWAKEUP_INEXACT";
347                     case AlarmManagerEconomicPolicy.ACTION_ALARM_CLOCK:
348                         return "ALARM_CLOCK";
349                 }
350                 break;
351 
352             case POLICY_JS:
353                 switch (eventId) {
354                     case JobSchedulerEconomicPolicy.ACTION_JOB_MAX_START:
355                         return "JOB_MAX_START";
356                     case JobSchedulerEconomicPolicy.ACTION_JOB_MAX_RUNNING:
357                         return "JOB_MAX_RUNNING";
358                     case JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_START:
359                         return "JOB_HIGH_START";
360                     case JobSchedulerEconomicPolicy.ACTION_JOB_HIGH_RUNNING:
361                         return "JOB_HIGH_RUNNING";
362                     case JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_START:
363                         return "JOB_DEFAULT_START";
364                     case JobSchedulerEconomicPolicy.ACTION_JOB_DEFAULT_RUNNING:
365                         return "JOB_DEFAULT_RUNNING";
366                     case JobSchedulerEconomicPolicy.ACTION_JOB_LOW_START:
367                         return "JOB_LOW_START";
368                     case JobSchedulerEconomicPolicy.ACTION_JOB_LOW_RUNNING:
369                         return "JOB_LOW_RUNNING";
370                     case JobSchedulerEconomicPolicy.ACTION_JOB_MIN_START:
371                         return "JOB_MIN_START";
372                     case JobSchedulerEconomicPolicy.ACTION_JOB_MIN_RUNNING:
373                         return "JOB_MIN_RUNNING";
374                     case JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT:
375                         return "JOB_TIMEOUT";
376                 }
377                 break;
378         }
379         return "UNKNOWN_ACTION:" + Integer.toHexString(eventId);
380     }
381 
382     @NonNull
regulationToString(int eventId)383     static String regulationToString(int eventId) {
384         switch (eventId) {
385             case REGULATION_BASIC_INCOME:
386                 return "BASIC_INCOME";
387             case REGULATION_BIRTHRIGHT:
388                 return "BIRTHRIGHT";
389             case REGULATION_WEALTH_RECLAMATION:
390                 return "WEALTH_RECLAMATION";
391             case REGULATION_PROMOTION:
392                 return "PROMOTION";
393             case REGULATION_DEMOTION:
394                 return "DEMOTION";
395         }
396         return "UNKNOWN_REGULATION:" + Integer.toHexString(eventId);
397     }
398 
399     @NonNull
rewardToString(int eventId)400     static String rewardToString(int eventId) {
401         switch (eventId) {
402             case REWARD_TOP_ACTIVITY:
403                 return "REWARD_TOP_ACTIVITY";
404             case REWARD_NOTIFICATION_SEEN:
405                 return "REWARD_NOTIFICATION_SEEN";
406             case REWARD_NOTIFICATION_INTERACTION:
407                 return "REWARD_NOTIFICATION_INTERACTION";
408             case REWARD_WIDGET_INTERACTION:
409                 return "REWARD_WIDGET_INTERACTION";
410             case REWARD_OTHER_USER_INTERACTION:
411                 return "REWARD_OTHER_USER_INTERACTION";
412         }
413         return "UNKNOWN_REWARD:" + Integer.toHexString(eventId);
414     }
415 
getConstantAsCake(@onNull KeyValueListParser parser, @Nullable DeviceConfig.Properties properties, String key, long defaultValCake)416     protected long getConstantAsCake(@NonNull KeyValueListParser parser,
417             @Nullable DeviceConfig.Properties properties, String key, long defaultValCake) {
418         // Don't cross the streams! Mixing Settings/local user config changes with DeviceConfig
419         // config can cause issues since the scales may be different, so use one or the other.
420         if (parser.size() > 0) {
421             // User settings take precedence. Just stick with the Settings constants, even if there
422             // are invalid values. It's not worth the time to evaluate all the key/value pairs to
423             // make sure there are valid ones before deciding.
424             return parseCreditValue(parser.getString(key, null), defaultValCake);
425         }
426         if (properties != null) {
427             return parseCreditValue(properties.getString(key, null), defaultValCake);
428         }
429         return defaultValCake;
430     }
431 
dumpActiveModifiers(IndentingPrintWriter pw)432     protected static void dumpActiveModifiers(IndentingPrintWriter pw) {
433         for (int i = 0; i < NUM_COST_MODIFIERS; ++i) {
434             pw.print("Modifier ");
435             pw.println(i);
436             pw.increaseIndent();
437 
438             Modifier modifier = COST_MODIFIER_BY_INDEX[i];
439             if (modifier != null) {
440                 modifier.dump(pw);
441             } else {
442                 pw.println("NOT ACTIVE");
443             }
444 
445             pw.decreaseIndent();
446         }
447     }
448 
dumpAction(IndentingPrintWriter pw, @NonNull Action action)449     protected static void dumpAction(IndentingPrintWriter pw, @NonNull Action action) {
450         pw.print(actionToString(action.id));
451         pw.print(": ");
452         pw.print("ctp=");
453         pw.print(cakeToString(action.costToProduce));
454         pw.print(", basePrice=");
455         pw.print(cakeToString(action.basePrice));
456         pw.println();
457     }
458 
dumpReward(IndentingPrintWriter pw, @NonNull Reward reward)459     protected static void dumpReward(IndentingPrintWriter pw, @NonNull Reward reward) {
460         pw.print(rewardToString(reward.id));
461         pw.print(": ");
462         pw.print("instant=");
463         pw.print(cakeToString(reward.instantReward));
464         pw.print(", ongoing/sec=");
465         pw.print(cakeToString(reward.ongoingRewardPerSecond));
466         pw.print(", maxDaily=");
467         pw.print(cakeToString(reward.maxDailyReward));
468         pw.println();
469     }
470 }
471