From 432884126fc804724d13403c179afd9166875d56 Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Tue, 25 May 2021 18:25:34 -0700 Subject: [PATCH] Flesh out the IRS some more. Add helper methods that other parts of TARE call, and add method stubs for things that are waiting for other files to be checked in. Bug: 158300259 Test: Android builds Change-Id: I6363a92df67508fd40af80f27eba73a794b8c82a --- .../server/tare/EconomyManagerInternal.java | 2 +- .../server/tare/InternalResourceService.java | 286 +++++++++++++++++- 2 files changed, 286 insertions(+), 2 deletions(-) diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java index 6d0b4157254fd..688ad03304c70 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomyManagerInternal.java @@ -53,7 +53,7 @@ interface EconomyManagerInternal { * Return {@code true} if the app has a balance equal to or greater than the specified min * balance. */ - boolean hasBalanceAtLeast(int userId, @NonNull String pkgName, double minBalance); + boolean hasBalanceAtLeast(int userId, @NonNull String pkgName, int minBalance); /** * Note that an instantaneous event has occurred. The event must be specified in one of the diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java index d196e00872326..2ce6544af8072 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java @@ -17,10 +17,32 @@ package com.android.server.tare; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.BatteryManagerInternal; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.UserHandle; +import android.util.ArraySet; import android.util.Log; +import android.util.Slog; +import android.util.SparseSetArray; +import com.android.internal.annotations.GuardedBy; +import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.tare.EconomyManagerInternal.BalanceChangeListener; + +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 @@ -33,11 +55,82 @@ import com.android.server.SystemService; */ public class InternalResourceService extends SystemService { public static final String TAG = "TARE-IRS"; - public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + public static final boolean DEBUG = Log.isLoggable("TARE", Log.DEBUG); /** Global local for all resource economy state. */ private final Object mLock = new Object(); + private final Handler mHandler; + private final BatteryManagerInternal mBatteryManagerInternal; + private final PackageManager mPackageManager; + + @GuardedBy("mLock") + private final CopyOnWriteArraySet mBalanceChangeListeners = + new CopyOnWriteArraySet<>(); + + @NonNull + private List mPkgCache = new ArrayList<>(); + + /** Cached mapping of UIDs (for all users) to a list of packages in the UID. */ + private final SparseSetArray mUidToPackageCache = new SparseSetArray<>(); + + @GuardedBy("mLock") + private boolean mIsSetup; + // In the range [0,100] to represent 0% to 100% battery. + @GuardedBy("mLock") + private int mCurrentBatteryLevel; + + @SuppressWarnings("FieldCanBeLocal") + 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; + } + } + }; + + private static final int MSG_NOTIFY_BALANCE_CHANGE_LISTENERS = 0; + /** * Initializes the system service. *

@@ -49,6 +142,25 @@ public class InternalResourceService extends SystemService { */ public InternalResourceService(Context context) { super(context); + + mHandler = new IrsHandler(TareHandlerThread.get().getLooper()); + mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class); + mPackageManager = context.getPackageManager(); + + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BATTERY_LEVEL_CHANGED); + context.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"); + context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, pkgFilter, null, null); + final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED); + userFilter.addAction(Intent.ACTION_USER_ADDED); + context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, userFilter, null, null); + + publishLocalService(EconomyManagerInternal.class, new LocalService()); } @Override @@ -60,4 +172,176 @@ public class InternalResourceService extends SystemService { Object getLock() { return mLock; } + + @NonNull + List getInstalledPackages() { + synchronized (mLock) { + return mPkgCache; + } + } + + @Nullable + @GuardedBy("mLock") + ArraySet getPackagesForUidLocked(final int uid) { + ArraySet packages = mUidToPackageCache.get(uid); + if (packages == null) { + String[] pkgs = mPackageManager.getPackagesForUid(uid); + if (pkgs != null) { + for (String pkg : pkgs) { + mUidToPackageCache.add(uid, pkg); + } + packages = mUidToPackageCache.get(uid); + } + } + return packages; + } + + void onAppStateChanged(final int userId, @NonNull final ArraySet pkgNames) { + } + + void onBatteryLevelChanged() { + synchronized (mLock) { + final int newBatteryLevel = getCurrentBatteryLevel(); + mCurrentBatteryLevel = newBatteryLevel; + } + } + + void onDeviceStateChanged() { + } + + void onPackageAdded(final int uid, @NonNull final String pkgName) { + final int userId = UserHandle.getUserId(uid); + final PackageInfo packageInfo; + try { + packageInfo = mPackageManager.getPackageInfoAsUser(pkgName, 0, userId); + } catch (PackageManager.NameNotFoundException e) { + Slog.wtf(TAG, "PM couldn't find newly added package: " + pkgName); + return; + } + synchronized (mLock) { + mPkgCache.add(packageInfo); + mUidToPackageCache.add(uid, 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 (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; + } + } + } + } + + void onUserAdded(final int userId) { + synchronized (mLock) { + loadInstalledPackageListLocked(); + } + } + + 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; + } + } + loadInstalledPackageListLocked(); + } + } + + void postSolvencyChanged(final int userId, @NonNull final String pkgName, boolean nowSolvent) { + mHandler.obtainMessage( + MSG_NOTIFY_BALANCE_CHANGE_LISTENERS, userId, nowSolvent ? 1 : 0, pkgName) + .sendToTarget(); + } + + private int getCurrentBatteryLevel() { + return mBatteryManagerInternal.getBatteryLevel(); + } + + @GuardedBy("mLock") + private void loadInstalledPackageListLocked() { + mPkgCache = mPackageManager.getInstalledPackages(0); + } + + private void setupEconomy() { + synchronized (mLock) { + loadInstalledPackageListLocked(); + mIsSetup = true; + } + } + + private final class IrsHandler extends Handler { + IrsHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_NOTIFY_BALANCE_CHANGE_LISTENERS: + final int userId = msg.arg1; + final String pkgName = (String) msg.obj; + final boolean nowSolvent = msg.arg2 == 1; + for (BalanceChangeListener listener : mBalanceChangeListeners) { + if (nowSolvent) { + listener.onSolvent(userId, pkgName); + } else { + listener.onBankruptcy(userId, pkgName); + } + } + break; + } + } + } + + // TODO: implement + private final class LocalService implements EconomyManagerInternal { + + @Override + public void registerBalanceChangeListener(@NonNull BalanceChangeListener listener) { + } + + @Override + public void unregisterBalanceChangeListener(@NonNull BalanceChangeListener listener) { + } + + @Override + public boolean hasBalanceAtLeast(int userId, @NonNull String pkgName, int minBalance) { + return false; + } + + @Override + public void noteInstantaneousEvent(int userId, @NonNull String pkgName, + @NonNull String event, @Nullable String tag) { + } + + @Override + public void noteOngoingEventStarted(int userId, @NonNull String pkgName, + @NonNull String event, @Nullable String tag) { + } + + @Override + public void noteOngoingEventStopped(int userId, @NonNull String pkgName, + @NonNull String event, @Nullable String tag) { + } + } }