blob: 6076eb1f69eed755f54b47e37a255f81e91493d1 [file] [log] [blame]
/*
* Copyright (C) 2022 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.am;
import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT;
import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI;
import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
import static android.app.ActivityManager.RESTRICTION_LEVEL_EXEMPTED;
import static android.app.ActivityManager.RESTRICTION_LEVEL_HIBERNATION;
import static android.app.ActivityManager.RESTRICTION_LEVEL_MAX;
import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN;
import static android.app.ActivityManager.RESTRICTION_LEVEL_UNRESTRICTED;
import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
import static android.app.ActivityManager.UID_OBSERVER_GONE;
import static android.app.ActivityManager.UID_OBSERVER_IDLE;
import static android.app.ActivityManager.UID_OBSERVER_PROCSTATE;
import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK;
import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
import static android.app.usage.UsageStatsManager.REASON_SUB_DEFAULT_UNDEFINED;
import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED;
import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_USER_FLAG_INTERACTION;
import static android.app.usage.UsageStatsManager.REASON_SUB_MASK;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYSTEM_UPDATE;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
import static android.app.usage.UsageStatsManager.reasonToString;
import static android.content.Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
import static android.os.PowerExemptionManager.REASON_ACTIVE_DEVICE_ADMIN;
import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
import static android.os.PowerExemptionManager.REASON_CARRIER_PRIVILEGED_APP;
import static android.os.PowerExemptionManager.REASON_COMPANION_DEVICE_MANAGER;
import static android.os.PowerExemptionManager.REASON_DENIED;
import static android.os.PowerExemptionManager.REASON_DEVICE_DEMO_MODE;
import static android.os.PowerExemptionManager.REASON_DEVICE_OWNER;
import static android.os.PowerExemptionManager.REASON_DISALLOW_APPS_CONTROL;
import static android.os.PowerExemptionManager.REASON_DPO_PROTECTED_APP;
import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_PLATFORM_VPN;
import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_VPN;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
import static android.os.PowerExemptionManager.REASON_PROFILE_OWNER;
import static android.os.PowerExemptionManager.REASON_ROLE_DIALER;
import static android.os.PowerExemptionManager.REASON_ROLE_EMERGENCY;
import static android.os.PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED;
import static android.os.PowerExemptionManager.REASON_SYSTEM_MODULE;
import static android.os.PowerExemptionManager.REASON_SYSTEM_UID;
import static android.os.PowerExemptionManager.getExemptionReasonForStatsd;
import static android.os.PowerExemptionManager.reasonCodeToString;
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.AppFGSTracker.foregroundServiceTypeToIndex;
import static com.android.server.am.BaseAppStateTracker.ONE_DAY;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManager.RestrictionLevel;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.IUidObserver;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageStatsManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ServiceInfo;
import android.content.pm.ServiceInfo.ForegroundServiceType;
import android.database.ContentObserver;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.AppBackgroundRestrictionsInfo;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.PowerExemptionManager.ReasonCode;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.OnPropertiesChangedListener;
import android.provider.DeviceConfig.Properties;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseArrayMap;
import android.util.TimeUtils;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.function.TriConsumer;
import com.android.server.AppStateTracker;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.am.AppBatteryTracker.ImmutableBatteryUsage;
import com.android.server.apphibernation.AppHibernationManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.usage.AppStandbyInternal;
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
/**
* This class tracks various state of the apps and mutates their restriction levels accordingly.
*/
public final class AppRestrictionController {
static final String TAG = TAG_WITH_CLASS_NAME ? "AppRestrictionController" : TAG_AM;
static final boolean DEBUG_BG_RESTRICTION_CONTROLLER = false;
/**
* The prefix for the sub-namespace of our device configs under
* the {@link android.provider.DeviceConfig#NAMESPACE_ACTIVITY_MANAGER}.
*/
static final String DEVICE_CONFIG_SUBNAMESPACE_PREFIX = "bg_";
static final int STOCK_PM_FLAGS = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE
| MATCH_DISABLED_UNTIL_USED_COMPONENTS;
/**
* Whether or not to show the foreground service manager on tapping notifications.
*/
private static final boolean ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER = true;
/**
* Whether or not to show the action to the foreground service manager when
* posting the notification for background restriction.
*/
private static final boolean ENABLE_SHOW_FGS_MANAGER_ACTION_ON_BG_RESTRICTION = false;
private static final String APP_RESTRICTION_SETTINGS_DIRNAME = "apprestriction";
private static final String APP_RESTRICTION_SETTINGS_FILENAME = "settings.xml";
private static final String TAG_SETTINGS = "settings";
private static final String ATTR_PACKAGE = "package";
private static final String ATTR_UID = "uid";
private static final String ATTR_CUR_LEVEL = "curlevel";
private static final String ATTR_LEVEL_TS = "levelts";
private static final String ATTR_REASON = "reason";
private static final String[] ROLES_IN_INTEREST = {
RoleManager.ROLE_DIALER,
RoleManager.ROLE_EMERGENCY,
};
private final Context mContext;
private final HandlerThread mBgHandlerThread;
private final BgHandler mBgHandler;
private final HandlerExecutor mBgExecutor;
// No lock is needed, as it's immutable after initialization in constructor.
private final ArrayList<BaseAppStateTracker> mAppStateTrackers = new ArrayList<>();
@VisibleForTesting
@GuardedBy("mSettingsLock")
final RestrictionSettings mRestrictionSettings = new RestrictionSettings();
private final CopyOnWriteArraySet<AppBackgroundRestrictionListener> mRestrictionListeners =
new CopyOnWriteArraySet<>();
/**
* A mapping between the UID/Pkg and its pending work which should be triggered on inactive;
* an active UID/pkg pair should have an entry here, although its pending work could be null.
*/
@GuardedBy("mSettingsLock")
private final SparseArrayMap<String, Runnable> mActiveUids = new SparseArrayMap<>();
// No lock is needed as it's accessed in bg handler thread only.
private final ArrayList<Runnable> mTmpRunnables = new ArrayList<>();
/**
* Power-save allowlisted app-ids (not including except-idle-allowlisted ones).
*/
private int[] mDeviceIdleAllowlist = new int[0]; // No lock is needed.
/**
* Power-save allowlisted app-ids (including except-idle-allowlisted ones).
*/
private int[] mDeviceIdleExceptIdleAllowlist = new int[0]; // No lock is needed.
/**
* The pre-configured system app-ids in the power-save allow list.
*
* @see #mDeviceIdleAllowlist.
*/
private final ArraySet<Integer> mSystemDeviceIdleAllowlist = new ArraySet<>();
/**
* The pre-configured system app-ids in the power-save allow list, except-idle.
*
* @see #mDeviceIdleExceptIdleAllowlist.
*/
private final ArraySet<Integer> mSystemDeviceIdleExceptIdleAllowlist = new ArraySet<>();
private final Object mLock = new Object();
private final Object mSettingsLock = new Object();
private final Injector mInjector;
private final NotificationHelper mNotificationHelper;
private final OnRoleHoldersChangedListener mRoleHolderChangedListener =
this::onRoleHoldersChanged;
/**
* The key is the UID, the value is the list of the roles it holds.
*/
@GuardedBy("mLock")
private final SparseArray<ArrayList<String>> mUidRolesMapping = new SparseArray<>();
/**
* Cache the package name and information about if it's a system module.
*/
@GuardedBy("mLock")
private final HashMap<String, Boolean> mSystemModulesCache = new HashMap<>();
/**
* The pre-config packages that are exempted from the background restrictions.
*/
ArraySet<String> mBgRestrictionExemptioFromSysConfig;
/**
* Lock specifically for bookkeeping around the carrier-privileged app set.
* Do not acquire any other locks while holding this one. Methods that
* require this lock to be held are named with a "CPL" suffix.
*/
private final Object mCarrierPrivilegedLock = new Object();
/**
* List of carrier-privileged apps that should be excluded from standby.
*/
@GuardedBy("mCarrierPrivilegedLock")
private List<String> mCarrierPrivilegedApps;
/**
* Whether or not we've loaded the restriction settings from the persistent storage.
*/
private final AtomicBoolean mRestrictionSettingsXmlLoaded = new AtomicBoolean();
final ActivityManagerService mActivityManagerService;
static final int TRACKER_TYPE_UNKNOWN = 0;
static final int TRACKER_TYPE_BATTERY = 1;
static final int TRACKER_TYPE_BATTERY_EXEMPTION = 2;
static final int TRACKER_TYPE_FGS = 3;
static final int TRACKER_TYPE_MEDIA_SESSION = 4;
static final int TRACKER_TYPE_PERMISSION = 5;
static final int TRACKER_TYPE_BROADCAST_EVENTS = 6;
static final int TRACKER_TYPE_BIND_SERVICE_EVENTS = 7;
@IntDef(prefix = { "TRACKER_TYPE_" }, value = {
TRACKER_TYPE_UNKNOWN,
TRACKER_TYPE_BATTERY,
TRACKER_TYPE_BATTERY_EXEMPTION,
TRACKER_TYPE_FGS,
TRACKER_TYPE_MEDIA_SESSION,
TRACKER_TYPE_PERMISSION,
TRACKER_TYPE_BROADCAST_EVENTS,
TRACKER_TYPE_BIND_SERVICE_EVENTS,
})
@interface TrackerType {}
private final TrackerInfo mEmptyTrackerInfo = new TrackerInfo();
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
switch (intent.getAction()) {
case Intent.ACTION_PACKAGE_ADDED: {
if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
if (uid >= 0) {
onUidAdded(uid);
}
}
}
// fall through.
case Intent.ACTION_PACKAGE_CHANGED: {
final String pkgName = intent.getData().getSchemeSpecificPart();
final String[] cmpList = intent.getStringArrayExtra(
Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
// If this is PACKAGE_ADDED (cmpList == null), or if it's a whole-package
// enable/disable event (cmpList is just the package name itself), drop
// our carrier privileged app & system-app caches and let them refresh
if (cmpList == null
|| (cmpList.length == 1 && pkgName.equals(cmpList[0]))) {
clearCarrierPrivilegedApps();
}
} break;
case Intent.ACTION_PACKAGE_FULLY_REMOVED: {
final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
final Uri data = intent.getData();
String ssp;
if (uid >= 0 && data != null
&& (ssp = data.getSchemeSpecificPart()) != null) {
onPackageRemoved(ssp, uid);
}
} break;
case Intent.ACTION_UID_REMOVED: {
if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
if (uid >= 0) {
onUidRemoved(uid);
}
}
} break;
case Intent.ACTION_USER_ADDED: {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userId >= 0) {
onUserAdded(userId);
}
} break;
case Intent.ACTION_USER_STARTED: {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userId >= 0) {
onUserStarted(userId);
}
} break;
case Intent.ACTION_USER_STOPPED: {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userId >= 0) {
onUserStopped(userId);
}
} break;
case Intent.ACTION_USER_REMOVED: {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (userId >= 0) {
onUserRemoved(userId);
}
} break;
}
}
};
private final BroadcastReceiver mBootReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
switch (intent.getAction()) {
case Intent.ACTION_LOCKED_BOOT_COMPLETED: {
onLockedBootCompleted();
} break;
}
}
};
/**
* The restriction levels that each package is on, the levels here are defined in
* {@link android.app.ActivityManager.RESTRICTION_LEVEL_*}.
*/
final class RestrictionSettings {
@GuardedBy("mSettingsLock")
final SparseArrayMap<String, PkgSettings> mRestrictionLevels = new SparseArrayMap();
final class PkgSettings {
private final String mPackageName;
private final int mUid;
private @RestrictionLevel int mCurrentRestrictionLevel;
private @RestrictionLevel int mLastRestrictionLevel;
private @CurrentTimeMillisLong long mLevelChangeTime;
private int mReason;
private @CurrentTimeMillisLong long[] mLastNotificationShownTime;
private int[] mNotificationId;
PkgSettings(String packageName, int uid) {
mPackageName = packageName;
mUid = uid;
mCurrentRestrictionLevel = mLastRestrictionLevel = RESTRICTION_LEVEL_UNKNOWN;
}
@GuardedBy("mSettingsLock")
@RestrictionLevel int update(@RestrictionLevel int level, int reason, int subReason) {
if (level != mCurrentRestrictionLevel) {
mLastRestrictionLevel = mCurrentRestrictionLevel;
mCurrentRestrictionLevel = level;
mLevelChangeTime = mInjector.currentTimeMillis();
mReason = (REASON_MAIN_MASK & reason) | (REASON_SUB_MASK & subReason);
mBgHandler.obtainMessage(BgHandler.MSG_APP_RESTRICTION_LEVEL_CHANGED,
mUid, level, mPackageName).sendToTarget();
}
return mLastRestrictionLevel;
}
@Override
@GuardedBy("mSettingsLock")
public String toString() {
final StringBuilder sb = new StringBuilder(128);
sb.append("RestrictionLevel{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(':');
sb.append(mPackageName);
sb.append('/');
sb.append(UserHandle.formatUid(mUid));
sb.append('}');
sb.append(' ');
sb.append(ActivityManager.restrictionLevelToName(mCurrentRestrictionLevel));
sb.append('(');
sb.append(reasonToString(mReason));
sb.append(')');
return sb.toString();
}
void dump(PrintWriter pw, @CurrentTimeMillisLong long now) {
synchronized (mSettingsLock) {
pw.print(toString());
if (mLastRestrictionLevel != RESTRICTION_LEVEL_UNKNOWN) {
pw.print('/');
pw.print(ActivityManager.restrictionLevelToName(mLastRestrictionLevel));
}
pw.print(" levelChange=");
TimeUtils.formatDuration(mLevelChangeTime - now, pw);
if (mLastNotificationShownTime != null) {
for (int i = 0; i < mLastNotificationShownTime.length; i++) {
if (mLastNotificationShownTime[i] > 0) {
pw.print(" lastNoti(");
pw.print(mNotificationHelper.notificationTypeToString(i));
pw.print(")=");
TimeUtils.formatDuration(mLastNotificationShownTime[i] - now, pw);
}
}
}
}
pw.print(" effectiveExemption=");
pw.print(reasonCodeToString(getBackgroundRestrictionExemptionReason(mUid)));
}
String getPackageName() {
return mPackageName;
}
int getUid() {
return mUid;
}
@GuardedBy("mSettingsLock")
@RestrictionLevel int getCurrentRestrictionLevel() {
return mCurrentRestrictionLevel;
}
@GuardedBy("mSettingsLock")
@RestrictionLevel int getLastRestrictionLevel() {
return mLastRestrictionLevel;
}
@GuardedBy("mSettingsLock")
int getReason() {
return mReason;
}
@GuardedBy("mSettingsLock")
@CurrentTimeMillisLong long getLastNotificationTime(
@NotificationHelper.NotificationType int notificationType) {
if (mLastNotificationShownTime == null) {
return 0;
}
return mLastNotificationShownTime[notificationType];
}
@GuardedBy("mSettingsLock")
void setLastNotificationTime(@NotificationHelper.NotificationType int notificationType,
@CurrentTimeMillisLong long timestamp) {
setLastNotificationTime(notificationType, timestamp, true);
}
@VisibleForTesting
@GuardedBy("mSettingsLock")
void setLastNotificationTime(@NotificationHelper.NotificationType int notificationType,
@CurrentTimeMillisLong long timestamp, boolean persist) {
if (mLastNotificationShownTime == null) {
mLastNotificationShownTime =
new long[NotificationHelper.NOTIFICATION_TYPE_LAST];
}
mLastNotificationShownTime[notificationType] = timestamp;
if (persist && mRestrictionSettingsXmlLoaded.get()) {
schedulePersistToXml(UserHandle.getUserId(mUid));
}
}
@GuardedBy("mSettingsLock")
int getNotificationId(@NotificationHelper.NotificationType int notificationType) {
if (mNotificationId == null) {
return 0;
}
return mNotificationId[notificationType];
}
@GuardedBy("mSettingsLock")
void setNotificationId(@NotificationHelper.NotificationType int notificationType,
int notificationId) {
if (mNotificationId == null) {
mNotificationId = new int[NotificationHelper.NOTIFICATION_TYPE_LAST];
}
mNotificationId[notificationType] = notificationId;
}
@VisibleForTesting
@GuardedBy("mSettingsLock")
void setLevelChangeTime(@CurrentTimeMillisLong long timestamp) {
mLevelChangeTime = timestamp;
}
@GuardedBy("mSettingsLock")
@Override
public Object clone() {
final PkgSettings newObj = new PkgSettings(mPackageName, mUid);
newObj.mCurrentRestrictionLevel = mCurrentRestrictionLevel;
newObj.mLastRestrictionLevel = mLastRestrictionLevel;
newObj.mLevelChangeTime = mLevelChangeTime;
newObj.mReason = mReason;
if (mLastNotificationShownTime != null) {
newObj.mLastNotificationShownTime = Arrays.copyOf(
mLastNotificationShownTime, mLastNotificationShownTime.length);
}
if (mNotificationId != null) {
newObj.mNotificationId = Arrays.copyOf(mNotificationId, mNotificationId.length);
}
return newObj;
}
@GuardedBy("mSettingsLock")
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (other == null || !(other instanceof PkgSettings)) {
return false;
}
final PkgSettings otherSettings = (PkgSettings) other;
return otherSettings.mUid == mUid
&& otherSettings.mCurrentRestrictionLevel == mCurrentRestrictionLevel
&& otherSettings.mLastRestrictionLevel == mLastRestrictionLevel
&& otherSettings.mLevelChangeTime == mLevelChangeTime
&& otherSettings.mReason == mReason
&& TextUtils.equals(otherSettings.mPackageName, mPackageName)
&& Arrays.equals(otherSettings.mLastNotificationShownTime,
mLastNotificationShownTime)
&& Arrays.equals(otherSettings.mNotificationId, mNotificationId);
}
}
/**
* Update the restriction level.
*
* @return The previous restriction level.
*/
@RestrictionLevel int update(String packageName, int uid, @RestrictionLevel int level,
int reason, int subReason) {
synchronized (mSettingsLock) {
PkgSettings settings = getRestrictionSettingsLocked(uid, packageName);
if (settings == null) {
settings = new PkgSettings(packageName, uid);
mRestrictionLevels.add(uid, packageName, settings);
}
return settings.update(level, reason, subReason);
}
}
/**
* @return The reason of why it's in this level.
*/
int getReason(String packageName, int uid) {
synchronized (mSettingsLock) {
final PkgSettings settings = mRestrictionLevels.get(uid, packageName);
return settings != null ? settings.getReason()
: (REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_UNDEFINED);
}
}
@RestrictionLevel int getRestrictionLevel(int uid) {
synchronized (mSettingsLock) {
final int uidKeyIndex = mRestrictionLevels.indexOfKey(uid);
if (uidKeyIndex < 0) {
return RESTRICTION_LEVEL_UNKNOWN;
}
final int numPackages = mRestrictionLevels.numElementsForKeyAt(uidKeyIndex);
if (numPackages == 0) {
return RESTRICTION_LEVEL_UNKNOWN;
}
@RestrictionLevel int level = RESTRICTION_LEVEL_UNKNOWN;
for (int i = 0; i < numPackages; i++) {
final PkgSettings setting = mRestrictionLevels.valueAt(uidKeyIndex, i);
if (setting != null) {
final @RestrictionLevel int l = setting.getCurrentRestrictionLevel();
level = (level == RESTRICTION_LEVEL_UNKNOWN) ? l : Math.min(level, l);
}
}
return level;
}
}
@RestrictionLevel int getRestrictionLevel(int uid, String packageName) {
synchronized (mSettingsLock) {
final PkgSettings settings = getRestrictionSettingsLocked(uid, packageName);
return settings == null
? getRestrictionLevel(uid) : settings.getCurrentRestrictionLevel();
}
}
@RestrictionLevel int getRestrictionLevel(String packageName, @UserIdInt int userId) {
final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
final int uid = pm.getPackageUid(packageName, STOCK_PM_FLAGS, userId);
return getRestrictionLevel(uid, packageName);
}
private @RestrictionLevel int getLastRestrictionLevel(int uid, String packageName) {
synchronized (mSettingsLock) {
final PkgSettings settings = mRestrictionLevels.get(uid, packageName);
return settings == null
? RESTRICTION_LEVEL_UNKNOWN : settings.getLastRestrictionLevel();
}
}
@GuardedBy("mSettingsLock")
void forEachPackageInUidLocked(int uid,
@NonNull TriConsumer<String, Integer, Integer> consumer) {
final int uidKeyIndex = mRestrictionLevels.indexOfKey(uid);
if (uidKeyIndex < 0) {
return;
}
final int numPackages = mRestrictionLevels.numElementsForKeyAt(uidKeyIndex);
for (int i = 0; i < numPackages; i++) {
final PkgSettings settings = mRestrictionLevels.valueAt(uidKeyIndex, i);
consumer.accept(mRestrictionLevels.keyAt(uidKeyIndex, i),
settings.getCurrentRestrictionLevel(), settings.getReason());
}
}
@GuardedBy("mSettingsLock")
void forEachUidLocked(@NonNull Consumer<Integer> consumer) {
for (int i = mRestrictionLevels.numMaps() - 1; i >= 0; i--) {
consumer.accept(mRestrictionLevels.keyAt(i));
}
}
@GuardedBy("mSettingsLock")
PkgSettings getRestrictionSettingsLocked(int uid, String packageName) {
return mRestrictionLevels.get(uid, packageName);
}
void removeUser(@UserIdInt int userId) {
synchronized (mSettingsLock) {
for (int i = mRestrictionLevels.numMaps() - 1; i >= 0; i--) {
final int uid = mRestrictionLevels.keyAt(i);
if (UserHandle.getUserId(uid) != userId) {
continue;
}
mRestrictionLevels.deleteAt(i);
}
}
}
void removePackage(String pkgName, int uid) {
removePackage(pkgName, uid, true);
}
void removePackage(String pkgName, int uid, boolean persist) {
synchronized (mSettingsLock) {
final int keyIndex = mRestrictionLevels.indexOfKey(uid);
mRestrictionLevels.delete(uid, pkgName);
if (keyIndex >= 0 && mRestrictionLevels.numElementsForKeyAt(keyIndex) == 0) {
mRestrictionLevels.deleteAt(keyIndex);
}
}
if (persist && mRestrictionSettingsXmlLoaded.get()) {
schedulePersistToXml(UserHandle.getUserId(uid));
}
}
void removeUid(int uid) {
removeUid(uid, true);
}
void removeUid(int uid, boolean persist) {
synchronized (mSettingsLock) {
mRestrictionLevels.delete(uid);
}
if (persist && mRestrictionSettingsXmlLoaded.get()) {
schedulePersistToXml(UserHandle.getUserId(uid));
}
}
@VisibleForTesting
void reset() {
synchronized (mSettingsLock) {
for (int i = mRestrictionLevels.numMaps() - 1; i >= 0; i--) {
mRestrictionLevels.deleteAt(i);
}
}
}
@VisibleForTesting
void resetToDefault() {
synchronized (mSettingsLock) {
mRestrictionLevels.forEach(settings -> {
settings.mCurrentRestrictionLevel = RESTRICTION_LEVEL_UNKNOWN;
settings.mLastRestrictionLevel = RESTRICTION_LEVEL_UNKNOWN;
settings.mLevelChangeTime = 0L;
settings.mReason = REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_UNDEFINED;
if (settings.mLastNotificationShownTime != null) {
for (int i = 0; i < settings.mLastNotificationShownTime.length; i++) {
settings.mLastNotificationShownTime[i] = 0L;
}
}
});
}
}
void dump(PrintWriter pw, String prefix) {
final ArrayList<PkgSettings> settings = new ArrayList<>();
synchronized (mSettingsLock) {
mRestrictionLevels.forEach(setting -> settings.add(setting));
}
Collections.sort(settings, Comparator.comparingInt(PkgSettings::getUid));
final long now = mInjector.currentTimeMillis();
for (int i = 0, size = settings.size(); i < size; i++) {
pw.print(prefix);
pw.print('#');
pw.print(i);
pw.print(' ');
settings.get(i).dump(pw, now);
pw.println();
}
}
@VisibleForTesting
void schedulePersistToXml(@UserIdInt int userId) {
mBgHandler.obtainMessage(BgHandler.MSG_PERSIST_RESTRICTION_SETTINGS, userId, 0)
.sendToTarget();
}
@VisibleForTesting
void scheduleLoadFromXml() {
mBgHandler.sendEmptyMessage(BgHandler.MSG_LOAD_RESTRICTION_SETTINGS);
}
@VisibleForTesting
File getXmlFileNameForUser(@UserIdInt int userId) {
final File dir = new File(mInjector.getDataSystemDeDirectory(
userId), APP_RESTRICTION_SETTINGS_DIRNAME);
return new File(dir, APP_RESTRICTION_SETTINGS_FILENAME);
}
@VisibleForTesting
void loadFromXml(boolean applyLevel) {
final int[] allUsers = mInjector.getUserManagerInternal().getUserIds();
for (int userId : allUsers) {
loadFromXml(userId, applyLevel);
}
mRestrictionSettingsXmlLoaded.set(true);
}
void loadFromXml(@UserIdInt int userId, boolean applyLevel) {
final File file = getXmlFileNameForUser(userId);
if (!file.exists()) {
return;
}
final long[] ts = new long[NotificationHelper.NOTIFICATION_TYPE_LAST];
try (InputStream in = new FileInputStream(file)) {
final TypedXmlPullParser parser = Xml.resolvePullParser(in);
final long now = SystemClock.elapsedRealtime();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String tagName = parser.getName();
if (!TAG_SETTINGS.equals(tagName)) {
Slog.w(TAG, "Unexpected tag name: " + tagName);
continue;
}
loadOneFromXml(parser, now, ts, applyLevel);
}
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Loaded from " + file);
}
} catch (IOException | XmlPullParserException e) {
}
}
private void loadOneFromXml(TypedXmlPullParser parser, long now, long[] ts,
boolean applyLevel) {
// Reset the buffer.
for (int i = 0; i < ts.length; i++) {
ts[i] = 0L;
}
// Walk through the attributes.
int uid = 0;
String packageName = null;
int curLevel = RESTRICTION_LEVEL_UNKNOWN;
int reason = REASON_MAIN_DEFAULT;
long levelTs = 0L;
for (int i = 0; i < parser.getAttributeCount(); i++) {
try {
final String attrName = parser.getAttributeName(i);
final String attrValue = parser.getAttributeValue(i);
switch (attrName) {
case ATTR_UID:
uid = Integer.parseInt(attrValue);
break;
case ATTR_PACKAGE:
packageName = attrValue;
break;
case ATTR_CUR_LEVEL:
curLevel = Integer.parseInt(attrValue);
break;
case ATTR_LEVEL_TS:
levelTs = Long.parseLong(attrValue);
break;
case ATTR_REASON:
reason = Integer.parseInt(attrValue);
break;
default:
@NotificationHelper.NotificationType int type =
NotificationHelper.notificationTimeAttrToType(attrName);
ts[type] = Long.parseLong(attrValue);
break;
}
} catch (IllegalArgumentException e) {
}
}
if (uid != 0) {
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Restoring " + packageName + "/" + UserHandle.formatUid(uid)
+ " level=" + curLevel + " reason=" + Integer.toHexString(reason)
+ " ts=" + levelTs + " noti=" + Arrays.toString(ts));
}
final PkgSettings pkgSettings;
synchronized (mSettingsLock) {
pkgSettings = getRestrictionSettingsLocked(uid, packageName);
if (pkgSettings == null) {
return;
}
for (int i = 0; i < ts.length; i++) {
if (pkgSettings.getLastNotificationTime(i) == 0 && ts[i] != 0) {
pkgSettings.setLastNotificationTime(i, ts[i], false);
}
}
if (pkgSettings.mCurrentRestrictionLevel >= curLevel) {
// The current restriction level is the same or more restrictive,
// don't restore.
return;
}
}
final int curBucket = mInjector.getAppStandbyInternal().getAppStandbyBucket(
packageName, UserHandle.getUserId(uid), now, false);
if (applyLevel) {
applyRestrictionLevel(packageName, uid, curLevel, mEmptyTrackerInfo,
curBucket, true, reason & REASON_MAIN_MASK, reason & REASON_SUB_MASK);
} else {
pkgSettings.update(curLevel,
reason & REASON_MAIN_MASK, reason & REASON_SUB_MASK);
}
synchronized (mSettingsLock) {
// Restore the mLevelChangeTime too.
pkgSettings.setLevelChangeTime(levelTs);
}
}
}
@VisibleForTesting
void persistToXml(@UserIdInt int userId) {
final File file = getXmlFileNameForUser(userId);
final File dir = file.getParentFile();
if (!dir.isDirectory() && !dir.mkdirs()) {
Slog.w(TAG, "Failed to create folder for " + userId);
return;
}
final AtomicFile atomicFile = new AtomicFile(file);
FileOutputStream stream = null;
try {
stream = atomicFile.startWrite();
stream.write(toXmlByteArray(userId));
} catch (Exception e) {
Slog.e(TAG, "Failed to write file " + file, e);
if (stream != null) {
atomicFile.failWrite(stream);
}
return;
}
atomicFile.finishWrite(stream);
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Successfully written to " + atomicFile);
}
}
private byte[] toXmlByteArray(@UserIdInt int userId) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
final TypedXmlSerializer serializer = Xml.resolveSerializer(os);
serializer.startDocument(/* encoding */ null, /* standalone */ true);
synchronized (mSettingsLock) {
for (int i = mRestrictionLevels.numMaps() - 1; i >= 0; i--) {
for (int j = mRestrictionLevels.numElementsForKeyAt(i) - 1; j >= 0; j--) {
final PkgSettings settings = mRestrictionLevels.valueAt(i, j);
final int uid = settings.getUid();
if (UserHandle.getUserId(uid) != userId) {
continue;
}
serializer.startTag(null, TAG_SETTINGS);
serializer.attributeInt(null, ATTR_UID, uid);
serializer.attribute(null, ATTR_PACKAGE, settings.getPackageName());
serializer.attributeInt(null, ATTR_CUR_LEVEL,
settings.mCurrentRestrictionLevel);
serializer.attributeLong(null, ATTR_LEVEL_TS,
settings.mLevelChangeTime);
serializer.attributeInt(null, ATTR_REASON, settings.mReason);
for (int k = 0; k < NotificationHelper.NOTIFICATION_TYPE_LAST; k++) {
serializer.attributeLong(null,
NotificationHelper.notificationTypeToTimeAttr(k),
settings.getLastNotificationTime(k));
}
serializer.endTag(null, TAG_SETTINGS);
}
}
}
serializer.endDocument();
serializer.flush();
return os.toByteArray();
} catch (IOException e) {
return null;
}
}
@VisibleForTesting
void removeXml() {
final int[] allUsers = mInjector.getUserManagerInternal().getUserIds();
for (int userId : allUsers) {
getXmlFileNameForUser(userId).delete();
}
}
@Override
public Object clone() {
final RestrictionSettings newObj = new RestrictionSettings();
synchronized (mSettingsLock) {
for (int i = mRestrictionLevels.numMaps() - 1; i >= 0; i--) {
for (int j = mRestrictionLevels.numElementsForKeyAt(i) - 1; j >= 0; j--) {
final PkgSettings settings = mRestrictionLevels.valueAt(i, j);
newObj.mRestrictionLevels.add(mRestrictionLevels.keyAt(i),
mRestrictionLevels.keyAt(i, j), (PkgSettings) settings.clone());
}
}
}
return newObj;
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (other == null || !(other instanceof RestrictionSettings)) {
return false;
}
final SparseArrayMap<String, PkgSettings> otherSettings = ((RestrictionSettings) other)
.mRestrictionLevels;
synchronized (mSettingsLock) {
if (otherSettings.numMaps() != mRestrictionLevels.numMaps()) {
return false;
}
for (int i = mRestrictionLevels.numMaps() - 1; i >= 0; i--) {
final int uid = mRestrictionLevels.keyAt(i);
if (otherSettings.numElementsForKey(uid)
!= mRestrictionLevels.numElementsForKeyAt(i)) {
return false;
}
for (int j = mRestrictionLevels.numElementsForKeyAt(i) - 1; j >= 0; j--) {
final PkgSettings settings = mRestrictionLevels.valueAt(i, j);
if (!settings.equals(otherSettings.get(uid, settings.getPackageName()))) {
return false;
}
}
}
}
return true;
}
}
final class ConstantsObserver extends ContentObserver implements
OnPropertiesChangedListener {
/**
* Whether or not to set the app to restricted standby bucket automatically
* when it's background-restricted.
*/
static final String KEY_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "auto_restricted_bucket_on_bg_restricted";
/**
* Whether or not to move the app to restricted standby level automatically
* when system detects it's abusive.
*/
static final String KEY_BG_AUTO_RESTRICT_ABUSIVE_APPS =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "auto_restrict_abusive_apps";
/**
* The minimal interval in ms before posting a notification again on abusive behaviors
* of a certain package.
*/
static final String KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "abusive_notification_minimal_interval";
/**
* The minimal interval in ms before posting a notification again on long running FGS
* from a certain package.
*/
static final String KEY_BG_LONG_FGS_NOTIFICATION_MINIMAL_INTERVAL =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "long_fgs_notification_minimal_interval";
/**
* The behavior for an app with a FGS and its notification is still showing, when the system
* detects it's abusive and should be put into bg restricted level. {@code true} - we'll
* show the prompt to user, {@code false} - we'll not show it.
*/
static final String KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "prompt_fgs_with_noti_to_bg_restricted";
/**
* The behavior for an app with a FGS and its notification is still showing, when the system
* detects it's running for a very long time, should we prompt the user.
* {@code true} - we'll show the prompt to user, {@code false} - we'll not show it.
*/
static final String KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_ON_LONG_RUNNING =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "prompt_fgs_with_noti_on_long_running";
/**
* The behavior for an app with a FGS, when the system detects it's running for
* a very long time, should we prompt the user.
* {@code true} - we'll show the prompt to user, {@code false} - we'll not show it.
*/
static final String KEY_BG_PROMPT_FGS_ON_LONG_RUNNING =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "prompt_fgs_on_long_running";
/**
* The list of packages to be exempted from all these background restrictions.
*/
static final String KEY_BG_RESTRICTION_EXEMPTED_PACKAGES =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "restriction_exempted_packages";
/**
* Whether or not to show the notification for abusive apps, i.e. when the system
* detects it's draining significant amount of battery in the background.
* {@code true} - we'll show the prompt to user, {@code false} - we'll not show it.
*/
static final String KEY_BG_PROMPT_ABUSIVE_APPS_TO_BG_RESTRICTED =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "prompt_abusive_apps_to_bg_restricted";
/**
* Default value to {@link #mBgAutoRestrictAbusiveApps}.
*/
static final boolean DEFAULT_BG_AUTO_RESTRICT_ABUSIVE_APPS = true;
/**
* Default value to {@link #mBgAutoRestrictedBucket}.
*/
static final boolean DEFAULT_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION = false;
/**
* Default value to {@link #mBgAbusiveNotificationMinIntervalMs}.
*/
static final long DEFAULT_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL_MS = 30 * ONE_DAY;
/**
* Default value to {@link #mBgAbusiveNotificationMinIntervalMs}.
*/
static final long DEFAULT_BG_LONG_FGS_NOTIFICATION_MINIMAL_INTERVAL_MS = 30 * ONE_DAY;
/**
* Default value to {@link #mBgPromptFgsWithNotiOnLongRunning}.
*/
static final boolean DEFAULT_BG_PROMPT_FGS_WITH_NOTIFICATION_ON_LONG_RUNNING = false;
/**
* Default value to {@link #mBgPromptFgsOnLongRunning}.
*/
static final boolean DEFAULT_BG_PROMPT_FGS_ON_LONG_RUNNING = true;
/**
* Default value to {@link #mBgPromptFgsWithNotiToBgRestricted}.
*/
final boolean mDefaultBgPromptFgsWithNotiToBgRestricted;
/**
* Default value to {@link #mBgPromptAbusiveAppsToBgRestricted}.
*/
final boolean mDefaultBgPromptAbusiveAppToBgRestricted;
volatile boolean mBgAutoRestrictedBucket;
volatile boolean mBgAutoRestrictAbusiveApps;
volatile boolean mRestrictedBucketEnabled;
volatile long mBgAbusiveNotificationMinIntervalMs;
volatile long mBgLongFgsNotificationMinIntervalMs;
/**
* @see #KEY_BG_RESTRICTION_EXEMPTED_PACKAGES.
*
*<p> Mutations on them would result in copy-on-write.</p>
*/
volatile Set<String> mBgRestrictionExemptedPackages = Collections.emptySet();
/**
* @see #KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED.
*/
volatile boolean mBgPromptFgsWithNotiToBgRestricted;
/**
* @see #KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_ON_LONG_RUNNING.
*/
volatile boolean mBgPromptFgsWithNotiOnLongRunning;
/**
* @see #KEY_BG_PROMPT_FGS_ON_LONG_RUNNING.
*/
volatile boolean mBgPromptFgsOnLongRunning;
/**
* @see #KEY_BG_PROMPT_ABUSIVE_APPS_TO_BG_RESTRICTED.
*/
volatile boolean mBgPromptAbusiveAppsToBgRestricted;
ConstantsObserver(Handler handler, Context context) {
super(handler);
mDefaultBgPromptFgsWithNotiToBgRestricted = context.getResources().getBoolean(
com.android.internal.R.bool.config_bg_prompt_fgs_with_noti_to_bg_restricted);
mDefaultBgPromptAbusiveAppToBgRestricted = context.getResources().getBoolean(
com.android.internal.R.bool.config_bg_prompt_abusive_apps_to_bg_restricted);
}
@Override
public void onPropertiesChanged(Properties properties) {
for (String name : properties.getKeyset()) {
if (name == null || !name.startsWith(DEVICE_CONFIG_SUBNAMESPACE_PREFIX)) {
return;
}
switch (name) {
case KEY_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION:
updateBgAutoRestrictedBucketChanged();
break;
case KEY_BG_AUTO_RESTRICT_ABUSIVE_APPS:
updateBgAutoRestrictAbusiveApps();
break;
case KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL:
updateBgAbusiveNotificationMinimalInterval();
break;
case KEY_BG_LONG_FGS_NOTIFICATION_MINIMAL_INTERVAL:
updateBgLongFgsNotificationMinimalInterval();
break;
case KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED:
updateBgPromptFgsWithNotiToBgRestricted();
break;
case KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_ON_LONG_RUNNING:
updateBgPromptFgsWithNotiOnLongRunning();
break;
case KEY_BG_PROMPT_FGS_ON_LONG_RUNNING:
updateBgPromptFgsOnLongRunning();
break;
case KEY_BG_PROMPT_ABUSIVE_APPS_TO_BG_RESTRICTED:
updateBgPromptAbusiveAppToBgRestricted();
break;
case KEY_BG_RESTRICTION_EXEMPTED_PACKAGES:
updateBgRestrictionExemptedPackages();
break;
}
AppRestrictionController.this.onPropertiesChanged(name);
}
}
@Override
public void onChange(boolean selfChange) {
updateSettings();
}
public void start() {
final ContentResolver cr = mContext.getContentResolver();
cr.registerContentObserver(Global.getUriFor(Global.ENABLE_RESTRICTED_BUCKET),
false, this);
updateSettings();
updateDeviceConfig();
}
void updateSettings() {
mRestrictedBucketEnabled = isRestrictedBucketEnabled();
}
private boolean isRestrictedBucketEnabled() {
return Global.getInt(mContext.getContentResolver(),
Global.ENABLE_RESTRICTED_BUCKET,
Global.DEFAULT_ENABLE_RESTRICTED_BUCKET) == 1;
}
void updateDeviceConfig() {
updateBgAutoRestrictedBucketChanged();
updateBgAutoRestrictAbusiveApps();
updateBgAbusiveNotificationMinimalInterval();
updateBgLongFgsNotificationMinimalInterval();
updateBgPromptFgsWithNotiToBgRestricted();
updateBgPromptFgsWithNotiOnLongRunning();
updateBgPromptFgsOnLongRunning();
updateBgPromptAbusiveAppToBgRestricted();
updateBgRestrictionExemptedPackages();
}
private void updateBgAutoRestrictedBucketChanged() {
boolean oldValue = mBgAutoRestrictedBucket;
mBgAutoRestrictedBucket = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION,
DEFAULT_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION);
if (oldValue != mBgAutoRestrictedBucket) {
dispatchAutoRestrictedBucketFeatureFlagChanged(mBgAutoRestrictedBucket);
}
}
private void updateBgAutoRestrictAbusiveApps() {
mBgAutoRestrictAbusiveApps = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_AUTO_RESTRICT_ABUSIVE_APPS,
DEFAULT_BG_AUTO_RESTRICT_ABUSIVE_APPS);
}
private void updateBgAbusiveNotificationMinimalInterval() {
mBgAbusiveNotificationMinIntervalMs = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL,
DEFAULT_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL_MS);
}
private void updateBgLongFgsNotificationMinimalInterval() {
mBgLongFgsNotificationMinIntervalMs = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_LONG_FGS_NOTIFICATION_MINIMAL_INTERVAL,
DEFAULT_BG_LONG_FGS_NOTIFICATION_MINIMAL_INTERVAL_MS);
}
private void updateBgPromptFgsWithNotiToBgRestricted() {
mBgPromptFgsWithNotiToBgRestricted = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED,
mDefaultBgPromptFgsWithNotiToBgRestricted);
}
private void updateBgPromptFgsWithNotiOnLongRunning() {
mBgPromptFgsWithNotiOnLongRunning = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_ON_LONG_RUNNING,
DEFAULT_BG_PROMPT_FGS_WITH_NOTIFICATION_ON_LONG_RUNNING);
}
private void updateBgPromptFgsOnLongRunning() {
mBgPromptFgsOnLongRunning = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_PROMPT_FGS_ON_LONG_RUNNING,
DEFAULT_BG_PROMPT_FGS_ON_LONG_RUNNING);
}
private void updateBgPromptAbusiveAppToBgRestricted() {
mBgPromptAbusiveAppsToBgRestricted = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_PROMPT_ABUSIVE_APPS_TO_BG_RESTRICTED,
mDefaultBgPromptAbusiveAppToBgRestricted);
}
private void updateBgRestrictionExemptedPackages() {
final String settings = DeviceConfig.getString(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_BG_RESTRICTION_EXEMPTED_PACKAGES,
null);
if (settings == null) {
mBgRestrictionExemptedPackages = Collections.emptySet();
return;
}
final String[] settingsList = settings.split(",");
final ArraySet<String> packages = new ArraySet<>();
for (String pkg : settingsList) {
packages.add(pkg);
}
mBgRestrictionExemptedPackages = Collections.unmodifiableSet(packages);
}
void dump(PrintWriter pw, String prefix) {
pw.print(prefix);
pw.println("BACKGROUND RESTRICTION POLICY SETTINGS:");
final String indent = " ";
prefix = indent + prefix;
pw.print(prefix);
pw.print(KEY_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION);
pw.print('=');
pw.println(mBgAutoRestrictedBucket);
pw.print(prefix);
pw.print(KEY_BG_AUTO_RESTRICT_ABUSIVE_APPS);
pw.print('=');
pw.println(mBgAutoRestrictAbusiveApps);
pw.print(prefix);
pw.print(KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL);
pw.print('=');
pw.println(mBgAbusiveNotificationMinIntervalMs);
pw.print(prefix);
pw.print(KEY_BG_LONG_FGS_NOTIFICATION_MINIMAL_INTERVAL);
pw.print('=');
pw.println(mBgLongFgsNotificationMinIntervalMs);
pw.print(prefix);
pw.print(KEY_BG_PROMPT_FGS_ON_LONG_RUNNING);
pw.print('=');
pw.println(mBgPromptFgsOnLongRunning);
pw.print(prefix);
pw.print(KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_ON_LONG_RUNNING);
pw.print('=');
pw.println(mBgPromptFgsWithNotiOnLongRunning);
pw.print(prefix);
pw.print(KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED);
pw.print('=');
pw.println(mBgPromptFgsWithNotiToBgRestricted);
pw.print(prefix);
pw.print(KEY_BG_PROMPT_ABUSIVE_APPS_TO_BG_RESTRICTED);
pw.print('=');
pw.println(mBgPromptAbusiveAppsToBgRestricted);
pw.print(prefix);
pw.print(KEY_BG_RESTRICTION_EXEMPTED_PACKAGES);
pw.print('=');
pw.println(mBgRestrictionExemptedPackages.toString());
}
}
/**
* A helper object which holds an app state tracker's type and its relevant info used for
* logging atoms to statsd.
*/
private class TrackerInfo {
final int mType; // tracker type
final byte[] mInfo; // tracker info proto object for statsd
TrackerInfo() {
mType = TRACKER_TYPE_UNKNOWN;
mInfo = null;
}
TrackerInfo(int type, byte[] info) {
mType = type;
mInfo = info;
}
}
private final ConstantsObserver mConstantsObserver;
private final AppStateTracker.BackgroundRestrictedAppListener mBackgroundRestrictionListener =
new AppStateTracker.BackgroundRestrictedAppListener() {
@Override
public void updateBackgroundRestrictedForUidPackage(int uid, String packageName,
boolean restricted) {
mBgHandler.obtainMessage(BgHandler.MSG_BACKGROUND_RESTRICTION_CHANGED,
uid, restricted ? 1 : 0, packageName).sendToTarget();
}
};
private final AppIdleStateChangeListener mAppIdleStateChangeListener =
new AppIdleStateChangeListener() {
@Override
public void onAppIdleStateChanged(String packageName, @UserIdInt int userId,
boolean idle, int bucket, int reason) {
mBgHandler.obtainMessage(BgHandler.MSG_APP_STANDBY_BUCKET_CHANGED,
userId, bucket, packageName).sendToTarget();
}
@Override
public void onUserInteractionStarted(String packageName, @UserIdInt int userId) {
mBgHandler.obtainMessage(BgHandler.MSG_USER_INTERACTION_STARTED,
userId, 0, packageName).sendToTarget();
}
};
private final IUidObserver mUidObserver =
new IUidObserver.Stub() {
@Override
public void onUidStateChanged(int uid, int procState, long procStateSeq,
int capability) {
mBgHandler.obtainMessage(BgHandler.MSG_UID_PROC_STATE_CHANGED, uid, procState)
.sendToTarget();
}
@Override
public void onUidIdle(int uid, boolean disabled) {
mBgHandler.obtainMessage(BgHandler.MSG_UID_IDLE, uid, disabled ? 1 : 0)
.sendToTarget();
}
@Override
public void onUidGone(int uid, boolean disabled) {
mBgHandler.obtainMessage(BgHandler.MSG_UID_GONE, uid, disabled ? 1 : 0)
.sendToTarget();
}
@Override
public void onUidActive(int uid) {
mBgHandler.obtainMessage(BgHandler.MSG_UID_ACTIVE, uid, 0).sendToTarget();
}
@Override
public void onUidCachedChanged(int uid, boolean cached) {
}
@Override
public void onUidProcAdjChanged(int uid) {
}
};
/**
* Register the background restriction listener callback.
*/
public void addAppBackgroundRestrictionListener(
@NonNull AppBackgroundRestrictionListener listener) {
mRestrictionListeners.add(listener);
}
AppRestrictionController(final Context context, final ActivityManagerService service) {
this(new Injector(context), service);
}
AppRestrictionController(final Injector injector, final ActivityManagerService service) {
mInjector = injector;
mContext = injector.getContext();
mActivityManagerService = service;
mBgHandlerThread = new HandlerThread("bgres-controller", THREAD_PRIORITY_BACKGROUND);
mBgHandlerThread.start();
mBgHandler = new BgHandler(mBgHandlerThread.getLooper(), injector);
mBgExecutor = new HandlerExecutor(mBgHandler);
mConstantsObserver = new ConstantsObserver(mBgHandler, mContext);
mNotificationHelper = new NotificationHelper(this);
injector.initAppStateTrackers(this);
}
void onSystemReady() {
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
mBgExecutor, mConstantsObserver);
mConstantsObserver.start();
initBgRestrictionExemptioFromSysConfig();
initRestrictionStates();
initSystemModuleNames();
initRolesInInterest();
registerForUidObservers();
registerForSystemBroadcasts();
mNotificationHelper.onSystemReady();
mInjector.getAppStateTracker().addBackgroundRestrictedAppListener(
mBackgroundRestrictionListener);
mInjector.getAppStandbyInternal().addListener(mAppIdleStateChangeListener);
mInjector.getRoleManager().addOnRoleHoldersChangedListenerAsUser(mBgExecutor,
mRoleHolderChangedListener, UserHandle.ALL);
mInjector.scheduleInitTrackers(mBgHandler, () -> {
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
mAppStateTrackers.get(i).onSystemReady();
}
});
}
@VisibleForTesting
void resetRestrictionSettings() {
synchronized (mSettingsLock) {
mRestrictionSettings.reset();
}
initRestrictionStates();
}
@VisibleForTesting
void tearDown() {
DeviceConfig.removeOnPropertiesChangedListener(mConstantsObserver);
unregisterForUidObservers();
unregisterForSystemBroadcasts();
mRestrictionSettings.removeXml();
}
private void initBgRestrictionExemptioFromSysConfig() {
final SystemConfig sysConfig = SystemConfig.getInstance();
mBgRestrictionExemptioFromSysConfig = sysConfig.getBgRestrictionExemption();
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
final ArraySet<String> exemptedPkgs = mBgRestrictionExemptioFromSysConfig;
for (int i = exemptedPkgs.size() - 1; i >= 0; i--) {
Slog.i(TAG, "bg-restriction-exemption: " + exemptedPkgs.valueAt(i));
}
}
loadAppIdsFromPackageList(sysConfig.getAllowInPowerSaveExceptIdle(),
mSystemDeviceIdleExceptIdleAllowlist);
loadAppIdsFromPackageList(sysConfig.getAllowInPowerSave(), mSystemDeviceIdleAllowlist);
}
private void loadAppIdsFromPackageList(ArraySet<String> packages, ArraySet<Integer> apps) {
final PackageManager pm = mInjector.getPackageManager();
for (int i = packages.size() - 1; i >= 0; i--) {
final String pkg = packages.valueAt(i);
try {
final ApplicationInfo ai = pm.getApplicationInfo(pkg,
PackageManager.MATCH_SYSTEM_ONLY);
if (ai == null) {
continue;
}
apps.add(UserHandle.getAppId(ai.uid));
} catch (PackageManager.NameNotFoundException e) {
}
}
}
private boolean isExemptedFromSysConfig(String packageName) {
return mBgRestrictionExemptioFromSysConfig != null
&& mBgRestrictionExemptioFromSysConfig.contains(packageName);
}
private void initRestrictionStates() {
final int[] allUsers = mInjector.getUserManagerInternal().getUserIds();
for (int userId : allUsers) {
refreshAppRestrictionLevelForUser(userId, REASON_MAIN_FORCED_BY_USER,
REASON_SUB_FORCED_USER_FLAG_INTERACTION);
}
if (!mInjector.isTest()) {
// Load the previously saved levels and update the current levels if needed.
mRestrictionSettings.scheduleLoadFromXml();
// Also save the current levels right away.
for (int userId : allUsers) {
mRestrictionSettings.schedulePersistToXml(userId);
}
}
}
private void initSystemModuleNames() {
final PackageManager pm = mInjector.getPackageManager();
final List<ModuleInfo> moduleInfos = pm.getInstalledModules(0 /* flags */);
if (moduleInfos == null) {
return;
}
synchronized (mLock) {
for (ModuleInfo info : moduleInfos) {
mSystemModulesCache.put(info.getPackageName(), Boolean.TRUE);
}
}
}
private boolean isSystemModule(String packageName) {
synchronized (mLock) {
final Boolean val = mSystemModulesCache.get(packageName);
if (val != null) {
return val.booleanValue();
}
}
// Slow path: check if the package is listed among the system modules.
final PackageManager pm = mInjector.getPackageManager();
boolean isSystemModule = false;
try {
isSystemModule = pm.getModuleInfo(packageName, 0 /* flags */) != null;
} catch (PackageManager.NameNotFoundException e) {
}
if (!isSystemModule) {
try {
final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
// Check if the package is contained in an APEX. There is no public API to properly
// check whether a given APK package comes from an APEX registered as module.
// Therefore we conservatively assume that any package scanned from an /apex path is
// a system package.
isSystemModule = pkg != null && pkg.applicationInfo.sourceDir.startsWith(
Environment.getApexDirectory().getAbsolutePath());
} catch (PackageManager.NameNotFoundException e) {
}
}
// Update the cache.
synchronized (mLock) {
mSystemModulesCache.put(packageName, isSystemModule);
}
return isSystemModule;
}
private void registerForUidObservers() {
try {
mInjector.getIActivityManager().registerUidObserver(mUidObserver,
UID_OBSERVER_ACTIVE | UID_OBSERVER_GONE | UID_OBSERVER_IDLE
| UID_OBSERVER_PROCSTATE, PROCESS_STATE_FOREGROUND_SERVICE, "android");
} catch (RemoteException e) {
// Intra-process call, it won't happen.
}
}
private void unregisterForUidObservers() {
try {
mInjector.getIActivityManager().unregisterUidObserver(mUidObserver);
} catch (RemoteException e) {
// Intra-process call, it won't happen.
}
}
/**
* Called when initializing a user.
*/
private void refreshAppRestrictionLevelForUser(@UserIdInt int userId, int reason,
int subReason) {
final List<AppStandbyInfo> appStandbyInfos = mInjector.getAppStandbyInternal()
.getAppStandbyBuckets(userId);
if (ArrayUtils.isEmpty(appStandbyInfos)) {
return;
}
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Refreshing restriction levels of user " + userId);
}
final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
for (AppStandbyInfo info: appStandbyInfos) {
final int uid = pm.getPackageUid(info.mPackageName, STOCK_PM_FLAGS, userId);
if (uid < 0) {
// Shouldn't happen.
Slog.e(TAG, "Unable to find " + info.mPackageName + "/u" + userId);
continue;
}
final Pair<Integer, TrackerInfo> levelTypePair = calcAppRestrictionLevel(
userId, uid, info.mPackageName, info.mStandbyBucket, false, false);
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Proposed restriction level of " + info.mPackageName + "/"
+ UserHandle.formatUid(uid) + ": "
+ ActivityManager.restrictionLevelToName(levelTypePair.first)
+ " " + info.mStandbyBucket);
}
applyRestrictionLevel(info.mPackageName, uid, levelTypePair.first, levelTypePair.second,
info.mStandbyBucket, true, reason, subReason);
}
}
void refreshAppRestrictionLevelForUid(int uid, int reason, int subReason,
boolean allowRequestBgRestricted) {
final String[] packages = mInjector.getPackageManager().getPackagesForUid(uid);
if (ArrayUtils.isEmpty(packages)) {
return;
}
final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
final int userId = UserHandle.getUserId(uid);
final long now = SystemClock.elapsedRealtime();
for (String pkg: packages) {
final int curBucket = appStandbyInternal.getAppStandbyBucket(pkg, userId, now, false);
final Pair<Integer, TrackerInfo> levelTypePair = calcAppRestrictionLevel(userId, uid,
pkg, curBucket, allowRequestBgRestricted, true);
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Proposed restriction level of " + pkg + "/"
+ UserHandle.formatUid(uid) + ": "
+ ActivityManager.restrictionLevelToName(levelTypePair.first));
}
applyRestrictionLevel(pkg, uid, levelTypePair.first, levelTypePair.second,
curBucket, true, reason, subReason);
}
}
private Pair<Integer, TrackerInfo> calcAppRestrictionLevel(@UserIdInt int userId, int uid,
String packageName, @UsageStatsManager.StandbyBuckets int standbyBucket,
boolean allowRequestBgRestricted, boolean calcTrackers) {
if (mInjector.getAppHibernationInternal().isHibernatingForUser(packageName, userId)) {
return new Pair<>(RESTRICTION_LEVEL_HIBERNATION, mEmptyTrackerInfo);
}
@RestrictionLevel int level;
TrackerInfo trackerInfo = null;
switch (standbyBucket) {
case STANDBY_BUCKET_EXEMPTED:
level = RESTRICTION_LEVEL_EXEMPTED;
break;
case STANDBY_BUCKET_NEVER:
level = RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
break;
case STANDBY_BUCKET_ACTIVE:
case STANDBY_BUCKET_WORKING_SET:
case STANDBY_BUCKET_FREQUENT:
case STANDBY_BUCKET_RARE:
case STANDBY_BUCKET_RESTRICTED:
default:
if (mInjector.getAppStateTracker()
.isAppBackgroundRestricted(uid, packageName)) {
return new Pair<>(RESTRICTION_LEVEL_BACKGROUND_RESTRICTED, mEmptyTrackerInfo);
}
level = mConstantsObserver.mRestrictedBucketEnabled
&& standbyBucket == STANDBY_BUCKET_RESTRICTED
? RESTRICTION_LEVEL_RESTRICTED_BUCKET
: RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
if (calcTrackers) {
Pair<Integer, TrackerInfo> levelTypePair = calcAppRestrictionLevelFromTackers(
uid, packageName, RESTRICTION_LEVEL_MAX);
@RestrictionLevel int l = levelTypePair.first;
if (l == RESTRICTION_LEVEL_EXEMPTED) {
return new Pair<>(RESTRICTION_LEVEL_EXEMPTED, levelTypePair.second);
}
if (l > level) {
level = l;
trackerInfo = levelTypePair.second;
}
if (level == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
// This level can't be entered without user consent
if (allowRequestBgRestricted) {
mBgHandler.obtainMessage(BgHandler.MSG_REQUEST_BG_RESTRICTED,
uid, 0, packageName).sendToTarget();
}
// Lower the level.
levelTypePair = calcAppRestrictionLevelFromTackers(uid, packageName,
RESTRICTION_LEVEL_BACKGROUND_RESTRICTED);
level = levelTypePair.first;
trackerInfo = levelTypePair.second;
}
}
break;
}
return new Pair<>(level, trackerInfo);
}
/**
* Ask each of the trackers for their proposed restriction levels for the given uid/package,
* and return the most restrictive level along with the type of tracker and its relevant info
* which applied this restriction level as a {@code Pair<@RestrictionLevel, TrackerInfo>}.
*
* <p>Note, it's different from the {@link #getRestrictionLevel} where it returns the least
* restrictive level. We're returning the most restrictive level here because each tracker
* monitors certain dimensions of the app, the abusive behaviors could be detected in one or
* more of these dimensions, but not necessarily all of them. </p>
*/
private Pair<Integer, TrackerInfo> calcAppRestrictionLevelFromTackers(int uid,
String packageName, @RestrictionLevel int maxLevel) {
@RestrictionLevel int level = RESTRICTION_LEVEL_UNKNOWN;
@RestrictionLevel int prevLevel = level;
BaseAppStateTracker resultTracker = null;
final boolean isRestrictedBucketEnabled = mConstantsObserver.mRestrictedBucketEnabled;
for (int i = mAppStateTrackers.size() - 1; i >= 0; i--) {
@RestrictionLevel int l = mAppStateTrackers.get(i).getPolicy()
.getProposedRestrictionLevel(packageName, uid, maxLevel);
if (!isRestrictedBucketEnabled && l == RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
l = RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
}
level = Math.max(level, l);
if (level != prevLevel) {
resultTracker = mAppStateTrackers.get(i);
prevLevel = level;
}
}
final TrackerInfo trackerInfo = resultTracker == null
? mEmptyTrackerInfo
: new TrackerInfo(resultTracker.getType(),
resultTracker.getTrackerInfoForStatsd(uid));
return new Pair<>(level, trackerInfo);
}
private static @RestrictionLevel int standbyBucketToRestrictionLevel(
@UsageStatsManager.StandbyBuckets int standbyBucket) {
switch (standbyBucket) {
case STANDBY_BUCKET_EXEMPTED:
return RESTRICTION_LEVEL_EXEMPTED;
case STANDBY_BUCKET_NEVER:
return RESTRICTION_LEVEL_BACKGROUND_RESTRICTED;
case STANDBY_BUCKET_ACTIVE:
case STANDBY_BUCKET_WORKING_SET:
case STANDBY_BUCKET_FREQUENT:
case STANDBY_BUCKET_RARE:
return RESTRICTION_LEVEL_ADAPTIVE_BUCKET;
case STANDBY_BUCKET_RESTRICTED:
return RESTRICTION_LEVEL_RESTRICTED_BUCKET;
default:
return RESTRICTION_LEVEL_UNKNOWN;
}
}
/**
* Get the restriction level of the given UID, if it hosts multiple packages,
* return least restricted one (or if any of them is exempted).
*/
@RestrictionLevel int getRestrictionLevel(int uid) {
return mRestrictionSettings.getRestrictionLevel(uid);
}
/**
* Get the restriction level of the given UID and package.
*/
@RestrictionLevel int getRestrictionLevel(int uid, String packageName) {
return mRestrictionSettings.getRestrictionLevel(uid, packageName);
}
/**
* Get the restriction level of the given package in given user id.
*/
@RestrictionLevel int getRestrictionLevel(String packageName, @UserIdInt int userId) {
return mRestrictionSettings.getRestrictionLevel(packageName, userId);
}
/**
* @return Whether or not to move the app to restricted level automatically
* when system detects it's abusive.
*/
boolean isAutoRestrictAbusiveAppEnabled() {
return mConstantsObserver.mBgAutoRestrictAbusiveApps;
}
/**
* @return The total foreground service durations for the given package/uid with given
* foreground service type, or the total durations regardless the type if the given type is 0.
*/
long getForegroundServiceTotalDurations(String packageName, int uid, long now,
@ForegroundServiceType int serviceType) {
return mInjector.getAppFGSTracker().getTotalDurations(packageName, uid, now,
foregroundServiceTypeToIndex(serviceType));
}
/**
* @return The total foreground service durations for the given uid with given
* foreground service type, or the total durations regardless the type if the given type is 0.
*/
long getForegroundServiceTotalDurations(int uid, long now,
@ForegroundServiceType int serviceType) {
return mInjector.getAppFGSTracker().getTotalDurations(uid, now,
foregroundServiceTypeToIndex(serviceType));
}
/**
* @return The foreground service durations since given timestamp for the given package/uid
* with given foreground service type, or the total durations regardless the type if the given
* type is 0.
*/
long getForegroundServiceTotalDurationsSince(String packageName, int uid, long since, long now,
@ForegroundServiceType int serviceType) {
return mInjector.getAppFGSTracker().getTotalDurationsSince(packageName, uid, since, now,
foregroundServiceTypeToIndex(serviceType));
}
/**
* @return The foreground service durations since given timestamp for the given uid with given
* foreground service type, or the total durations regardless the type if the given type is 0.
*/
long getForegroundServiceTotalDurationsSince(int uid, long since, long now,
@ForegroundServiceType int serviceType) {
return mInjector.getAppFGSTracker().getTotalDurationsSince(uid, since, now,
foregroundServiceTypeToIndex(serviceType));
}
/**
* @return The total durations for the given package/uid with active media session.
*/
long getMediaSessionTotalDurations(String packageName, int uid, long now) {
return mInjector.getAppMediaSessionTracker().getTotalDurations(packageName, uid, now);
}
/**
* @return The total durations for the given uid with active media session.
*/
long getMediaSessionTotalDurations(int uid, long now) {
return mInjector.getAppMediaSessionTracker().getTotalDurations(uid, now);
}
/**
* @return The durations since given timestamp for the given package/uid with
* active media session.
*/
long getMediaSessionTotalDurationsSince(String packageName, int uid, long since, long now) {
return mInjector.getAppMediaSessionTracker().getTotalDurationsSince(packageName, uid, since,
now);
}
/**
* @return The durations since given timestamp for the given uid with active media session.
*/
long getMediaSessionTotalDurationsSince(int uid, long since, long now) {
return mInjector.getAppMediaSessionTracker().getTotalDurationsSince(uid, since, now);
}
/**
* @return The durations over the given window, where the given package/uid has either
* foreground services with type "mediaPlayback" running, or active media session running.
*/
long getCompositeMediaPlaybackDurations(String packageName, int uid, long now, long window) {
final long since = Math.max(0, now - window);
final long mediaPlaybackDuration = Math.max(
getMediaSessionTotalDurationsSince(packageName, uid, since, now),
getForegroundServiceTotalDurationsSince(packageName, uid, since, now,
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK));
return mediaPlaybackDuration;
}
/**
* @return The durations over the given window, where the given uid has either foreground
* services with type "mediaPlayback" running, or active media session running.
*/
long getCompositeMediaPlaybackDurations(int uid, long now, long window) {
final long since = Math.max(0, now - window);
final long mediaPlaybackDuration = Math.max(
getMediaSessionTotalDurationsSince(uid, since, now),
getForegroundServiceTotalDurationsSince(uid, since, now,
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK));
return mediaPlaybackDuration;
}
/**
* @return If the given package/uid has an active foreground service running.
*/
boolean hasForegroundServices(String packageName, int uid) {
return mInjector.getAppFGSTracker().hasForegroundServices(packageName, uid);
}
/**
* @return If the given uid has an active foreground service running.
*/
boolean hasForegroundServices(int uid) {
return mInjector.getAppFGSTracker().hasForegroundServices(uid);
}
/**
* @return If the given package/uid has a foreground service notification or not.
*/
boolean hasForegroundServiceNotifications(String packageName, int uid) {
return mInjector.getAppFGSTracker().hasForegroundServiceNotifications(packageName, uid);
}
/**
* @return If the given uid has a foreground service notification or not.
*/
boolean hasForegroundServiceNotifications(int uid) {
return mInjector.getAppFGSTracker().hasForegroundServiceNotifications(uid);
}
/**
* @return The to-be-exempted battery usage of the given UID in the given duration; it could
* be considered as "exempted" due to various use cases, i.e. media playback.
*/
ImmutableBatteryUsage getUidBatteryExemptedUsageSince(int uid, long since, long now,
int types) {
return mInjector.getAppBatteryExemptionTracker()
.getUidBatteryExemptedUsageSince(uid, since, now, types);
}
/**
* @return The total battery usage of the given UID since the system boots.
*/
@NonNull ImmutableBatteryUsage getUidBatteryUsage(int uid) {
return mInjector.getUidBatteryUsageProvider().getUidBatteryUsage(uid);
}
interface UidBatteryUsageProvider {
/**
* @return The total battery usage of the given UID since the system boots.
*/
@NonNull ImmutableBatteryUsage getUidBatteryUsage(int uid);
}
void dump(PrintWriter pw, String prefix) {
pw.print(prefix);
pw.println("APP BACKGROUND RESTRICTIONS");
prefix = " " + prefix;
pw.print(prefix);
pw.println("BACKGROUND RESTRICTION LEVEL SETTINGS");
mRestrictionSettings.dump(pw, " " + prefix);
mConstantsObserver.dump(pw, " " + prefix);
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
pw.println();
mAppStateTrackers.get(i).dump(pw, prefix);
}
}
void dumpAsProto(ProtoOutputStream proto, int uid) {
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
mAppStateTrackers.get(i).dumpAsProto(proto, uid);
}
}
private int getRestrictionLevelStatsd(@RestrictionLevel int level) {
switch (level) {
case RESTRICTION_LEVEL_UNKNOWN:
return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN;
case RESTRICTION_LEVEL_UNRESTRICTED:
return AppBackgroundRestrictionsInfo.LEVEL_UNRESTRICTED;
case RESTRICTION_LEVEL_EXEMPTED:
return AppBackgroundRestrictionsInfo.LEVEL_EXEMPTED;
case RESTRICTION_LEVEL_ADAPTIVE_BUCKET:
return AppBackgroundRestrictionsInfo.LEVEL_ADAPTIVE_BUCKET;
case RESTRICTION_LEVEL_RESTRICTED_BUCKET:
return AppBackgroundRestrictionsInfo.LEVEL_RESTRICTED_BUCKET;
case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
return AppBackgroundRestrictionsInfo.LEVEL_BACKGROUND_RESTRICTED;
case RESTRICTION_LEVEL_HIBERNATION:
return AppBackgroundRestrictionsInfo.LEVEL_HIBERNATION;
default:
return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN;
}
}
private int getThresholdStatsd(int reason) {
switch (reason) {
case REASON_MAIN_FORCED_BY_SYSTEM:
return AppBackgroundRestrictionsInfo.THRESHOLD_RESTRICTED;
case REASON_MAIN_FORCED_BY_USER:
return AppBackgroundRestrictionsInfo.THRESHOLD_USER;
default:
return AppBackgroundRestrictionsInfo.THRESHOLD_UNKNOWN;
}
}
private int getTrackerTypeStatsd(@TrackerType int type) {
switch (type) {
case TRACKER_TYPE_BATTERY:
return AppBackgroundRestrictionsInfo.BATTERY_TRACKER;
case TRACKER_TYPE_BATTERY_EXEMPTION:
return AppBackgroundRestrictionsInfo.BATTERY_EXEMPTION_TRACKER;
case TRACKER_TYPE_FGS:
return AppBackgroundRestrictionsInfo.FGS_TRACKER;
case TRACKER_TYPE_MEDIA_SESSION:
return AppBackgroundRestrictionsInfo.MEDIA_SESSION_TRACKER;
case TRACKER_TYPE_PERMISSION:
return AppBackgroundRestrictionsInfo.PERMISSION_TRACKER;
case TRACKER_TYPE_BROADCAST_EVENTS:
return AppBackgroundRestrictionsInfo.BROADCAST_EVENTS_TRACKER;
case TRACKER_TYPE_BIND_SERVICE_EVENTS:
return AppBackgroundRestrictionsInfo.BIND_SERVICE_EVENTS_TRACKER;
default:
return AppBackgroundRestrictionsInfo.UNKNOWN_TRACKER;
}
}
private int getExemptionReasonStatsd(int uid, @RestrictionLevel int level) {
if (level != RESTRICTION_LEVEL_EXEMPTED) {
return AppBackgroundRestrictionsInfo.REASON_DENIED;
}
@ReasonCode final int reasonCode = getBackgroundRestrictionExemptionReason(uid);
return getExemptionReasonForStatsd(reasonCode);
}
private int getOptimizationLevelStatsd(@RestrictionLevel int level) {
switch (level) {
case RESTRICTION_LEVEL_UNKNOWN:
return AppBackgroundRestrictionsInfo.UNKNOWN;
case RESTRICTION_LEVEL_UNRESTRICTED:
return AppBackgroundRestrictionsInfo.NOT_OPTIMIZED;
case RESTRICTION_LEVEL_ADAPTIVE_BUCKET:
return AppBackgroundRestrictionsInfo.OPTIMIZED;
case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED:
return AppBackgroundRestrictionsInfo.BACKGROUND_RESTRICTED;
default:
return AppBackgroundRestrictionsInfo.UNKNOWN;
}
}
@SuppressWarnings("AndroidFrameworkCompatChange")
private int getTargetSdkStatsd(String packageName) {
final PackageManager pm = mInjector.getPackageManager();
if (pm == null) {
return AppBackgroundRestrictionsInfo.SDK_UNKNOWN;
}
try {
final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
if (pkg == null || pkg.applicationInfo == null) {
return AppBackgroundRestrictionsInfo.SDK_UNKNOWN;
}
final int targetSdk = pkg.applicationInfo.targetSdkVersion;
if (targetSdk < Build.VERSION_CODES.S) {
return AppBackgroundRestrictionsInfo.SDK_PRE_S;
} else if (targetSdk < Build.VERSION_CODES.TIRAMISU) {
return AppBackgroundRestrictionsInfo.SDK_S;
} else if (targetSdk == Build.VERSION_CODES.TIRAMISU) {
return AppBackgroundRestrictionsInfo.SDK_T;
} else {
return AppBackgroundRestrictionsInfo.SDK_UNKNOWN;
}
} catch (PackageManager.NameNotFoundException ignored) {
}
return AppBackgroundRestrictionsInfo.SDK_UNKNOWN;
}
private void applyRestrictionLevel(String pkgName, int uid,
@RestrictionLevel int level, TrackerInfo trackerInfo,
int curBucket, boolean allowUpdateBucket, int reason, int subReason) {
int curLevel;
int prevReason;
final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
if (trackerInfo == null) {
trackerInfo = mEmptyTrackerInfo;
}
synchronized (mSettingsLock) {
curLevel = getRestrictionLevel(uid, pkgName);
if (curLevel == level) {
// Nothing to do.
return;
}
final int levelOfBucket = standbyBucketToRestrictionLevel(curBucket);
if (levelOfBucket == level) {
// If the standby bucket yield the same level, use the reason from standby bucket.
final int bucketReason = appStandbyInternal.getAppStandbyBucketReason(
pkgName, UserHandle.getUserId(uid), SystemClock.elapsedRealtime());
if (bucketReason != 0) {
reason = bucketReason & REASON_MAIN_MASK;
subReason = bucketReason & REASON_SUB_MASK;
}
}
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Updating the restriction level of " + pkgName + "/"
+ UserHandle.formatUid(uid) + " from "
+ ActivityManager.restrictionLevelToName(curLevel) + " to "
+ ActivityManager.restrictionLevelToName(level)
+ " reason=" + reason + ", subReason=" + subReason);
}
prevReason = mRestrictionSettings.getReason(pkgName, uid);
mRestrictionSettings.update(pkgName, uid, level, reason, subReason);
}
if (!allowUpdateBucket || curBucket == STANDBY_BUCKET_EXEMPTED) {
return;
}
if (level >= RESTRICTION_LEVEL_RESTRICTED_BUCKET
&& curLevel < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
if (!mConstantsObserver.mRestrictedBucketEnabled) {
return;
}
// Moving the app standby bucket to restricted in the meanwhile.
if (DEBUG_BG_RESTRICTION_CONTROLLER
&& level == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
Slog.i(TAG, pkgName + "/" + UserHandle.formatUid(uid)
+ " is bg-restricted, moving to restricted standby bucket");
}
if (curBucket != STANDBY_BUCKET_RESTRICTED
&& (mConstantsObserver.mBgAutoRestrictedBucket
|| level == RESTRICTION_LEVEL_RESTRICTED_BUCKET)) {
// restrict the app if it hasn't done so.
boolean doIt = true;
synchronized (mSettingsLock) {
final int index = mActiveUids.indexOfKey(uid, pkgName);
if (index >= 0) {
// It's currently active, enqueue it.
final int localReason = reason;
final int localSubReason = subReason;
final TrackerInfo localTrackerInfo = trackerInfo;
mActiveUids.add(uid, pkgName, () -> {
appStandbyInternal.restrictApp(pkgName, UserHandle.getUserId(uid),
localReason, localSubReason);
logAppBackgroundRestrictionInfo(pkgName, uid, curLevel, level,
localTrackerInfo, localReason);
});
doIt = false;
}
}
if (doIt) {
appStandbyInternal.restrictApp(pkgName, UserHandle.getUserId(uid),
reason, subReason);
logAppBackgroundRestrictionInfo(pkgName, uid, curLevel, level, trackerInfo,
reason);
}
}
} else if (curLevel >= RESTRICTION_LEVEL_RESTRICTED_BUCKET
&& level < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
// Moved out of the background-restricted state.
synchronized (mSettingsLock) {
final int index = mActiveUids.indexOfKey(uid, pkgName);
if (index >= 0) {
mActiveUids.add(uid, pkgName, null);
}
}
appStandbyInternal.maybeUnrestrictApp(pkgName, UserHandle.getUserId(uid),
prevReason & REASON_MAIN_MASK, prevReason & REASON_SUB_MASK,
reason, subReason);
logAppBackgroundRestrictionInfo(pkgName, uid, curLevel, level, trackerInfo,
reason);
}
}
private void logAppBackgroundRestrictionInfo(String pkgName, int uid,
@RestrictionLevel int prevLevel, @RestrictionLevel int level,
@NonNull TrackerInfo trackerInfo, int reason) {
FrameworkStatsLog.write(FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO, uid,
getRestrictionLevelStatsd(level),
getThresholdStatsd(reason),
getTrackerTypeStatsd(trackerInfo.mType),
trackerInfo.mType == TRACKER_TYPE_FGS ? trackerInfo.mInfo : null,
trackerInfo.mType == TRACKER_TYPE_BATTERY ? trackerInfo.mInfo : null,
trackerInfo.mType == TRACKER_TYPE_BROADCAST_EVENTS ? trackerInfo.mInfo : null,
trackerInfo.mType == TRACKER_TYPE_BIND_SERVICE_EVENTS ? trackerInfo.mInfo : null,
getExemptionReasonStatsd(uid, level),
getOptimizationLevelStatsd(level),
getTargetSdkStatsd(pkgName),
ActivityManager.isLowRamDeviceStatic(),
getRestrictionLevelStatsd(prevLevel));
}
private void handleBackgroundRestrictionChanged(int uid, String pkgName, boolean restricted) {
// Firstly, notify the trackers.
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
mAppStateTrackers.get(i)
.onBackgroundRestrictionChanged(uid, pkgName, restricted);
}
final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
final int userId = UserHandle.getUserId(uid);
final long now = SystemClock.elapsedRealtime();
final int curBucket = appStandbyInternal.getAppStandbyBucket(pkgName, userId, now, false);
if (restricted) {
// The app could fall into the background restricted with user consent only,
// so set the reason to it.
applyRestrictionLevel(pkgName, uid, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED,
mEmptyTrackerInfo, curBucket, true, REASON_MAIN_FORCED_BY_USER,
REASON_SUB_FORCED_USER_FLAG_INTERACTION);
mBgHandler.obtainMessage(BgHandler.MSG_CANCEL_REQUEST_BG_RESTRICTED, uid, 0, pkgName)
.sendToTarget();
} else {
// Moved out of the background-restricted state, we'd need to check if it should
// stay in the restricted standby bucket.
final @RestrictionLevel int lastLevel =
mRestrictionSettings.getLastRestrictionLevel(uid, pkgName);
final int tentativeBucket = curBucket == STANDBY_BUCKET_EXEMPTED
? STANDBY_BUCKET_EXEMPTED
: (lastLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET
? STANDBY_BUCKET_RESTRICTED : STANDBY_BUCKET_RARE);
final Pair<Integer, TrackerInfo> levelTypePair = calcAppRestrictionLevel(
UserHandle.getUserId(uid), uid, pkgName, tentativeBucket, false, true);
applyRestrictionLevel(pkgName, uid, levelTypePair.first, levelTypePair.second,
curBucket, true, REASON_MAIN_USAGE, REASON_SUB_USAGE_USER_INTERACTION);
}
}
private void dispatchAppRestrictionLevelChanges(int uid, String pkgName,
@RestrictionLevel int newLevel) {
mRestrictionListeners.forEach(
l -> l.onRestrictionLevelChanged(uid, pkgName, newLevel));
}
private void dispatchAutoRestrictedBucketFeatureFlagChanged(boolean newValue) {
final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
final ArrayList<Runnable> pendingTasks = new ArrayList<>();
synchronized (mSettingsLock) {
mRestrictionSettings.forEachUidLocked(uid -> {
mRestrictionSettings.forEachPackageInUidLocked(uid, (pkgName, level, reason) -> {
if (level == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
pendingTasks.add(newValue
? () -> appStandbyInternal.restrictApp(pkgName,
UserHandle.getUserId(uid), reason & REASON_MAIN_MASK,
reason & REASON_SUB_MASK)
: () -> appStandbyInternal.maybeUnrestrictApp(pkgName,
UserHandle.getUserId(uid), reason & REASON_MAIN_MASK,
reason & REASON_SUB_MASK, REASON_MAIN_USAGE,
REASON_SUB_USAGE_SYSTEM_UPDATE));
}
});
});
}
for (int i = 0; i < pendingTasks.size(); i++) {
pendingTasks.get(i).run();
}
mRestrictionListeners.forEach(
l -> l.onAutoRestrictedBucketFeatureFlagChanged(newValue));
}
private void handleAppStandbyBucketChanged(int bucket, String packageName,
@UserIdInt int userId) {
final int uid = mInjector.getPackageManagerInternal().getPackageUid(
packageName, STOCK_PM_FLAGS, userId);
final Pair<Integer, TrackerInfo> levelTypePair = calcAppRestrictionLevel(
userId, uid, packageName, bucket, false, false);
applyRestrictionLevel(packageName, uid, levelTypePair.first, levelTypePair.second,
bucket, false, REASON_MAIN_DEFAULT, REASON_SUB_DEFAULT_UNDEFINED);
}
void handleRequestBgRestricted(String packageName, int uid) {
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Requesting background restricted " + packageName + " "
+ UserHandle.formatUid(uid));
}
mNotificationHelper.postRequestBgRestrictedIfNecessary(packageName, uid);
}
void handleCancelRequestBgRestricted(String packageName, int uid) {
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Cancelling requesting background restricted " + packageName + " "
+ UserHandle.formatUid(uid));
}
mNotificationHelper.cancelRequestBgRestrictedIfNecessary(packageName, uid);
}
void handleUidProcStateChanged(int uid, int procState) {
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
mAppStateTrackers.get(i).onUidProcStateChanged(uid, procState);
}
}
void handleUidGone(int uid) {
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
mAppStateTrackers.get(i).onUidGone(uid);
}
}
static class NotificationHelper {
static final String PACKAGE_SCHEME = "package";
static final String GROUP_KEY = "com.android.app.abusive_bg_apps";
static final int SUMMARY_NOTIFICATION_ID = SystemMessage.NOTE_ABUSIVE_BG_APPS_BASE;
static final int NOTIFICATION_TYPE_ABUSIVE_CURRENT_DRAIN = 0;
static final int NOTIFICATION_TYPE_LONG_RUNNING_FGS = 1;
static final int NOTIFICATION_TYPE_LAST = 2;
static final String ATTR_LAST_BATTERY_NOTIFICATION_TIME = "last_batt_noti_ts";
static final String ATTR_LAST_LONG_FGS_NOTIFICATION_TIME = "last_long_fgs_noti_ts";
@IntDef(prefix = { "NOTIFICATION_TYPE_"}, value = {
NOTIFICATION_TYPE_ABUSIVE_CURRENT_DRAIN,
NOTIFICATION_TYPE_LONG_RUNNING_FGS,
})
@Retention(RetentionPolicy.SOURCE)
static @interface NotificationType{}
static final String[] NOTIFICATION_TYPE_STRINGS = {
"Abusive current drain",
"Long-running FGS",
};
static final String[] NOTIFICATION_TIME_ATTRS = {
ATTR_LAST_BATTERY_NOTIFICATION_TIME,
ATTR_LAST_LONG_FGS_NOTIFICATION_TIME,
};
static @NotificationType int notificationTimeAttrToType(@NonNull String attr) {
switch (attr) {
case ATTR_LAST_BATTERY_NOTIFICATION_TIME:
return NOTIFICATION_TYPE_ABUSIVE_CURRENT_DRAIN;
case ATTR_LAST_LONG_FGS_NOTIFICATION_TIME:
return NOTIFICATION_TYPE_LONG_RUNNING_FGS;
}
throw new IllegalArgumentException();
}
static @NonNull String notificationTypeToTimeAttr(@NotificationType int type) {
return NOTIFICATION_TIME_ATTRS[type];
}
static final String ACTION_FGS_MANAGER_TRAMPOLINE =
"com.android.server.am.ACTION_FGS_MANAGER_TRAMPOLINE";
static String notificationTypeToString(@NotificationType int notificationType) {
return NOTIFICATION_TYPE_STRINGS[notificationType];
}
private final AppRestrictionController mBgController;
private final NotificationManager mNotificationManager;
private final Injector mInjector;
private final Object mLock;
private final Object mSettingsLock;
private final Context mContext;
private final BroadcastReceiver mActionButtonReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
switch (intent.getAction()) {
case ACTION_FGS_MANAGER_TRAMPOLINE:
final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
final int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
cancelRequestBgRestrictedIfNecessary(packageName, uid);
final Intent newIntent = new Intent(ACTION_SHOW_FOREGROUND_SERVICE_MANAGER);
newIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
// Task manager runs in SystemUI, which is SYSTEM user only.
mContext.sendBroadcastAsUser(newIntent, UserHandle.SYSTEM);
break;
}
}
};
@GuardedBy("mSettingsLock")
private int mNotificationIDStepper = SUMMARY_NOTIFICATION_ID + 1;
NotificationHelper(AppRestrictionController controller) {
mBgController = controller;
mInjector = controller.mInjector;
mNotificationManager = mInjector.getNotificationManager();
mLock = controller.mLock;
mSettingsLock = controller.mSettingsLock;
mContext = mInjector.getContext();
}
void onSystemReady() {
mContext.registerReceiverForAllUsers(mActionButtonReceiver,
new IntentFilter(ACTION_FGS_MANAGER_TRAMPOLINE),
MANAGE_ACTIVITY_TASKS, mBgController.mBgHandler, Context.RECEIVER_NOT_EXPORTED);
}
void postRequestBgRestrictedIfNecessary(String packageName, int uid) {
if (!mBgController.mConstantsObserver.mBgPromptAbusiveAppsToBgRestricted) {
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Not requesting bg-restriction due to config");
}
return;
}
final Intent intent = new Intent(Settings.ACTION_VIEW_ADVANCED_POWER_USAGE_DETAIL);
intent.setData(Uri.fromParts(PACKAGE_SCHEME, packageName, null));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(mContext, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE, null,
UserHandle.of(UserHandle.getUserId(uid)));
Notification.Action[] actions = null;
final boolean hasForegroundServices =
mBgController.hasForegroundServices(packageName, uid);
final boolean hasForegroundServiceNotifications =
mBgController.hasForegroundServiceNotifications(packageName, uid);
if (!mBgController.mConstantsObserver.mBgPromptFgsWithNotiToBgRestricted) {
// We're not going to prompt the user if the FGS is active and its notification
// is still showing (not dismissed/silenced/denied).
if (hasForegroundServices && hasForegroundServiceNotifications) {
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Not requesting bg-restriction due to FGS with notification");
}
return;
}
}
if (ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER
&& ENABLE_SHOW_FGS_MANAGER_ACTION_ON_BG_RESTRICTION
&& hasForegroundServices) {
final Intent trampoline = new Intent(ACTION_FGS_MANAGER_TRAMPOLINE);
trampoline.setPackage("android");
trampoline.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
trampoline.putExtra(Intent.EXTRA_UID, uid);
final PendingIntent fgsMgrTrampoline = PendingIntent.getBroadcastAsUser(
mContext, 0, trampoline,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE,
UserHandle.CURRENT);
actions = new Notification.Action[] {
new Notification.Action.Builder(null,
mContext.getString(
com.android.internal.R.string.notification_action_check_bg_apps),
fgsMgrTrampoline)
.build()
};
}
postNotificationIfNecessary(NOTIFICATION_TYPE_ABUSIVE_CURRENT_DRAIN,
com.android.internal.R.string.notification_title_abusive_bg_apps,
com.android.internal.R.string.notification_content_abusive_bg_apps,
pendingIntent, packageName, uid, actions);
}
void postLongRunningFgsIfNecessary(String packageName, int uid) {
// Log the event in statsd.
FrameworkStatsLog.write(FrameworkStatsLog.APP_BACKGROUND_RESTRICTIONS_INFO,
uid,
mBgController.getRestrictionLevel(uid),
AppBackgroundRestrictionsInfo.THRESHOLD_UNKNOWN,
AppBackgroundRestrictionsInfo.FGS_TRACKER,
mInjector.getAppFGSTracker().getTrackerInfoForStatsd(uid),
null, // BatteryTrackerInfo
null, // BroadcastEventsTrackerInfo
null, // BindServiceEventsTrackerInfo
getExemptionReasonForStatsd(
mBgController.getBackgroundRestrictionExemptionReason(uid)),
AppBackgroundRestrictionsInfo.UNKNOWN, // OptimizationLevel
AppBackgroundRestrictionsInfo.SDK_UNKNOWN, // TargetSdk
ActivityManager.isLowRamDeviceStatic(),
mBgController.getRestrictionLevel(uid));
PendingIntent pendingIntent;
if (!mBgController.mConstantsObserver.mBgPromptFgsOnLongRunning) {
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Long-running FGS prompt is disabled.");
}
return;
}
if (!mBgController.mConstantsObserver.mBgPromptFgsWithNotiOnLongRunning
&& mBgController.hasForegroundServiceNotifications(packageName, uid)) {
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Not prompt long-running due to FGS with notification");
}
return;
}
if (ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER) {
final Intent intent = new Intent(ACTION_SHOW_FOREGROUND_SERVICE_MANAGER);
intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
// Task manager runs in SystemUI, which is SYSTEM user only.
pendingIntent = PendingIntent.getBroadcastAsUser(mContext, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE,
UserHandle.SYSTEM);
} else {
final Intent intent = new Intent(Settings.ACTION_VIEW_ADVANCED_POWER_USAGE_DETAIL);
intent.setData(Uri.fromParts(PACKAGE_SCHEME, packageName, null));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
pendingIntent = PendingIntent.getActivityAsUser(mContext, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE,
null, UserHandle.of(UserHandle.getUserId(uid)));
}
postNotificationIfNecessary(NOTIFICATION_TYPE_LONG_RUNNING_FGS,
com.android.internal.R.string.notification_title_long_running_fgs,
com.android.internal.R.string.notification_content_long_running_fgs,
pendingIntent, packageName, uid, null);
}
long getNotificationMinInterval(@NotificationType int notificationType) {
switch (notificationType) {
case NOTIFICATION_TYPE_ABUSIVE_CURRENT_DRAIN:
return mBgController.mConstantsObserver.mBgAbusiveNotificationMinIntervalMs;
case NOTIFICATION_TYPE_LONG_RUNNING_FGS:
return mBgController.mConstantsObserver.mBgLongFgsNotificationMinIntervalMs;
default:
return 0L;
}
}
int getNotificationIdIfNecessary(@NotificationType int notificationType,
String packageName, int uid) {
synchronized (mSettingsLock) {
final RestrictionSettings.PkgSettings settings = mBgController.mRestrictionSettings
.getRestrictionSettingsLocked(uid, packageName);
if (settings == null) {
return 0;
}
final long now = mInjector.currentTimeMillis();
final long lastNotificationShownTime =
settings.getLastNotificationTime(notificationType);
if (lastNotificationShownTime != 0 && (lastNotificationShownTime
+ getNotificationMinInterval(notificationType) > now)) {
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Not showing notification as last notification was shown "
+ TimeUtils.formatDuration(now - lastNotificationShownTime)
+ " ago");
}
return 0;
}
settings.setLastNotificationTime(notificationType, now);
int notificationId = settings.getNotificationId(notificationType);
if (notificationId <= 0) {
notificationId = mNotificationIDStepper++;
settings.setNotificationId(notificationType, notificationId);
}
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Showing notification for " + packageName
+ "/" + UserHandle.formatUid(uid)
+ ", id=" + notificationId
+ ", now=" + now
+ ", lastShown=" + lastNotificationShownTime);
}
return notificationId;
}
}
void postNotificationIfNecessary(@NotificationType int notificationType, int titleRes,
int messageRes, PendingIntent pendingIntent, String packageName, int uid,
@Nullable Notification.Action[] actions) {
int notificationId = getNotificationIdIfNecessary(notificationType, packageName, uid);
if (notificationId <= 0) {
return;
}
final PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
final PackageManager pm = mInjector.getPackageManager();
final ApplicationInfo ai = pmi.getApplicationInfo(packageName, STOCK_PM_FLAGS,
SYSTEM_UID, UserHandle.getUserId(uid));
final String title = mContext.getString(titleRes);
final String message = mContext.getString(messageRes,
ai != null ? ai.loadLabel(pm) : packageName);
final Icon icon = ai != null ? Icon.createWithResource(packageName, ai.icon) : null;
postNotification(notificationId, packageName, uid, title, message, icon, pendingIntent,
actions);
}
void postNotification(int notificationId, String packageName, int uid, String title,
String message, Icon icon, PendingIntent pendingIntent,
@Nullable Notification.Action[] actions) {
final UserHandle targetUser = UserHandle.of(UserHandle.getUserId(uid));
postSummaryNotification(targetUser);
final Notification.Builder notificationBuilder = new Notification.Builder(mContext,
ABUSIVE_BACKGROUND_APPS)
.setAutoCancel(true)
.setGroup(GROUP_KEY)
.setWhen(mInjector.currentTimeMillis())
.setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
.setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color))
.setContentTitle(title)
.setContentText(message)
.setContentIntent(pendingIntent);
if (icon != null) {
notificationBuilder.setLargeIcon(icon);
}
if (actions != null) {
for (Notification.Action action : actions) {
notificationBuilder.addAction(action);
}
}
final Notification notification = notificationBuilder.build();
// Remember the package name for testing.
notification.extras.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
mNotificationManager.notifyAsUser(null, notificationId, notification, targetUser);
}
private void postSummaryNotification(@NonNull UserHandle targetUser) {
final Notification summary = new Notification.Builder(mContext,
ABUSIVE_BACKGROUND_APPS)
.setGroup(GROUP_KEY)
.setGroupSummary(true)
.setStyle(new Notification.BigTextStyle())
.setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
.setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color))
.build();
mNotificationManager.notifyAsUser(null, SUMMARY_NOTIFICATION_ID, summary, targetUser);
}
void cancelRequestBgRestrictedIfNecessary(String packageName, int uid) {
synchronized (mSettingsLock) {
final RestrictionSettings.PkgSettings settings = mBgController.mRestrictionSettings
.getRestrictionSettingsLocked(uid, packageName);
if (settings != null) {
final int notificationId =
settings.getNotificationId(NOTIFICATION_TYPE_ABUSIVE_CURRENT_DRAIN);
if (notificationId > 0) {
mNotificationManager.cancel(notificationId);
}
}
}
}
void cancelLongRunningFGSNotificationIfNecessary(String packageName, int uid) {
synchronized (mSettingsLock) {
final RestrictionSettings.PkgSettings settings = mBgController.mRestrictionSettings
.getRestrictionSettingsLocked(uid, packageName);
if (settings != null) {
final int notificationId =
settings.getNotificationId(NOTIFICATION_TYPE_LONG_RUNNING_FGS);
if (notificationId > 0) {
mNotificationManager.cancel(notificationId);
}
}
}
}
}
void handleUidInactive(int uid, boolean disabled) {
final ArrayList<Runnable> pendingTasks = mTmpRunnables;
synchronized (mSettingsLock) {
final int index = mActiveUids.indexOfKey(uid);
if (index < 0) {
return;
}
final int numPackages = mActiveUids.numElementsForKeyAt(index);
for (int i = 0; i < numPackages; i++) {
final Runnable pendingTask = mActiveUids.valueAt(index, i);
if (pendingTask != null) {
pendingTasks.add(pendingTask);
}
}
mActiveUids.deleteAt(index);
}
for (int i = 0, size = pendingTasks.size(); i < size; i++) {
pendingTasks.get(i).run();
}
pendingTasks.clear();
}
void handleUidActive(int uid) {
synchronized (mSettingsLock) {
final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
final int userId = UserHandle.getUserId(uid);
mRestrictionSettings.forEachPackageInUidLocked(uid, (pkgName, level, reason) -> {
if (mConstantsObserver.mBgAutoRestrictedBucket
&& level == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) {
mActiveUids.add(uid, pkgName, () -> appStandbyInternal.restrictApp(pkgName,
userId, reason & REASON_MAIN_MASK, reason & REASON_SUB_MASK));
} else {
mActiveUids.add(uid, pkgName, null);
}
});
}
}
boolean isOnDeviceIdleAllowlist(int uid) {
final int appId = UserHandle.getAppId(uid);
return Arrays.binarySearch(mDeviceIdleAllowlist, appId) >= 0
|| Arrays.binarySearch(mDeviceIdleExceptIdleAllowlist, appId) >= 0;
}
boolean isOnSystemDeviceIdleAllowlist(int uid) {
final int appId = UserHandle.getAppId(uid);
return mSystemDeviceIdleAllowlist.contains(appId)
|| mSystemDeviceIdleExceptIdleAllowlist.contains(appId);
}
void setDeviceIdleAllowlist(int[] allAppids, int[] exceptIdleAppids) {
mDeviceIdleAllowlist = allAppids;
mDeviceIdleExceptIdleAllowlist = exceptIdleAppids;
}
/**
* @return The reason code of whether or not the given UID should be exempted from background
* restrictions here.
*
* <p>
* Note: Call it with caution as it'll try to acquire locks in other services.
* </p>
*/
@ReasonCode
int getBackgroundRestrictionExemptionReason(int uid) {
if (UserHandle.isCore(uid)) {
return REASON_SYSTEM_UID;
}
if (isOnSystemDeviceIdleAllowlist(uid)) {
return REASON_SYSTEM_ALLOW_LISTED;
}
if (isOnDeviceIdleAllowlist(uid)) {
return REASON_ALLOWLISTED_PACKAGE;
}
final ActivityManagerInternal am = mInjector.getActivityManagerInternal();
if (am.isAssociatedCompanionApp(UserHandle.getUserId(uid), uid)) {
return REASON_COMPANION_DEVICE_MANAGER;
}
if (UserManager.isDeviceInDemoMode(mContext)) {
return REASON_DEVICE_DEMO_MODE;
}
final int userId = UserHandle.getUserId(uid);
if (mInjector.getUserManagerInternal()
.hasUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userId)) {
return REASON_DISALLOW_APPS_CONTROL;
}
if (am.isDeviceOwner(uid)) {
return REASON_DEVICE_OWNER;
}
if (am.isProfileOwner(uid)) {
return REASON_PROFILE_OWNER;
}
final int uidProcState = am.getUidProcessState(uid);
if (uidProcState <= PROCESS_STATE_PERSISTENT) {
return REASON_PROC_STATE_PERSISTENT;
} else if (uidProcState <= PROCESS_STATE_PERSISTENT_UI) {
return REASON_PROC_STATE_PERSISTENT_UI;
}
final String[] packages = mInjector.getPackageManager().getPackagesForUid(uid);
if (packages != null) {
final AppOpsManager appOpsManager = mInjector.getAppOpsManager();
final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
final AppStandbyInternal appStandbyInternal = mInjector.getAppStandbyInternal();
for (String pkg : packages) {
if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_VPN,
uid, pkg) == AppOpsManager.MODE_ALLOWED) {
return REASON_OP_ACTIVATE_VPN;
} else if (appOpsManager.checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN,
uid, pkg) == AppOpsManager.MODE_ALLOWED) {
return REASON_OP_ACTIVATE_PLATFORM_VPN;
} else if (isSystemModule(pkg)) {
return REASON_SYSTEM_MODULE;
} else if (isCarrierApp(pkg)) {
return REASON_CARRIER_PRIVILEGED_APP;
} else if (isExemptedFromSysConfig(pkg)) {
return REASON_SYSTEM_ALLOW_LISTED;
} else if (mConstantsObserver.mBgRestrictionExemptedPackages.contains(pkg)) {
return REASON_SYSTEM_ALLOW_LISTED;
} else if (pm.isPackageStateProtected(pkg, userId)) {
return REASON_DPO_PROTECTED_APP;
} else if (appStandbyInternal.isActiveDeviceAdmin(pkg, userId)) {
return REASON_ACTIVE_DEVICE_ADMIN;
}
}
}
if (isRoleHeldByUid(RoleManager.ROLE_DIALER, uid)) {
return REASON_ROLE_DIALER;
}
if (isRoleHeldByUid(RoleManager.ROLE_EMERGENCY, uid)) {
return REASON_ROLE_EMERGENCY;
}
return REASON_DENIED;
}
private boolean isCarrierApp(String packageName) {
synchronized (mCarrierPrivilegedLock) {
if (mCarrierPrivilegedApps == null) {
fetchCarrierPrivilegedAppsCPL();
}
if (mCarrierPrivilegedApps != null) {
return mCarrierPrivilegedApps.contains(packageName);
}
return false;
}
}
private void clearCarrierPrivilegedApps() {
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.i(TAG, "Clearing carrier privileged apps list");
}
synchronized (mCarrierPrivilegedLock) {
mCarrierPrivilegedApps = null; // Need to be refetched.
}
}
@GuardedBy("mCarrierPrivilegedLock")
private void fetchCarrierPrivilegedAppsCPL() {
final TelephonyManager telephonyManager = mInjector.getTelephonyManager();
mCarrierPrivilegedApps =
telephonyManager.getCarrierPrivilegedPackagesForAllActiveSubscriptions();
if (DEBUG_BG_RESTRICTION_CONTROLLER) {
Slog.d(TAG, "apps with carrier privilege " + mCarrierPrivilegedApps);
}
}
private boolean isRoleHeldByUid(@NonNull String roleName, int uid) {
synchronized (mLock) {
final ArrayList<String> roles = mUidRolesMapping.get(uid);
return roles != null && roles.indexOf(roleName) >= 0;
}
}
private void initRolesInInterest() {
final int[] allUsers = mInjector.getUserManagerInternal().getUserIds();
for (String role : ROLES_IN_INTEREST) {
if (mInjector.getRoleManager().isRoleAvailable(role)) {
for (int userId : allUsers) {
final UserHandle user = UserHandle.of(userId);
onRoleHoldersChanged(role, user);
}
}
}
}
private void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
final List<String> rolePkgs = mInjector.getRoleManager().getRoleHoldersAsUser(
roleName, user);
final ArraySet<Integer> roleUids = new ArraySet<>();
final int userId = user.getIdentifier();
if (rolePkgs != null) {
final PackageManagerInternal pm = mInjector.getPackageManagerInternal();
for (String pkg: rolePkgs) {
roleUids.add(pm.getPackageUid(pkg, STOCK_PM_FLAGS, userId));
}
}
synchronized (mLock) {
for (int i = mUidRolesMapping.size() - 1; i >= 0; i--) {
final int uid = mUidRolesMapping.keyAt(i);
if (UserHandle.getUserId(uid) != userId) {
continue;
}
final ArrayList<String> roles = mUidRolesMapping.valueAt(i);
final int index = roles.indexOf(roleName);
final boolean isRole = roleUids.contains(uid);
if (index >= 0) {
if (!isRole) { // Not holding this role anymore, remove it.
roles.remove(index);
if (roles.isEmpty()) {
mUidRolesMapping.removeAt(i);
}
}
} else if (isRole) { // Got this new role, add it.
roles.add(roleName);
roleUids.remove(uid);
}
}
for (int i = roleUids.size() - 1; i >= 0; i--) { // Take care of the leftovers.
final ArrayList<String> roles = new ArrayList<>();
roles.add(roleName);
mUidRolesMapping.put(roleUids.valueAt(i), roles);
}
}
}
/**
* @return The background handler of this controller.
*/
Handler getBackgroundHandler() {
return mBgHandler;
}
/**
* @return The background handler thread of this controller.
*/
@VisibleForTesting
HandlerThread getBackgroundHandlerThread() {
return mBgHandlerThread;
}
/**
* @return The global lock of this controller.
*/
Object getLock() {
return mLock;
}
@VisibleForTesting
void addAppStateTracker(@NonNull BaseAppStateTracker tracker) {
mAppStateTrackers.add(tracker);
}
/**
* @return The tracker instance of the given class.
*/
<T extends BaseAppStateTracker> T getAppStateTracker(Class<T> trackerClass) {
for (BaseAppStateTracker tracker : mAppStateTrackers) {
if (trackerClass.isAssignableFrom(tracker.getClass())) {
return (T) tracker;
}
}
return null;
}
void postLongRunningFgsIfNecessary(String packageName, int uid) {
mNotificationHelper.postLongRunningFgsIfNecessary(packageName, uid);
}
void cancelLongRunningFGSNotificationIfNecessary(String packageName, int uid) {
mNotificationHelper.cancelLongRunningFGSNotificationIfNecessary(packageName, uid);
}
String getPackageName(int pid) {
return mInjector.getPackageName(pid);
}
static class BgHandler extends Handler {
static final int MSG_BACKGROUND_RESTRICTION_CHANGED = 0;
static final int MSG_APP_RESTRICTION_LEVEL_CHANGED = 1;
static final int MSG_APP_STANDBY_BUCKET_CHANGED = 2;
static final int MSG_USER_INTERACTION_STARTED = 3;
static final int MSG_REQUEST_BG_RESTRICTED = 4;
static final int MSG_UID_IDLE = 5;
static final int MSG_UID_ACTIVE = 6;
static final int MSG_UID_GONE = 7;
static final int MSG_UID_PROC_STATE_CHANGED = 8;
static final int MSG_CANCEL_REQUEST_BG_RESTRICTED = 9;
static final int MSG_LOAD_RESTRICTION_SETTINGS = 10;
static final int MSG_PERSIST_RESTRICTION_SETTINGS = 11;
private final Injector mInjector;
BgHandler(Looper looper, Injector injector) {
super(looper);
mInjector = injector;
}
@Override
public void handleMessage(Message msg) {
final AppRestrictionController c = mInjector
.getAppRestrictionController();
switch (msg.what) {
case MSG_BACKGROUND_RESTRICTION_CHANGED: {
c.handleBackgroundRestrictionChanged(msg.arg1, (String) msg.obj, msg.arg2 == 1);
} break;
case MSG_APP_RESTRICTION_LEVEL_CHANGED: {
c.dispatchAppRestrictionLevelChanges(msg.arg1, (String) msg.obj, msg.arg2);
} break;
case MSG_APP_STANDBY_BUCKET_CHANGED: {
c.handleAppStandbyBucketChanged(msg.arg2, (String) msg.obj, msg.arg1);
} break;
case MSG_USER_INTERACTION_STARTED: {
c.onUserInteractionStarted((String) msg.obj, msg.arg1);
} break;
case MSG_REQUEST_BG_RESTRICTED: {
c.handleRequestBgRestricted((String) msg.obj, msg.arg1);
} break;
case MSG_UID_IDLE: {
c.handleUidInactive(msg.arg1, msg.arg2 == 1);
} break;
case MSG_UID_ACTIVE: {
c.handleUidActive(msg.arg1);
} break;
case MSG_CANCEL_REQUEST_BG_RESTRICTED: {
c.handleCancelRequestBgRestricted((String) msg.obj, msg.arg1);
} break;
case MSG_UID_PROC_STATE_CHANGED: {
c.handleUidProcStateChanged(msg.arg1, msg.arg2);
} break;
case MSG_UID_GONE: {
// It also means this UID is inactive now.
c.handleUidInactive(msg.arg1, msg.arg2 == 1);
c.handleUidGone(msg.arg1);
} break;
case MSG_LOAD_RESTRICTION_SETTINGS: {
c.mRestrictionSettings.loadFromXml(true);
} break;
case MSG_PERSIST_RESTRICTION_SETTINGS: {
c.mRestrictionSettings.persistToXml(msg.arg1);
} break;
}
}
}
static class Injector {
private final Context mContext;
private ActivityManagerInternal mActivityManagerInternal;
private AppRestrictionController mAppRestrictionController;
private AppOpsManager mAppOpsManager;
private AppStandbyInternal mAppStandbyInternal;
private AppStateTracker mAppStateTracker;
private AppHibernationManagerInternal mAppHibernationInternal;
private IActivityManager mIActivityManager;
private UserManagerInternal mUserManagerInternal;
private PackageManagerInternal mPackageManagerInternal;
private NotificationManager mNotificationManager;
private RoleManager mRoleManager;
private AppBatteryTracker mAppBatteryTracker;
private AppBatteryExemptionTracker mAppBatteryExemptionTracker;
private AppFGSTracker mAppFGSTracker;
private AppMediaSessionTracker mAppMediaSessionTracker;
private AppPermissionTracker mAppPermissionTracker;
private TelephonyManager mTelephonyManager;
Injector(Context context) {
mContext = context;
}
Context getContext() {
return mContext;
}
void initAppStateTrackers(AppRestrictionController controller) {
mAppRestrictionController = controller;
mAppBatteryTracker = new AppBatteryTracker(mContext, controller);
mAppBatteryExemptionTracker = new AppBatteryExemptionTracker(mContext, controller);
mAppFGSTracker = new AppFGSTracker(mContext, controller);
mAppMediaSessionTracker = new AppMediaSessionTracker(mContext, controller);
mAppPermissionTracker = new AppPermissionTracker(mContext, controller);
controller.mAppStateTrackers.add(mAppBatteryTracker);
controller.mAppStateTrackers.add(mAppBatteryExemptionTracker);
controller.mAppStateTrackers.add(mAppFGSTracker);
controller.mAppStateTrackers.add(mAppMediaSessionTracker);
controller.mAppStateTrackers.add(mAppPermissionTracker);
controller.mAppStateTrackers.add(new AppBroadcastEventsTracker(mContext, controller));
controller.mAppStateTrackers.add(new AppBindServiceEventsTracker(mContext, controller));
}
ActivityManagerInternal getActivityManagerInternal() {
if (mActivityManagerInternal == null) {
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
}
return mActivityManagerInternal;
}
AppRestrictionController getAppRestrictionController() {
return mAppRestrictionController;
}
AppOpsManager getAppOpsManager() {
if (mAppOpsManager == null) {
mAppOpsManager = getContext().getSystemService(AppOpsManager.class);
}
return mAppOpsManager;
}
AppStandbyInternal getAppStandbyInternal() {
if (mAppStandbyInternal == null) {
mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class);
}
return mAppStandbyInternal;
}
AppHibernationManagerInternal getAppHibernationInternal() {
if (mAppHibernationInternal == null) {
mAppHibernationInternal = LocalServices.getService(
AppHibernationManagerInternal.class);
}
return mAppHibernationInternal;
}
AppStateTracker getAppStateTracker() {
if (mAppStateTracker == null) {
mAppStateTracker = LocalServices.getService(AppStateTracker.class);
}
return mAppStateTracker;
}
IActivityManager getIActivityManager() {
return ActivityManager.getService();
}
UserManagerInternal getUserManagerInternal() {
if (mUserManagerInternal == null) {
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
}
return mUserManagerInternal;
}
PackageManagerInternal getPackageManagerInternal() {
if (mPackageManagerInternal == null) {
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
}
return mPackageManagerInternal;
}
PackageManager getPackageManager() {
return getContext().getPackageManager();
}
NotificationManager getNotificationManager() {
if (mNotificationManager == null) {
mNotificationManager = getContext().getSystemService(NotificationManager.class);
}
return mNotificationManager;
}
RoleManager getRoleManager() {
if (mRoleManager == null) {
mRoleManager = getContext().getSystemService(RoleManager.class);
}
return mRoleManager;
}
TelephonyManager getTelephonyManager() {
if (mTelephonyManager == null) {
mTelephonyManager = getContext().getSystemService(TelephonyManager.class);
}
return mTelephonyManager;
}
AppFGSTracker getAppFGSTracker() {
return mAppFGSTracker;
}
AppMediaSessionTracker getAppMediaSessionTracker() {
return mAppMediaSessionTracker;
}
ActivityManagerService getActivityManagerService() {
return mAppRestrictionController.mActivityManagerService;
}
UidBatteryUsageProvider getUidBatteryUsageProvider() {
return mAppBatteryTracker;
}
AppBatteryExemptionTracker getAppBatteryExemptionTracker() {
return mAppBatteryExemptionTracker;
}
AppPermissionTracker getAppPermissionTracker() {
return mAppPermissionTracker;
}
String getPackageName(int pid) {
final ActivityManagerService am = getActivityManagerService();
final ProcessRecord app;
synchronized (am.mPidsSelfLocked) {
app = am.mPidsSelfLocked.get(pid);
if (app != null) {
final ApplicationInfo ai = app.info;
if (ai != null) {
return ai.packageName;
}
}
}
return null;
}
void scheduleInitTrackers(Handler handler, Runnable initializers) {
handler.post(initializers);
}
File getDataSystemDeDirectory(@UserIdInt int userId) {
return Environment.getDataSystemDeDirectory(userId);
}
@CurrentTimeMillisLong long currentTimeMillis() {
return System.currentTimeMillis();
}
boolean isTest() {
return false;
}
}
private void registerForSystemBroadcasts() {
final IntentFilter packageFilter = new IntentFilter();
packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
packageFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
packageFilter.addDataScheme("package");
mContext.registerReceiverForAllUsers(mBroadcastReceiver, packageFilter, null, mBgHandler);
final IntentFilter userFilter = new IntentFilter();
userFilter.addAction(Intent.ACTION_USER_ADDED);
userFilter.addAction(Intent.ACTION_USER_REMOVED);
userFilter.addAction(Intent.ACTION_UID_REMOVED);
mContext.registerReceiverForAllUsers(mBroadcastReceiver, userFilter, null, mBgHandler);
final IntentFilter bootFilter = new IntentFilter();
bootFilter.addAction(Intent.ACTION_LOCKED_BOOT_COMPLETED);
mContext.registerReceiverAsUser(mBootReceiver, UserHandle.SYSTEM,
bootFilter, null, mBgHandler);
}
private void unregisterForSystemBroadcasts() {
mContext.unregisterReceiver(mBroadcastReceiver);
mContext.unregisterReceiver(mBootReceiver);
}
void forEachTracker(Consumer<BaseAppStateTracker> sink) {
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
sink.accept(mAppStateTrackers.get(i));
}
}
private void onUserAdded(@UserIdInt int userId) {
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
mAppStateTrackers.get(i).onUserAdded(userId);
}
}
private void onUserStarted(@UserIdInt int userId) {
refreshAppRestrictionLevelForUser(userId, REASON_MAIN_FORCED_BY_USER,
REASON_SUB_FORCED_USER_FLAG_INTERACTION);
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
mAppStateTrackers.get(i).onUserStarted(userId);
}
}
private void onUserStopped(@UserIdInt int userId) {
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
mAppStateTrackers.get(i).onUserStopped(userId);
}
}
private void onUserRemoved(@UserIdInt int userId) {
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
mAppStateTrackers.get(i).onUserRemoved(userId);
}
mRestrictionSettings.removeUser(userId);
}
private void onUidAdded(int uid) {
refreshAppRestrictionLevelForUid(uid, REASON_MAIN_FORCED_BY_SYSTEM,
REASON_SUB_FORCED_SYSTEM_FLAG_UNDEFINED, false);
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
mAppStateTrackers.get(i).onUidAdded(uid);
}
}
private void onPackageRemoved(String pkgName, int uid) {
mRestrictionSettings.removePackage(pkgName, uid);
}
private void onUidRemoved(int uid) {
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
mAppStateTrackers.get(i).onUidRemoved(uid);
}
mRestrictionSettings.removeUid(uid);
}
private void onLockedBootCompleted() {
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
mAppStateTrackers.get(i).onLockedBootCompleted();
}
}
boolean isBgAutoRestrictedBucketFeatureFlagEnabled() {
return mConstantsObserver.mBgAutoRestrictedBucket;
}
private void onPropertiesChanged(String name) {
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
mAppStateTrackers.get(i).onPropertiesChanged(name);
}
}
private void onUserInteractionStarted(String packageName, @UserIdInt int userId) {
final int uid = mInjector.getPackageManagerInternal()
.getPackageUid(packageName, STOCK_PM_FLAGS, userId);
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
mAppStateTrackers.get(i).onUserInteractionStarted(packageName, uid);
}
}
}