Android 14 でのDozeの挙動について

# Dozeについて気づいたこと
バッテリーステータスについて調査をしていた時についでにDozeについても確認してみたところ、Android14では自分が知っている挙動とは異なることに気づきました

| | Doze |
|---|---|
| Android13 | ![85d188c5-ec78-47b4-b94b-0e7fe84df9e9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2699725/5e936f6c-877e-8077-ac7b-8bae503d45c0.png)  |
| Android14 | ![eaa8216f-48fd-498b-8a8f-e0d5e5f87dc8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2699725/b1461b62-1682-a04d-0bc1-f61038ef109b.png) |![85d188c5-ec78-47b4-b94b-0e7fe84df9e9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2699725/5e936f6c-877e-8077-ac7b-8bae503d45c0.png)  |

2台のデバイスを横に並べて同様に動かしたので、操作によってこのような違いが出てるとは考えられません。少なくとも結果から<b>Android 14でDozeの挙動が大きく変わっている</b>ことは間違いなさそうです        

リファレンスも読んでみたのですが、言及されておらず原因不明
なんだこりゃ

https://source.android.com/docs/core/power/platform_mgmt?hl=ja


https://developer.android.com/training/monitoring-device-state/doze-standby?hl=ja


# 調べてみる

https://tsungi.medium.com/android-doze-tweaks-83dadb5b4a9a

https://mayinjian.wordpress.com/2020/06/16/android-doze-%E4%BC%91%E7%9C%A0%E6%A8%A1%E5%BC%8F/

わかったこととしては、以下の通り
### Deep-Doze
 - DeepDozeとLightDozeは並列で実行されている
 - DeepDozeは以下の流れでステートが遷移されている
   - STATE_ACTIVE　：アクティブ状態
   - STATE_INACTIVE　：非アクティブ (画面OFF、物理的に動いていない)
   - STATE_IDLE_PENDING　：次のスリープまで待機
   - STATE_SENSING　：センサー検知中
   - STATE_LOCATING　：現在地を特定中
   - STATE_IDLE　：スリープ状態
   - STATE_IDLE_MAINTENANCE	：一時的にスリープ状態を抜ける
- DeepDozeはSTATE_INACTIVEから下の状態を指しているよう

### Light-Doze

- LightDozeは以下の流れでステートが遷移されている
    - LIGHT_STATE_ACTIVE　：デバイスは現在アクティブです
    - LIGHT_STATE_INACTIVE　：非アクティブ (画面OFF) 
    - LIGHT_STATE_PRE_IDLE　：現在の作業が終わるまで一時的なスリープ状態
    - LIGHT_STATE_IDLE：スリープ状態
    - LIGHT_STATE_WAITING_FOR_NETWORK　：ネット接続を待機
    - LIGHT_STATE_IDLE_MAINTENANCE：一時的にスリープから抜ける
- LightDozeはLIGHT_STATE_INACTIVEから下の状態を指しているよう


# AOSP読んでみる
調べてみたものの、やはり言及されている記事は見当たらなかったので上記の知識を踏まえてコードを確認してみる

https://cs.android.com/android/_/android/platform/frameworks/base/+/6e47c7075b91983ae501114425ea25e6df7690c8:apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java;dlc=b88801a2bd139a17120099641ac1608cac923d8e;drc=6531a739abe5971f81cb5a6c7a34dca81005ff2c;bpv=0;bpt=0

## DeepDoze編
はじめにDeep-Dozeについて確認していく
デバイスが使われている状態（STATE_ACTIVE）から非アクティブ状態（STATE_INACTIVE）へ遷移する該当コードは以下の通り
ここは 13と 14で多少差はあるが、今回の主題に関係がない改修だった


```JAVA
@GuardedBy("this")
    void becomeInactiveIfAppropriateLocked() {
        verifyAlarmStateLocked();

        ~~ 中略 ~~
        if (mDeepEnabled) {
            if (mQuickDozeActivated) {
                ~~ 中略 ~~
            } else if (mState == STATE_ACTIVE) {
                moveToStateLocked(STATE_INACTIVE, "no activity");
                resetIdleManagementLocked();
                long delay = mInactiveTimeout;
                if (shouldUseIdleTimeoutFactorLocked()) {
                    delay = (long) (mPreIdleFactor * delay);
                }
                if (isUpcomingAlarmClock()) {
                    // If there's an upcoming AlarmClock alarm, we won't go into idle, so
                    // setting a wakeup alarm before the upcoming alarm is futile. Set the idle
                    // alarm to after the upcoming AlarmClock alarm.
                    scheduleAlarmLocked(
                            mAlarmManager.getNextWakeFromIdleTime() - mInjector.getElapsedRealtime()
                                    + delay);
                } else {
                    scheduleAlarmLocked(delay);
                }
            }
        }
        if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
            moveToLightStateLocked(LIGHT_STATE_INACTIVE, "no activity");
            resetLightIdleManagementLocked();
            scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT,
                    mConstants.FLEX_TIME_SHORT, true);
        }
    }
```
アクティブ状態（STATE_ACTIVE）であった場合、非アクティブ状態へと遷移させ、```scheduleAlarmLocked(delay)```でアラームをかけてDeepDoze状態へ入るようにしている
```scheduleAlarmLocked```のコードも念のため載せておく

