Power saving mode for Android 9.0 power management

On Android's platform, the issue of power consumption has been criticized.Since Lollipop, Google has also attached great importance to transforming its power-saving model.This article will be based on the latest Android Pie code to analyze the current power saving mode process for Android, and give some suggestions for some points that can continue to be optimized.This article will start with SystemUI.

QuickSettings

 

This picture is believed to impress everyone who uses an android phone, QuickSettings belonging to SystemUI.

    @Override
    protected void handleClick() {
        mBatteryController.setPowerSaveMode(!mPowerSave);
    }

Battery SaverTile's handleCllick responds to a click on power saving mode.
mBatteryController is an instantiated object of the BatteryController class, so setPowerSaveMode is implemented in BatteryControllerImpl.

    @Override
    public void setPowerSaveMode(boolean powerSave) {
        BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true);
    }

BatterySaverUtils is a class in frameworks/base/packages/SettingsLib whose methods are of type static, making it easy for other classes to make method calls.
When we click on the power saving mode button to start the power saving mode, the parameter powerSave here will be set to true, and the needFirstTimeWarning will also be true.

    /**
     * Enable / disable battery saver by user request.
     * - If it's the first time and needFirstTimeWarning, show the first time dialog.
     * - If it's 4th time through 8th time, show the schedule suggestion notification.
     *
     * @param enable true to disable battery saver.
     *
     * @return true if the request succeeded.
     */
    public static synchronized boolean setPowerSaveMode(Context context,
            boolean enable, boolean needFirstTimeWarning) {

        if (DEBUG) {
            Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF"));
        }

        final ContentResolver cr = context.getContentResolver();

        if (enable && needFirstTimeWarning && maybeShowBatterySaverConfirmation(context)) {
            return false;
        }

        if (enable && !needFirstTimeWarning) {
            setBatterySaverConfirmationAcknowledged(context);
        }

        if (context.getSystemService(PowerManager.class).setPowerSaveMode(enable)) {
            if (enable) {
                final int count =
                        Secure.getInt(cr, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 0) + 1;
                Secure.putInt(cr, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, count);

                final Parameters parameters = new Parameters(context);

                if ((count >= parameters.startNth)
                        && (count <= parameters.endNth)
                        && Global.getInt(cr, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0) == 0
                        && Secure.getInt(cr,
                        Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, 0) == 0) {
                    showAutoBatterySaverSuggestion(context);
                }
            }

            return true;
        }
        return false;
    }

Within this class, the main method implemented is context.getSystemService(PowerManager.class).setPowerSaveMode(enable).
GettSystemService takes the PowerManager object and calls the function setPowerSaveMode to set it up.

