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) { + } + } }