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

Commit df6dd147 authored by jackqdyulei's avatar jackqdyulei
Browse files

Framework for the anomaly detection.

This cl adds the following components for anomaly dection:
1. AnomalyLoader: run all the anomaly checks in the background
2. Anomaly: Data class to represent what anomaly it is
3. Detector: Different kinds of anomaly detector with common interface
4. Action:  Suggestions when facing anomaly(Force stop, uninstall)
5. AnomalyDialogFragment: show the confirm dialog for action
6. AnomalyPreferenceController: handle update and click for
anomalyPreference, since it will be used in multiple fragments.
7. AnomalyUtils: utility class for anomaly

This cl also adds skeleton for the wakelock check and action. Following
cls will add real implementation about it.

Bug: 36924669
Test: RunSettingsRoboTests
Change-Id: I89fc4b6963757869b93791b4275ca53c04ab9604
parent 4869effb
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -25,6 +25,9 @@
        android:selectable="true"
        android:layout="@layout/battery_header"/>

    <Preference
        android:key="high_usage"/>

    <PreferenceCategory
        android:key="device_usage_list">

+41 −1
Original line number Diff line number Diff line
@@ -17,9 +17,11 @@
package com.android.settings.fuelgauge;

import android.app.Activity;
import android.app.LoaderManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.Loader;
import android.graphics.drawable.Drawable;
import android.os.BatteryStats;
import android.os.Build;
@@ -59,6 +61,10 @@ import com.android.settings.dashboard.SummaryLoader;
import com.android.settings.display.AutoBrightnessPreferenceController;
import com.android.settings.display.BatteryPercentagePreferenceController;
import com.android.settings.display.TimeoutPreferenceController;
import com.android.settings.fuelgauge.anomaly.Anomaly;
import com.android.settings.fuelgauge.anomaly.AnomalyDialogFragment;
import com.android.settings.fuelgauge.anomaly.AnomalyLoader;
import com.android.settings.fuelgauge.anomaly.AnomalyPreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.FooterPreferenceMixin;
@@ -74,7 +80,8 @@ import java.util.List;
 * Displays a list of apps and subsystems that consume power, ordered by how much power was
 * consumed since the last time it was unplugged.
 */
