| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.power; |
| |
| import static android.os.PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL; |
| import static android.os.PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST; |
| import static android.os.PowerManager.lowPowerStandbyAllowedReasonsToString; |
| |
| import android.Manifest; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.ActivityManager; |
| import android.app.ActivityManagerInternal; |
| import android.app.AlarmManager; |
| import android.app.IActivityManager; |
| import android.app.IForegroundServiceObserver; |
| import android.content.BroadcastReceiver; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ServiceInfo; |
| import android.content.res.Resources; |
| import android.database.ContentObserver; |
| import android.net.Uri; |
| import android.os.Environment; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.PowerManager; |
| import android.os.PowerManager.LowPowerStandbyAllowedReason; |
| import android.os.PowerManager.LowPowerStandbyPolicy; |
| import android.os.PowerManager.LowPowerStandbyPortDescription; |
| import android.os.PowerManagerInternal; |
| import android.os.RemoteException; |
| import android.os.SystemClock; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.provider.DeviceConfig; |
| import android.provider.Settings; |
| import android.text.TextUtils; |
| import android.util.ArraySet; |
| import android.util.AtomicFile; |
| import android.util.IndentingPrintWriter; |
| import android.util.Slog; |
| import android.util.SparseBooleanArray; |
| import android.util.SparseIntArray; |
| import android.util.Xml; |
| import android.util.proto.ProtoOutputStream; |
| |
| import com.android.internal.R; |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.modules.utils.TypedXmlPullParser; |
| import com.android.modules.utils.TypedXmlSerializer; |
| import com.android.server.LocalServices; |
| import com.android.server.PowerAllowlistInternal; |
| import com.android.server.net.NetworkPolicyManagerInternal; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.concurrent.Executor; |
| import java.util.function.Supplier; |
| |
| /** |
| * Controls Low Power Standby state. |
| * |
| * Instantiated by {@link PowerManagerService} only if Low Power Standby is supported. |
| * |
| * <p>Low Power Standby is active when all of the following conditions are met: |
| * <ul> |
| * <li>Low Power Standby is enabled |
| * <li>The device is not interactive, and has been non-interactive for a given timeout |
| * <li>The device is not in a doze maintenance window (devices may be configured to also |
| * apply restrictions during doze maintenance windows, see {@link #setActiveDuringMaintenance}) |
| * </ul> |
| * |
| * <p>When Low Power Standby is active, the following restrictions are applied to applications |
| * with procstate less important than {@link android.app.ActivityManager#PROCESS_STATE_BOUND_TOP} |
| * unless they are exempted (see {@link LowPowerStandbyPolicy}): |
| * <ul> |
| * <li>Network access is blocked |
| * <li>Wakelocks are disabled |
| * </ul> |
| * |
| * @hide |
| */ |
| public class LowPowerStandbyController { |
| private static final String TAG = "LowPowerStandbyController"; |
| private static final boolean DEBUG = false; |
| private static final boolean DEFAULT_ACTIVE_DURING_MAINTENANCE = false; |
| |
| private static final int MSG_STANDBY_TIMEOUT = 0; |
| private static final int MSG_NOTIFY_ACTIVE_CHANGED = 1; |
| private static final int MSG_NOTIFY_ALLOWLIST_CHANGED = 2; |
| private static final int MSG_NOTIFY_POLICY_CHANGED = 3; |
| private static final int MSG_FOREGROUND_SERVICE_STATE_CHANGED = 4; |
| private static final int MSG_NOTIFY_STANDBY_PORTS_CHANGED = 5; |
| |
| private static final String TAG_ROOT = "low-power-standby-policy"; |
| private static final String TAG_IDENTIFIER = "identifier"; |
| private static final String TAG_EXEMPT_PACKAGE = "exempt-package"; |
| private static final String TAG_ALLOWED_REASONS = "allowed-reasons"; |
| private static final String TAG_ALLOWED_FEATURES = "allowed-features"; |
| private static final String ATTR_VALUE = "value"; |
| |
| private final Handler mHandler; |
| private final SettingsObserver mSettingsObserver; |
| private final DeviceConfigWrapper mDeviceConfig; |
| private final Supplier<IActivityManager> mActivityManager; |
| private final File mPolicyFile; |
| private final Object mLock = new Object(); |
| |
| private final Context mContext; |
| private final Clock mClock; |
| private final AlarmManager.OnAlarmListener mOnStandbyTimeoutExpired = |
| this::onStandbyTimeoutExpired; |
| private final LowPowerStandbyControllerInternal mLocalService = new LocalService(); |
| private final SparseIntArray mUidAllowedReasons = new SparseIntArray(); |
| private final List<StandbyPortsLock> mStandbyPortLocks = new ArrayList<>(); |
| |
| @GuardedBy("mLock") |
| private boolean mEnableCustomPolicy; |
| private boolean mEnableStandbyPorts; |
| |
| private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| switch (intent.getAction()) { |
| case Intent.ACTION_SCREEN_OFF: |
| onNonInteractive(); |
| break; |
| case Intent.ACTION_SCREEN_ON: |
| onInteractive(); |
| break; |
| case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED: |
| onDeviceIdleModeChanged(); |
| break; |
| } |
| } |
| }; |
| private final TempAllowlistChangeListener mTempAllowlistChangeListener = |
| new TempAllowlistChangeListener(); |
| private final PhoneCallServiceTracker mPhoneCallServiceTracker = new PhoneCallServiceTracker(); |
| |
| private final BroadcastReceiver mPackageBroadcastReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (DEBUG) { |
| Slog.d(TAG, "Received package intent: action=" + intent.getAction() + ", data=" |
| + intent.getData()); |
| } |
| final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); |
| if (replacing) { |
| return; |
| } |
| final Uri intentUri = intent.getData(); |
| final String packageName = (intentUri != null) ? intentUri.getSchemeSpecificPart() |
| : null; |
| synchronized (mLock) { |
| final LowPowerStandbyPolicy policy = getPolicy(); |
| if (policy.getExemptPackages().contains(packageName)) { |
| enqueueNotifyAllowlistChangedLocked(); |
| } |
| } |
| } |
| }; |
| |
| private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (DEBUG) { |
| Slog.d(TAG, "Received user intent: action=" + intent.getAction()); |
| } |
| synchronized (mLock) { |
| enqueueNotifyAllowlistChangedLocked(); |
| } |
| } |
| }; |
| |
| private final class StandbyPortsLock implements IBinder.DeathRecipient { |
| private final IBinder mToken; |
| private final int mUid; |
| private final List<LowPowerStandbyPortDescription> mPorts; |
| |
| StandbyPortsLock(IBinder token, int uid, List<LowPowerStandbyPortDescription> ports) { |
| mToken = token; |
| mUid = uid; |
| mPorts = ports; |
| } |
| |
| public boolean linkToDeath() { |
| try { |
| mToken.linkToDeath(this, 0); |
| return true; |
| } catch (RemoteException e) { |
| Slog.i(TAG, "StandbyPorts token already died"); |
| return false; |
| } |
| } |
| |
| public void unlinkToDeath() { |
| mToken.unlinkToDeath(this, 0); |
| } |
| |
| public IBinder getToken() { |
| return mToken; |
| } |
| |
| public int getUid() { |
| return mUid; |
| } |
| |
| public List<LowPowerStandbyPortDescription> getPorts() { |
| return mPorts; |
| } |
| |
| @Override |
| public void binderDied() { |
| releaseStandbyPorts(mToken); |
| } |
| } |
| |
| @GuardedBy("mLock") |
| private AlarmManager mAlarmManager; |
| @GuardedBy("mLock") |
| private PowerManager mPowerManager; |
| private ActivityManagerInternal mActivityManagerInternal; |
| @GuardedBy("mLock") |
| private boolean mSupportedConfig; |
| @GuardedBy("mLock") |
| private boolean mEnabledByDefaultConfig; |
| @GuardedBy("mLock") |
| private int mStandbyTimeoutConfig; |
| |
| /** Whether Low Power Standby is enabled in Settings */ |
| @GuardedBy("mLock") |
| private boolean mIsEnabled; |
| |
| /** |
| * Whether Low Power Standby is currently active (enforcing restrictions). |
| */ |
| @GuardedBy("mLock") |
| private boolean mIsActive; |
| |
| /** Whether the device is currently interactive */ |
| @GuardedBy("mLock") |
| private boolean mIsInteractive; |
| |
| /** The time the device was last interactive, in {@link SystemClock#elapsedRealtime()}. */ |
| @GuardedBy("mLock") |
| private long mLastInteractiveTimeElapsed; |
| |
| /** |
| * Whether we are in device idle mode. |
| * During maintenance windows Low Power Standby is deactivated to allow |
| * apps to run maintenance tasks. |
| */ |
| @GuardedBy("mLock") |
| private boolean mIsDeviceIdle; |
| |
| /** |
| * Whether the device has entered idle mode since becoming non-interactive. |
| * In the initial non-idle period after turning the screen off, Low Power Standby is already |
| * allowed to become active. Later non-idle periods are treated as maintenance windows, during |
| * which Low Power Standby is deactivated to allow apps to run maintenance tasks. |
| */ |
| @GuardedBy("mLock") |
| private boolean mIdleSinceNonInteractive; |
| |
| /** Whether Low Power Standby restrictions should be active during doze maintenance mode. */ |
| @GuardedBy("mLock") |
| private boolean mActiveDuringMaintenance; |
| |
| /** Force Low Power Standby to be active. */ |
| @GuardedBy("mLock") |
| private boolean mForceActive; |
| |
| /** Current Low Power Standby policy. */ |
| @GuardedBy("mLock") |
| @Nullable |
| private LowPowerStandbyPolicy mPolicy; |
| |
| @VisibleForTesting |
| static final LowPowerStandbyPolicy DEFAULT_POLICY = new LowPowerStandbyPolicy( |
| "DEFAULT_POLICY", |
| Collections.emptySet(), |
| PowerManager.LOW_POWER_STANDBY_ALLOWED_REASON_VOICE_INTERACTION, |
| Collections.emptySet()); |
| |
| /** Functional interface for providing time. */ |
| @VisibleForTesting |
| interface Clock { |
| /** Returns milliseconds since boot, including time spent in sleep. */ |
| long elapsedRealtime(); |
| } |
| |
| public LowPowerStandbyController(Context context, Looper looper) { |
| this(context, looper, SystemClock::elapsedRealtime, |
| new DeviceConfigWrapper(), () -> ActivityManager.getService(), |
| new File(Environment.getDataSystemDirectory(), "low_power_standby_policy.xml")); |
| } |
| |
| @VisibleForTesting |
| LowPowerStandbyController(Context context, Looper looper, Clock clock, |
| DeviceConfigWrapper deviceConfig, Supplier<IActivityManager> activityManager, |
| File policyFile) { |
| mContext = context; |
| mHandler = new LowPowerStandbyHandler(looper); |
| mClock = clock; |
| mSettingsObserver = new SettingsObserver(mHandler); |
| mDeviceConfig = deviceConfig; |
| mActivityManager = activityManager; |
| mPolicyFile = policyFile; |
| } |
| |
| /** Call when system services are ready */ |
| @VisibleForTesting |
| public void systemReady() { |
| final Resources resources = mContext.getResources(); |
| synchronized (mLock) { |
| mSupportedConfig = resources.getBoolean( |
| com.android.internal.R.bool.config_lowPowerStandbySupported); |
| |
| if (!mSupportedConfig) { |
| return; |
| } |
| |
| mAlarmManager = mContext.getSystemService(AlarmManager.class); |
| mPowerManager = mContext.getSystemService(PowerManager.class); |
| mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); |
| |
| mStandbyTimeoutConfig = resources.getInteger( |
| R.integer.config_lowPowerStandbyNonInteractiveTimeout); |
| mEnabledByDefaultConfig = resources.getBoolean( |
| R.bool.config_lowPowerStandbyEnabledByDefault); |
| |
| mIsInteractive = mPowerManager.isInteractive(); |
| |
| mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor( |
| Settings.Global.LOW_POWER_STANDBY_ENABLED), |
| false, mSettingsObserver, UserHandle.USER_ALL); |
| mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor( |
| Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE), |
| false, mSettingsObserver, UserHandle.USER_ALL); |
| |
| mDeviceConfig.registerPropertyUpdateListener(mContext.getMainExecutor(), |
| properties -> onDeviceConfigFlagsChanged()); |
| mEnableCustomPolicy = mDeviceConfig.enableCustomPolicy(); |
| mEnableStandbyPorts = mDeviceConfig.enableStandbyPorts(); |
| |
| if (mEnableCustomPolicy) { |
| mPolicy = loadPolicy(); |
| } else { |
| mPolicy = DEFAULT_POLICY; |
| } |
| initSettingsLocked(); |
| updateSettingsLocked(); |
| |
| if (mIsEnabled) { |
| registerListeners(); |
| } |
| } |
| |
| LocalServices.addService(LowPowerStandbyControllerInternal.class, mLocalService); |
| } |
| |
| private void onDeviceConfigFlagsChanged() { |
| synchronized (mLock) { |
| boolean enableCustomPolicy = mDeviceConfig.enableCustomPolicy(); |
| if (mEnableCustomPolicy != enableCustomPolicy) { |
| enqueueNotifyPolicyChangedLocked(); |
| enqueueNotifyAllowlistChangedLocked(); |
| mEnableCustomPolicy = enableCustomPolicy; |
| } |
| |
| mEnableStandbyPorts = mDeviceConfig.enableStandbyPorts(); |
| } |
| } |
| |
| @GuardedBy("mLock") |
| private void initSettingsLocked() { |
| final ContentResolver resolver = mContext.getContentResolver(); |
| if (mSupportedConfig) { |
| final int enabledSetting = Settings.Global.getInt(resolver, |
| Settings.Global.LOW_POWER_STANDBY_ENABLED, /* def= */ -1); |
| |
| // If the ENABLED setting hasn't been assigned yet, set it to its default value. |
| // This ensures reading the setting reflects the enabled state, without having to know |
| // the default value for this device. |
| if (enabledSetting == -1) { |
| Settings.Global.putInt(resolver, Settings.Global.LOW_POWER_STANDBY_ENABLED, |
| /* value= */ mEnabledByDefaultConfig ? 1 : 0); |
| } |
| } |
| } |
| |
| @GuardedBy("mLock") |
| private void updateSettingsLocked() { |
| final ContentResolver resolver = mContext.getContentResolver(); |
| mIsEnabled = mSupportedConfig && Settings.Global.getInt(resolver, |
| Settings.Global.LOW_POWER_STANDBY_ENABLED, |
| mEnabledByDefaultConfig ? 1 : 0) != 0; |
| mActiveDuringMaintenance = Settings.Global.getInt(resolver, |
| Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE, |
| DEFAULT_ACTIVE_DURING_MAINTENANCE ? 1 : 0) != 0; |
| |
| updateActiveLocked(); |
| } |
| |
| @Nullable |
| private LowPowerStandbyPolicy loadPolicy() { |
| final AtomicFile file = getPolicyFile(); |
| if (!file.exists()) { |
| return null; |
| } |
| if (DEBUG) { |
| Slog.d(TAG, "Loading policy from " + file.getBaseFile()); |
| } |
| |
| try (FileInputStream in = file.openRead()) { |
| String identifier = null; |
| Set<String> exemptPackages = new ArraySet<>(); |
| int allowedReasons = 0; |
| Set<String> allowedFeatures = new ArraySet<>(); |
| |
| TypedXmlPullParser parser = Xml.resolvePullParser(in); |
| |
| int type; |
| while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { |
| if (type != XmlPullParser.START_TAG) { |
| continue; |
| } |
| final int depth = parser.getDepth(); |
| // Check the root tag |
| final String tag = parser.getName(); |
| if (depth == 1) { |
| if (!TAG_ROOT.equals(tag)) { |
| Slog.e(TAG, "Invalid root tag: " + tag); |
| return null; |
| } |
| continue; |
| } |
| // Assume depth == 2 |
| switch (tag) { |
| case TAG_IDENTIFIER: |
| identifier = parser.getAttributeValue(null, ATTR_VALUE); |
| break; |
| case TAG_EXEMPT_PACKAGE: |
| exemptPackages.add(parser.getAttributeValue(null, ATTR_VALUE)); |
| break; |
| case TAG_ALLOWED_REASONS: |
| allowedReasons = parser.getAttributeInt(null, ATTR_VALUE); |
| break; |
| case TAG_ALLOWED_FEATURES: |
| allowedFeatures.add(parser.getAttributeValue(null, ATTR_VALUE)); |
| break; |
| default: |
| Slog.e(TAG, "Invalid tag: " + tag); |
| break; |
| } |
| } |
| |
| final LowPowerStandbyPolicy policy = new LowPowerStandbyPolicy(identifier, |
| exemptPackages, allowedReasons, allowedFeatures); |
| if (DEBUG) { |
| Slog.d(TAG, "Loaded policy: " + policy); |
| } |
| return policy; |
| } catch (FileNotFoundException e) { |
| // Use the default |
| return null; |
| } catch (IOException | NullPointerException | IllegalArgumentException |
| | XmlPullParserException e) { |
| Slog.e(TAG, "Failed to read policy file " + file.getBaseFile(), e); |
| return null; |
| } |
| } |
| |
| static void writeTagValue(TypedXmlSerializer out, String tag, String value) throws IOException { |
| if (TextUtils.isEmpty(value)) return; |
| |
| out.startTag(null, tag); |
| out.attribute(null, ATTR_VALUE, value); |
| out.endTag(null, tag); |
| } |
| |
| static void writeTagValue(TypedXmlSerializer out, String tag, int value) throws IOException { |
| out.startTag(null, tag); |
| out.attributeInt(null, ATTR_VALUE, value); |
| out.endTag(null, tag); |
| } |
| |
| private void savePolicy(@Nullable LowPowerStandbyPolicy policy) { |
| final AtomicFile file = getPolicyFile(); |
| if (DEBUG) { |
| Slog.d(TAG, "Saving policy to " + file.getBaseFile()); |
| } |
| if (policy == null) { |
| file.delete(); |
| return; |
| } |
| |
| FileOutputStream outs = null; |
| try { |
| file.getBaseFile().mkdirs(); |
| outs = file.startWrite(); |
| |
| // Write to XML |
| TypedXmlSerializer out = Xml.resolveSerializer(outs); |
| out.startDocument(null, true); |
| out.startTag(null, TAG_ROOT); |
| |
| // Body. |
| writeTagValue(out, TAG_IDENTIFIER, policy.getIdentifier()); |
| for (String exemptPackage : policy.getExemptPackages()) { |
| writeTagValue(out, TAG_EXEMPT_PACKAGE, exemptPackage); |
| } |
| writeTagValue(out, TAG_ALLOWED_REASONS, policy.getAllowedReasons()); |
| for (String allowedFeature : policy.getAllowedFeatures()) { |
| writeTagValue(out, TAG_ALLOWED_FEATURES, allowedFeature); |
| } |
| |
| // Epilogue. |
| out.endTag(null, TAG_ROOT); |
| out.endDocument(); |
| |
| // Close. |
| file.finishWrite(outs); |
| } catch (IOException e) { |
| Slog.e(TAG, "Failed to write policy to file " + file.getBaseFile(), e); |
| file.failWrite(outs); |
| } |
| } |
| |
| private void enqueueSavePolicy(@Nullable LowPowerStandbyPolicy policy) { |
| mHandler.post(() -> savePolicy(policy)); |
| } |
| |
| private AtomicFile getPolicyFile() { |
| return new AtomicFile(mPolicyFile); |
| } |
| |
| @GuardedBy("mLock") |
| private void updateActiveLocked() { |
| final long now = mClock.elapsedRealtime(); |
| final boolean standbyTimeoutExpired = |
| (now - mLastInteractiveTimeElapsed) >= mStandbyTimeoutConfig; |
| final boolean maintenanceMode = mIdleSinceNonInteractive && !mIsDeviceIdle; |
| final boolean newActive = |
| mForceActive || (mIsEnabled && !mIsInteractive && standbyTimeoutExpired |
| && (!maintenanceMode || mActiveDuringMaintenance)); |
| if (DEBUG) { |
| Slog.d(TAG, "updateActiveLocked: mIsEnabled=" + mIsEnabled + ", mIsInteractive=" |
| + mIsInteractive + ", standbyTimeoutExpired=" + standbyTimeoutExpired |
| + ", mIdleSinceNonInteractive=" + mIdleSinceNonInteractive + ", mIsDeviceIdle=" |
| + mIsDeviceIdle + ", mActiveDuringMaintenance=" + mActiveDuringMaintenance |
| + ", mForceActive=" + mForceActive + ", mIsActive=" + mIsActive + ", newActive=" |
| + newActive); |
| } |
| if (mIsActive != newActive) { |
| mIsActive = newActive; |
| if (DEBUG) { |
| Slog.d(TAG, "mIsActive changed, mIsActive=" + mIsActive); |
| } |
| enqueueNotifyActiveChangedLocked(); |
| } |
| } |
| |
| private void onNonInteractive() { |
| if (DEBUG) { |
| Slog.d(TAG, "onNonInteractive"); |
| } |
| final long now = mClock.elapsedRealtime(); |
| synchronized (mLock) { |
| mIsInteractive = false; |
| mIsDeviceIdle = false; |
| mLastInteractiveTimeElapsed = now; |
| |
| if (mStandbyTimeoutConfig > 0) { |
| scheduleStandbyTimeoutAlarmLocked(); |
| } |
| |
| updateActiveLocked(); |
| } |
| } |
| |
| private void onInteractive() { |
| if (DEBUG) { |
| Slog.d(TAG, "onInteractive"); |
| } |
| |
| synchronized (mLock) { |
| cancelStandbyTimeoutAlarmLocked(); |
| mIsInteractive = true; |
| mIsDeviceIdle = false; |
| mIdleSinceNonInteractive = false; |
| updateActiveLocked(); |
| } |
| } |
| |
| @GuardedBy("mLock") |
| private void scheduleStandbyTimeoutAlarmLocked() { |
| final long nextAlarmTime = SystemClock.elapsedRealtime() + mStandbyTimeoutConfig; |
| mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, |
| nextAlarmTime, "LowPowerStandbyController.StandbyTimeout", |
| mOnStandbyTimeoutExpired, mHandler); |
| } |
| |
| @GuardedBy("mLock") |
| private void cancelStandbyTimeoutAlarmLocked() { |
| mAlarmManager.cancel(mOnStandbyTimeoutExpired); |
| } |
| |
| private void onDeviceIdleModeChanged() { |
| synchronized (mLock) { |
| mIsDeviceIdle = mPowerManager.isDeviceIdleMode(); |
| if (DEBUG) { |
| Slog.d(TAG, "onDeviceIdleModeChanged, mIsDeviceIdle=" + mIsDeviceIdle); |
| } |
| |
| mIdleSinceNonInteractive = mIdleSinceNonInteractive || mIsDeviceIdle; |
| updateActiveLocked(); |
| } |
| } |
| |
| @GuardedBy("mLock") |
| private void onEnabledLocked() { |
| if (DEBUG) { |
| Slog.d(TAG, "onEnabledLocked"); |
| } |
| |
| if (mPowerManager.isInteractive()) { |
| onInteractive(); |
| } else { |
| onNonInteractive(); |
| } |
| |
| registerListeners(); |
| } |
| |
| @GuardedBy("mLock") |
| private void onDisabledLocked() { |
| if (DEBUG) { |
| Slog.d(TAG, "onDisabledLocked"); |
| } |
| |
| cancelStandbyTimeoutAlarmLocked(); |
| unregisterListeners(); |
| updateActiveLocked(); |
| } |
| |
| @VisibleForTesting |
| void onSettingsChanged() { |
| if (DEBUG) { |
| Slog.d(TAG, "onSettingsChanged"); |
| } |
| synchronized (mLock) { |
| final boolean oldEnabled = mIsEnabled; |
| updateSettingsLocked(); |
| |
| if (mIsEnabled != oldEnabled) { |
| if (mIsEnabled) { |
| onEnabledLocked(); |
| } else { |
| onDisabledLocked(); |
| } |
| |
| notifyEnabledChangedLocked(); |
| } |
| } |
| } |
| |
| private void registerListeners() { |
| IntentFilter intentFilter = new IntentFilter(); |
| intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); |
| intentFilter.addAction(Intent.ACTION_SCREEN_ON); |
| intentFilter.addAction(Intent.ACTION_SCREEN_OFF); |
| |
| mContext.registerReceiver(mBroadcastReceiver, intentFilter); |
| |
| IntentFilter packageFilter = new IntentFilter(); |
| packageFilter.addDataScheme(IntentFilter.SCHEME_PACKAGE); |
| packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); |
| packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); |
| packageFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); |
| mContext.registerReceiver(mPackageBroadcastReceiver, packageFilter); |
| |
| final IntentFilter userFilter = new IntentFilter(); |
| userFilter.addAction(Intent.ACTION_USER_ADDED); |
| userFilter.addAction(Intent.ACTION_USER_REMOVED); |
| mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler); |
| |
| PowerAllowlistInternal pai = LocalServices.getService(PowerAllowlistInternal.class); |
| pai.registerTempAllowlistChangeListener(mTempAllowlistChangeListener); |
| |
| mPhoneCallServiceTracker.register(); |
| } |
| |
| private void unregisterListeners() { |
| mContext.unregisterReceiver(mBroadcastReceiver); |
| mContext.unregisterReceiver(mPackageBroadcastReceiver); |
| mContext.unregisterReceiver(mUserReceiver); |
| |
| PowerAllowlistInternal pai = LocalServices.getService(PowerAllowlistInternal.class); |
| pai.unregisterTempAllowlistChangeListener(mTempAllowlistChangeListener); |
| } |
| |
| @GuardedBy("mLock") |
| private void notifyEnabledChangedLocked() { |
| if (DEBUG) { |
| Slog.d(TAG, "notifyEnabledChangedLocked, mIsEnabled=" + mIsEnabled); |
| } |
| |
| final Intent intent = new Intent(PowerManager.ACTION_LOW_POWER_STANDBY_ENABLED_CHANGED); |
| intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); |
| mContext.sendBroadcastAsUser(intent, UserHandle.ALL); |
| } |
| |
| @GuardedBy("mLock") |
| private void enqueueNotifyPolicyChangedLocked() { |
| final long now = mClock.elapsedRealtime(); |
| final Message msg = mHandler.obtainMessage(MSG_NOTIFY_POLICY_CHANGED, getPolicy()); |
| mHandler.sendMessageAtTime(msg, now); |
| } |
| |
| private void notifyPolicyChanged(LowPowerStandbyPolicy policy) { |
| if (DEBUG) { |
| Slog.d(TAG, "notifyPolicyChanged, policy=" + policy); |
| } |
| |
| final Intent intent = new Intent( |
| PowerManager.ACTION_LOW_POWER_STANDBY_POLICY_CHANGED); |
| intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); |
| mContext.sendBroadcastAsUser(intent, UserHandle.ALL); |
| } |
| |
| private void onStandbyTimeoutExpired() { |
| if (DEBUG) { |
| Slog.d(TAG, "onStandbyTimeoutExpired"); |
| } |
| synchronized (mLock) { |
| updateActiveLocked(); |
| } |
| } |
| |
| @GuardedBy("mLock") |
| private void enqueueNotifyActiveChangedLocked() { |
| final long now = mClock.elapsedRealtime(); |
| final Message msg = mHandler.obtainMessage(MSG_NOTIFY_ACTIVE_CHANGED, mIsActive); |
| mHandler.sendMessageAtTime(msg, now); |
| } |
| |
| /** Notify other system components about the updated Low Power Standby active state */ |
| private void notifyActiveChanged(boolean active) { |
| if (DEBUG) { |
| Slog.d(TAG, "notifyActiveChanged, active=" + active); |
| } |
| final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); |
| final NetworkPolicyManagerInternal npmi = LocalServices.getService( |
| NetworkPolicyManagerInternal.class); |
| |
| pmi.setLowPowerStandbyActive(active); |
| npmi.setLowPowerStandbyActive(active); |
| } |
| |
| @VisibleForTesting |
| boolean isActive() { |
| synchronized (mLock) { |
| return mIsActive; |
| } |
| } |
| |
| boolean isSupported() { |
| synchronized (mLock) { |
| return mSupportedConfig; |
| } |
| } |
| |
| boolean isEnabled() { |
| synchronized (mLock) { |
| return mSupportedConfig && mIsEnabled; |
| } |
| } |
| |
| void setEnabled(boolean enabled) { |
| synchronized (mLock) { |
| if (!mSupportedConfig) { |
| Slog.w(TAG, "Low Power Standby cannot be enabled " |
| + "because it is not supported on this device"); |
| return; |
| } |
| |
| Settings.Global.putInt(mContext.getContentResolver(), |
| Settings.Global.LOW_POWER_STANDBY_ENABLED, enabled ? 1 : 0); |
| onSettingsChanged(); |
| } |
| } |
| |
| /** Set whether Low Power Standby should be active during doze maintenance mode. */ |
| @VisibleForTesting |
| public void setActiveDuringMaintenance(boolean activeDuringMaintenance) { |
| synchronized (mLock) { |
| if (!mSupportedConfig) { |
| Slog.w(TAG, "Low Power Standby settings cannot be changed " |
| + "because it is not supported on this device"); |
| return; |
| } |
| |
| Settings.Global.putInt(mContext.getContentResolver(), |
| Settings.Global.LOW_POWER_STANDBY_ACTIVE_DURING_MAINTENANCE, |
| activeDuringMaintenance ? 1 : 0); |
| onSettingsChanged(); |
| } |
| } |
| |
| void forceActive(boolean active) { |
| synchronized (mLock) { |
| mForceActive = active; |
| updateActiveLocked(); |
| } |
| } |
| |
| void setPolicy(@Nullable LowPowerStandbyPolicy policy) { |
| synchronized (mLock) { |
| if (!mSupportedConfig) { |
| Slog.w(TAG, "Low Power Standby policy cannot be changed " |
| + "because it is not supported on this device"); |
| return; |
| } |
| |
| if (!mEnableCustomPolicy) { |
| Slog.d(TAG, "Custom policies are not enabled."); |
| return; |
| } |
| |
| if (DEBUG) { |
| Slog.d(TAG, "setPolicy: policy=" + policy); |
| } |
| if (Objects.equals(mPolicy, policy)) { |
| return; |
| } |
| |
| boolean allowlistChanged = policyChangeAffectsAllowlistLocked(mPolicy, policy); |
| mPolicy = policy; |
| enqueueSavePolicy(mPolicy); |
| if (allowlistChanged) { |
| enqueueNotifyAllowlistChangedLocked(); |
| } |
| enqueueNotifyPolicyChangedLocked(); |
| } |
| } |
| |
| @Nullable |
| LowPowerStandbyPolicy getPolicy() { |
| synchronized (mLock) { |
| if (!mSupportedConfig) { |
| return null; |
| } else if (mEnableCustomPolicy) { |
| return policyOrDefault(mPolicy); |
| } else { |
| return DEFAULT_POLICY; |
| } |
| } |
| } |
| |
| @NonNull |
| private LowPowerStandbyPolicy policyOrDefault(@Nullable LowPowerStandbyPolicy policy) { |
| if (policy == null) { |
| return DEFAULT_POLICY; |
| } |
| return policy; |
| } |
| |
| boolean isPackageExempt(int uid) { |
| synchronized (mLock) { |
| if (!isEnabled()) { |
| return true; |
| } |
| |
| return getExemptPackageAppIdsLocked().contains(UserHandle.getAppId(uid)); |
| } |
| } |
| |
| boolean isAllowed(@LowPowerStandbyAllowedReason int reason) { |
| synchronized (mLock) { |
| if (!isEnabled()) { |
| return true; |
| } |
| |
| return (getPolicy().getAllowedReasons() & reason) != 0; |
| } |
| } |
| |
| boolean isAllowed(String feature) { |
| synchronized (mLock) { |
| if (!mSupportedConfig) { |
| return true; |
| } |
| |
| return !isEnabled() || getPolicy().getAllowedFeatures().contains(feature); |
| } |
| } |
| |
| private int findIndexOfStandbyPorts(@NonNull IBinder token) { |
| for (int i = 0; i < mStandbyPortLocks.size(); i++) { |
| if (mStandbyPortLocks.get(i).getToken() == token) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| void acquireStandbyPorts(@NonNull IBinder token, int uid, |
| @NonNull List<LowPowerStandbyPortDescription> ports) { |
| validatePorts(ports); |
| |
| StandbyPortsLock standbyPortsLock = new StandbyPortsLock(token, uid, ports); |
| synchronized (mLock) { |
| if (findIndexOfStandbyPorts(token) != -1) { |
| return; |
| } |
| |
| if (standbyPortsLock.linkToDeath()) { |
| mStandbyPortLocks.add(standbyPortsLock); |
| if (mEnableStandbyPorts && isEnabled() && isPackageExempt(uid)) { |
| enqueueNotifyStandbyPortsChangedLocked(); |
| } |
| } |
| } |
| } |
| |
| void validatePorts(@NonNull List<LowPowerStandbyPortDescription> ports) { |
| for (LowPowerStandbyPortDescription portDescription : ports) { |
| int port = portDescription.getPortNumber(); |
| if (port < 0 || port > 0xFFFF) { |
| throw new IllegalArgumentException("port out of range:" + port); |
| } |
| } |
| } |
| |
| void releaseStandbyPorts(@NonNull IBinder token) { |
| synchronized (mLock) { |
| int index = findIndexOfStandbyPorts(token); |
| if (index == -1) { |
| return; |
| } |
| |
| StandbyPortsLock standbyPortsLock = mStandbyPortLocks.remove(index); |
| standbyPortsLock.unlinkToDeath(); |
| if (mEnableStandbyPorts && isEnabled() && isPackageExempt(standbyPortsLock.getUid())) { |
| enqueueNotifyStandbyPortsChangedLocked(); |
| } |
| } |
| } |
| |
| @NonNull |
| List<LowPowerStandbyPortDescription> getActiveStandbyPorts() { |
| List<LowPowerStandbyPortDescription> activeStandbyPorts = new ArrayList<>(); |
| synchronized (mLock) { |
| if (!isEnabled() || !mEnableStandbyPorts) { |
| return activeStandbyPorts; |
| } |
| |
| List<Integer> exemptPackageAppIds = getExemptPackageAppIdsLocked(); |
| for (StandbyPortsLock standbyPortsLock : mStandbyPortLocks) { |
| int standbyPortsAppid = UserHandle.getAppId(standbyPortsLock.getUid()); |
| if (exemptPackageAppIds.contains(standbyPortsAppid)) { |
| activeStandbyPorts.addAll(standbyPortsLock.getPorts()); |
| } |
| } |
| |
| return activeStandbyPorts; |
| } |
| } |
| |
| private boolean policyChangeAffectsAllowlistLocked( |
| @Nullable LowPowerStandbyPolicy oldPolicy, @Nullable LowPowerStandbyPolicy newPolicy) { |
| final LowPowerStandbyPolicy policyA = policyOrDefault(oldPolicy); |
| final LowPowerStandbyPolicy policyB = policyOrDefault(newPolicy); |
| int allowedReasonsInUse = 0; |
| for (int i = 0; i < mUidAllowedReasons.size(); i++) { |
| allowedReasonsInUse |= mUidAllowedReasons.valueAt(i); |
| } |
| |
| int policyAllowedReasonsChanged = policyA.getAllowedReasons() ^ policyB.getAllowedReasons(); |
| |
| boolean exemptPackagesChanged = !policyA.getExemptPackages().equals( |
| policyB.getExemptPackages()); |
| |
| return (policyAllowedReasonsChanged & allowedReasonsInUse) != 0 || exemptPackagesChanged; |
| } |
| |
| void dump(PrintWriter pw) { |
| final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); |
| |
| ipw.println(); |
| ipw.println("Low Power Standby Controller:"); |
| ipw.increaseIndent(); |
| synchronized (mLock) { |
| ipw.print("mIsActive="); |
| ipw.println(mIsActive); |
| ipw.print("mIsEnabled="); |
| ipw.println(mIsEnabled); |
| ipw.print("mSupportedConfig="); |
| ipw.println(mSupportedConfig); |
| ipw.print("mEnabledByDefaultConfig="); |
| ipw.println(mEnabledByDefaultConfig); |
| ipw.print("mStandbyTimeoutConfig="); |
| ipw.println(mStandbyTimeoutConfig); |
| ipw.print("mEnableCustomPolicy="); |
| ipw.println(mEnableCustomPolicy); |
| |
| if (mIsActive || mIsEnabled) { |
| ipw.print("mIsInteractive="); |
| ipw.println(mIsInteractive); |
| ipw.print("mLastInteractiveTime="); |
| ipw.println(mLastInteractiveTimeElapsed); |
| ipw.print("mIdleSinceNonInteractive="); |
| ipw.println(mIdleSinceNonInteractive); |
| ipw.print("mIsDeviceIdle="); |
| ipw.println(mIsDeviceIdle); |
| } |
| |
| final int[] allowlistUids = getAllowlistUidsLocked(); |
| ipw.print("Allowed UIDs="); |
| ipw.println(Arrays.toString(allowlistUids)); |
| |
| final LowPowerStandbyPolicy policy = getPolicy(); |
| if (policy != null) { |
| ipw.println(); |
| ipw.println("mPolicy:"); |
| ipw.increaseIndent(); |
| ipw.print("mIdentifier="); |
| ipw.println(policy.getIdentifier()); |
| ipw.print("mExemptPackages="); |
| ipw.println(String.join(",", policy.getExemptPackages())); |
| ipw.print("mAllowedReasons="); |
| ipw.println(lowPowerStandbyAllowedReasonsToString(policy.getAllowedReasons())); |
| ipw.print("mAllowedFeatures="); |
| ipw.println(String.join(",", policy.getAllowedFeatures())); |
| ipw.decreaseIndent(); |
| } |
| |
| ipw.println(); |
| ipw.println("UID allowed reasons:"); |
| ipw.increaseIndent(); |
| for (int i = 0; i < mUidAllowedReasons.size(); i++) { |
| if (mUidAllowedReasons.valueAt(i) > 0) { |
| ipw.print(mUidAllowedReasons.keyAt(i)); |
| ipw.print(": "); |
| ipw.println( |
| lowPowerStandbyAllowedReasonsToString(mUidAllowedReasons.valueAt(i))); |
| } |
| } |
| ipw.decreaseIndent(); |
| |
| final List<LowPowerStandbyPortDescription> activeStandbyPorts = getActiveStandbyPorts(); |
| if (!activeStandbyPorts.isEmpty()) { |
| ipw.println(); |
| ipw.println("Active standby ports locks:"); |
| ipw.increaseIndent(); |
| for (LowPowerStandbyPortDescription portDescription : activeStandbyPorts) { |
| ipw.print(portDescription.toString()); |
| } |
| ipw.decreaseIndent(); |
| } |
| } |
| ipw.decreaseIndent(); |
| } |
| |
| void dumpProto(ProtoOutputStream proto, long tag) { |
| synchronized (mLock) { |
| final long token = proto.start(tag); |
| proto.write(LowPowerStandbyControllerDumpProto.IS_ACTIVE, mIsActive); |
| proto.write(LowPowerStandbyControllerDumpProto.IS_ENABLED, mIsEnabled); |
| proto.write(LowPowerStandbyControllerDumpProto.IS_SUPPORTED_CONFIG, mSupportedConfig); |
| proto.write(LowPowerStandbyControllerDumpProto.IS_ENABLED_BY_DEFAULT_CONFIG, |
| mEnabledByDefaultConfig); |
| proto.write(LowPowerStandbyControllerDumpProto.IS_INTERACTIVE, mIsInteractive); |
| proto.write(LowPowerStandbyControllerDumpProto.LAST_INTERACTIVE_TIME, |
| mLastInteractiveTimeElapsed); |
| proto.write(LowPowerStandbyControllerDumpProto.STANDBY_TIMEOUT_CONFIG, |
| mStandbyTimeoutConfig); |
| proto.write(LowPowerStandbyControllerDumpProto.IDLE_SINCE_NON_INTERACTIVE, |
| mIdleSinceNonInteractive); |
| proto.write(LowPowerStandbyControllerDumpProto.IS_DEVICE_IDLE, mIsDeviceIdle); |
| |
| final int[] allowlistUids = getAllowlistUidsLocked(); |
| for (int appId : allowlistUids) { |
| proto.write(LowPowerStandbyControllerDumpProto.ALLOWLIST, appId); |
| } |
| |
| final LowPowerStandbyPolicy policy = getPolicy(); |
| if (policy != null) { |
| long policyToken = proto.start(LowPowerStandbyControllerDumpProto.POLICY); |
| proto.write(LowPowerStandbyPolicyProto.IDENTIFIER, policy.getIdentifier()); |
| for (String exemptPackage : policy.getExemptPackages()) { |
| proto.write(LowPowerStandbyPolicyProto.EXEMPT_PACKAGES, exemptPackage); |
| } |
| proto.write(LowPowerStandbyPolicyProto.ALLOWED_REASONS, policy.getAllowedReasons()); |
| for (String feature : policy.getAllowedFeatures()) { |
| proto.write(LowPowerStandbyPolicyProto.ALLOWED_FEATURES, feature); |
| } |
| proto.end(policyToken); |
| } |
| proto.end(token); |
| } |
| } |
| |
| private class LowPowerStandbyHandler extends Handler { |
| LowPowerStandbyHandler(Looper looper) { |
| super(looper); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_STANDBY_TIMEOUT: |
| onStandbyTimeoutExpired(); |
| break; |
| case MSG_NOTIFY_ACTIVE_CHANGED: |
| boolean active = (boolean) msg.obj; |
| notifyActiveChanged(active); |
| break; |
| case MSG_NOTIFY_ALLOWLIST_CHANGED: |
| final int[] allowlistUids = (int[]) msg.obj; |
| notifyAllowlistChanged(allowlistUids); |
| break; |
| case MSG_NOTIFY_POLICY_CHANGED: |
| notifyPolicyChanged((LowPowerStandbyPolicy) msg.obj); |
| break; |
| case MSG_FOREGROUND_SERVICE_STATE_CHANGED: |
| final int uid = msg.arg1; |
| mPhoneCallServiceTracker.foregroundServiceStateChanged(uid); |
| break; |
| case MSG_NOTIFY_STANDBY_PORTS_CHANGED: |
| notifyStandbyPortsChanged(); |
| break; |
| } |
| } |
| } |
| |
| @GuardedBy("mLock") |
| private boolean hasAllowedReasonLocked(int uid, |
| @LowPowerStandbyAllowedReason int allowedReason) { |
| int allowedReasons = mUidAllowedReasons.get(uid); |
| return (allowedReasons & allowedReason) != 0; |
| } |
| |
| @GuardedBy("mLock") |
| private boolean addAllowedReasonLocked(int uid, |
| @LowPowerStandbyAllowedReason int allowedReason) { |
| int allowedReasons = mUidAllowedReasons.get(uid); |
| final int newAllowReasons = allowedReasons | allowedReason; |
| mUidAllowedReasons.put(uid, newAllowReasons); |
| return allowedReasons != newAllowReasons; |
| } |
| |
| @GuardedBy("mLock") |
| private boolean removeAllowedReasonLocked(int uid, |
| @LowPowerStandbyAllowedReason int allowedReason) { |
| int allowedReasons = mUidAllowedReasons.get(uid); |
| if (allowedReasons == 0) { |
| return false; |
| } |
| |
| final int newAllowedReasons = allowedReasons & ~allowedReason; |
| if (newAllowedReasons == 0) { |
| mUidAllowedReasons.removeAt(mUidAllowedReasons.indexOfKey(uid)); |
| } else { |
| mUidAllowedReasons.put(uid, newAllowedReasons); |
| } |
| return allowedReasons != newAllowedReasons; |
| } |
| |
| private void addToAllowlistInternal(int uid, @LowPowerStandbyAllowedReason int allowedReason) { |
| if (DEBUG) { |
| Slog.i(TAG, |
| "Adding to allowlist: uid=" + uid + ", allowedReason=" + allowedReason); |
| } |
| synchronized (mLock) { |
| if (!mSupportedConfig) { |
| return; |
| } |
| if (allowedReason != 0 && !hasAllowedReasonLocked(uid, allowedReason)) { |
| addAllowedReasonLocked(uid, allowedReason); |
| if ((getPolicy().getAllowedReasons() & allowedReason) != 0) { |
| enqueueNotifyAllowlistChangedLocked(); |
| } |
| } |
| } |
| } |
| |
| private void removeFromAllowlistInternal(int uid, |
| @LowPowerStandbyAllowedReason int allowedReason) { |
| if (DEBUG) { |
| Slog.i(TAG, "Removing from allowlist: uid=" + uid + ", allowedReason=" + allowedReason); |
| } |
| synchronized (mLock) { |
| if (!mSupportedConfig) { |
| return; |
| } |
| if (allowedReason != 0 && hasAllowedReasonLocked(uid, allowedReason)) { |
| removeAllowedReasonLocked(uid, allowedReason); |
| if ((getPolicy().getAllowedReasons() & allowedReason) != 0) { |
| enqueueNotifyAllowlistChangedLocked(); |
| } |
| } |
| } |
| } |
| |
| @GuardedBy("mLock") |
| @NonNull |
| private List<Integer> getExemptPackageAppIdsLocked() { |
| final PackageManager packageManager = mContext.getPackageManager(); |
| final LowPowerStandbyPolicy policy = getPolicy(); |
| final List<Integer> appIds = new ArrayList<>(); |
| if (policy == null) { |
| return appIds; |
| } |
| |
| for (String packageName : policy.getExemptPackages()) { |
| try { |
| int packageUid = packageManager.getPackageUid(packageName, |
| PackageManager.PackageInfoFlags.of(0)); |
| int appId = UserHandle.getAppId(packageUid); |
| appIds.add(appId); |
| } catch (PackageManager.NameNotFoundException e) { |
| if (DEBUG) { |
| Slog.d(TAG, "Package UID cannot be resolved: packageName=" + packageName); |
| } |
| } |
| } |
| |
| return appIds; |
| } |
| |
| @GuardedBy("mLock") |
| private int[] getAllowlistUidsLocked() { |
| final UserManager userManager = mContext.getSystemService(UserManager.class); |
| final List<UserHandle> userHandles = userManager.getUserHandles(true); |
| final ArraySet<Integer> uids = new ArraySet<>(mUidAllowedReasons.size()); |
| final LowPowerStandbyPolicy policy = getPolicy(); |
| if (policy == null) { |
| return new int[0]; |
| } |
| |
| final int policyAllowedReasons = policy.getAllowedReasons(); |
| for (int i = 0; i < mUidAllowedReasons.size(); i++) { |
| Integer uid = mUidAllowedReasons.keyAt(i); |
| if ((mUidAllowedReasons.valueAt(i) & policyAllowedReasons) != 0) { |
| uids.add(uid); |
| } |
| } |
| |
| for (int appId : getExemptPackageAppIdsLocked()) { |
| for (int uid : uidsForAppId(appId, userHandles)) { |
| uids.add(uid); |
| } |
| } |
| |
| int[] allowlistUids = new int[uids.size()]; |
| for (int i = 0; i < uids.size(); i++) { |
| allowlistUids[i] = uids.valueAt(i); |
| } |
| Arrays.sort(allowlistUids); |
| return allowlistUids; |
| } |
| |
| private int[] uidsForAppId(int appUid, List<UserHandle> userHandles) { |
| final int appId = UserHandle.getAppId(appUid); |
| final int[] uids = new int[userHandles.size()]; |
| for (int i = 0; i < userHandles.size(); i++) { |
| uids[i] = userHandles.get(i).getUid(appId); |
| } |
| return uids; |
| } |
| |
| @GuardedBy("mLock") |
| private void enqueueNotifyAllowlistChangedLocked() { |
| final long now = mClock.elapsedRealtime(); |
| final int[] allowlistUids = getAllowlistUidsLocked(); |
| |
| if (DEBUG) { |
| Slog.d(TAG, "enqueueNotifyAllowlistChangedLocked: allowlistUids=" + Arrays.toString( |
| allowlistUids)); |
| } |
| |
| final Message msg = mHandler.obtainMessage(MSG_NOTIFY_ALLOWLIST_CHANGED, allowlistUids); |
| mHandler.sendMessageAtTime(msg, now); |
| } |
| |
| private void notifyAllowlistChanged(int[] allowlistUids) { |
| if (DEBUG) { |
| Slog.d(TAG, "notifyAllowlistChanged: " + Arrays.toString(allowlistUids)); |
| } |
| |
| final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); |
| final NetworkPolicyManagerInternal npmi = LocalServices.getService( |
| NetworkPolicyManagerInternal.class); |
| pmi.setLowPowerStandbyAllowlist(allowlistUids); |
| npmi.setLowPowerStandbyAllowlist(allowlistUids); |
| } |
| |
| @GuardedBy("mLock") |
| private void enqueueNotifyStandbyPortsChangedLocked() { |
| final long now = mClock.elapsedRealtime(); |
| |
| if (DEBUG) { |
| Slog.d(TAG, "enqueueNotifyStandbyPortsChangedLocked"); |
| } |
| |
| final Message msg = mHandler.obtainMessage(MSG_NOTIFY_STANDBY_PORTS_CHANGED); |
| mHandler.sendMessageAtTime(msg, now); |
| } |
| |
| private void notifyStandbyPortsChanged() { |
| if (DEBUG) { |
| Slog.d(TAG, "notifyStandbyPortsChanged"); |
| } |
| |
| final Intent intent = new Intent(PowerManager.ACTION_LOW_POWER_STANDBY_PORTS_CHANGED); |
| intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); |
| mContext.sendBroadcastAsUser(intent, UserHandle.ALL, |
| Manifest.permission.MANAGE_LOW_POWER_STANDBY); |
| } |
| |
| /** |
| * Class that is used to read device config for low power standby configuration. |
| */ |
| @VisibleForTesting |
| public static class DeviceConfigWrapper { |
| public static final String NAMESPACE = "low_power_standby"; |
| public static final String FEATURE_FLAG_ENABLE_POLICY = "enable_policy"; |
| public static final String FEATURE_FLAG_ENABLE_STANDBY_PORTS = "enable_standby_ports"; |
| |
| /** |
| * Returns true if custom policies are enabled. |
| * Otherwise, returns false, and the default policy will be used. |
| */ |
| public boolean enableCustomPolicy() { |
| return DeviceConfig.getBoolean(NAMESPACE, FEATURE_FLAG_ENABLE_POLICY, false); |
| } |
| |
| /** |
| * Returns true if standby ports are enabled. |
| * Otherwise, returns false, and {@link #getActiveStandbyPorts()} will always be empty. |
| */ |
| public boolean enableStandbyPorts() { |
| return DeviceConfig.getBoolean(NAMESPACE, FEATURE_FLAG_ENABLE_STANDBY_PORTS, false); |
| } |
| |
| /** |
| * Registers a DeviceConfig update listener. |
| */ |
| public void registerPropertyUpdateListener( |
| @NonNull Executor executor, |
| @NonNull DeviceConfig.OnPropertiesChangedListener onPropertiesChangedListener) { |
| DeviceConfig.addOnPropertiesChangedListener(NAMESPACE, executor, |
| onPropertiesChangedListener); |
| } |
| } |
| |
| private final class LocalService extends LowPowerStandbyControllerInternal { |
| @Override |
| public void addToAllowlist(int uid, @LowPowerStandbyAllowedReason int allowedReason) { |
| addToAllowlistInternal(uid, allowedReason); |
| } |
| |
| @Override |
| public void removeFromAllowlist(int uid, @LowPowerStandbyAllowedReason int allowedReason) { |
| removeFromAllowlistInternal(uid, allowedReason); |
| } |
| } |
| |
| private final class SettingsObserver extends ContentObserver { |
| SettingsObserver(Handler handler) { |
| super(handler); |
| } |
| |
| @Override |
| public void onChange(boolean selfChange, Uri uri) { |
| onSettingsChanged(); |
| } |
| } |
| |
| final class TempAllowlistChangeListener implements |
| PowerAllowlistInternal.TempAllowlistChangeListener { |
| @Override |
| public void onAppAdded(int uid) { |
| addToAllowlistInternal(uid, LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST); |
| } |
| |
| @Override |
| public void onAppRemoved(int uid) { |
| removeFromAllowlistInternal(uid, |
| LOW_POWER_STANDBY_ALLOWED_REASON_TEMP_POWER_SAVE_ALLOWLIST); |
| } |
| } |
| |
| final class PhoneCallServiceTracker extends IForegroundServiceObserver.Stub { |
| private boolean mRegistered = false; |
| private final SparseBooleanArray mUidsWithPhoneCallService = new SparseBooleanArray(); |
| |
| public void register() { |
| if (mRegistered) { |
| return; |
| } |
| try { |
| mActivityManager.get().registerForegroundServiceObserver(this); |
| mRegistered = true; |
| } catch (RemoteException e) { |
| // call within system server |
| } |
| } |
| |
| @Override |
| public void onForegroundStateChanged(IBinder serviceToken, String packageName, |
| int userId, boolean isForeground) { |
| try { |
| final long now = mClock.elapsedRealtime(); |
| final int uid = mContext.getPackageManager() |
| .getPackageUidAsUser(packageName, userId); |
| final Message message = |
| mHandler.obtainMessage(MSG_FOREGROUND_SERVICE_STATE_CHANGED, uid, 0); |
| mHandler.sendMessageAtTime(message, now); |
| } catch (PackageManager.NameNotFoundException e) { |
| if (DEBUG) { |
| Slog.d(TAG, "onForegroundStateChanged: Unknown package: " + packageName |
| + ", userId=" + userId); |
| } |
| } |
| } |
| |
| public void foregroundServiceStateChanged(int uid) { |
| if (DEBUG) { |
| Slog.d(TAG, "foregroundServiceStateChanged: uid=" + uid); |
| } |
| |
| final boolean hadPhoneCallService = mUidsWithPhoneCallService.get(uid); |
| final boolean hasPhoneCallService = |
| mActivityManagerInternal.hasRunningForegroundService(uid, |
| ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL); |
| |
| if (DEBUG) { |
| Slog.d(TAG, "uid=" + uid + ", hasPhoneCallService=" + hasPhoneCallService |
| + ", hadPhoneCallService=" + hadPhoneCallService); |
| } |
| |
| if (hasPhoneCallService == hadPhoneCallService) { |
| return; |
| } |
| |
| if (hasPhoneCallService) { |
| mUidsWithPhoneCallService.append(uid, true); |
| uidStartedPhoneCallService(uid); |
| } else { |
| mUidsWithPhoneCallService.delete(uid); |
| uidStoppedPhoneCallService(uid); |
| } |
| } |
| |
| private void uidStartedPhoneCallService(int uid) { |
| if (DEBUG) { |
| Slog.d(TAG, "FGS of type phoneCall started: uid=" + uid); |
| } |
| addToAllowlistInternal(uid, LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL); |
| } |
| |
| private void uidStoppedPhoneCallService(int uid) { |
| if (DEBUG) { |
| Slog.d(TAG, "FGSs of type phoneCall stopped: uid=" + uid); |
| } |
| removeFromAllowlistInternal(uid, LOW_POWER_STANDBY_ALLOWED_REASON_ONGOING_CALL); |
| } |
| } |
| } |