All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.tare.InternalResourceService Maven / Gradle / Ivy

/*
 * 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 static android.provider.Settings.Global.TARE_ALARM_MANAGER_CONSTANTS;
import static android.provider.Settings.Global.TARE_JOB_SCHEDULER_CONSTANTS;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;

import static com.android.server.tare.TareUtils.appToString;
import static com.android.server.tare.TareUtils.cakeToString;
import static com.android.server.tare.TareUtils.getCurrentTimeMillis;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.tare.IEconomyManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.BatteryManagerInternal;
import android.os.Binder;
import android.os.Handler;
import android.os.IDeviceIdleController;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArrayMap;
import android.util.SparseSetArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;
import com.android.server.tare.EconomicPolicy.Cost;
import com.android.server.tare.EconomyManagerInternal.TareStateChangeListener;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * Responsible for handling app's ARC count based on events, ensuring ARCs are credited when
 * appropriate, and reclaiming ARCs at the right times. The IRS deals with the high level details
 * while the {@link Agent} deals with the nitty-gritty details.
 *
 * Note on locking: Any function with the suffix 'Locked' needs to lock on {@link #mLock}.
 *
 * @hide
 */
public class InternalResourceService extends SystemService {
    public static final String TAG = "TARE-IRS";
    public static final boolean DEBUG = Log.isLoggable("TARE", Log.DEBUG);

    static final long UNUSED_RECLAMATION_PERIOD_MS = 24 * HOUR_IN_MILLIS;
    /** How much of an app's unused wealth should be reclaimed periodically. */
    private static final float DEFAULT_UNUSED_RECLAMATION_PERCENTAGE = .1f;
    /**
     * The minimum amount of time an app must not have been used by the user before we start
     * periodically reclaiming ARCs from it.
     */
    private static final long MIN_UNUSED_TIME_MS = 3 * DAY_IN_MILLIS;
    /** The amount of time to delay reclamation by after boot. */
    private static final long RECLAMATION_STARTUP_DELAY_MS = 30_000L;
    /**
     * The battery level above which we may consider quantitative easing (increasing the consumption
     * limit).
     */
    private static final int QUANTITATIVE_EASING_BATTERY_THRESHOLD = 50;
    private static final int PACKAGE_QUERY_FLAGS =
            PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                    | PackageManager.MATCH_APEX;

    /** Global lock for all resource economy state. */
    private final Object mLock = new Object();

    private final Handler mHandler;
    private final BatteryManagerInternal mBatteryManagerInternal;
    private final PackageManager mPackageManager;
    private final PackageManagerInternal mPackageManagerInternal;

    private IDeviceIdleController mDeviceIdleController;

    private final Agent mAgent;
    private final Analyst mAnalyst;
    private final ConfigObserver mConfigObserver;
    private final EconomyManagerStub mEconomyManagerStub;
    private final Scribe mScribe;

    @GuardedBy("mLock")
    private CompleteEconomicPolicy mCompleteEconomicPolicy;

    @NonNull
    @GuardedBy("mLock")
    private final List mPkgCache = new ArrayList<>();

    /** Cached mapping of UIDs (for all users) to a list of packages in the UID. */
    @GuardedBy("mLock")
    private final SparseSetArray mUidToPackageCache = new SparseSetArray<>();

    /** Cached mapping of userId+package to their UIDs (for all users) */
    @GuardedBy("mPackageToUidCache")
    private final SparseArrayMap mPackageToUidCache = new SparseArrayMap<>();

    private final CopyOnWriteArraySet mStateChangeListeners =
            new CopyOnWriteArraySet<>();

    /** List of packages that are "exempted" from battery restrictions. */
    // TODO(144864180): include userID
    @GuardedBy("mLock")
    private ArraySet mExemptedApps = new ArraySet<>();