public class PowerUsageSummary extends PowerUsageBase {
public class PowerUsageSummary extends PowerUsageBase implements
        AnomalyDialogFragment.AnomalyDialogListener {

    static final String TAG = "PowerUsageSummary";

@@ -84,6 +91,7 @@ public class PowerUsageSummary extends PowerUsageBase {
    private static final String KEY_BATTERY_HEADER = "battery_header";
    private static final int MAX_ITEMS_TO_LIST = USE_FAKE_DATA ? 30 : 10;
    private static final int MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP = 10;
    private static final int ANOMALY_LOADER = 1;

    private static final String KEY_SCREEN_USAGE = "screen_usage";
    private static final String KEY_TIME_SINCE_LAST_FULL_CHARGE = "last_full_charge";
@@ -117,8 +125,29 @@ public class PowerUsageSummary extends PowerUsageBase {

    private LayoutPreference mBatteryLayoutPref;
    private PreferenceGroup mAppListGroup;
    private AnomalyPreferenceController mAnomalyPreferenceController;
    private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;

    private LoaderManager.LoaderCallbacks<List<Anomaly>> mAnomalyLoaderCallbacks =
            new LoaderManager.LoaderCallbacks<List<Anomaly>>() {

                @Override
                public Loader<List<Anomaly>> onCreateLoader(int id, Bundle args) {
                    return new AnomalyLoader(getContext(), mStatsHelper);
                }

                @Override
                public void onLoadFinished(Loader<List<Anomaly>> loader, List<Anomaly> data) {
                    // show high usage preference if possible
                    mAnomalyPreferenceController.updateAnomalyPreference(data);
                }

                @Override
                public void onLoaderReset(Loader<List<Anomaly>> loader) {

                }
            };

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
@@ -130,6 +159,7 @@ public class PowerUsageSummary extends PowerUsageBase {
        mLastFullChargePref = (PowerGaugePreference) findPreference(
                KEY_TIME_SINCE_LAST_FULL_CHARGE);
        mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.battery_footer_summary);
        mAnomalyPreferenceController = new AnomalyPreferenceController(this);

        mBatteryUtils = BatteryUtils.getInstance(getContext());

@@ -163,6 +193,9 @@ public class PowerUsageSummary extends PowerUsageBase {

    @Override
    public boolean onPreferenceTreeClick(Preference preference) {
        if (mAnomalyPreferenceController.onPreferenceTreeClick(preference)) {
            return true;
        }
        if (KEY_BATTERY_HEADER.equals(preference.getKey())) {
            performBatteryHeaderClick();
            return true;
@@ -403,6 +436,8 @@ public class PowerUsageSummary extends PowerUsageBase {
            return;
        }

        getLoaderManager().initLoader(ANOMALY_LOADER, null, mAnomalyLoaderCallbacks);

        cacheRemoveAllPrefs(mAppListGroup);
        mAppListGroup.setOrderingAsAdded(false);
        boolean addedSome = false;
@@ -690,6 +725,11 @@ public class PowerUsageSummary extends PowerUsageBase {
        }
    };

    @Override
    public void onAnomalyHandled(Anomaly anomaly) {
        mAnomalyPreferenceController.hideAnomalyPreference();
    }

    private static class SummaryProvider implements SummaryLoader.SummaryProvider {
        private final Context mContext;
        private final SummaryLoader mLoader;
+136 −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;

import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.IntDef;

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

/**
 * Data that represents an app has been detected as anomaly. It contains
 *
 * 1. Basic information of the app(i.e. uid, package name)
 * 2. Type of anomaly
 * 3. Data that has been detected as anomaly(i.e wakelock time)
 */
public class Anomaly implements Parcelable {
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({AnomalyType.WAKE_LOCK})
    public @interface AnomalyType {
        int WAKE_LOCK = 0;
    }

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({AnomalyActionType.FORCE_STOP})
    public @interface AnomalyActionType {
        int FORCE_STOP = 0;
    }

    /**
     * Type of this this anomaly
     */
    public final int type;
    public final int uid;
    public final long wakelockTimeMs;

    /**
     * Display name of this anomaly, usually it is the app name
     */
    public final String displayName;
    public final String packageName;

    private Anomaly(Builder builder) {
        type = builder.mType;
        uid = builder.mUid;
        displayName = builder.mDisplayName;
        packageName = builder.mPackageName;
        wakelockTimeMs = builder.mWakeLockTimeMs;
    }

    private Anomaly(Parcel in) {
        type = in.readInt();
        uid = in.readInt();
        displayName = in.readString();
        packageName = in.readString();
        wakelockTimeMs = in.readLong();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(type);
        dest.writeInt(uid);
        dest.writeString(displayName);
        dest.writeString(packageName);
        dest.writeLong(wakelockTimeMs);
    }

    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
        public Anomaly createFromParcel(Parcel in) {
            return new Anomaly(in);
        }

        public Anomaly[] newArray(int size) {
            return new Anomaly[size];
        }
    };

    public static final class Builder {
        @AnomalyType
        private int mType;
        private int mUid;
        private String mDisplayName;
        private String mPackageName;
        private long mWakeLockTimeMs;

        public Builder setType(@AnomalyType int type) {
            mType = type;
            return this;
        }

        public Builder setUid(int uid) {
            mUid = uid;
            return this;
        }

        public Builder setDisplayName(String displayName) {
            mDisplayName = displayName;
            return this;
        }

        public Builder setPackageName(String packageName) {
            mPackageName = packageName;
            return this;
        }

        public Builder setWakeLockTimeMs(long wakeLockTimeMs) {
            mWakeLockTimeMs = wakeLockTimeMs;
            return this;
        }

        public Anomaly build() {
            return new Anomaly(this);
        }
    }
}
+103 −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;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.annotation.VisibleForTesting;

import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.fuelgauge.anomaly.action.AnomalyAction;

/**
 * Dialog Fragment to show action dialog for each anomaly
 */
public class AnomalyDialogFragment extends InstrumentedDialogFragment implements
        DialogInterface.OnClickListener {

    private static final String ARG_ANOMALY = "anomaly";

    @VisibleForTesting
    Anomaly mAnomaly;

    /**
     * Listener to give the control back to target fragment
     */
    public interface AnomalyDialogListener {
        /**
         * This method is invoked once anomaly is handled, then target fragment could do
         * extra work. One example is that fragment could remove the anomaly preference
         * since it has been handled
         *
         * @param anomaly that has been handled
         */
        void onAnomalyHandled(Anomaly anomaly);
    }

    public static AnomalyDialogFragment newInstance(Anomaly anomaly) {
        AnomalyDialogFragment dialogFragment = new AnomalyDialogFragment();

        Bundle args = new Bundle(1);
        args.putParcelable(ARG_ANOMALY, anomaly);
        dialogFragment.setArguments(args);

        return dialogFragment;
    }

    @Override
    public int getMetricsCategory() {
        // TODO(b/37681923): add anomaly metric id
        return 0;
    }

    @Override
    public void onClick(DialogInterface dialog, int which) {
        final AnomalyDialogListener lsn = (AnomalyDialogListener) getTargetFragment();
        if (lsn == null) {
            return;
        }

        final AnomalyAction anomalyAction = AnomalyUtils.getAnomalyAction(mAnomaly.type);
        anomalyAction.handlePositiveAction(mAnomaly.packageName);
        lsn.onAnomalyHandled(mAnomaly);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        final Bundle bundle = getArguments();
        mAnomaly = bundle.getParcelable(ARG_ANOMALY);

        final Context context = getContext();
        final AnomalyAction anomalyAction = AnomalyUtils.getAnomalyAction(mAnomaly.type);
        switch (anomalyAction.getActionType()) {
            case Anomaly.AnomalyActionType.FORCE_STOP:
                return new AlertDialog.Builder(context)
                        .setTitle(R.string.force_stop_dlg_title)
                        .setMessage(R.string.force_stop_dlg_text)
                        .setPositiveButton(R.string.dlg_ok, this)
                        .setNegativeButton(R.string.dlg_cancel, null)
                        .create();
            default:
                throw new IllegalArgumentException("unknown type " + mAnomaly.type);
        }
    }

}
+51 −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;

import android.content.Context;

import com.android.internal.os.BatteryStatsHelper;
import com.android.settings.fuelgauge.anomaly.checker.WakeLockAnomalyDetector;
import com.android.settings.utils.AsyncLoader;

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

/**
 * Loader to compute which apps are anomaly and return a anomaly list. It will return
 * an empty list if there is no anomaly.
 */
//TODO(b/36924669): add test for this file, for now it seems there is nothing to test
public class AnomalyLoader extends AsyncLoader<List<Anomaly>> {
    private BatteryStatsHelper mBatteryStatsHelper;

    public AnomalyLoader(Context context, BatteryStatsHelper batteryStatsHelper) {
        super(context);
        mBatteryStatsHelper = batteryStatsHelper;
    }

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

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

        return anomalies;
    }
}
Loading