```JAVA
@GuardedBy("this")
    @VisibleForTesting
    void scheduleAlarmLocked(long delay) {
        if (DEBUG) Slog.d(TAG, "scheduleAlarmLocked(" + delay + ", " + stateToString(mState) + ")");

        if (mUseMotionSensor && mMotionSensor == null
                && mState != STATE_QUICK_DOZE_DELAY
                && mState != STATE_IDLE
                && mState != STATE_IDLE_MAINTENANCE) {
            // If there is no motion sensor on this device, but we need one, then we won't schedule
            // alarms, because we can't determine if the device is not moving.  This effectively
            // turns off normal execution of device idling, although it is still possible to
            // manually poke it by pretending like the alarm is going off.
            // STATE_QUICK_DOZE_DELAY skips the motion sensing so if the state is past the motion
            // sensing stage (ie, is QUICK_DOZE_DELAY, IDLE, or IDLE_MAINTENANCE), then idling
            // can continue until the user interacts with the device.
            return;
        }
        mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
        if (mState == STATE_IDLE) {
            mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                    mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
        } else if (mState == STATE_LOCATING) {
            // Use setExact so we don't keep the GPS active for too long.
            mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                    mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
        } else {
            if (mConstants.USE_WINDOW_ALARMS) {
                mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                        mNextAlarmTime, mConstants.FLEX_TIME_SHORT,
                        "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
            } else {
                mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                        mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
            }
        }
    }
```


注目していただきたい点としては、この時のアラームの間隔は ```long delay = mInactiveTimeout;```であるということだ
この時delayで用いられている```mInactiveTimeout```について遡っていくと13 と14 で大きな違いがある

### mInactiveTimeout
#### Android13
Android13では以下の通りである
```JAVA
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
```
```JAVA
/**
 * This is the time, after becoming inactive, at which we start looking at the
 * motion sensor to determine if the device is being left alone.  We don't do this
 * immediately after going inactive just because we don't want to be continually running
 * the motion sensor whenever the screen is off.
 * @see #KEY_INACTIVE_TIMEOUT
 */
public long INACTIVE_TIMEOUT = DEFAULT_INACTIVE_TIMEOUT;
```
```JAVA
private static final long DEFAULT_INACTIVE_TIMEOUT =(30 * 60 * 1000L) / (!COMPRESS_TIME ? 1 : 10);
```
<b>以上から短くて３分。最長で30分</b>であることがわかる

#### Android14 
Android14では以下のようである
```JAVA
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
```
```JAVA
/**
 * This is the time, after becoming inactive, at which we start looking at the
 * motion sensor to determine if the device is being left alone.  We don't do this
 * immediately after going inactive just because we don't want to be continually running
 * the motion sensor whenever the screen is off.
 * @see #KEY_INACTIVE_TIMEOUT
 */
public long INACTIVE_TIMEOUT = DEFAULT_INACTIVE_TIMEOUT;
```
```JAVA
private static final long DEFAULT_INACTIVE_TIMEOUT =(30 * 60 * 1000L) / (!COMPRESS_TIME ? 1 : 10);
```
```JAVA
private void initDefault() {
    ~~中略~~
    mDefaultInactiveTimeout = getTimeout(
            res.getInteger(com.android.internal.R.integer.device_idle_inactive_to_ms),
                    mDefaultInactiveTimeout);
```
```JAVA
private long getTimeout(long defTimeout, long compTimeout) {
            return (!COMPRESS_TIME || defTimeout < compTimeout) ? defTimeout : compTimeout;
        }
```
```
<!-- Default for DeviceIdleController.Constants.INACTIVE_TIMEOUT -->
    <integer name="device_idle_inactive_to_ms">60000</integer>
```
getTimeoutのコードから
 - COMPRESS_TIME が false または defTimeout < compTimeout の場合
   - defTimeout を返す
 - COMPRESS_TIME が true かつ defTimeout >= compTimeout の場合
   - compTimeout を返す

defTimeoutは常に1分であり、compTimeoutの値は最小でも3分であることから、常にdefTimeout(1分)を返す
したがって、<b>常に1分</b>である

## DeepDoze編　結論

|  | Android13 | Android14 |
|---|---|---|
| long |　30min | 1min |
| short | 3min | 1min |

Android13では状況によって3~30分の時間でDeepDozeに入る仕様であったが、Android14では常に1分でDeepDozeへ入るように変更が加えられていた


開発者が望んだ挙動かどうかは定かではないが、1分後に速攻でDeepDozeかかるのは不便という他ない

## LightDoze編
LightDozeについても同様にみていく
先ほどのbecomeInactiveIfAppropriateLockedの
```JAVA
scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT,mConstants.FLEX_TIME_SHORT, true);
```
が該当箇所

<br>

＝編集中…＝