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

Commit 9a68befe authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Framework for the anomaly detection."

parents d427f4fe df6dd147
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