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