PowerManager is one of Android's core service s, and its code is located in frameworks/base/core/java/android/os/PowerManager.java

    /**
     * Set the current power save mode.
     *
     * @return True if the set was allowed.
     *
     * @see #isPowerSaveMode()
     *
     * @hide
     */
    public boolean setPowerSaveMode(boolean mode) {
        try {
            return mService.setPowerSaveMode(mode);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

PowerManager is the framework exposed interface, the real implementation is the PowerManager Service.

        @Override // Binder call
        public boolean setPowerSaveMode(boolean enabled) {
            mContext.enforceCallingOrSelfPermission(
                    android.Manifest.permission.DEVICE_POWER, null);
            final long ident = Binder.clearCallingIdentity();
            try { 
                return setLowPowerModeInternal(enabled);
            } finally {
                Binder.restoreCallingIdentity(ident);
            }
        }

Here are a few things to note:

  1. enforceCallingOrSelfPermission is a secure memory of the Android framework, primarily to check if the UID of the current calling process has privileges to operate on a system.For example, in power-saving mode, because it operates inside the SystemUI, the AndroidManifest.xml inside the SystemUI must declare the rights of DEVICE_POWER, otherwise it will be thrown as an exception.
<uses-permission android:name="android.permission.DEVICE_POWER" />
  1. When the SystemUI calls PowerManager and checks permissions, Binder.clearCallingIdentity clears the UID,PID information of the SystemUI Process and replaces it with the UID,PID content of the process PowerManager is in.Because this involves binder communication, we will discuss it later in the Binder section.
  2. In try{}finally{}, Binder.restoreCallingIdentity() is called.
    This is used to restore uid and pid information on the remote caller side, which is the inverse of clearCallingIdentity.
  3. Next, there is the core call, setLowPowerModeInternal (enable), where the parameter, enable = true, is clicked on the power saving mode.
    private boolean setLowPowerModeInternal(boolean enabled) {
        synchronized (mLock) {
            if (DEBUG) {
                Slog.d(TAG, "setLowPowerModeInternal " + enabled + " mIsPowered=" + mIsPowered);
            }
            if (mIsPowered) {
                return false;
            }

            mBatterySaverStateMachine.setBatterySaverEnabledManually(enabled);

            return true;
        }
    }

The operation of setLowPowerModel Internal is really simple, using the object mBatterySaverStateMachine of BatterySaverStateMachine to call setBatterySaverEnabledManual.

    /**
     * {@link com.android.server.power.PowerManagerService} calls it when
     * {@link android.os.PowerManager#setPowerSaveMode} is called.
     *
     * Note this could? be called before {@link #onBootCompleted} too.
     */
    public void setBatterySaverEnabledManually(boolean enabled) {
        if (DEBUG) { 
            Slog.d(TAG, "setBatterySaverEnabledManually: enabled=" + enabled);
        }
        synchronized (mLock) {
            enableBatterySaverLocked(/*enable=*/ enabled, /*manual=*/ true,
                    (enabled ? BatterySaverController.REASON_MANUAL_ON
                            : BatterySaverController.REASON_MANUAL_OFF),
                    (enabled ? "Manual ON" : "Manual OFF"));
        }
    }

Because enabled came in before it was true, it can be translated as

enableBatterySaverLocked(true, true, BatterySaverController.REASON_MANUAL_ON, "Manual ON"
);

public static final int REASON_MANUAL_ON = 2;

The next enableBatterySaverLocked function updates the content to the global setting s.

    /**
     * Actually enable / disable battery saver. Write the new state to the global settings
     * and propagate it to {@link #mBatterySaverController}.
     */
    private void enableBatterySaverLocked(boolean enable, boolean manual, int intReason,
            String strReason) {
        if (DEBUG) {
            Slog.d(TAG, "enableBatterySaver: enable=" + enable + " manual=" + manual
                    + " reason=" + strReason + "(" + intReason + ")");
        }
        final boolean wasEnabled = mBatterySaverController.isEnabled();

        if (wasEnabled == enable) {
            if (DEBUG) {
                Slog.d(TAG, "Already " + (enable ? "enabled" : "disabled"));
            }
            return;
        }
        if (enable && mIsPowered) {
            if (DEBUG) Slog.d(TAG, "Can't enable: isPowered");
            return; 
        }    
        mLastChangedIntReason = intReason;
        mLastChangedStrReason = strReason;

        if (manual) {
            if (enable) {
                updateSnoozingLocked(false, "Manual snooze OFF");
            } else {
                // When battery saver is disabled manually (while battery saver is enabled)
                // when the battery level is low, we "snooze" BS -- i.e. disable auto battery saver.
                // We resume auto-BS once the battery level is not low, or the device is plugged in.
                if (isBatterySaverEnabled() && mIsBatteryLevelLow) {
                    updateSnoozingLocked(true, "Manual snooze");
                }
            }
        }

        mSettingBatterySaverEnabled = enable;
        putGlobalSetting(Global.LOW_POWER_MODE, enable ? 1 : 0);

        if (manual) {
            mSettingBatterySaverEnabledSticky = enable;
            putGlobalSetting(Global.LOW_POWER_MODE_STICKY, enable ? 1 : 0);
        }
        mBatterySaverController.enableBatterySaver(enable, intReason);

        if (DEBUG) {
            Slog.d(TAG, "Battery saver: Enabled=" + enable
                    + " manual=" + manual
                    + " reason=" + strReason + "(" + intReason + ")");
        }
    }
  1. wasEnabled first determines if it was enable d before and, if so, return s.
  2. isPowered also returns directly if true.
  3. MLastChangedIntReason, the value of mLastChangedStrReason is saved as the previously passed in value, that is, mLastChangedIntReason=2, mLastChangedStrReason="Manual ON".
  4. Both manual and enable s are true states, so upateSnoozingLocked. This function simply sets the value of mBatterySaverSnoozing to true.We will encounter this value later.
  5. PutGlobalSetting (Global.LOW_POWER_MODE, enable? 1: 0); set the LOW_POWER_MODE value to 1 in the database.
  6. PutGlobalSetting (Global.LOW_POWER_MODE_STICKY, enable? 1:0); Set the LOW_POWER_MODE_STICKY value to 1 in the database.
  7. mBatterySaverController.enableBatterySaver(enable, intReason); this function will be called for real operation after the corresponding database has been saved.
    /**
     * Called by {@link PowerManagerService} to update the battery saver stete.
     */
    public void enableBatterySaver(boolean enable, int reason) {
        synchronized (mLock) {
            if (mEnabled == enable) {
                return;
            }
            mEnabled = enable;

            mHandler.postStateChanged(/*sendBroadcast=*/ true, reason);
        }
    }

In the enableBatterySaver function that calls the controller, the main point is that mEnable is set to true.And pass the actual reason ing to the Handler.

public void postStateChanged(boolean sendBroadcast, int reason) {
        obtainMessage(MSG_STATE_CHANGED, sendBroadcast ?
                ARG_SEND_BROADCAST : ARG_DONT_SEND_BROADCAST, reason).sendToTarget();
 }

The purpose of this function is to populate the message and sendToTarget.

    @Override
    public void dispatchMessage(Message msg) {
        switch (msg.what) {
            case MSG_STATE_CHANGED:
                handleBatterySaverStateChanged(
                        msg.arg1 == ARG_SEND_BROADCAST,
                        msg.arg2);
                break;

            case MSG_SYSTEM_READY:
                for (Plugin p : mPlugins) {
                    p.onSystemReady(BatterySaverController.this);
                }
                break;
        }
    }
  1. Because MSG_STATE_CHANGED is a previously populated and sent message, it is processed in the case of MSG_STATE_CHANGED.
  2. handleBatterySaverStateChanged(msg.arg1 == ARG_SEND_BROADCAST, msg.arg2); in the function, the first parameter is true because it was also passed ARG_SEND_BROADCAST; the second parameter is the previously populated reason, so the function of handleBatterySaverStateChanged is very complex, involving information such as jni, native devices, adbrocast, etc., so followHere's the real big game.
    /**
     * Dispatch power save events to the listeners.
     *
     * This method is always called on the handler thread.
     *
     * This method is called only in the following cases:
     * - When battery saver becomes activated.
     * - When battery saver becomes deactivated.
     * - When battery saver is on the interactive state changes.
     * - When battery saver is on the battery saver policy changes.
     */
    void handleBatterySaverStateChanged(boolean sendBroadcast, int reason) {
        final LowPowerModeListener[] listeners;

        final boolean enabled;
        final boolean isInteractive = getPowerManager().isInteractive();
        final ArrayMap<String, String> fileValues;

        synchronized (mLock) {
            EventLogTags.writeBatterySaverMode(
                    mPreviouslyEnabled ? 1 : 0, // Previously off or on.
                    mEnabled ? 1 : 0, // Now off or on.
                    isInteractive ?  1 : 0, // Device interactive state.
                    mEnabled ? mBatterySaverPolicy.toEventLogString() : "",
                    reason);
            mPreviouslyEnabled = mEnabled;

            listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]);

            enabled = mEnabled;
            mIsInteractive = isInteractive;

            if (enabled) {
                fileValues = mBatterySaverPolicy.getFileValues(isInteractive);
            } else {
                fileValues = null;
            }
        }

        final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
        if (pmi != null) {
            pmi.powerHint(PowerHint.LOW_POWER, enabled ? 1 : 0);
        }

        updateBatterySavingStats();

        if (ArrayUtils.isEmpty(fileValues)) {
            mFileUpdater.restoreDefault();
        } else {
            mFileUpdater.writeFiles(fileValues);
        }

        for (Plugin p : mPlugins) {
            p.onBatterySaverChanged(this);
        }
        if (sendBroadcast) {

            if (DEBUG) {
                Slog.i(TAG, "Sending broadcasts for mode: " + enabled);
            }

            // Send the broadcasts and notify the listeners. We only do this when the battery saver
            // mode changes, but not when only the screen state changes.
            Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)
                    .putExtra(PowerManager.EXTRA_POWER_SAVE_MODE, enabled)
                    .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);

            intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);

            // Send internal version that requires signature permission.
            intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
            mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
                    Manifest.permission.DEVICE_POWER);

            for (LowPowerModeListener listener : listeners) {
                final PowerSaveState result =
                        mBatterySaverPolicy.getBatterySaverPolicy(
                                listener.getServiceType(), enabled);
                listener.onLowPowerModeChanged(result);
            }
        }
    }

This function is very large, and the operation is broken down as follows:

  1. final boolean isInteractive = getPowerManager().isInteractive();
    Here, get the status of Interactive from PowerManager.
    isInteractive is simple:
    /**
     * Returns true if the wakefulness state represents an interactive state
     * as defined by {@link android.os.PowerManager#isInteractive}.
     */
    public static boolean isInteractive(int wakefulness) {
        return wakefulness == WAKEFULNESS_AWAKE || wakefulness == WAKEFULNESS_DREAMING;
    }

In fact, it is to determine whether the value of wakefulness is WAKEFULNESS_AWAKE or WAKEFULNESS_DREAMING, so what does this value represent?

WAKEFULNESS_ASLEEP: Indicates that the system is currently dormant and can only be awakened by a wakeUp() call.
WAKEFULNESS_AWAKE: Indicates that the system is currently functioning properly.
WAKEFULNESS_DREAMING: Indicates that the system is currently in the state of an interactive screen saver.
WAKEFULNESS_DOZING: Indicates that the system is in a "doze" state

Since we are analyzing the power saving mode here, we will not expand in detail.
isInteractive returns true when it normally clicks on the power-saving mode button.

  1. listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]);
    The mListeners here were actually all service s that added LowPowerModeListener when they were previously registered.Includes VibratorService, NetworkPolicyManagerService, etc.These are saved once for later message distribution.
  2. The call to PowerManager Internal here is complex, but it doesn't really work.But for each manufacturer, you can focus here because the frequency of subsequent CPUs can be set along this line.
        final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
        if (pmi != null) {
            pmi.powerHint(PowerHint.LOW_POWER, enabled ? 1 : 0);
        }

First is the function of powerHint

        @Override // Binder call
        public void powerHint(int hintId, int data) {
            if (!mSystemReady) {
                // Service not ready yet, so who the heck cares about power hints, bah.
                return;
            }
            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
            powerHintInternal(hintId, data);
        }

As before, this side will still check to see if there are any rights to DEVICE_POWER.
The powerHintInternal function is then called for processing.

private void powerHintInternal(int hintId, int data) {
        // Maybe filter the event.
        switch (hintId) {
            case PowerHint.LAUNCH: // 1: activate launch boost 0: deactivate.
                if (data == 1 && mBatterySaverController.isLaunchBoostDisabled()) {
                    return;
                }
                break;
        }

        nativeSendPowerHint(hintId, data);
    }

In this function, the hintId we passed in is 5, and the value of PowerHint.LAUNCH is defined in the class of PowerHint, which is public static final int LAUNCH = 8;.
So native's method is called directly.
The corresponding files and functions are:

frameworks/base/services/core/jni/com_android_server_power_PowerManagerService.cpp

static void nativeSendPowerHint(JNIEnv* /* env */, jclass /* clazz */, jint hintId, jint data) {
    sendPowerHint(static_cast<PowerHint>(hintId), data);
}

Because it's a jni call, this side is just a simple encapsulation.

static void sendPowerHint(PowerHint hintId, uint32_t data) {
    sp<IPowerV1_1> powerHalV1_1 = getPowerHalV1_1();
    Return<void> ret;
    if (powerHalV1_1 != nullptr) {
        ret = powerHalV1_1->powerHintAsync(hintId, data);
        processPowerHalReturn(ret, "powerHintAsync");
    } else {
        sp<IPowerV1_0> powerHalV1_0 = getPowerHalV1_0();
        if (powerHalV1_0 != nullptr) {
            ret = powerHalV1_0->powerHint(hintId, data);
            processPowerHalReturn(ret, "powerHint");
        }
    }
}

The mobile phone I use is pixel xl, its corresponding devices are marlin, and powerHal is powerHalV1_1 version, so I will walk to it

ret = powerHalV1_1->powerHintAsync(hintId, data);
processPowerHalReturn(ret, "powerHintAsync");

So the corresponding function is:
powerHintAsync device/google/marlin/power/Power.cpp
The following is the implementation:

Return<void> Power::powerHintAsync(PowerHint hint, int32_t data) {
    // just call the normal power hint in this oneway function
    return powerHint(hint, data);
}

It's a crazy encapsulation again.

Return<void> Power::powerHint(PowerHint hint, int32_t data) {
    if (android::base::GetProperty("init.svc.vendor.perfd", "") != "running") {
        ALOGW("perfd is not started");
        return Void();
    }
    power_hint(static_cast<power_hint_t>(hint), data ? (&data) : NULL);
    return Void();
}

For perfd, we are not going to do any analysis here.Because normally, it continues to be power_hint.
Next, the power_hint implements a large code, but it is useless.
This function sets the CPU frequency in different power states, but it does not judge the incoming hint=5, so it has no practical effect.But when we want to set the processing of the low frequency state of the cpu, this is undoubtedly the best choice.

void power_hint(power_hint_t hint, void *data)
{

    /* Check if this hint has been overridden. */
    if (power_hint_override(hint, data) == HINT_HANDLED) {
        ALOGE("power_hint_override");
        /* The power_hint has been handled. We can skip the rest. */
        return;
    }

    switch(hint) {
        case POWER_HINT_VSYNC:
        break;
        case POWER_HINT_SUSTAINED_PERFORMANCE:
        {
            ...
            break;
        }
        case POWER_HINT_VR_MODE:
        {
            ...
            break;
        }
        case POWER_HINT_INTERACTION:
        {
            ...
            break;
        }
        default:
        break;
}

Here's default break...

  1. Let's go back to the main function and do the analysis.
    After analyzing the pmi calls, updateBattery SavingStats ();
    private void updateBatterySavingStats() {
        final PowerManager pm = getPowerManager();
        if (pm == null) {
            Slog.wtf(TAG, "PowerManager not initialized");
            return;
        }
        final boolean isInteractive = pm.isInteractive();
        final int dozeMode =
                pm.isDeviceIdleMode() ? DozeState.DEEP
                        : pm.isLightDeviceIdleMode() ? DozeState.LIGHT
                        : DozeState.NOT_DOZING;

        synchronized (mLock) {
            if (mIsPluggedIn) {
                mBatterySavingStats.startCharging();
                return;
            }
            mBatterySavingStats.transitionState(
                    mEnabled ? BatterySaverState.ON : BatterySaverState.OFF,
                    isInteractive ? InteractiveState.INTERACTIVE : InteractiveState.NON_INTERACTIVE,
                    dozeMode);
        }
    }

The core of this function is transitionState, but it is only used to preserve the current state, so we won't go into it.

  1. fileValues is empty by default, and we don't handle analysis either.
  2. The next Plugin is a bit interesting because it's a plug-in way to operate.
        for (Plugin p : mPlugins) {
            p.onBatterySaverChanged(this);
        }

But for real implementation, aosp only implements one:
onBatterySaverChanged

frameworks/base/services/core/java/com/android/server/power/batterysaver/BatterySaverLocationPlugin.java
Here the specific implementation is:

    private void updateLocationState(BatterySaverController caller) {
        final boolean kill =
                (caller.getBatterySaverPolicy().getGpsMode()
                        == PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF) &&
                caller.isEnabled() && !caller.isInteractive();


        boolean gpsMode = (caller.getBatterySaverPolicy().getGpsMode() == PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF);
        if (DEBUG) {
            Slog.d(TAG, "Battery saver " + (kill ? "stopping" : "restoring") + " location.");
        }
        Settings.Global.putInt(mContext.getContentResolver(),
                Global.LOCATION_GLOBAL_KILL_SWITCH, kill ? 1 : 0);
    }

killer = false; therefore, when setting up the Global Settings database here, set LOCATION_GLOBAL_KILL_SWITCH to zero.

  1. The next step is to process the broadcast request for each service, package.
            Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)
                    .putExtra(PowerManager.EXTRA_POWER_SAVE_MODE, enabled)
                    .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);

            intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);

            // Send internal version that requires signature permission.
            intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
            mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
                    Manifest.permission.DEVICE_POWER);

            for (LowPowerModeListener listener : listeners) {
                final PowerSaveState result =
                        mBatterySaverPolicy.getBatterySaverPolicy(
                                listener.getServiceType(), enabled);
                listener.onLowPowerModeChanged(result);
            }

There are mainly three broadcasts: ACTION_POWER_SAVE_MODE_CHANGING, ACTION_POWER_SAVE_MODE_CHANGED, ACTION_POWER_SAVE_MODE_CHANGED, and ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL.

Next, we do a one-to-one analysis of the three broadcasts.

  1. ACTION_POWER_SAVE_MODE_CHANGING
    The broadcast first added a flag FLAG_RECEIVER_REGISTERED_ONLY to indicate that only dynamically registered receipts are allowed.
    The main places accepted are:
BatterySaverReceiver.java   (PowerManager.ACTION_POWER_SAVE_MODE_CHANGING.equals(action)) 
BatteryControllerImpl.java  (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)) 

One is in Packages/apps/Settings
One is frameworks/base/packages/SystemUI/
For Battery SaverReceiver, the main update here is the status inside settings.
For sBatteryControllerImpl, the call here is

    private void setPowerSave(boolean powerSave) {
        if (powerSave == mPowerSave) return;
        mPowerSave = powerSave;

        // AOD power saving setting might be different from PowerManager power saving mode.
        PowerSaveState state = mPowerManager.getPowerSaveState(PowerManager.ServiceType.AOD);
        mAodPowerSave = state.batterySaverEnabled;

        if (DEBUG) Log.d(TAG, "Power save is " + (mPowerSave ? "on" : "off"));
        firePowerSaveChanged();
    }

The state of PowerSave is saved here and implemented by calling the firePowerSaveChanged method.

    private void firePowerSaveChanged() {
        synchronized (mChangeCallbacks) {
            final int N = mChangeCallbacks.size();
            for (int i = 0; i < N; i++) {
                mChangeCallbacks.get(i).onPowerSaveChanged(mPowerSave);
            }
        }
    }

Here you will iterate through mChangeCallbacks and call back the method of onPowerSaveChanged.
The main ways to implement callbacks are:

BatteryMeterView.java
StatusBar.java
KeyguardStatusBarView.java
LightBarController.java
BatterySaverTile.java

This is where the SystemUI and interface show some of the above actions.

  1. ACTION_POWER_SAVE_MODE_CHANGED
    The broadcast, like before, added a Flag: FLAG_RECEIVER_REGISTERED_ONLY
    The main operations of the recipient are:
BatteryBroadcastReceiver.java    
(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())
BatteryControllerImpl.java
(action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED))
PowerUI.java
(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(action)) 
DeviceStateMonitor.java
case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
SoundTriggerHelper.java
if (!PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
GnssLocationProvider.java
PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(action)

And what are the six radio stations doing?

class Effect
BatteryBroadcastReceiver Notify the change of battery power, enter power save mode
PowerUI If in power save mode, ignore the warning that the battery is low
DeviceStateMonitor Set modem to power save mode
SoundTriggerHelper Turn off voice interaction
GnssLocationProvider Limit gps usage, shut down gps after screen goes off

The final call to DeviceStateMonitor is as follows:

    /**
     * Send the device state to the modem.
     *
     * @param type Device state type. See DeviceStateType defined in types.hal.
     * @param state True if enabled/on, otherwise disabled/off
     */
    private void sendDeviceState(int type, boolean state) {
        log("send type: " + deviceTypeToString(type) + ", state=" + state, true);
        mPhone.mCi.sendDeviceState(type, state, null);
    }

The implementation of SoundTriggerHelper is to change the value of mIsPowerSaveMode for the following purposes:

    // Whether we are allowed to run any recognition at all. The conditions that let us run
    // a recognition include: no active phone call or not being in a power save mode. Also,
    // the native service should be enabled.
    private boolean isRecognitionAllowed() {
        return !mCallActive && !mServiceDisabled && !mIsPowerSaveMode;
    }

The call to GnssLocationProvider is implemented as follows and can be easily read from comments.

    private void updateLowPowerMode() {
        // Disable GPS if we are in device idle mode.
        boolean disableGps = mPowerManager.isDeviceIdleMode();
        final PowerSaveState result =
                mPowerManager.getPowerSaveState(ServiceType.GPS);
        switch (result.gpsMode) {
            case PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
                // If we are in battery saver mode and the screen is off, disable GPS.
                disableGps |= result.batterySaverEnabled && !mPowerManager.isInteractive();
                break;
        }
        if (disableGps != mDisableGps) {
            mDisableGps = disableGps;
            updateRequirements();
        }
    }
  1. Finally, an array callback function that was previously saved:
            for (LowPowerModeListener listener : listeners) {
                final PowerSaveState result =
                        mBatterySaverPolicy.getBatterySaverPolicy(
                                listener.getServiceType(), enabled);
                listener.onLowPowerModeChanged(result);
            }

Listeners mentioned this in our previous article, which summarizes in detail:

class Effect
VibratorService.java Cancel the vibration effect of mobile phone
NetworkPolicyManagerService.java Update whitelist and apply restrictions on network access
WindowManagerService.java Cancel window animation

The NetworkPolicyManagerService calls as follows:

    // NOTE: since both fw_dozable and fw_powersave uses the same map
    // (mPowerSaveTempWhitelistAppIds) for whitelisting, we can reuse their logic in this method.
    private void updateRulesForWhitelistedPowerSaveUL(boolean enabled, int chain,
            SparseIntArray rules) {
        if (enabled) {
            // Sync the whitelists before enabling the chain.  We don't care about the rules if
            // we are disabling the chain.
            final SparseIntArray uidRules = rules;
            uidRules.clear();
            final List<UserInfo> users = mUserManager.getUsers();
            for (int ui = users.size() - 1; ui >= 0; ui--) {
                UserInfo user = users.get(ui);
                updateRulesForWhitelistedAppIds(uidRules, mPowerSaveTempWhitelistAppIds, user.id);
                updateRulesForWhitelistedAppIds(uidRules, mPowerSaveWhitelistAppIds, user.id);
                if (chain == FIREWALL_CHAIN_POWERSAVE) {
                    updateRulesForWhitelistedAppIds(uidRules,
                            mPowerSaveWhitelistExceptIdleAppIds, user.id);
                }
            }
            for (int i = mUidState.size() - 1; i >= 0; i--) {
                if (isProcStateAllowedWhileIdleOrPowerSaveMode(mUidState.valueAt(i))) {
                    uidRules.put(mUidState.keyAt(i), FIREWALL_RULE_ALLOW);
                }
            }
            setUidFirewallRulesUL(chain, uidRules, CHAIN_TOGGLE_ENABLE);
        } else {
            setUidFirewallRulesUL(chain, null, CHAIN_TOGGLE_DISABLE);
        }
    }
  1. Updating temporary whitelist, whitelist, whitelist except idle app will allow network access
  2. Allow network access if process priority is above foreground service
    private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules, boolean paroled) {
        if (!isUidValidForBlacklistRules(uid)) {
            if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
            return RULE_NONE;
        }

        final boolean isIdle = !paroled && isUidIdle(uid);
        final boolean restrictMode = isIdle || mRestrictPower || mDeviceIdleMode;
        final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid);

        final boolean isWhitelisted = isWhitelistedBatterySaverUL(uid, mDeviceIdleMode);
        final int oldRule = oldUidRules & MASK_ALL_NETWORKS;
        int newRule = RULE_NONE;

        // First step: define the new rule based on user restrictions and foreground state.

        // NOTE: if statements below could be inlined, but it's easier to understand the logic
        // by considering the foreground and non-foreground states.
        if (isForeground) {
            if (restrictMode) {
                newRule = RULE_ALLOW_ALL;
            }
        } else if (restrictMode) {
            newRule = isWhitelisted ? RULE_ALLOW_ALL : RULE_REJECT_ALL;
        }

        final int newUidRules = (oldUidRules & MASK_METERED_NETWORKS) | newRule;

        if (LOGV) {
            Log.v(TAG, "updateRulesForPowerRestrictionsUL(" + uid + ")"
                    + ", isIdle: " + isIdle
                    + ", mRestrictPower: " + mRestrictPower
                    + ", mDeviceIdleMode: " + mDeviceIdleMode
                    + ", isForeground=" + isForeground
                    + ", isWhitelisted=" + isWhitelisted
                    + ", oldRule=" + uidRulesToString(oldRule)
                    + ", newRule=" + uidRulesToString(newRule)
                    + ", newUidRules=" + uidRulesToString(newUidRules)
                    + ", oldUidRules=" + uidRulesToString(oldUidRules));
        }

        // Second step: notify listeners if state changed.
        if (newRule != oldRule) {
            if (newRule == RULE_NONE || hasRule(newRule, RULE_ALLOW_ALL)) {
                if (LOGV) Log.v(TAG, "Allowing non-metered access for UID " + uid);
            } else if (hasRule(newRule, RULE_REJECT_ALL)) {
                if (LOGV) Log.v(TAG, "Rejecting non-metered access for UID " + uid);
            } else {
                // All scenarios should have been covered above
                Log.wtf(TAG, "Unexpected change of non-metered UID state for " + uid
                        + ": foreground=" + isForeground
                        + ", whitelisted=" + isWhitelisted
                        + ", newRule=" + uidRulesToString(newUidRules)
                        + ", oldRule=" + uidRulesToString(oldUidRules));
            }
            mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget();
        }

        return newUidRules;
    }
  1. isUidValidForBlacklistRules do not allow updates when uid is of type media or drm, or app s previously authorized for INTERNET network access
    // TODO: the MEDIA / DRM restriction might not be needed anymore, in which case both
    // methods below could be merged into a isUidValidForRules() method.
    private boolean isUidValidForBlacklistRules(int uid) {
        // allow rules on specific system services, and any apps
        if (uid == android.os.Process.MEDIA_UID || uid == android.os.Process.DRM_UID
            || (UserHandle.isApp(uid) && hasInternetPermissions(uid))) {
            return true;
        }

        return false;
    }
  1. If it is a foreground process, access to the network is allowed even in restricted mode
  2. Other processes, non-whitelist will be set to deny access to RULE_REJECT_ALL
        if (isForeground) {
            if (restrictMode) {
                newRule = RULE_ALLOW_ALL;
            }
        } else if (restrictMode) {
            newRule = isWhitelisted ? RULE_ALLOW_ALL : RULE_REJECT_ALL;
        }

For WindowManagerService, the effect is to cancel the window animation.

                @Override
                public void onLowPowerModeChanged(PowerSaveState result) {
                    synchronized (mWindowMap) {
                        final boolean enabled = result.batterySaverEnabled;
                        if (mAnimationsDisabled != enabled && !mAllowAnimationsInLowPowerMode) {
                            mAnimationsDisabled = enabled;
                            dispatchNewAnimatorScaleLocked(null);
                        }
                    }
                }

Keywords: Java Android network Database

Added by phpvn.org on Sun, 19 May 2019 00:31:51 +0300