diff --git a/res/layout/battery_header.xml b/res/layout/battery_header.xml index ca5c90e3219..aeb61e18907 100644 --- a/res/layout/battery_header.xml +++ b/res/layout/battery_header.xml @@ -48,6 +48,12 @@ android:layout_marginTop="12dp" android:textAppearance="@android:style/TextAppearance.Material.Small"/> + + T findViewById(int id) { return mRootView.findViewById(id); } diff --git a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java index ecf0f9f1460..bdd2413a8e3 100644 --- a/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceController.java @@ -48,7 +48,9 @@ public class BatteryHeaderPreferenceController extends PreferenceController @VisibleForTesting TextView mTimeText; @VisibleForTesting - TextView mSummary; + TextView mSummary1; + @VisibleForTesting + TextView mSummary2; private final Activity mActivity; private final PreferenceFragment mHost; @@ -73,8 +75,9 @@ public class BatteryHeaderPreferenceController extends PreferenceController mBatteryLayoutPref = (LayoutPreference) screen.findPreference(KEY_BATTERY_HEADER); mBatteryMeterView = (BatteryMeterView) mBatteryLayoutPref .findViewById(R.id.battery_header_icon); - mTimeText = (TextView) mBatteryLayoutPref.findViewById(R.id.battery_percent); - mSummary = (TextView) mBatteryLayoutPref.findViewById(R.id.summary1); + mTimeText = mBatteryLayoutPref.findViewById(R.id.battery_percent); + mSummary1 = mBatteryLayoutPref.findViewById(R.id.summary1); + mSummary2 = mBatteryLayoutPref.findViewById(R.id.summary2); Intent batteryBroadcast = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); @@ -105,10 +108,13 @@ public class BatteryHeaderPreferenceController extends PreferenceController public void updateHeaderPreference(BatteryInfo info) { mTimeText.setText(Utils.formatPercentage(info.batteryLevel)); if (info.remainingLabel == null) { - mSummary.setText(info.statusLabel); + mSummary1.setText(info.statusLabel); } else { - mSummary.setText(info.remainingLabel); + mSummary1.setText(info.remainingLabel); } + // Clear this just to be sure we don't get UI jank on re-entering this view from another + // activity. + mSummary2.setText(""); mBatteryMeterView.setBatteryLevel(info.batteryLevel); mBatteryMeterView.setCharging(!info.discharging); diff --git a/src/com/android/settings/fuelgauge/BatteryInfo.java b/src/com/android/settings/fuelgauge/BatteryInfo.java index 6daf23b5bbc..cb732e30b92 100644 --- a/src/com/android/settings/fuelgauge/BatteryInfo.java +++ b/src/com/android/settings/fuelgauge/BatteryInfo.java @@ -30,7 +30,6 @@ import android.text.format.Formatter; import android.util.SparseIntArray; import com.android.internal.os.BatteryStatsHelper; - import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.R; import com.android.settings.Utils; diff --git a/src/com/android/settings/fuelgauge/DebugEstimatesLoader.java b/src/com/android/settings/fuelgauge/DebugEstimatesLoader.java new file mode 100644 index 00000000000..08bd148ee88 --- /dev/null +++ b/src/com/android/settings/fuelgauge/DebugEstimatesLoader.java @@ -0,0 +1,68 @@ +/* + * 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; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryStats; +import android.os.SystemClock; +import com.android.internal.os.BatteryStatsHelper; +import com.android.settings.overlay.FeatureFactory; +import com.android.settings.utils.AsyncLoader; +import java.util.ArrayList; +import java.util.List; + +public class DebugEstimatesLoader extends AsyncLoader> { + private BatteryStatsHelper mStatsHelper; + + public DebugEstimatesLoader(Context context, BatteryStatsHelper statsHelper) { + super(context); + mStatsHelper = statsHelper; + } + + @Override + protected void onDiscardResult(List result) { + + } + + @Override + public List loadInBackground() { + Context context = getContext(); + PowerUsageFeatureProvider powerUsageFeatureProvider = + FeatureFactory.getFactory(context).getPowerUsageFeatureProvider(context); + + // get stuff we'll need for both BatteryInfo + BatteryUtils batteryUtils = BatteryUtils.getInstance(context); + final long elapsedRealtimeUs = batteryUtils.convertMsToUs(SystemClock.elapsedRealtime()); + Intent batteryBroadcast = getContext().registerReceiver(null, + new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + BatteryStats stats = mStatsHelper.getStats(); + + BatteryInfo oldinfo = BatteryInfo.getBatteryInfoOld(getContext(), batteryBroadcast, + stats, elapsedRealtimeUs, false); + + final long timeRemainingEnhanced = batteryUtils.convertMsToUs( + powerUsageFeatureProvider.getEnhancedBatteryPrediction(getContext())); + BatteryInfo newinfo = BatteryInfo.getBatteryInfo(getContext(), batteryBroadcast, stats, + elapsedRealtimeUs, false, timeRemainingEnhanced, true); + + List infos = new ArrayList<>(); + infos.add(oldinfo); + infos.add(newinfo); + return infos; + } +} diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java index 4a4d28c5a13..e6b1d9ddf2c 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java +++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java @@ -91,4 +91,25 @@ public interface PowerUsageFeatureProvider { * Check whether a specific anomaly detector is enabled */ boolean isAnomalyDetectorEnabled(@Anomaly.AnomalyType int type); + + /** + * Checks whether debugging should be enabled for battery estimates. + * @return + */ + boolean isEstimateDebugEnabled(); + + /** + * Converts the provided string containing the remaining time into a debug string for enhanced + * estimates. + * @param timeRemaining + * @return A string containing the estimate and a label indicating it is an enhanced estimate + */ + String getEnhancedEstimateDebugString(String timeRemaining); + + /** + * Converts the provided string containing the remaining time into a debug string. + * @param timeRemaining + * @return A string containing the estimate and a label indicating it is a normal estimate + */ + String getOldEstimateDebugString(String timeRemaining); } diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java index 5ef6fe5384d..c43eebd2034 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java +++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java @@ -117,4 +117,19 @@ public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider public boolean isAnomalyDetectorEnabled(@Anomaly.AnomalyType int type) { return false; } + + @Override + public String getEnhancedEstimateDebugString(String timeRemaining) { + return null; + } + + @Override + public boolean isEstimateDebugEnabled() { + return false; + } + + @Override + public String getOldEstimateDebugString(String timeRemaining) { + return null; + } } diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java index e59e58711fd..808a4896c4c 100644 --- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java +++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java @@ -18,6 +18,7 @@ package com.android.settings.fuelgauge; import android.app.Activity; import android.app.LoaderManager; +import android.app.LoaderManager.LoaderCallbacks; import android.content.Context; import android.content.Loader; import android.content.res.TypedArray; @@ -35,12 +36,16 @@ import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceGroup; import android.text.TextUtils; import android.text.format.DateUtils; +import android.text.format.Formatter; import android.util.Log; import android.util.SparseArray; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; - +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; +import android.widget.TextView; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.os.BatterySipper; import com.android.internal.os.BatterySipper.DrainType; @@ -58,12 +63,11 @@ 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.AnomalyDialogFragment.AnomalyDialogListener; import com.android.settings.fuelgauge.anomaly.AnomalyLoader; import com.android.settings.fuelgauge.anomaly.AnomalySummaryPreferenceController; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -73,7 +77,7 @@ import java.util.List; * consumed since the last time it was unplugged. */ public class PowerUsageSummary extends PowerUsageBase implements - AnomalyDialogFragment.AnomalyDialogListener { + AnomalyDialogListener, OnLongClickListener, OnClickListener { static final String TAG = "PowerUsageSummary"; @@ -104,6 +108,7 @@ public class PowerUsageSummary extends PowerUsageBase implements @VisibleForTesting static final int MENU_TOGGLE_APPS = Menu.FIRST + 5; private static final int MENU_HELP = Menu.FIRST + 6; + public static final int DEBUG_INFO_LOADER = 3; @VisibleForTesting boolean mShowAllApps = false; @@ -115,15 +120,15 @@ public class PowerUsageSummary extends PowerUsageBase implements PowerUsageFeatureProvider mPowerFeatureProvider; @VisibleForTesting BatteryUtils mBatteryUtils; + @VisibleForTesting + LayoutPreference mBatteryLayoutPref; /** * SparseArray that maps uid to {@link Anomaly}, so we could find {@link Anomaly} by uid */ @VisibleForTesting SparseArray> mAnomalySparseArray; - private BatteryHeaderPreferenceController mBatteryHeaderPreferenceController; - private LayoutPreference mBatteryLayoutPref; private PreferenceGroup mAppListGroup; private AnomalySummaryPreferenceController mAnomalySummaryPreferenceController; private int mStatsType = BatteryStats.STATS_SINCE_CHARGED; @@ -152,7 +157,7 @@ public class PowerUsageSummary extends PowerUsageBase implements }; @VisibleForTesting - LoaderManager.LoaderCallbacks BatteryInfoLoaderCallbacks = + LoaderManager.LoaderCallbacks mBatteryInfoLoaderCallbacks = new LoaderManager.LoaderCallbacks() { @Override @@ -171,12 +176,61 @@ public class PowerUsageSummary extends PowerUsageBase implements } }; + LoaderManager.LoaderCallbacks> mBatteryInfoDebugLoaderCallbacks = + new LoaderCallbacks>() { + @Override + public Loader> onCreateLoader(int i, Bundle bundle) { + return new DebugEstimatesLoader(getContext(), mStatsHelper); + } + + @Override + public void onLoadFinished(Loader> loader, + List batteryInfos) { + final BatteryMeterView batteryView = (BatteryMeterView) mBatteryLayoutPref + .findViewById(R.id.battery_header_icon); + final TextView percentRemaining = + mBatteryLayoutPref.findViewById(R.id.battery_percent); + final TextView summary1 = mBatteryLayoutPref.findViewById(R.id.summary1); + final TextView summary2 = mBatteryLayoutPref.findViewById(R.id.summary2); + BatteryInfo oldInfo = batteryInfos.get(0); + BatteryInfo newInfo = batteryInfos.get(1); + percentRemaining.setText(Utils.formatPercentage(oldInfo.batteryLevel)); + + // set the text to the old estimate (copied from battery info). Note that this + // can sometimes say 0 time remaining because battery stats requires the phone + // be unplugged for a period of time before being willing ot make an estimate. + summary1.setText(mPowerFeatureProvider.getOldEstimateDebugString( + Formatter.formatShortElapsedTime(getContext(), + mBatteryUtils.convertUsToMs(oldInfo.remainingTimeUs)))); + + // for this one we can just set the string directly + summary2.setText(mPowerFeatureProvider.getEnhancedEstimateDebugString( + Formatter.formatShortElapsedTime(getContext(), + mBatteryUtils.convertUsToMs(newInfo.remainingTimeUs)))); + + batteryView.setBatteryLevel(oldInfo.batteryLevel); + batteryView.setCharging(!oldInfo.discharging); + } + + @Override + public void onLoaderReset(Loader> loader) { + } + }; + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setAnimationAllowed(true); + initFeatureProvider(); mBatteryLayoutPref = (LayoutPreference) findPreference(KEY_BATTERY_HEADER); + View header = mBatteryLayoutPref.findViewById(R.id.summary1); + // Unfortunately setting a long click listener on a means it will no longer pass the regular + // click event to the parent, so we have to register a regular click listener as well. + if (mPowerFeatureProvider.isEstimateDebugEnabled()) { + header.setOnLongClickListener(this); + header.setOnClickListener(this); + } mAppListGroup = (PreferenceGroup) findPreference(KEY_APP_LIST); mScreenUsagePref = (PowerGaugePreference) findPreference(KEY_SCREEN_USAGE); mLastFullChargePref = (PowerGaugePreference) findPreference( @@ -187,7 +241,6 @@ public class PowerUsageSummary extends PowerUsageBase implements mBatteryUtils = BatteryUtils.getInstance(getContext()); mAnomalySparseArray = new SparseArray<>(); - initFeatureProvider(); restartBatteryInfoLoader(); } @@ -305,8 +358,7 @@ public class PowerUsageSummary extends PowerUsageBase implements MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_OPTIMIZATION); return true; case MENU_ADDITIONAL_BATTERY_INFO: - startActivity(FeatureFactory.getFactory(getContext()) - .getPowerUsageFeatureProvider(getContext()) + startActivity(mPowerFeatureProvider .getAdditionalBatteryInfoIntent()); metricsFeatureProvider.action(context, MetricsEvent.ACTION_SETTINGS_MENU_BATTERY_USAGE_ALERTS); @@ -335,11 +387,7 @@ public class PowerUsageSummary extends PowerUsageBase implements } private void performBatteryHeaderClick() { - final Context context = getContext(); - final PowerUsageFeatureProvider featureProvider = FeatureFactory.getFactory(context) - .getPowerUsageFeatureProvider(context); - - if (featureProvider.isAdvancedUiEnabled()) { + if (mPowerFeatureProvider.isAdvancedUiEnabled()) { Utils.startWithFragment(getContext(), PowerUsageAdvanced.class.getName(), null, null, 0, R.string.advanced_battery_title, null, getMetricsCategory()); } else { @@ -617,6 +665,17 @@ public class PowerUsageSummary extends PowerUsageBase implements timeSequence)); } + @VisibleForTesting + void showBothEstimates() { + final Context context = getContext(); + if (context == null + || !mPowerFeatureProvider.isEnhancedBatteryPredictionEnabled(context)) { + return; + } + getLoaderManager().restartLoader(DEBUG_INFO_LOADER, Bundle.EMPTY, + mBatteryInfoDebugLoaderCallbacks); + } + @VisibleForTesting double calculatePercentage(double powerUsage, double dischargeAmount) { final double totalPower = mStatsHelper.getTotalPower(); @@ -678,7 +737,7 @@ public class PowerUsageSummary extends PowerUsageBase implements if (mPowerFeatureProvider != null && mPowerFeatureProvider.isEnhancedBatteryPredictionEnabled(getContext())) { getLoaderManager().restartLoader(BATTERY_INFO_LOADER, Bundle.EMPTY, - BatteryInfoLoaderCallbacks); + mBatteryInfoLoaderCallbacks); } } @@ -753,6 +812,18 @@ public class PowerUsageSummary extends PowerUsageBase implements mAnomalySummaryPreferenceController.hideHighUsagePreference(); } + @Override + public boolean onLongClick(View view) { + showBothEstimates(); + view.setOnLongClickListener(null); + return true; + } + + @Override + public void onClick(View view) { + performBatteryHeaderClick(); + } + private static class SummaryProvider implements SummaryLoader.SummaryProvider { private final Context mContext; private final SummaryLoader mLoader; diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java index c793434a660..3f721b244d4 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryHeaderPreferenceControllerTest.java @@ -83,6 +83,7 @@ public class BatteryHeaderPreferenceControllerTest { private BatteryMeterView mBatteryMeterView; private TextView mTimeText; private TextView mSummary; + private TextView mSummary2; private LayoutPreference mBatteryLayoutPref; private Intent mBatteryIntent; private Lifecycle mLifecycle; @@ -97,6 +98,7 @@ public class BatteryHeaderPreferenceControllerTest { mTimeText = new TextView(mContext); mSummary = new TextView(mContext); ShadowEntityHeaderController.setUseMock(mEntityHeaderController); + mSummary2 = new TextView(mContext); mBatteryIntent = new Intent(); mBatteryIntent.putExtra(BatteryManager.EXTRA_LEVEL, BATTERY_LEVEL); @@ -113,7 +115,8 @@ public class BatteryHeaderPreferenceControllerTest { mContext, mActivity, mPreferenceFragment, mLifecycle); mController.mBatteryMeterView = mBatteryMeterView; mController.mTimeText = mTimeText; - mController.mSummary = mSummary; + mController.mSummary1 = mSummary; + mController.mSummary2 = mSummary2; } @After diff --git a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java index 156a1ea7946..daee1822dfd 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/PowerUsageSummaryTest.java @@ -15,7 +15,9 @@ */ package com.android.settings.fuelgauge; +import android.view.View; import java.util.List; +import org.robolectric.Robolectric; import org.robolectric.RuntimeEnvironment; import android.app.LoaderManager; import android.content.Context; @@ -106,6 +108,8 @@ public class PowerUsageSummaryTest { private static final double POWER_USAGE_PERCENTAGE = 50; private static final Intent ADDITIONAL_BATTERY_INFO_INTENT = new Intent("com.example.app.ADDITIONAL_BATTERY_INFO"); + public static final String NEW_ML_EST_SUFFIX = "(New ML est)"; + public static final String OLD_EST_SUFFIX = "(Old est)"; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; @@ -450,6 +454,18 @@ public class PowerUsageSummaryTest { any()); } + @Test + public void testShowBothEstimates_summariesAreBothModified() { + doReturn(new TextView(mRealContext)).when(mBatteryLayoutPref).findViewById(R.id.summary2); + doReturn(new TextView(mRealContext)).when(mBatteryLayoutPref).findViewById(R.id.summary1); + mFragment.onLongClick(new View(mRealContext)); + TextView summary1 = mFragment.mBatteryLayoutPref.findViewById(R.id.summary1); + TextView summary2 = mFragment.mBatteryLayoutPref.findViewById(R.id.summary2); + Robolectric.flushBackgroundThreadScheduler(); + assertThat(summary2.getText().toString().contains(NEW_ML_EST_SUFFIX)); + assertThat(summary1.getText().toString().contains(OLD_EST_SUFFIX)); + } + public static class TestFragment extends PowerUsageSummary { private Context mContext;