Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f842fd94 authored by Lei Yu's avatar Lei Yu Committed by Android (Google) Code Review
Browse files

Merge "Add wakelock anomaly detector"

parents 73619d16 4aa3358c
Loading
Loading
Loading
Loading
+30 −5
Original line number Diff line number Diff line
@@ -19,9 +19,11 @@ package com.android.settings.fuelgauge.anomaly;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.IntDef;
import android.text.TextUtils;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;

/**
 * Data that represents an app has been detected as anomaly. It contains
@@ -53,7 +55,7 @@ public class Anomaly implements Parcelable {
    /**
     * Display name of this anomaly, usually it is the app name
     */
    public final String displayName;
    public final CharSequence displayName;
    public final String packageName;

    private Anomaly(Builder builder) {
@@ -67,7 +69,7 @@ public class Anomaly implements Parcelable {
    private Anomaly(Parcel in) {
        type = in.readInt();
        uid = in.readInt();
        displayName = in.readString();
        displayName = in.readCharSequence();
        packageName = in.readString();
        wakelockTimeMs = in.readLong();
    }
@@ -81,11 +83,34 @@ public class Anomaly implements Parcelable {
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(type);
        dest.writeInt(uid);
        dest.writeString(displayName);
        dest.writeCharSequence(displayName);
        dest.writeString(packageName);
        dest.writeLong(wakelockTimeMs);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Anomaly)) {
            return false;
        }

        Anomaly other = (Anomaly) obj;

        return type == other.type
                && uid == other.uid
                && wakelockTimeMs == other.wakelockTimeMs
                && TextUtils.equals(displayName, other.displayName)
                && TextUtils.equals(packageName, other.packageName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(type, uid, displayName, packageName, wakelockTimeMs);
    }

    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
        public Anomaly createFromParcel(Parcel in) {
            return new Anomaly(in);
@@ -100,7 +125,7 @@ public class Anomaly implements Parcelable {
        @AnomalyType
        private int mType;
        private int mUid;
        private String mDisplayName;
        private CharSequence mDisplayName;
        private String mPackageName;
        private long mWakeLockTimeMs;

@@ -114,7 +139,7 @@ public class Anomaly implements Parcelable {
            return this;
        }

        public Builder setDisplayName(String displayName) {
        public Builder setDisplayName(CharSequence displayName) {
            mDisplayName = displayName;
            return this;
        }
+5 −2
Original line number Diff line number Diff line
@@ -39,13 +39,16 @@ public class AnomalyLoader extends AsyncLoader<List<Anomaly>> {
    }

    @Override
    protected void onDiscardResult(List<Anomaly> result) {}
    protected void onDiscardResult(List<Anomaly> result) {
    }

    @Override
    public List<Anomaly> loadInBackground() {
        final List<Anomaly> anomalies = new ArrayList<>();
        anomalies.addAll(new WakeLockAnomalyDetector().detectAnomalies(mBatteryStatsHelper));
        anomalies.addAll(new WakeLockAnomalyDetector(getContext())
                .detectAnomalies(mBatteryStatsHelper));

        return anomalies;
    }

}
+6 −2
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import android.support.annotation.VisibleForTesting;
import android.support.v14.preference.PreferenceFragment;
import android.support.v7.preference.Preference;

import com.android.settings.R;

import java.util.List;

/**
@@ -66,9 +68,11 @@ public class AnomalyPreferenceController {
    public void updateAnomalyPreference(List<Anomaly> anomalies) {
        mAnomalies = anomalies;

        if (!mAnomalies.isEmpty()) {
            mAnomalyPreference.setVisible(true);
            //TODO(b/36924669): update summary for anomaly preference
        }
    }

    public void hideAnomalyPreference() {
        mAnomalyPreference.setVisible(false);
+82 −1
Original line number Diff line number Diff line
@@ -16,7 +16,20 @@

package com.android.settings.fuelgauge.anomaly.checker;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.BatteryStats;
import android.os.SystemClock;
import android.support.annotation.VisibleForTesting;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.Log;

import com.android.internal.os.BatterySipper;
import com.android.internal.os.BatteryStatsHelper;
import com.android.settings.Utils;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.anomaly.Anomaly;

import java.util.ArrayList;
@@ -26,11 +39,79 @@ import java.util.List;
 * Check whether apps holding wakelock too long
 */
public class WakeLockAnomalyDetector implements AnomalyDetector {
    private static final String TAG = "WakeLockAnomalyChecker";
    // TODO: get threshold form server side
    private static final long WAKE_LOCK_THRESHOLD_MS = 2 * DateUtils.MINUTE_IN_MILLIS;
    private PackageManager mPackageManager;
    private Context mContext;
    @VisibleForTesting
    BatteryUtils mBatteryUtils;

    public WakeLockAnomalyDetector(Context context) {
        mContext = context;
        mPackageManager = context.getPackageManager();
        mBatteryUtils = BatteryUtils.getInstance(context);
    }

    @Override
    public List<Anomaly> detectAnomalies(BatteryStatsHelper batteryStatsHelper) {
        //TODO(b/36921529): check anomaly using the batteryStatsHelper
        final List<BatterySipper> batterySippers = batteryStatsHelper.getUsageList();
        final List<Anomaly> anomalies = new ArrayList<>();
        final long rawRealtime = SystemClock.elapsedRealtime();

        // Check the app one by one
        for (int i = 0, size = batterySippers.size(); i < size; i++) {
            final BatterySipper sipper = batterySippers.get(i);
            final BatteryStats.Uid uid = sipper.uidObj;
            if (uid == null) {
                continue;
            }
            final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks =
                    uid.getWakelockStats();
            long maxPartialWakeLockMs = 0;

            for (int iw = wakelocks.size() - 1; iw >= 0; iw--) {
                final BatteryStats.Timer timer = wakelocks.valueAt(iw).getWakeTime(
                        BatteryStats.WAKE_TYPE_PARTIAL);
                if (timer == null) {
                    continue;
                }
                maxPartialWakeLockMs = Math.max(maxPartialWakeLockMs,
                        getTotalDurationMs(timer, rawRealtime));
            }

            // Report it if wakelock time is too long and it is not a hidden batterysipper
            // TODO: add more attributes to detect wakelock anomaly
            if (maxPartialWakeLockMs > WAKE_LOCK_THRESHOLD_MS
                    && !mBatteryUtils.shouldHideSipper(sipper)) {
                final String packageName = getPackageName(uid.getUid());
                final CharSequence displayName = Utils.getApplicationLabel(mContext,
                        packageName);

                Anomaly anomaly = new Anomaly.Builder()
                        .setUid(uid.getUid())
                        .setType(Anomaly.AnomalyType.WAKE_LOCK)
                        .setDisplayName(displayName)
                        .setPackageName(packageName)
                        .build();
                anomalies.add(anomaly);
            }

        }
        return anomalies;
    }

    private String getPackageName(int uid) {
        final String[] packageNames = mPackageManager.getPackagesForUid(uid);

        return packageNames == null ? null : packageNames[0];
    }

    @VisibleForTesting
    long getTotalDurationMs(BatteryStats.Timer timer, long rawRealtime) {
        if (timer == null) {
            return 0;
        }
        return timer.getTotalDurationMsLocked(rawRealtime);
    }
}
+140 −0
Original line number Diff line number Diff line
/*
 * 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.settings.fuelgauge.anomaly.checker;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.BatteryStats;
import android.text.format.DateUtils;
import android.util.ArrayMap;

import com.android.internal.os.BatterySipper;
import com.android.internal.os.BatteryStatsHelper;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.anomaly.Anomaly;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;

import java.util.ArrayList;
import java.util.List;

@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class WakeLockAnomalyDetectorTest {
    private static final long ANOMALY_WAKELOCK_TIME_MS = DateUtils.HOUR_IN_MILLIS;
    private static final long NORMAL_WAKELOCK_TIME_MS = DateUtils.SECOND_IN_MILLIS;
    private static final int ANOMALY_UID = 111;
    private static final int NORMAL_UID = 222;
    @Mock
    private BatteryStatsHelper mBatteryStatsHelper;
    @Mock
    private BatterySipper mAnomalySipper;
    @Mock
    private BatteryStats.Timer mAnomalyTimer;
    @Mock
    private BatteryStats.Uid.Wakelock mAnomalyWakelock;
    @Mock
    private BatterySipper mNormalSipper;
    @Mock
    private BatteryStats.Timer mNormalTimer;
    @Mock
    private BatteryStats.Uid.Wakelock mNormalWakelock;
    @Mock
    private BatteryStats.Uid mAnomalyUid;
    @Mock
    private BatteryStats.Uid mNormalUid;
    @Mock
    private BatteryUtils mBatteryUtils;
    @Mock
    private PackageManager mPackageManager;
    @Mock
    private ApplicationInfo mApplicationInfo;

    private ArrayMap<String, BatteryStats.Uid.Wakelock> mAnomalyWakelocks;
    private ArrayMap<String, BatteryStats.Uid.Wakelock> mNormalWakelocks;
    private WakeLockAnomalyDetector mWakelockAnomalyDetector;
    private Context mContext;
    private List<BatterySipper> mUsageList;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        mContext = spy(RuntimeEnvironment.application);

        doReturn(false).when(mBatteryUtils).shouldHideSipper(any());
        doReturn(mPackageManager).when(mContext).getPackageManager();
        doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(anyString(), anyInt());

        mAnomalySipper.uidObj = mAnomalyUid;
        mAnomalyWakelocks = new ArrayMap<>();
        mAnomalyWakelocks.put("", mAnomalyWakelock);
        doReturn(mAnomalyWakelocks).when(mAnomalyUid).getWakelockStats();
        doReturn(mAnomalyTimer).when(mAnomalyWakelock).getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
        doReturn(ANOMALY_UID).when(mAnomalyUid).getUid();

        mNormalSipper.uidObj = mNormalUid;
        mNormalWakelocks = new ArrayMap<>();
        mNormalWakelocks.put("", mNormalWakelock);
        doReturn(mNormalTimer).when(mNormalWakelock).getWakeTime(BatteryStats.WAKE_TYPE_PARTIAL);
        doReturn(mNormalWakelocks).when(mNormalUid).getWakelockStats();
        doReturn(NORMAL_UID).when(mNormalUid).getUid();

        mUsageList = new ArrayList<>();
        mUsageList.add(mAnomalySipper);
        mUsageList.add(mNormalSipper);
        doReturn(mUsageList).when(mBatteryStatsHelper).getUsageList();

        mWakelockAnomalyDetector = spy(new WakeLockAnomalyDetector(mContext));
        mWakelockAnomalyDetector.mBatteryUtils = mBatteryUtils;
        doReturn(ANOMALY_WAKELOCK_TIME_MS).when(mWakelockAnomalyDetector).getTotalDurationMs(
                eq(mAnomalyTimer), anyLong());
        doReturn(NORMAL_WAKELOCK_TIME_MS).when(mWakelockAnomalyDetector).getTotalDurationMs(
                eq(mNormalTimer), anyLong());
    }

    @Test
    public void testDetectAnomalies_containsAnomaly_detectIt() {
        final Anomaly anomaly = new Anomaly.Builder()
                .setUid(ANOMALY_UID)
                .setType(Anomaly.AnomalyType.WAKE_LOCK)
                .build();

        List<Anomaly> mAnomalies = mWakelockAnomalyDetector.detectAnomalies(mBatteryStatsHelper);

        assertThat(mAnomalies).containsExactly(anomaly);
    }
}