Merge changes Ie4c98617,I6a7248d9

* changes:
  Add BatteryTipDialogFragment.
  Add high usage battery tip
This commit is contained in:
TreeHugger Robot
2018-01-10 03:15:27 +00:00
committed by Android (Google) Code Review
25 changed files with 914 additions and 118 deletions

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2018 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:paddingStart="?android:dialogPreferredPadding"
android:paddingEnd="?android:dialogPreferredPadding"
android:orientation="horizontal">
<ImageView
android:id="@+id/app_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"/>
<TextView
android:id="@+id/app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="24dp"
android:textColor="?android:textColorPrimary"
android:paddingEnd="7dp"/>
<TextView
android:id="@+id/app_screen_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:paddingEnd="16dp"
android:textAlignment="viewEnd"
android:textColor="?android:textColorPrimary"/>
</LinearLayout>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2018 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.
-->
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="8dp"
android:scrollbars="vertical"/>

View File

@@ -4773,6 +4773,21 @@
<string name="battery_tip_low_battery_title">Low battery capacity</string>
<!-- Summary for the low battery tip [CHAR LIMIT=NONE] -->
<string name="battery_tip_low_battery_summary">Battery can\'t provide good battery life</string>
<!-- Title for the battery high usage tip [CHAR LIMIT=NONE] -->
<string name="battery_tip_high_usage_title" product="default">Phone used heavily</string>
<!-- Title for the battery high usage tip [CHAR LIMIT=NONE] -->
<string name="battery_tip_high_usage_title" product="tablet">Tablet used heavily</string>
<!-- Title for the battery high usage tip [CHAR LIMIT=NONE] -->
<string name="battery_tip_high_usage_title" product="device">Device used heavily</string>
<!-- Summary for the battery high usage tip, which presents how many hours the device been used since last full charge [CHAR LIMIT=NONE] -->
<string name="battery_tip_high_usage_summary">About <xliff:g id="hour">%1$s</xliff:g> used since last full charge</string>
<!-- Message for battery tip dialog to show the status about the battery [CHAR LIMIT=NONE] -->
<string name="battery_tip_dialog_message" product="default">Your phone was used heavily and this consumed a lot of battery. Your battery is behaving normally.\n\n Your phone was used for about <xliff:g id="hour">%1$s</xliff:g> since last full charge.\n\n Total usage:</string>
<!-- Message for battery tip dialog to show the status about the battery [CHAR LIMIT=NONE] -->
<string name="battery_tip_dialog_message" product="tablet">Your tablet was used heavily and this consumed a lot of battery. Your battery is behaving normally.\n\n Your tablet was used for about <xliff:g id="hour">%1$s</xliff:g> since last full charge.\n\n Total usage:</string>
<!-- Message for battery tip dialog to show the status about the battery [CHAR LIMIT=NONE] -->
<string name="battery_tip_dialog_message" product="device">Your device was used heavily and this consumed a lot of battery. Your battery is behaving normally.\n\n Your device was used for about <xliff:g id="hour">%1$s</xliff:g> since last full charge.\n\n Total usage:</string>
<!-- Title for the smart battery manager preference [CHAR LIMIT=NONE] -->
<string name="smart_battery_manager_title">Smart battery manager</string>
<!-- Title for the smart battery toggle [CHAR LIMIT=NONE] -->

View File

@@ -94,6 +94,7 @@ import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.TtsSpan;
import android.util.ArraySet;
import android.util.IconDrawableFactory;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
@@ -1382,4 +1383,18 @@ public final class Utils extends com.android.settingslib.Utils {
}
return new BitmapDrawable(null, bitmap);
}
/**
* Get the {@link Drawable} that represents the app icon
*/
public static Drawable getBadgedIcon(IconDrawableFactory iconDrawableFactory,
PackageManager packageManager, String packageName, int userId) {
try {
final ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName,
PackageManager.GET_META_DATA);
return iconDrawableFactory.getBadgedIcon(appInfo, userId);
} catch (PackageManager.NameNotFoundException e) {
return packageManager.getDefaultActivityIcon();
}
}
}

View File

@@ -345,6 +345,17 @@ public class BatteryUtils {
}
/**
* Calculate the screen usage time since last full charge.
* @param batteryStatsHelper utility class that contains the screen usage data
* @return time in millis
*/
public long calculateScreenUsageTime(BatteryStatsHelper batteryStatsHelper) {
final BatterySipper sipper = findBatterySipperByType(
batteryStatsHelper.getUsageList(), BatterySipper.DrainType.SCREEN);
return sipper != null ? sipper.usageTimeMs : 0;
}
public static void logRuntime(String tag, String message, long startTime) {
Log.d(tag, message + ": " + (System.currentTimeMillis() - startTime) + "ms");
}
@@ -432,6 +443,20 @@ public class BatteryUtils {
return batteryInfo;
}
/**
* Find the {@link BatterySipper} with the corresponding {@link BatterySipper.DrainType}
*/
public BatterySipper findBatterySipperByType(List<BatterySipper> usageList,
BatterySipper.DrainType type) {
for (int i = 0, size = usageList.size(); i < size; i++) {
final BatterySipper sipper = usageList.get(i);
if (sipper.drainType == type) {
return sipper;
}
}
return null;
}
private boolean isDataCorrupted() {
return mPackageManager == null || mAppOpsManager == null;
}