    private volatile boolean mIsEnabled;
    private volatile int mBootPhase;
    private volatile boolean mExemptListLoaded;
    // In the range [0,100] to represent 0% to 100% battery.
    @GuardedBy("mLock")
    private int mCurrentBatteryLevel;

    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Nullable
        private String getPackageName(Intent intent) {
            Uri uri = intent.getData();
            return uri != null ? uri.getSchemeSpecificPart() : null;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            switch (intent.getAction()) {
                case Intent.ACTION_BATTERY_LEVEL_CHANGED:
                    onBatteryLevelChanged();
                    break;
                case Intent.ACTION_PACKAGE_FULLY_REMOVED: {
                    final String pkgName = getPackageName(intent);
                    final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
                    onPackageRemoved(pkgUid, pkgName);
                }
                break;
                case Intent.ACTION_PACKAGE_ADDED: {
                    if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                        final String pkgName = getPackageName(intent);
                        final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
                        onPackageAdded(pkgUid, pkgName);
                    }
                }
                break;
                case Intent.ACTION_PACKAGE_RESTARTED: {
                    final String pkgName = getPackageName(intent);
                    final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
                    final int userId = UserHandle.getUserId(pkgUid);
                    onPackageForceStopped(userId, pkgName);
                }
                break;
                case Intent.ACTION_USER_ADDED: {
                    final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
                    onUserAdded(userId);
                }
                break;
                case Intent.ACTION_USER_REMOVED: {
                    final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
                    onUserRemoved(userId);
                }
                break;
                case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
                    onExemptionListChanged();
                    break;
            }
        }
    };

    private final UsageStatsManagerInternal.UsageEventListener mSurveillanceAgent =
            new UsageStatsManagerInternal.UsageEventListener() {
                /**
                 * Callback to inform listeners of a new event.
                 */
                @Override
                public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) {
                    mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event)
                            .sendToTarget();
                }
            };

    private final AlarmManager.OnAlarmListener mUnusedWealthReclamationListener =
            new AlarmManager.OnAlarmListener() {
                @Override
                public void onAlarm() {
                    synchronized (mLock) {
                        mAgent.reclaimUnusedAssetsLocked(
                                DEFAULT_UNUSED_RECLAMATION_PERCENTAGE, MIN_UNUSED_TIME_MS, false);
                        mScribe.setLastReclamationTimeLocked(getCurrentTimeMillis());
                        scheduleUnusedWealthReclamationLocked();
                    }
                }
            };

    private static final int MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER = 0;
    private static final int MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT = 1;
    private static final int MSG_PROCESS_USAGE_EVENT = 2;
    private static final int MSG_NOTIFY_STATE_CHANGE_LISTENERS = 3;
    private static final String ALARM_TAG_WEALTH_RECLAMATION = "*tare.reclamation*";

    /**
     * Initializes the system service.
     * 

* Subclasses must define a single argument constructor that accepts the context * and passes it to super. *

* * @param context The system server context. */ public InternalResourceService(Context context) { super(context); mHandler = new IrsHandler(TareHandlerThread.get().getLooper()); mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class); mPackageManager = context.getPackageManager(); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mEconomyManagerStub = new EconomyManagerStub(); mAnalyst = new Analyst(); mScribe = new Scribe(this, mAnalyst); mCompleteEconomicPolicy = new CompleteEconomicPolicy(this); mAgent = new Agent(this, mScribe, mAnalyst); mConfigObserver = new ConfigObserver(mHandler, context); publishLocalService(EconomyManagerInternal.class, new LocalService()); } @Override public void onStart() { publishBinderService(Context.RESOURCE_ECONOMY_SERVICE, mEconomyManagerStub); } @Override public void onBootPhase(int phase) { mBootPhase = phase; if (PHASE_SYSTEM_SERVICES_READY == phase) { mConfigObserver.start(); mDeviceIdleController = IDeviceIdleController.Stub.asInterface( ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); setupEverything(); } else if (PHASE_BOOT_COMPLETED == phase) { if (!mExemptListLoaded) { synchronized (mLock) { try { mExemptedApps = new ArraySet<>(mDeviceIdleController.getFullPowerWhitelist()); } catch (RemoteException e) { // Shouldn't happen. Slog.wtf(TAG, e); } mExemptListLoaded = true; } } } } @NonNull Object getLock() { return mLock; } /** Returns the installed packages for all users. */ @NonNull @GuardedBy("mLock") CompleteEconomicPolicy getCompleteEconomicPolicyLocked() { return mCompleteEconomicPolicy; } @NonNull List getInstalledPackages() { synchronized (mLock) { return mPkgCache; } } /** Returns the installed packages for the specified user. */ @NonNull List getInstalledPackages(final int userId) { final List userPkgs = new ArrayList<>(); synchronized (mLock) { for (int i = 0; i < mPkgCache.size(); ++i) { final PackageInfo packageInfo = mPkgCache.get(i); if (packageInfo.applicationInfo != null && UserHandle.getUserId(packageInfo.applicationInfo.uid) == userId) { userPkgs.add(packageInfo); } } } return userPkgs; } @GuardedBy("mLock") long getConsumptionLimitLocked() { return mCurrentBatteryLevel * mScribe.getSatiatedConsumptionLimitLocked() / 100; } @GuardedBy("mLock") long getMinBalanceLocked(final int userId, @NonNull final String pkgName) { return mCurrentBatteryLevel * mCompleteEconomicPolicy.getMinSatiatedBalance(userId, pkgName) / 100; } @GuardedBy("mLock") long getInitialSatiatedConsumptionLimitLocked() { return mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit(); } int getUid(final int userId, @NonNull final String pkgName) { synchronized (mPackageToUidCache) { Integer uid = mPackageToUidCache.get(userId, pkgName); if (uid == null) { uid = mPackageManagerInternal.getPackageUid(pkgName, 0, userId); mPackageToUidCache.add(userId, pkgName, uid); } return uid; } } boolean isEnabled() { return mIsEnabled; } boolean isPackageExempted(final int userId, @NonNull String pkgName) { synchronized (mLock) { return mExemptedApps.contains(pkgName); } } boolean isSystem(final int userId, @NonNull String pkgName) { if ("android".equals(pkgName)) { return true; } return UserHandle.isCore(getUid(userId, pkgName)); } void onBatteryLevelChanged() { synchronized (mLock) { final int newBatteryLevel = getCurrentBatteryLevel(); mAnalyst.noteBatteryLevelChange(newBatteryLevel); final boolean increased = newBatteryLevel > mCurrentBatteryLevel; if (increased) { mAgent.distributeBasicIncomeLocked(newBatteryLevel); } else if (newBatteryLevel == mCurrentBatteryLevel) { // The broadcast is also sent when the plug type changes... return; } mCurrentBatteryLevel = newBatteryLevel; adjustCreditSupplyLocked(increased); } } void onDeviceStateChanged() { synchronized (mLock) { mAgent.onDeviceStateChangedLocked(); } } void onExemptionListChanged() { final int[] userIds = LocalServices.getService(UserManagerInternal.class).getUserIds(); synchronized (mLock) { final ArraySet removed = mExemptedApps; final ArraySet added = new ArraySet<>(); try { mExemptedApps = new ArraySet<>(mDeviceIdleController.getFullPowerWhitelist()); } catch (RemoteException e) { // Shouldn't happen. Slog.wtf(TAG, e); return; } for (int i = mExemptedApps.size() - 1; i >= 0; --i) { final String pkg = mExemptedApps.valueAt(i); if (!removed.contains(pkg)) { added.add(pkg); } removed.remove(pkg); } for (int a = added.size() - 1; a >= 0; --a) { final String pkgName = added.valueAt(a); for (int userId : userIds) { // Since the exemption list doesn't specify user ID and we track by user ID, // we need to see if the app exists on the user before talking to the agent. // Otherwise, we may end up with invalid ledgers. final boolean appExists = getUid(userId, pkgName) >= 0; if (appExists) { mAgent.onAppExemptedLocked(userId, pkgName); } } } for (int r = removed.size() - 1; r >= 0; --r) { final String pkgName = removed.valueAt(r); for (int userId : userIds) { // Since the exemption list doesn't specify user ID and we track by user ID, // we need to see if the app exists on the user before talking to the agent. // Otherwise, we may end up with invalid ledgers. final boolean appExists = getUid(userId, pkgName) >= 0; if (appExists) { mAgent.onAppUnexemptedLocked(userId, pkgName); } } } } } void onPackageAdded(final int uid, @NonNull final String pkgName) { final int userId = UserHandle.getUserId(uid); final PackageInfo packageInfo; try { packageInfo = mPackageManager.getPackageInfoAsUser(pkgName, PACKAGE_QUERY_FLAGS, userId); } catch (PackageManager.NameNotFoundException e) { Slog.wtf(TAG, "PM couldn't find newly added package: " + pkgName, e); return; } synchronized (mPackageToUidCache) { mPackageToUidCache.add(userId, pkgName, uid); } synchronized (mLock) { mPkgCache.add(packageInfo); mUidToPackageCache.add(uid, pkgName); // TODO: only do this when the user first launches the app (app leaves stopped state) mAgent.grantBirthrightLocked(userId, pkgName); } } void onPackageForceStopped(final int userId, @NonNull final String pkgName) { synchronized (mLock) { // TODO: reduce ARC count by some amount } } void onPackageRemoved(final int uid, @NonNull final String pkgName) { final int userId = UserHandle.getUserId(uid); synchronized (mPackageToUidCache) { mPackageToUidCache.delete(userId, pkgName); } synchronized (mLock) { mUidToPackageCache.remove(uid, pkgName); for (int i = 0; i < mPkgCache.size(); ++i) { PackageInfo pkgInfo = mPkgCache.get(i); if (UserHandle.getUserId(pkgInfo.applicationInfo.uid) == userId && pkgName.equals(pkgInfo.packageName)) { mPkgCache.remove(i); break; } } mAgent.onPackageRemovedLocked(userId, pkgName); } } void onUidStateChanged(final int uid) { synchronized (mLock) { final ArraySet pkgNames = getPackagesForUidLocked(uid); if (pkgNames == null) { Slog.e(TAG, "Don't have packages for uid " + uid); } else { mAgent.onAppStatesChangedLocked(UserHandle.getUserId(uid), pkgNames); } } } void onUserAdded(final int userId) { synchronized (mLock) { mPkgCache.addAll( mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId)); mAgent.grantBirthrightsLocked(userId); } } void onUserRemoved(final int userId) { synchronized (mLock) { ArrayList removedPkgs = new ArrayList<>(); for (int i = mPkgCache.size() - 1; i >= 0; --i) { PackageInfo pkgInfo = mPkgCache.get(i); if (UserHandle.getUserId(pkgInfo.applicationInfo.uid) == userId) { removedPkgs.add(pkgInfo.packageName); mUidToPackageCache.remove(pkgInfo.applicationInfo.uid); mPkgCache.remove(i); break; } } mAgent.onUserRemovedLocked(userId, removedPkgs); } } /** * Try to increase the consumption limit if apps are reaching the current limit too quickly. */ @GuardedBy("mLock") void maybePerformQuantitativeEasingLocked() { // We don't need to increase the limit if the device runs out of consumable credits // when the battery is low. final long remainingConsumableCakes = mScribe.getRemainingConsumableCakesLocked(); if (mCurrentBatteryLevel <= QUANTITATIVE_EASING_BATTERY_THRESHOLD || remainingConsumableCakes > 0) { return; } final long currentConsumptionLimit = mScribe.getSatiatedConsumptionLimitLocked(); final long shortfall = (mCurrentBatteryLevel - QUANTITATIVE_EASING_BATTERY_THRESHOLD) * currentConsumptionLimit / 100; final long newConsumptionLimit = Math.min(currentConsumptionLimit + shortfall, mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()); if (newConsumptionLimit != currentConsumptionLimit) { Slog.i(TAG, "Increasing consumption limit from " + cakeToString(currentConsumptionLimit) + " to " + cakeToString(newConsumptionLimit)); mScribe.setConsumptionLimitLocked(newConsumptionLimit); adjustCreditSupplyLocked(/* allowIncrease */ true); } } void postAffordabilityChanged(final int userId, @NonNull final String pkgName, @NonNull Agent.ActionAffordabilityNote affordabilityNote) { if (DEBUG) { Slog.d(TAG, userId + ":" + pkgName + " affordability changed to " + affordabilityNote.isCurrentlyAffordable()); } final SomeArgs args = SomeArgs.obtain(); args.argi1 = userId; args.arg1 = pkgName; args.arg2 = affordabilityNote; mHandler.obtainMessage(MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER, args).sendToTarget(); } @GuardedBy("mLock") private void adjustCreditSupplyLocked(boolean allowIncrease) { final long newLimit = getConsumptionLimitLocked(); final long remainingConsumableCakes = mScribe.getRemainingConsumableCakesLocked(); if (remainingConsumableCakes == newLimit) { return; } if (remainingConsumableCakes > newLimit) { mScribe.adjustRemainingConsumableCakesLocked(newLimit - remainingConsumableCakes); } else if (allowIncrease) { final double perc = mCurrentBatteryLevel / 100d; final long shortfall = newLimit - remainingConsumableCakes; mScribe.adjustRemainingConsumableCakesLocked((long) (perc * shortfall)); } mAgent.onCreditSupplyChanged(); } @GuardedBy("mLock") private void processUsageEventLocked(final int userId, @NonNull UsageEvents.Event event) { if (!mIsEnabled) { return; } final String pkgName = event.getPackageName(); if (DEBUG) { Slog.d(TAG, "Processing event " + event.getEventType() + " (" + event.mInstanceId + ")" + " for " + appToString(userId, pkgName)); } final long nowElapsed = SystemClock.elapsedRealtime(); switch (event.getEventType()) { case UsageEvents.Event.ACTIVITY_RESUMED: mAgent.noteOngoingEventLocked(userId, pkgName, EconomicPolicy.REWARD_TOP_ACTIVITY, String.valueOf(event.mInstanceId), nowElapsed); break; case UsageEvents.Event.ACTIVITY_PAUSED: case UsageEvents.Event.ACTIVITY_STOPPED: case UsageEvents.Event.ACTIVITY_DESTROYED: final long now = getCurrentTimeMillis(); mAgent.stopOngoingActionLocked(userId, pkgName, EconomicPolicy.REWARD_TOP_ACTIVITY, String.valueOf(event.mInstanceId), nowElapsed, now); break; case UsageEvents.Event.USER_INTERACTION: case UsageEvents.Event.CHOOSER_ACTION: mAgent.noteInstantaneousEventLocked(userId, pkgName, EconomicPolicy.REWARD_OTHER_USER_INTERACTION, null); break; case UsageEvents.Event.NOTIFICATION_INTERRUPTION: case UsageEvents.Event.NOTIFICATION_SEEN: mAgent.noteInstantaneousEventLocked(userId, pkgName, EconomicPolicy.REWARD_NOTIFICATION_SEEN, null); break; } } @GuardedBy("mLock") private void scheduleUnusedWealthReclamationLocked() { final long now = getCurrentTimeMillis(); final long nextReclamationTime = Math.max(now + RECLAMATION_STARTUP_DELAY_MS, mScribe.getLastReclamationTimeLocked() + UNUSED_RECLAMATION_PERIOD_MS); mHandler.post(() -> { // Never call out to AlarmManager with the lock held. This sits below AM. AlarmManager alarmManager = getContext().getSystemService(AlarmManager.class); if (alarmManager != null) { alarmManager.setWindow(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + (nextReclamationTime - now), 30 * MINUTE_IN_MILLIS, ALARM_TAG_WEALTH_RECLAMATION, mUnusedWealthReclamationListener, mHandler); } else { mHandler.sendEmptyMessageDelayed( MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT, RECLAMATION_STARTUP_DELAY_MS); } }); } private int getCurrentBatteryLevel() { return mBatteryManagerInternal.getBatteryLevel(); } @Nullable @GuardedBy("mLock") private ArraySet getPackagesForUidLocked(final int uid) { ArraySet packages = mUidToPackageCache.get(uid); if (packages == null) { final String[] pkgs = mPackageManager.getPackagesForUid(uid); if (pkgs != null) { for (String pkg : pkgs) { mUidToPackageCache.add(uid, pkg); } packages = mUidToPackageCache.get(uid); } } return packages; } @GuardedBy("mLock") private void loadInstalledPackageListLocked() { mPkgCache.clear(); final UserManagerInternal userManagerInternal = LocalServices.getService(UserManagerInternal.class); final int[] userIds = userManagerInternal.getUserIds(); for (int userId : userIds) { mPkgCache.addAll( mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId)); } } private void registerListeners() { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED); filter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED); getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null); final IntentFilter pkgFilter = new IntentFilter(); pkgFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED); pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED); pkgFilter.addDataScheme("package"); getContext() .registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, pkgFilter, null, null); final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED); userFilter.addAction(Intent.ACTION_USER_ADDED); getContext() .registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, userFilter, null, null); UsageStatsManagerInternal usmi = LocalServices.getService(UsageStatsManagerInternal.class); usmi.registerListener(mSurveillanceAgent); } /** Perform long-running and/or heavy setup work. This should be called off the main thread. */ private void setupHeavyWork() { synchronized (mLock) { loadInstalledPackageListLocked(); if (mBootPhase >= PHASE_BOOT_COMPLETED && !mExemptListLoaded) { try { mExemptedApps = new ArraySet<>(mDeviceIdleController.getFullPowerWhitelist()); } catch (RemoteException e) { // Shouldn't happen. Slog.wtf(TAG, e); } mExemptListLoaded = true; } final boolean isFirstSetup = !mScribe.recordExists(); if (isFirstSetup) { mAgent.grantBirthrightsLocked(); mScribe.setConsumptionLimitLocked( mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()); } else { mScribe.loadFromDiskLocked(); if (mScribe.getSatiatedConsumptionLimitLocked() < mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit() || mScribe.getSatiatedConsumptionLimitLocked() > mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) { // Reset the consumption limit since several factors may have changed. mScribe.setConsumptionLimitLocked( mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()); } } scheduleUnusedWealthReclamationLocked(); } } private void setupEverything() { if (mBootPhase < PHASE_SYSTEM_SERVICES_READY || !mIsEnabled) { return; } synchronized (mLock) { registerListeners(); mCurrentBatteryLevel = getCurrentBatteryLevel(); mHandler.post(this::setupHeavyWork); mCompleteEconomicPolicy.setup(mConfigObserver.getAllDeviceConfigProperties()); } } private void tearDownEverything() { if (mIsEnabled) { return; } synchronized (mLock) { mAgent.tearDownLocked(); mAnalyst.tearDown(); mCompleteEconomicPolicy.tearDown(); mExemptedApps.clear(); mExemptListLoaded = false; mHandler.post(() -> { // Never call out to AlarmManager with the lock held. This sits below AM. AlarmManager alarmManager = getContext().getSystemService(AlarmManager.class); if (alarmManager != null) { alarmManager.cancel(mUnusedWealthReclamationListener); } }); mPkgCache.clear(); mScribe.tearDownLocked(); mUidToPackageCache.clear(); getContext().unregisterReceiver(mBroadcastReceiver); UsageStatsManagerInternal usmi = LocalServices.getService(UsageStatsManagerInternal.class); usmi.unregisterListener(mSurveillanceAgent); } synchronized (mPackageToUidCache) { mPackageToUidCache.clear(); } } private final class IrsHandler extends Handler { IrsHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_NOTIFY_AFFORDABILITY_CHANGE_LISTENER: { final SomeArgs args = (SomeArgs) msg.obj; final int userId = args.argi1; final String pkgName = (String) args.arg1; final Agent.ActionAffordabilityNote affordabilityNote = (Agent.ActionAffordabilityNote) args.arg2; final EconomyManagerInternal.AffordabilityChangeListener listener = affordabilityNote.getListener(); listener.onAffordabilityChanged(userId, pkgName, affordabilityNote.getActionBill(), affordabilityNote.isCurrentlyAffordable()); args.recycle(); } break; case MSG_NOTIFY_STATE_CHANGE_LISTENERS: { for (TareStateChangeListener listener : mStateChangeListeners) { listener.onTareEnabledStateChanged(mIsEnabled); } } break; case MSG_PROCESS_USAGE_EVENT: { final int userId = msg.arg1; final UsageEvents.Event event = (UsageEvents.Event) msg.obj; synchronized (mLock) { processUsageEventLocked(userId, event); } } break; case MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT: { removeMessages(MSG_SCHEDULE_UNUSED_WEALTH_RECLAMATION_EVENT); synchronized (mLock) { scheduleUnusedWealthReclamationLocked(); } } break; } } } /** * Binder stub trampoline implementation */ final class EconomyManagerStub extends IEconomyManager.Stub { /** * "dumpsys" infrastructure */ @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return; boolean dumpAll = true; if (!ArrayUtils.isEmpty(args)) { String arg = args[0]; if ("-h".equals(arg) || "--help".equals(arg)) { dumpHelp(pw); return; } else if ("-a".equals(arg)) { // -a is passed when dumping a bug report. Bug reports have a time limit for // each service dump, so we can't dump everything. dumpAll = false; } else if (arg.length() > 0 && arg.charAt(0) == '-') { pw.println("Unknown option: " + arg); return; } } final long identityToken = Binder.clearCallingIdentity(); try { dumpInternal(new IndentingPrintWriter(pw, " "), dumpAll); } finally { Binder.restoreCallingIdentity(identityToken); } } } private final class LocalService implements EconomyManagerInternal { /** * Use an extremely large value to indicate that an app can pay for a bill indefinitely. * The value set here should be large/long enough that there's no reasonable expectation * of a device operating uninterrupted (or in the exact same state) for that period of time. * We intentionally don't use Long.MAX_VALUE to avoid potential overflow if a client * doesn't check the value and just immediately adds it to the current time. */ private static final long FOREVER_MS = 27 * 365 * 24 * HOUR_IN_MILLIS; @Override public void registerAffordabilityChangeListener(int userId, @NonNull String pkgName, @NonNull AffordabilityChangeListener listener, @NonNull ActionBill bill) { if (isSystem(userId, pkgName)) { // The system's affordability never changes. return; } synchronized (mLock) { mAgent.registerAffordabilityChangeListenerLocked(userId, pkgName, listener, bill); } } @Override public void unregisterAffordabilityChangeListener(int userId, @NonNull String pkgName, @NonNull AffordabilityChangeListener listener, @NonNull ActionBill bill) { if (isSystem(userId, pkgName)) { // The system's affordability never changes. return; } synchronized (mLock) { mAgent.unregisterAffordabilityChangeListenerLocked(userId, pkgName, listener, bill); } } @Override public void registerTareStateChangeListener(@NonNull TareStateChangeListener listener) { mStateChangeListeners.add(listener); } @Override public void unregisterTareStateChangeListener(@NonNull TareStateChangeListener listener) { mStateChangeListeners.remove(listener); } @Override public boolean canPayFor(int userId, @NonNull String pkgName, @NonNull ActionBill bill) { if (!mIsEnabled) { return true; } if (isSystem(userId, pkgName)) { // The government, I mean the system, can create ARCs as it needs to in order to // operate. return true; } // TODO: take temp-allowlist into consideration long requiredBalance = 0; final List projectedActions = bill.getAnticipatedActions(); synchronized (mLock) { for (int i = 0; i < projectedActions.size(); ++i) { AnticipatedAction action = projectedActions.get(i); final Cost cost = mCompleteEconomicPolicy.getCostOfAction( action.actionId, userId, pkgName); requiredBalance += cost.price * action.numInstantaneousCalls + cost.price * (action.ongoingDurationMs / 1000); } return mAgent.getBalanceLocked(userId, pkgName) >= requiredBalance && mScribe.getRemainingConsumableCakesLocked() >= requiredBalance; } } @Override public long getMaxDurationMs(int userId, @NonNull String pkgName, @NonNull ActionBill bill) { if (!mIsEnabled) { return FOREVER_MS; } if (isSystem(userId, pkgName)) { return FOREVER_MS; } long totalCostPerSecond = 0; final List projectedActions = bill.getAnticipatedActions(); synchronized (mLock) { for (int i = 0; i < projectedActions.size(); ++i) { AnticipatedAction action = projectedActions.get(i); final Cost cost = mCompleteEconomicPolicy.getCostOfAction( action.actionId, userId, pkgName); totalCostPerSecond += cost.price; } if (totalCostPerSecond == 0) { return FOREVER_MS; } final long minBalance = Math.min( mAgent.getBalanceLocked(userId, pkgName), mScribe.getRemainingConsumableCakesLocked()); return minBalance * 1000 / totalCostPerSecond; } } @Override public boolean isEnabled() { return mIsEnabled; } @Override public void noteInstantaneousEvent(int userId, @NonNull String pkgName, int eventId, @Nullable String tag) { if (!mIsEnabled) { return; } synchronized (mLock) { mAgent.noteInstantaneousEventLocked(userId, pkgName, eventId, tag); } } @Override public void noteOngoingEventStarted(int userId, @NonNull String pkgName, int eventId, @Nullable String tag) { if (!mIsEnabled) { return; } synchronized (mLock) { final long nowElapsed = SystemClock.elapsedRealtime(); mAgent.noteOngoingEventLocked(userId, pkgName, eventId, tag, nowElapsed); } } @Override public void noteOngoingEventStopped(int userId, @NonNull String pkgName, int eventId, @Nullable String tag) { if (!mIsEnabled) { return; } final long nowElapsed = SystemClock.elapsedRealtime(); final long now = getCurrentTimeMillis(); synchronized (mLock) { mAgent.stopOngoingActionLocked(userId, pkgName, eventId, tag, nowElapsed, now); } } } private class ConfigObserver extends ContentObserver implements DeviceConfig.OnPropertiesChangedListener { private static final String KEY_DC_ENABLE_TARE = "enable_tare"; private final ContentResolver mContentResolver; ConfigObserver(Handler handler, Context context) { super(handler); mContentResolver = context.getContentResolver(); } public void start() { DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_TARE, TareHandlerThread.getExecutor(), this); mContentResolver.registerContentObserver( Settings.Global.getUriFor(Settings.Global.ENABLE_TARE), false, this); mContentResolver.registerContentObserver( Settings.Global.getUriFor(TARE_ALARM_MANAGER_CONSTANTS), false, this); mContentResolver.registerContentObserver( Settings.Global.getUriFor(TARE_JOB_SCHEDULER_CONSTANTS), false, this); onPropertiesChanged(getAllDeviceConfigProperties()); updateEnabledStatus(); } @NonNull DeviceConfig.Properties getAllDeviceConfigProperties() { // Don't want to cache the Properties object locally in case it ends up being large, // especially since it'll only be used once/infrequently (during setup or on a change). return DeviceConfig.getProperties(DeviceConfig.NAMESPACE_TARE); } @Override public void onChange(boolean selfChange, Uri uri) { if (uri.equals(Settings.Global.getUriFor(Settings.Global.ENABLE_TARE))) { updateEnabledStatus(); } else if (uri.equals(Settings.Global.getUriFor(TARE_ALARM_MANAGER_CONSTANTS)) || uri.equals(Settings.Global.getUriFor(TARE_JOB_SCHEDULER_CONSTANTS))) { updateEconomicPolicy(); } } @Override public void onPropertiesChanged(DeviceConfig.Properties properties) { boolean economicPolicyUpdated = false; synchronized (mLock) { for (String name : properties.getKeyset()) { if (name == null) { continue; } switch (name) { case KEY_DC_ENABLE_TARE: updateEnabledStatus(); break; default: if (!economicPolicyUpdated && (name.startsWith("am") || name.startsWith("js"))) { updateEconomicPolicy(); economicPolicyUpdated = true; } } } } } private void updateEnabledStatus() { // User setting should override DeviceConfig setting. // NOTE: There's currently no way for a user to reset the value (via UI), so if a user // manually toggles TARE via UI, we'll always defer to the user's current setting // TODO: add a "reset" value if the user toggle is an issue final boolean isTareEnabledDC = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TARE, KEY_DC_ENABLE_TARE, Settings.Global.DEFAULT_ENABLE_TARE == 1); final boolean isTareEnabled = Settings.Global.getInt(mContentResolver, Settings.Global.ENABLE_TARE, isTareEnabledDC ? 1 : 0) == 1; if (mIsEnabled != isTareEnabled) { mIsEnabled = isTareEnabled; if (mIsEnabled) { setupEverything(); } else { tearDownEverything(); } mHandler.sendEmptyMessage(MSG_NOTIFY_STATE_CHANGE_LISTENERS); } } private void updateEconomicPolicy() { synchronized (mLock) { final long initialLimit = mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit(); final long hardLimit = mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit(); mCompleteEconomicPolicy.tearDown(); mCompleteEconomicPolicy = new CompleteEconomicPolicy(InternalResourceService.this); if (mIsEnabled && mBootPhase >= PHASE_SYSTEM_SERVICES_READY) { mCompleteEconomicPolicy.setup(getAllDeviceConfigProperties()); if (initialLimit != mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit() || hardLimit != mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) { // Reset the consumption limit since several factors may have changed. mScribe.setConsumptionLimitLocked( mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()); } mAgent.onPricingChangedLocked(); } } } } private static void dumpHelp(PrintWriter pw) { pw.println("Resource Economy (economy) dump options:"); pw.println(" [-h|--help] [package] ..."); pw.println(" -h | --help: print this help"); pw.println(" [package] is an optional package name to limit the output to."); } private void dumpInternal(final IndentingPrintWriter pw, final boolean dumpAll) { synchronized (mLock) { pw.print("Is enabled: "); pw.println(mIsEnabled); pw.print("Current battery level: "); pw.println(mCurrentBatteryLevel); final long consumptionLimit = getConsumptionLimitLocked(); pw.print("Consumption limit (current/initial-satiated/current-satiated): "); pw.print(cakeToString(consumptionLimit)); pw.print("/"); pw.print(cakeToString(mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit())); pw.print("/"); pw.println(cakeToString(mScribe.getSatiatedConsumptionLimitLocked())); final long remainingConsumable = mScribe.getRemainingConsumableCakesLocked(); pw.print("Goods remaining: "); pw.print(cakeToString(remainingConsumable)); pw.print(" ("); pw.print(String.format("%.2f", 100f * remainingConsumable / consumptionLimit)); pw.println("% of current limit)"); pw.print("Device wealth: "); pw.println(cakeToString(mScribe.getCakesInCirculationForLoggingLocked())); pw.println(); pw.print("Exempted apps", mExemptedApps); pw.println(); pw.println(); mCompleteEconomicPolicy.dump(pw); pw.println(); mScribe.dumpLocked(pw, dumpAll); pw.println(); mAgent.dumpLocked(pw); pw.println(); mAnalyst.dump(pw); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy