| /* |
| * Copyright (C) 2017 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.internal.telephony; |
| |
| import static android.hardware.radio.V1_0.DeviceStateType.CHARGING_STATE; |
| import static android.hardware.radio.V1_0.DeviceStateType.LOW_DATA_EXPECTED; |
| import static android.hardware.radio.V1_0.DeviceStateType.POWER_SAVE_MODE; |
| |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.hardware.display.DisplayManager; |
| import android.hardware.radio.V1_0.IndicationFilter; |
| import android.net.ConnectivityManager; |
| import android.os.BatteryManager; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.PowerManager; |
| import android.telephony.Rlog; |
| import android.util.LocalLog; |
| import android.view.Display; |
| |
| import com.android.internal.util.IndentingPrintWriter; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| |
| /** |
| * The device state monitor monitors the device state such as charging state, power saving sate, |
| * and then passes down the information to the radio modem for the modem to perform its own |
| * proprietary power saving strategy. Device state monitor also turns off the unsolicited |
| * response from the modem when the device does not need to receive it, for example, device's |
| * screen is off and does not have activities like tethering, remote display, etc...This effectively |
| * prevents the CPU from waking up by those unnecessary unsolicited responses such as signal |
| * strength update. |
| */ |
| public class DeviceStateMonitor extends Handler { |
| protected static final boolean DBG = false; /* STOPSHIP if true */ |
| protected static final String TAG = DeviceStateMonitor.class.getSimpleName(); |
| |
| private static final int EVENT_RIL_CONNECTED = 0; |
| private static final int EVENT_SCREEN_STATE_CHANGED = 1; |
| private static final int EVENT_POWER_SAVE_MODE_CHANGED = 2; |
| private static final int EVENT_CHARGING_STATE_CHANGED = 3; |
| private static final int EVENT_TETHERING_STATE_CHANGED = 4; |
| |
| private final Phone mPhone; |
| |
| private final LocalLog mLocalLog = new LocalLog(100); |
| |
| /** |
| * Flag for wifi/usb/bluetooth tethering turned on or not |
| */ |
| private boolean mIsTetheringOn; |
| |
| /** |
| * Screen state provided by Display Manager. True indicates one of the screen is on, otherwise |
| * all off. |
| */ |
| private boolean mIsScreenOn; |
| |
| /** |
| * Indicating the device is plugged in and is supplying sufficient power that the battery level |
| * is going up (or the battery is fully charged). See BatteryManager.isCharging() for the |
| * details |
| */ |
| private boolean mIsCharging; |
| |
| /** |
| * Flag for device power save mode. See PowerManager.isPowerSaveMode() for the details. |
| * Note that it is not possible both mIsCharging and mIsPowerSaveOn are true at the same time. |
| * The system will automatically end power save mode when the device starts charging. |
| */ |
| private boolean mIsPowerSaveOn; |
| |
| /** |
| * Low data expected mode. True indicates low data traffic is expected, for example, when the |
| * device is idle (e.g. screen is off and not doing tethering in the background). Note this |
| * doesn't mean no data is expected. |
| */ |
| private boolean mIsLowDataExpected; |
| |
| /** |
| * The unsolicited response filter. See IndicationFilter defined in types.hal for the definition |
| * of each bit. |
| */ |
| private int mUnsolicitedResponseFilter = IndicationFilter.ALL; |
| |
| private final DisplayManager.DisplayListener mDisplayListener = |
| new DisplayManager.DisplayListener() { |
| @Override |
| public void onDisplayAdded(int displayId) { } |
| |
| @Override |
| public void onDisplayRemoved(int displayId) { } |
| |
| @Override |
| public void onDisplayChanged(int displayId) { |
| boolean screenOn = isScreenOn(); |
| Message msg = obtainMessage(EVENT_SCREEN_STATE_CHANGED); |
| msg.arg1 = screenOn ? 1 : 0; |
| sendMessage(msg); |
| } |
| }; |
| |
| /** |
| * Device state broadcast receiver |
| */ |
| private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| log("received: " + intent, true); |
| |
| Message msg; |
| switch (intent.getAction()) { |
| case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED: |
| msg = obtainMessage(EVENT_POWER_SAVE_MODE_CHANGED); |
| msg.arg1 = isPowerSaveModeOn() ? 1 : 0; |
| log("Power Save mode " + ((msg.arg1 == 1) ? "on" : "off"), true); |
| break; |
| case BatteryManager.ACTION_CHARGING: |
| msg = obtainMessage(EVENT_CHARGING_STATE_CHANGED); |
| msg.arg1 = 1; // charging |
| break; |
| case BatteryManager.ACTION_DISCHARGING: |
| msg = obtainMessage(EVENT_CHARGING_STATE_CHANGED); |
| msg.arg1 = 0; // not charging |
| break; |
| case ConnectivityManager.ACTION_TETHER_STATE_CHANGED: |
| ArrayList<String> activeTetherIfaces = intent.getStringArrayListExtra( |
| ConnectivityManager.EXTRA_ACTIVE_TETHER); |
| |
| boolean isTetheringOn = activeTetherIfaces != null |
| && activeTetherIfaces.size() > 0; |
| log("Tethering " + (isTetheringOn ? "on" : "off"), true); |
| msg = obtainMessage(EVENT_TETHERING_STATE_CHANGED); |
| msg.arg1 = isTetheringOn ? 1 : 0; |
| break; |
| default: |
| log("Unexpected broadcast intent: " + intent, false); |
| return; |
| } |
| sendMessage(msg); |
| } |
| }; |
| |
| /** |
| * Device state monitor constructor. Note that each phone object should have its own device |
| * state monitor, meaning there will be two device monitors on the multi-sim device. |
| * |
| * @param phone Phone object |
| */ |
| public DeviceStateMonitor(Phone phone) { |
| mPhone = phone; |
| DisplayManager dm = (DisplayManager) phone.getContext().getSystemService( |
| Context.DISPLAY_SERVICE); |
| dm.registerDisplayListener(mDisplayListener, null); |
| |
| mIsPowerSaveOn = isPowerSaveModeOn(); |
| mIsCharging = isDeviceCharging(); |
| mIsScreenOn = isScreenOn(); |
| // Assuming tethering is always off after boot up. |
| mIsTetheringOn = false; |
| mIsLowDataExpected = false; |
| |
| log("DeviceStateMonitor mIsPowerSaveOn=" + mIsPowerSaveOn + ",mIsScreenOn=" |
| + mIsScreenOn + ",mIsCharging=" + mIsCharging, false); |
| |
| final IntentFilter filter = new IntentFilter(); |
| filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED); |
| filter.addAction(BatteryManager.ACTION_CHARGING); |
| filter.addAction(BatteryManager.ACTION_DISCHARGING); |
| filter.addAction(ConnectivityManager.ACTION_TETHER_STATE_CHANGED); |
| mPhone.getContext().registerReceiver(mBroadcastReceiver, filter, null, mPhone); |
| |
| mPhone.mCi.registerForRilConnected(this, EVENT_RIL_CONNECTED, null); |
| } |
| |
| /** |
| * @return True if low data is expected |
| */ |
| private boolean isLowDataExpected() { |
| return mIsPowerSaveOn || (!mIsCharging && !mIsTetheringOn && !mIsScreenOn); |
| } |
| |
| /** |
| * @return True if signal strength update should be turned off. |
| */ |
| private boolean shouldTurnOffSignalStrength() { |
| return mIsPowerSaveOn || (!mIsCharging && !mIsScreenOn); |
| } |
| |
| /** |
| * @return True if full network update should be turned off. Only significant changes will |
| * trigger the network update unsolicited response. |
| */ |
| private boolean shouldTurnOffFullNetworkUpdate() { |
| return mIsPowerSaveOn || (!mIsCharging && !mIsScreenOn && !mIsTetheringOn); |
| } |
| |
| /** |
| * @return True if data dormancy status update should be turned off. |
| */ |
| private boolean shouldTurnOffDormancyUpdate() { |
| return mIsPowerSaveOn || (!mIsCharging && !mIsTetheringOn && !mIsScreenOn); |
| } |
| |
| /** |
| * Message handler |
| * |
| * @param msg The message |
| */ |
| @Override |
| public void handleMessage(Message msg) { |
| log("handleMessage msg=" + msg, false); |
| switch (msg.what) { |
| case EVENT_RIL_CONNECTED: |
| onRilConnected(); |
| break; |
| default: |
| updateDeviceState(msg.what, msg.arg1 != 0); |
| } |
| } |
| |
| /** |
| * Update the device and send the information to the modem. |
| * |
| * @param eventType Device state event type |
| * @param state True if enabled/on, otherwise disabled/off. |
| */ |
| private void updateDeviceState(int eventType, boolean state) { |
| switch (eventType) { |
| case EVENT_SCREEN_STATE_CHANGED: |
| if (mIsScreenOn == state) return; |
| mIsScreenOn = state; |
| break; |
| case EVENT_CHARGING_STATE_CHANGED: |
| if (mIsCharging == state) return; |
| mIsCharging = state; |
| sendDeviceState(CHARGING_STATE, mIsCharging); |
| break; |
| case EVENT_TETHERING_STATE_CHANGED: |
| if (mIsTetheringOn == state) return; |
| mIsTetheringOn = state; |
| break; |
| case EVENT_POWER_SAVE_MODE_CHANGED: |
| if (mIsPowerSaveOn == state) return; |
| mIsPowerSaveOn = state; |
| sendDeviceState(POWER_SAVE_MODE, mIsPowerSaveOn); |
| break; |
| default: |
| return; |
| } |
| |
| if (mIsLowDataExpected != isLowDataExpected()) { |
| mIsLowDataExpected = !mIsLowDataExpected; |
| sendDeviceState(LOW_DATA_EXPECTED, mIsLowDataExpected); |
| } |
| |
| int newFilter = 0; |
| if (!shouldTurnOffSignalStrength()) { |
| newFilter |= IndicationFilter.SIGNAL_STRENGTH; |
| } |
| |
| if (!shouldTurnOffFullNetworkUpdate()) { |
| newFilter |= IndicationFilter.FULL_NETWORK_STATE; |
| } |
| |
| if (!shouldTurnOffDormancyUpdate()) { |
| newFilter |= IndicationFilter.DATA_CALL_DORMANCY_CHANGED; |
| } |
| |
| setUnsolResponseFilter(newFilter, false); |
| } |
| |
| /** |
| * Called when RIL is connected during boot up or reconnected after modem restart. |
| * |
| * When modem crashes, if the user turns the screen off before RIL reconnects, device |
| * state and filter cannot be sent to modem. Resend the state here so that modem |
| * has the correct state (to stop signal strength reporting, etc). |
| */ |
| private void onRilConnected() { |
| log("RIL connected.", true); |
| sendDeviceState(CHARGING_STATE, mIsCharging); |
| sendDeviceState(LOW_DATA_EXPECTED, mIsLowDataExpected); |
| sendDeviceState(POWER_SAVE_MODE, mIsPowerSaveOn); |
| setUnsolResponseFilter(mUnsolicitedResponseFilter, true); |
| } |
| |
| /** |
| * Convert the device state type into string |
| * |
| * @param type Device state type |
| * @return The converted string |
| */ |
| private String deviceTypeToString(int type) { |
| switch (type) { |
| case CHARGING_STATE: return "CHARGING_STATE"; |
| case LOW_DATA_EXPECTED: return "LOW_DATA_EXPECTED"; |
| case POWER_SAVE_MODE: return "POWER_SAVE_MODE"; |
| default: return "UNKNOWN"; |
| } |
| } |
| |
| /** |
| * 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); |
| } |
| |
| /** |
| * Turn on/off the unsolicited response from the modem. |
| * |
| * @param newFilter See UnsolicitedResponseFilter in types.hal for the definition of each bit. |
| * @param force Always set the filter when true. |
| */ |
| private void setUnsolResponseFilter(int newFilter, boolean force) { |
| if (force || newFilter != mUnsolicitedResponseFilter) { |
| log("old filter: " + mUnsolicitedResponseFilter + ", new filter: " + newFilter, true); |
| mPhone.mCi.setUnsolResponseFilter(newFilter, null); |
| mUnsolicitedResponseFilter = newFilter; |
| } |
| } |
| |
| /** |
| * @return True if the device is currently in power save mode. |
| * See {@link android.os.BatteryManager#isPowerSaveMode BatteryManager.isPowerSaveMode()}. |
| */ |
| private boolean isPowerSaveModeOn() { |
| final PowerManager pm = (PowerManager) mPhone.getContext().getSystemService( |
| Context.POWER_SERVICE); |
| return pm.isPowerSaveMode(); |
| } |
| |
| /** |
| * @return Return true if the battery is currently considered to be charging. This means that |
| * the device is plugged in and is supplying sufficient power that the battery level is |
| * going up (or the battery is fully charged). |
| * See {@link android.os.BatteryManager#isCharging BatteryManager.isCharging()}. |
| */ |
| private boolean isDeviceCharging() { |
| final BatteryManager bm = (BatteryManager) mPhone.getContext().getSystemService( |
| Context.BATTERY_SERVICE); |
| return bm.isCharging(); |
| } |
| |
| /** |
| * @return True if one the device's screen (e.g. main screen, wifi display, HDMI display, or |
| * Android auto, etc...) is on. |
| */ |
| private boolean isScreenOn() { |
| // Note that we don't listen to Intent.SCREEN_ON and Intent.SCREEN_OFF because they are no |
| // longer adequate for monitoring the screen state since they are not sent in cases where |
| // the screen is turned off transiently such as due to the proximity sensor. |
| final DisplayManager dm = (DisplayManager) mPhone.getContext().getSystemService( |
| Context.DISPLAY_SERVICE); |
| Display[] displays = dm.getDisplays(); |
| |
| if (displays != null) { |
| for (Display display : displays) { |
| // Anything other than STATE_ON is treated as screen off, such as STATE_DOZE, |
| // STATE_DOZE_SUSPEND, etc... |
| if (display.getState() == Display.STATE_ON) { |
| log("Screen " + Display.typeToString(display.getType()) + " on", true); |
| return true; |
| } |
| } |
| log("Screens all off", true); |
| return false; |
| } |
| |
| log("No displays found", true); |
| return false; |
| } |
| |
| /** |
| * @param msg Debug message |
| * @param logIntoLocalLog True if log into the local log |
| */ |
| private void log(String msg, boolean logIntoLocalLog) { |
| if (DBG) Rlog.d(TAG, msg); |
| if (logIntoLocalLog) { |
| mLocalLog.log(msg); |
| } |
| } |
| |
| /** |
| * Print the DeviceStateMonitor into the given stream. |
| * |
| * @param fd The raw file descriptor that the dump is being sent to. |
| * @param pw A PrintWriter to which the dump is to be set. |
| * @param args Additional arguments to the dump request. |
| */ |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); |
| ipw.increaseIndent(); |
| ipw.println("mIsTetheringOn=" + mIsTetheringOn); |
| ipw.println("mIsScreenOn=" + mIsScreenOn); |
| ipw.println("mIsCharging=" + mIsCharging); |
| ipw.println("mIsPowerSaveOn=" + mIsPowerSaveOn); |
| ipw.println("mIsLowDataExpected=" + mIsLowDataExpected); |
| ipw.println("mUnsolicitedResponseFilter=" + mUnsolicitedResponseFilter); |
| ipw.println("Local logs:"); |
| ipw.increaseIndent(); |
| mLocalLog.dump(fd, ipw, args); |
| ipw.decreaseIndent(); |
| ipw.decreaseIndent(); |
| ipw.flush(); |
| } |
| } |