View File

@@ -31,6 +31,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.fuelgauge.anomaly.Anomaly;
import com.android.settings.fuelgauge.anomaly.AnomalyDialogFragment;
@@ -151,12 +152,6 @@ public class PowerUsageAnomalyDetails extends DashboardFragment implements
@VisibleForTesting
Drawable getBadgedIcon(String packageName, int userId) {
try {
final ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName,
PackageManager.GET_META_DATA);
return mIconDrawableFactory.getBadgedIcon(appInfo, userId);
} catch (PackageManager.NameNotFoundException e) {
return mPackageManager.getDefaultActivityIcon();
}
return Utils.getBadgedIcon(mIconDrawableFactory, mPackageManager, packageName, userId);
}
}

View File

@@ -266,7 +266,7 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList
KEY_APP_LIST, lifecycle, activity, this);
controllers.add(mBatteryAppListPreferenceController);
mBatteryTipPreferenceController = new BatteryTipPreferenceController(context,
KEY_BATTERY_TIP, this);
KEY_BATTERY_TIP, this, this);
controllers.add(mBatteryTipPreferenceController);
controllers.add(new BatterySaverController(context, getLifecycle()));
controllers.add(new BatteryPercentagePreferenceController(context));
@@ -369,8 +369,9 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList
restartBatteryInfoLoader();
final long lastFullChargeTime = mBatteryUtils.calculateLastFullChargeTime(mStatsHelper,
System.currentTimeMillis());
updateScreenPreference();
updateLastFullChargePreference(lastFullChargeTime);
mScreenUsagePref.setSubtitle(Utils.formatElapsedTime(getContext(),
mBatteryUtils.calculateScreenUsageTime(mStatsHelper), false));
final CharSequence timeSequence = Utils.formatRelativeTime(context, lastFullChargeTime,
false);
@@ -393,26 +394,6 @@ public class PowerUsageSummary extends PowerUsageBase implements OnLongClickList
return new AnomalyDetectionPolicy(getContext());
}
@VisibleForTesting
BatterySipper findBatterySipperByType(List<BatterySipper> usageList, DrainType type) {
for (int i = 0, size = usageList.size(); i < size; i++) {
final BatterySipper sipper = usageList.get(i);
if (sipper.drainType == type) {
return sipper;
}
}
return null;
}
@VisibleForTesting
void updateScreenPreference() {
final BatterySipper sipper = findBatterySipperByType(
mStatsHelper.getUsageList(), DrainType.SCREEN);
final long usageTimeMs = sipper != null ? sipper.usageTimeMs : 0;
mScreenUsagePref.setSubtitle(Utils.formatElapsedTime(getContext(), usageTimeMs, false));
}
@VisibleForTesting
void updateLastFullChargePreference(long timeMs) {
final CharSequence timeSequence = Utils.formatRelativeTime(getContext(), timeMs, false);

View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2018 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.batterytip;
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 android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController.BatteryTipListener;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.fuelgauge.batterytip.tips.HighUsageTip;
/**
* Dialog Fragment to show action dialog for each anomaly
*/
public class BatteryTipDialogFragment extends InstrumentedDialogFragment implements
DialogInterface.OnClickListener {
private static final String ARG_BATTERY_TIP = "battery_tip";
@VisibleForTesting
BatteryTip mBatteryTip;
public static BatteryTipDialogFragment newInstance(BatteryTip batteryTip) {
BatteryTipDialogFragment dialogFragment = new BatteryTipDialogFragment();
Bundle args = new Bundle(1);
args.putParcelable(ARG_BATTERY_TIP, batteryTip);
dialogFragment.setArguments(args);
return dialogFragment;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle bundle = getArguments();
final Context context = getContext();
mBatteryTip = bundle.getParcelable(ARG_BATTERY_TIP);
switch (mBatteryTip.getType()) {
case BatteryTip.TipType.SUMMARY:
case BatteryTip.TipType.LOW_BATTERY:
//TODO(b/70570352): add dialog
return null;
case BatteryTip.TipType.HIGH_DEVICE_USAGE:
final HighUsageTip highUsageTip = (HighUsageTip) mBatteryTip;
final RecyclerView view = (RecyclerView) LayoutInflater.from(context).inflate(
R.layout.recycler_view,
null);
view.setLayoutManager(new LinearLayoutManager(context));
view.setAdapter(new HighUsageAdapter(context,
highUsageTip.getHighUsageAppList()));
return new AlertDialog.Builder(context)
.setMessage(getString(R.string.battery_tip_dialog_message,
highUsageTip.getScreenTimeMs()))
.setView(view)
.setPositiveButton(android.R.string.ok, null)
.create();
default:
throw new IllegalArgumentException("unknown type " + mBatteryTip.getType());
}
}
@Override
public int getMetricsCategory() {
//TODO(b/70570352): add correct metric id
return 0;
}
@Override
public void onClick(DialogInterface dialog, int which) {
final BatteryTipListener lsn = (BatteryTipListener) getTargetFragment();
if (lsn == null) {
return;
}
mBatteryTip.action();
lsn.onBatteryTipHandled(mBatteryTip);
}
}

View File

@@ -23,6 +23,7 @@ import com.android.internal.os.BatteryStatsHelper;
import com.android.settings.fuelgauge.BatteryInfo;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.batterytip.detectors.BatteryTipDetector;
import com.android.settings.fuelgauge.batterytip.detectors.HighUsageDetector;
import com.android.settings.fuelgauge.batterytip.detectors.LowBatteryDetector;
import com.android.settings.fuelgauge.batterytip.detectors.SummaryDetector;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
@@ -65,6 +66,8 @@ public class BatteryTipLoader extends AsyncLoader<List<BatteryTip>> {
mVisibleTips = 0;
addBatteryTipFromDetector(tips, new LowBatteryDetector(policy, batteryInfo));
addBatteryTipFromDetector(tips,
new HighUsageDetector(getContext(), policy, mBatteryStatsHelper));
// Add summary detector at last since it need other detectors to update the mVisibleTips
addBatteryTipFromDetector(tips, new SummaryDetector(policy, mVisibleTips));

View File

@@ -18,6 +18,7 @@ package com.android.settings.fuelgauge.batterytip;
import android.content.Context;
import android.support.annotation.VisibleForTesting;
import android.support.v14.preference.PreferenceFragment;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceGroup;
import android.support.v7.preference.PreferenceScreen;
@@ -34,6 +35,9 @@ import java.util.Map;
* Controller in charge of the battery tip group
*/
public class BatteryTipPreferenceController extends BasePreferenceController {
private static final String TAG = "BatteryTipPreferenceController";
private static final int REQUEST_ANOMALY_ACTION = 0;
private BatteryTipListener mBatteryTipListener;
private List<BatteryTip> mBatteryTips;
private Map<String, BatteryTip> mBatteryTipMap;
@@ -41,16 +45,18 @@ public class BatteryTipPreferenceController extends BasePreferenceController {
PreferenceGroup mPreferenceGroup;
@VisibleForTesting
Context mPrefContext;
PreferenceFragment mFragment;
public BatteryTipPreferenceController(Context context, String preferenceKey) {
this(context, preferenceKey, null);
this(context, preferenceKey, null, null);
}
public BatteryTipPreferenceController(Context context, String preferenceKey,
BatteryTipListener batteryTipListener) {
PreferenceFragment fragment, BatteryTipListener batteryTipListener) {
super(context, preferenceKey);
mBatteryTipListener = batteryTipListener;
mBatteryTipMap = new HashMap<>();
mFragment = fragment;
}
@Override
@@ -96,7 +102,10 @@ public class BatteryTipPreferenceController extends BasePreferenceController {
final BatteryTip batteryTip = mBatteryTipMap.get(preference.getKey());
if (batteryTip != null) {
if (batteryTip.shouldShowDialog()) {
// build and show the dialog
BatteryTipDialogFragment dialogFragment = BatteryTipDialogFragment.newInstance(
batteryTip);
dialogFragment.setTargetFragment(mFragment, REQUEST_ANOMALY_ACTION);
dialogFragment.show(mFragment.getFragmentManager(), TAG);
} else {
batteryTip.action();
if (mBatteryTipListener != null) {

View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2018 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.batterytip;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.support.v7.widget.RecyclerView;
import android.util.IconDrawableFactory;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.settings.R;
import com.android.settings.Utils;
import java.util.List;
/**
* Adapter for the high usage app list
*/
public class HighUsageAdapter extends RecyclerView.Adapter<HighUsageAdapter.ViewHolder> {
private final Context mContext;
private final IconDrawableFactory mIconDrawableFactory;
private final PackageManager mPackageManager;
private final List<HighUsageApp> mHighUsageAppList;
public static class ViewHolder extends RecyclerView.ViewHolder {
public View view;
public ImageView appIcon;
public TextView appName;
public TextView appTime;
public ViewHolder(View v) {
super(v);
view = v;
appIcon = v.findViewById(R.id.app_icon);
appName = v.findViewById(R.id.app_name);
appTime = v.findViewById(R.id.app_screen_time);
}
}
public HighUsageAdapter(Context context, List<HighUsageApp> highUsageAppList) {
mContext = context;
mHighUsageAppList = highUsageAppList;
mIconDrawableFactory = IconDrawableFactory.newInstance(context);
mPackageManager = context.getPackageManager();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(mContext).inflate(R.layout.app_high_usage_item,
parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
final HighUsageApp app = mHighUsageAppList.get(position);
holder.appIcon.setImageDrawable(
Utils.getBadgedIcon(mIconDrawableFactory, mPackageManager, app.packageName,
UserHandle.myUserId()));
holder.appName.setText(Utils.getApplicationLabel(mContext, app.packageName));
holder.appTime.setText(Utils.formatElapsedTime(mContext, app.screenOnTimeMs, false));
}
@Override
public int getItemCount() {
return mHighUsageAppList.size();
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (C) 2018 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.batterytip;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Class representing app with high screen usage
*/
public class HighUsageApp implements Comparable<HighUsageApp>, Parcelable {
public final String packageName;
public final long screenOnTimeMs;
public HighUsageApp(String packageName, long screenOnTimeMs) {
this.packageName = packageName;
this.screenOnTimeMs = screenOnTimeMs;
}
private HighUsageApp(Parcel in) {
packageName = in.readString();
screenOnTimeMs = in.readLong();
}
@Override
public int compareTo(HighUsageApp o) {
return Long.compare(screenOnTimeMs, o.screenOnTimeMs);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(packageName);
dest.writeLong(screenOnTimeMs);
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public HighUsageApp createFromParcel(Parcel in) {
return new HighUsageApp(in);
}
public HighUsageApp[] newArray(int size) {
return new HighUsageApp[size];
}
};
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2018 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.batterytip.detectors;
import android.content.Context;
import android.os.BatteryStats;
import android.support.annotation.VisibleForTesting;
import android.text.format.DateUtils;
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.batterytip.BatteryTipPolicy;
import com.android.settings.fuelgauge.batterytip.HighUsageApp;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.fuelgauge.batterytip.tips.HighUsageTip;
import com.android.settings.fuelgauge.batterytip.tips.SummaryTip;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Detector whether to show summary tip. This detector should be executed as the last
* {@link BatteryTipDetector} since it need the most up-to-date {@code visibleTips}
*/
public class HighUsageDetector implements BatteryTipDetector {
private BatteryTipPolicy mPolicy;
private BatteryStatsHelper mBatteryStatsHelper;
private List<HighUsageApp> mHighUsageAppList;
private Context mContext;
@VisibleForTesting
BatteryUtils mBatteryUtils;
public HighUsageDetector(Context context, BatteryTipPolicy policy,
BatteryStatsHelper batteryStatsHelper) {
mContext = context;
mPolicy = policy;
mBatteryStatsHelper = batteryStatsHelper;
mHighUsageAppList = new ArrayList<>();
mBatteryUtils = BatteryUtils.getInstance(context);
}
@Override
public BatteryTip detect() {
final long screenUsageTimeMs = mBatteryUtils.calculateScreenUsageTime(mBatteryStatsHelper);
//TODO(b/70570352): Change it to detect whether battery drops 25% in last 2 hours
if (mPolicy.highUsageEnabled && screenUsageTimeMs > DateUtils.HOUR_IN_MILLIS) {
final List<BatterySipper> batterySippers = mBatteryStatsHelper.getUsageList();
for (int i = 0, size = batterySippers.size(); i < size; i++) {
final BatterySipper batterySipper = batterySippers.get(i);
if (!mBatteryUtils.shouldHideSipper(batterySipper)) {
final long foregroundTimeMs = mBatteryUtils.getProcessTimeMs(
BatteryUtils.StatusType.FOREGROUND, batterySipper.uidObj,
BatteryStats.STATS_SINCE_CHARGED);
mHighUsageAppList.add(new HighUsageApp(
mBatteryUtils.getPackageName(batterySipper.getUid()),
foregroundTimeMs));
}
}
mHighUsageAppList = mHighUsageAppList.subList(0,
Math.min(mPolicy.highUsageAppCount, mHighUsageAppList.size()));
Collections.sort(mHighUsageAppList, Collections.reverseOrder());
}
return new HighUsageTip(screenUsageTimeMs, mHighUsageAppList);
}
}

View File

@@ -16,8 +16,9 @@
package com.android.settings.fuelgauge.batterytip.tips;
import android.app.Dialog;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.IdRes;
import android.support.annotation.IntDef;
import android.support.v7.preference.Preference;
@@ -31,7 +32,7 @@ import java.lang.annotation.RetentionPolicy;
* Each {@link BatteryTip} contains basic data(e.g. title, summary, icon) as well as the
* pre-defined action(e.g. turn on battery saver)
*/
public abstract class BatteryTip implements Comparable<BatteryTip> {
public abstract class BatteryTip implements Comparable<BatteryTip>, Parcelable {
@Retention(RetentionPolicy.SOURCE)
@IntDef({StateType.NEW,
StateType.HANDLED,
@@ -62,12 +63,34 @@ public abstract class BatteryTip implements Comparable<BatteryTip> {
private static final String KEY_PREFIX = "key_battery_tip";
@TipType
protected int mType;
@StateType
protected int mState;
protected boolean mShowDialog;
BatteryTip(Parcel in) {
mType = in.readInt();
mState = in.readInt();
mShowDialog = in.readBoolean();
}
BatteryTip(int type, int state, boolean showDialog) {
mType = type;
mState = state;
mShowDialog = showDialog;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeInt(mState);
dest.writeBoolean(mShowDialog);
}
public abstract CharSequence getTitle(Context context);
public abstract CharSequence getSummary(Context context);
@@ -77,6 +100,7 @@ public abstract class BatteryTip implements Comparable<BatteryTip> {
/**
* Update the current {@link #mState} using the new {@code tip}.
*
* @param tip used to update
*/
public abstract void updateState(BatteryTip tip);
@@ -86,12 +110,6 @@ public abstract class BatteryTip implements Comparable<BatteryTip> {
*/
public abstract void action();
/**
* Build the dialog to display either the info about {@link BatteryTip} or confirmation
* about the action.
*/
public abstract Dialog buildDialog();
public Preference buildPreference(Context context) {
Preference preference = new Preference(context);
@@ -110,6 +128,10 @@ public abstract class BatteryTip implements Comparable<BatteryTip> {
return KEY_PREFIX + mType;
}
public int getType() {
return mType;
}
@StateType
public int getState() {
return mState;

View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2018 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.batterytip.tips;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.fuelgauge.batterytip.HighUsageApp;
import java.util.List;
/**
* Tip to show general summary about battery life
*/
public class HighUsageTip extends BatteryTip {
private final long mScreenTimeMs;
@VisibleForTesting
final List<HighUsageApp> mHighUsageAppList;
public HighUsageTip(long screenTimeMs, List<HighUsageApp> appList) {
super(TipType.HIGH_DEVICE_USAGE, appList.isEmpty() ? StateType.INVISIBLE : StateType.NEW,
true /* showDialog */);
mScreenTimeMs = screenTimeMs;
mHighUsageAppList = appList;
}
@VisibleForTesting
HighUsageTip(Parcel in) {
super(in);
mScreenTimeMs = in.readLong();
mHighUsageAppList = in.createTypedArrayList(HighUsageApp.CREATOR);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeLong(mScreenTimeMs);
dest.writeTypedList(mHighUsageAppList);
}
@Override
public CharSequence getTitle(Context context) {
return context.getString(R.string.battery_tip_high_usage_title);
}
@Override
public CharSequence getSummary(Context context) {
return context.getString(R.string.battery_tip_high_usage_summary,
Utils.formatElapsedTime(context, mScreenTimeMs, false));
}
@Override
public int getIconId() {
return R.drawable.ic_perm_device_information_red_24dp;
}
@Override
public void updateState(BatteryTip tip) {
mState = tip.mState;
}
@Override
public void action() {
// do nothing
}
public long getScreenTimeMs() {
return mScreenTimeMs;
}
public List<HighUsageApp> getHighUsageAppList() {
return mHighUsageAppList;
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public BatteryTip createFromParcel(Parcel in) {
return new HighUsageTip(in);
}
public BatteryTip[] newArray(int size) {
return new HighUsageTip[size];
}
};
}

View File

@@ -16,8 +16,9 @@
package com.android.settings.fuelgauge.batterytip.tips;
import android.app.Dialog;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.settings.R;
@@ -27,9 +28,11 @@ import com.android.settings.R;
public class LowBatteryTip extends BatteryTip {
public LowBatteryTip(@StateType int state) {
mShowDialog = false;
mState = state;
mType = TipType.LOW_BATTERY;
super(TipType.LOW_BATTERY, state, false /* showDialog */);
}
private LowBatteryTip(Parcel in) {
super(in);
}
@Override
@@ -57,9 +60,14 @@ public class LowBatteryTip extends BatteryTip {
// do nothing
}
@Override
public Dialog buildDialog() {
//TODO(b/70570352): create the dialog for low battery tip and add test
return null;
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public BatteryTip createFromParcel(Parcel in) {
return new LowBatteryTip(in);
}
public BatteryTip[] newArray(int size) {
return new LowBatteryTip[size];
}
};
}

View File

@@ -16,8 +16,9 @@
package com.android.settings.fuelgauge.batterytip.tips;
import android.app.Dialog;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.settings.R;
@@ -27,9 +28,11 @@ import com.android.settings.R;
public class SummaryTip extends BatteryTip {
public SummaryTip(@StateType int state) {
mShowDialog = false;
mState = state;
mType = TipType.SUMMARY;
super(TipType.SUMMARY, state, false /* showDialog */);
}
private SummaryTip(Parcel in) {
super(in);
}
@Override
@@ -57,9 +60,13 @@ public class SummaryTip extends BatteryTip {
// do nothing
}
@Override
public Dialog buildDialog() {
//TODO(b/70570352): create the dialog for summary tip and add test
return null;
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public BatteryTip createFromParcel(Parcel in) {
return new SummaryTip(in);
}
public BatteryTip[] newArray(int size) {
return new SummaryTip[size];
}
};
}

View File

@@ -4,13 +4,16 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
@@ -25,6 +28,7 @@ import android.os.storage.VolumeInfo;
import android.text.SpannableStringBuilder;
import android.text.format.DateUtils;
import android.text.style.TtsSpan;
import android.util.IconDrawableFactory;
import android.widget.EditText;
import android.widget.TextView;
@@ -46,8 +50,8 @@ import java.util.List;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class UtilsTest {
private static final String PACKAGE_NAME = "com.android.app";
private static final int USER_ID = 1;
@Mock
private WifiManager wifiManager;
@@ -59,6 +63,12 @@ public class UtilsTest {
private DevicePolicyManagerWrapper mDevicePolicyManager;
@Mock
private UserManager mUserManager;
@Mock
private PackageManager mPackageManager;
@Mock
private IconDrawableFactory mIconDrawableFactory;
@Mock
private ApplicationInfo mApplicationInfo;
private Context mContext;
@Before
@@ -332,4 +342,17 @@ public class UtilsTest {
assertThat(editText.getSelectionEnd()).isEqualTo(length);
}
@Test
public void testGetBadgedIcon_usePackageNameAndUserId() throws
PackageManager.NameNotFoundException {
doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(PACKAGE_NAME,
PackageManager.GET_META_DATA);
Utils.getBadgedIcon(mIconDrawableFactory, mPackageManager, PACKAGE_NAME, USER_ID);
// Verify that it uses the correct user id
verify(mIconDrawableFactory).getBadgedIcon(mApplicationInfo, USER_ID);
}
}

View File

@@ -20,7 +20,9 @@ import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND;
import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE;
import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP;
import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING;
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;
@@ -141,6 +143,7 @@ public class BatteryUtilsTest {
private BatteryUtils mBatteryUtils;
private FakeFeatureFactory mFeatureFactory;
private PowerUsageFeatureProvider mProvider;
private List<BatterySipper> mUsageList;
@Before
public void setUp() {
@@ -194,6 +197,12 @@ public class BatteryUtilsTest {
mBatteryUtils.mPowerUsageFeatureProvider = mProvider;
doReturn(0L).when(mBatteryUtils).getForegroundServiceTotalTimeUs(
any(BatteryStats.Uid.class), anyLong());
mUsageList = new ArrayList<>();
mUsageList.add(mNormalBatterySipper);
mUsageList.add(mScreenBatterySipper);
mUsageList.add(mCellBatterySipper);
doReturn(mUsageList).when(mBatteryStatsHelper).getUsageList();
}
@Test
@@ -468,4 +477,28 @@ public class BatteryUtilsTest {
verify(mBatteryStatsHelper).refreshStats(BatteryStats.STATS_SINCE_CHARGED,
mUserManager.getUserProfiles());
}
@Test
public void testFindBatterySipperByType_findTypeScreen() {
BatterySipper sipper = mBatteryUtils.findBatterySipperByType(mUsageList,
BatterySipper.DrainType.SCREEN);
assertThat(sipper).isSameAs(mScreenBatterySipper);
}
@Test
public void testFindBatterySipperByType_findTypeApp() {
BatterySipper sipper = mBatteryUtils.findBatterySipperByType(mUsageList,
BatterySipper.DrainType.APP);
assertThat(sipper).isSameAs(mNormalBatterySipper);
}
@Test
public void testCalculateScreenUsageTime_returnCorrectTime() {
mScreenBatterySipper.usageTimeMs = TIME_EXPECTED_FOREGROUND;
assertThat(mBatteryUtils.calculateScreenUsageTime(mBatteryStatsHelper)).isEqualTo(
TIME_EXPECTED_FOREGROUND);
}
}

View File

@@ -67,7 +67,6 @@ public class PowerUsageAnomalyDetailsTest {
private static final String PACKAGE_NAME_1 = "com.android.app1";
private static final String PACKAGE_NAME_2 = "com.android.app2";
private static final String PACKAGE_NAME_3 = "com.android.app3";
private static final int USER_ID = 1;
@Mock
private SettingsActivity mSettingsActivity;
@@ -198,16 +197,4 @@ public class PowerUsageAnomalyDetailsTest {
assertThat(mBundle.getParcelableArrayList(
PowerUsageAnomalyDetails.EXTRA_ANOMALY_LIST)).isEqualTo(mAnomalyList);
}
@Test
public void testGetBadgedIcon_usePackageNameAndUserId() throws
PackageManager.NameNotFoundException {
doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(PACKAGE_NAME_1,
PackageManager.GET_META_DATA);
mFragment.getBadgedIcon(PACKAGE_NAME_1, USER_ID);
// Verify that it uses the correct user id
verify(mIconDrawableFactory).getBadgedIcon(mApplicationInfo, USER_ID);
}
}

View File

@@ -247,34 +247,6 @@ public class PowerUsageSummaryTest {
assertThat(mFragment.mShowAllApps).isEqualTo(!isShowApps);
}
@Test
public void testFindBatterySipperByType_findTypeScreen() {
BatterySipper sipper = mFragment.findBatterySipperByType(mUsageList,
BatterySipper.DrainType.SCREEN);
assertThat(sipper).isSameAs(mScreenBatterySipper);
}
@Test
public void testFindBatterySipperByType_findTypeApp() {
BatterySipper sipper = mFragment.findBatterySipperByType(mUsageList,
BatterySipper.DrainType.APP);
assertThat(sipper).isSameAs(mNormalBatterySipper);
}
@Test
public void testUpdateScreenPreference_showCorrectSummary() {
doReturn(mScreenBatterySipper).when(mFragment).findBatterySipperByType(any(), any());
doReturn(mRealContext).when(mFragment).getContext();
final CharSequence expectedSummary = Utils.formatElapsedTime(mRealContext, USAGE_TIME_MS,
false);
mFragment.updateScreenPreference();
assertThat(mScreenUsagePref.getSubtitle()).isEqualTo(expectedSummary);
}
@Test
public void testUpdateLastFullChargePreference_showCorrectSummary() {
doReturn(mRealContext).when(mFragment).getContext();
@@ -284,16 +256,6 @@ public class PowerUsageSummaryTest {
assertThat(mLastFullChargePref.getSubtitle()).isEqualTo("2 hr. ago");
}
@Test
public void testUpdatePreference_usageListEmpty_shouldNotCrash() {
when(mBatteryHelper.getUsageList()).thenReturn(new ArrayList<BatterySipper>());
doReturn(STUB_STRING).when(mFragment).getString(anyInt(), any());
doReturn(mRealContext).when(mFragment).getContext();
// Should not crash when update
mFragment.updateScreenPreference();
}
@Test
public void testNonIndexableKeys_MatchPreferenceKeys() {
final Context context = RuntimeEnvironment.application;

View File

@@ -85,7 +85,7 @@ public class BatteryTipPreferenceControllerTest {
mNewBatteryTips.add(new SummaryTip(BatteryTip.StateType.INVISIBLE));
mBatteryTipPreferenceController = new BatteryTipPreferenceController(mContext, KEY_PREF,
mBatteryTipListener);
null, mBatteryTipListener);
mBatteryTipPreferenceController.mPreferenceGroup = mPreferenceGroup;
mBatteryTipPreferenceController.mPrefContext = mContext;
}

View File

@@ -0,0 +1,94 @@
/*
* 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.batterytip.detectors;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import android.content.Context;
import android.os.BatteryStats;
import android.text.format.DateUtils;
import com.android.internal.os.BatterySipper;
import com.android.internal.os.BatteryStatsHelper;
import com.android.settings.TestConfig;
import com.android.settings.fuelgauge.BatteryInfo;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
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 org.robolectric.util.ReflectionHelpers;
import java.util.ArrayList;
import java.util.List;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class HighUsageDetectorTest {
private Context mContext;
@Mock
private BatteryStatsHelper mBatteryStatsHelper;
@Mock
private BatteryUtils mBatteryUtils;
@Mock
private BatterySipper mBatterySipper;
private BatteryTipPolicy mPolicy;
private HighUsageDetector mHighUsageDetector;
private List<BatterySipper> mUsageList;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mPolicy = spy(new BatteryTipPolicy(mContext));
mHighUsageDetector = new HighUsageDetector(mContext, mPolicy, mBatteryStatsHelper);
mHighUsageDetector.mBatteryUtils = mBatteryUtils;
mUsageList = new ArrayList<>();
mUsageList.add(mBatterySipper);
}
@Test
public void testDetect_disabledByPolicy_tipInvisible() {
ReflectionHelpers.setField(mPolicy, "highUsageEnabled", false);
assertThat(mHighUsageDetector.detect().isVisible()).isFalse();
}
@Test
public void testDetect_containsHighUsageApp_tipVisible() {
doReturn(2 * DateUtils.HOUR_IN_MILLIS).when(mBatteryUtils).calculateScreenUsageTime(
mBatteryStatsHelper);
doReturn(mUsageList).when(mBatteryStatsHelper).getUsageList();
doReturn(DateUtils.HOUR_IN_MILLIS).when(mBatteryUtils).getProcessTimeMs(
BatteryUtils.StatusType.FOREGROUND, mBatterySipper.uidObj,
BatteryStats.STATS_SINCE_CHARGED);
assertThat(mHighUsageDetector.detect().isVisible()).isTrue();
}
}

View File

@@ -19,6 +19,8 @@ import static com.google.common.truth.Truth.assertThat;
import android.app.Dialog;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.IdRes;
import android.support.v7.preference.Preference;
@@ -58,10 +60,32 @@ public class BatteryTipTest {
assertThat(preference.getIcon()).isEqualTo(mContext.getDrawable(ICON_ID));
}
@Test
public void testParcelable() {
final BatteryTip batteryTip = new TestBatteryTip();
Parcel parcel = Parcel.obtain();
batteryTip.writeToParcel(parcel, batteryTip.describeContents());
parcel.setDataPosition(0);
final BatteryTip parcelTip = new TestBatteryTip(parcel);
assertThat(parcelTip.getTitle(mContext)).isEqualTo(TITLE);
assertThat(parcelTip.getSummary(mContext)).isEqualTo(SUMMARY);
assertThat(parcelTip.getIconId()).isEqualTo(ICON_ID);
}
/**
* Used to test the non abstract methods in {@link TestBatteryTip}
*/
public class TestBatteryTip extends BatteryTip {
public static class TestBatteryTip extends BatteryTip {
TestBatteryTip() {
super(TipType.SUMMARY, StateType.NEW, true);
}
TestBatteryTip(Parcel in) {
super(in);
}
@Override
public String getTitle(Context context) {
@@ -88,10 +112,15 @@ public class BatteryTipTest {
// do nothing
}
@Override
public Dialog buildDialog() {
return null;
public final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public BatteryTip createFromParcel(Parcel in) {
return new TestBatteryTip(in);
}
public BatteryTip[] newArray(int size) {
return new TestBatteryTip[size];
}
};
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2018 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.batterytip.tips;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.os.Parcel;
import android.text.format.DateUtils;
import com.android.settings.TestConfig;
import com.android.settings.fuelgauge.batterytip.HighUsageApp;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
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 HighUsageTipTest {
private static final String PACKAGE_NAME = "com.android.app";
private static final long SCREEN_TIME = 30 * DateUtils.MINUTE_IN_MILLIS;
private Context mContext;
private HighUsageTip mBatteryTip;
private List<HighUsageApp> mUsageAppList;
@Before
public void setUp() {
mContext = RuntimeEnvironment.application;
mUsageAppList = new ArrayList<>();
mUsageAppList.add(new HighUsageApp(PACKAGE_NAME, SCREEN_TIME));
mBatteryTip = new HighUsageTip(SCREEN_TIME, mUsageAppList);
}
@Test
public void testParcelable() {
Parcel parcel = Parcel.obtain();
mBatteryTip.writeToParcel(parcel, mBatteryTip.describeContents());
parcel.setDataPosition(0);
final HighUsageTip parcelTip = new HighUsageTip(parcel);
assertThat(parcelTip.getTitle(mContext)).isEqualTo("Phone used heavily");
assertThat(parcelTip.getType()).isEqualTo(BatteryTip.TipType.HIGH_DEVICE_USAGE);
assertThat(parcelTip.getState()).isEqualTo(BatteryTip.StateType.NEW);
assertThat(parcelTip.getScreenTimeMs()).isEqualTo(SCREEN_TIME);
assertThat(parcelTip.mHighUsageAppList.size()).isEqualTo(1);
final HighUsageApp app = parcelTip.mHighUsageAppList.get(0);
assertThat(app.packageName).isEqualTo(PACKAGE_NAME);
assertThat(app.screenOnTimeMs).isEqualTo(SCREEN_TIME);
}
}