Move battery % down in QS, fix a lot of things

- Move the battery percentage next to the system icons in quick settings
- Always show the percentage, and optionally show the estimate text
based on battery settings
- Move the estimate text into the BatteryMeterView
- Move the fetching of the estimate off of the main thread

Test: visual
Change-Id: Ie37952079b2394c67464f69eb8a2f0089b08875d
Fixes: 119799219 - padding
Fixes: 120996084 - moving estimate fetch off main thread
Bug: 116481529
diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
index 2000104..74002ac 100644
--- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml
+++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
@@ -51,11 +51,4 @@
         android:layout_width="wrap_content"
         android:paddingEnd="2dp" />
 
-    <TextView
-        android:id="@+id/batteryRemainingText"
-        android:textAppearance="@style/TextAppearance.QS.TileLabel"
-        android:layout_height="match_parent"
-        android:layout_width="wrap_content"
-        android:gravity="center_vertical" />
-
 </LinearLayout>
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
index 4b65b6a..cd9f780 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
@@ -64,11 +64,5 @@
 
     <include layout="@layout/ongoing_privacy_chip" />
 
-    <com.android.systemui.BatteryMeterView
-        android:id="@+id/battery"
-        android:layout_height="match_parent"
-        android:layout_width="wrap_content"
-        android:gravity="center_vertical|end"
-        android:layout_gravity="center_vertical|end" />
     </LinearLayout>
 </LinearLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 6864ea1..9c62686 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -71,11 +71,12 @@
 
 
     @Retention(SOURCE)
-    @IntDef({MODE_DEFAULT, MODE_ON, MODE_OFF})
+    @IntDef({MODE_DEFAULT, MODE_ON, MODE_OFF, MODE_ESTIMATE})
     public @interface BatteryPercentMode {}
     public static final int MODE_DEFAULT = 0;
     public static final int MODE_ON = 1;
     public static final int MODE_OFF = 2;
+    public static final int MODE_ESTIMATE = 3;
 
     private final BatteryMeterDrawableBase mDrawable;
     private final String mSlotBattery;
@@ -93,6 +94,7 @@
     // Some places may need to show the battery conditionally, and not obey the tuner
     private boolean mIgnoreTunerUpdates;
     private boolean mIsSubscribedForTunerUpdates;
+    private boolean mCharging;
 
     private int mDarkModeBackgroundColor;
     private int mDarkModeFillColor;
@@ -308,6 +310,7 @@
     public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
         mDrawable.setBatteryLevel(level);
         mDrawable.setCharging(pluggedIn);
+        mCharging = pluggedIn;
         mLevel = level;
         updatePercentText();
         setContentDescription(
@@ -337,9 +340,19 @@
     }
 
     private void updatePercentText() {
+        if (mBatteryController == null) {
+            return;
+        }
+
         if (mBatteryPercentView != null) {
-            mBatteryPercentView.setText(
-                    NumberFormat.getPercentInstance().format(mLevel / 100f));
+            if (mShowPercentMode == MODE_ESTIMATE && !mCharging) {
+                mBatteryController.getEstimatedTimeRemainingString((String estimate) -> {
+                    mBatteryPercentView.setText(estimate);
+                });
+            } else {
+                mBatteryPercentView.setText(
+                        NumberFormat.getPercentInstance().format(mLevel / 100f));
+            }
         }
     }
 
@@ -350,7 +363,7 @@
                 SHOW_BATTERY_PERCENT, 0, mUser);
 
         if ((mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
-                || mShowPercentMode == MODE_ON) {
+                || mShowPercentMode == MODE_ON || mShowPercentMode == MODE_ESTIMATE) {
             if (!showing) {
                 mBatteryPercentView = loadPercentView();
                 if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 28285e14..75ab5df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -141,14 +141,11 @@
     private View mStatusSeparator;
     private ImageView mRingerModeIcon;
     private TextView mRingerModeTextView;
-    private BatteryMeterView mBatteryMeterView;
     private Clock mClockView;
     private DateView mDateView;
     private OngoingPrivacyChip mPrivacyChip;
     private Space mSpace;
     private BatteryMeterView mBatteryRemainingIcon;
-    private TextView mBatteryRemainingText;
-    private boolean mShowBatteryPercentAndEstimate;
 
     private PrivacyItemController mPrivacyItemController;
     /** Counts how many times the long press tooltip has been shown to the user. */
@@ -229,13 +226,6 @@
         // Set the correct tint for the status icons so they contrast
         mIconManager.setTint(fillColor);
 
-        mShowBatteryPercentAndEstimate = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_battery_percentage_setting_available);
-
-        mBatteryMeterView = findViewById(R.id.battery);
-        mBatteryMeterView.setPercentShowMode(mShowBatteryPercentAndEstimate
-                ? BatteryMeterView.MODE_ON : BatteryMeterView.MODE_OFF);
-        mBatteryMeterView.setOnClickListener(this);
         mClockView = findViewById(R.id.clock);
         mClockView.setOnClickListener(this);
         mDateView = findViewById(R.id.date);
@@ -245,13 +235,8 @@
 
         // Tint for the battery icons are handled in setupHost()
         mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon);
-        mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_OFF);
         // Don't need to worry about tuner settings for this icon
         mBatteryRemainingIcon.setIgnoreTunerUpdates(true);
-
-        mBatteryRemainingText = findViewById(R.id.batteryRemainingText);
-        mBatteryRemainingText.setTextColor(fillColor);
-
         updateShowPercent();
     }
 
@@ -268,10 +253,8 @@
     }
 
     private void setChipVisibility(boolean chipVisible) {
-        mBatteryMeterView.setVisibility(View.VISIBLE);
         if (chipVisible) {
             mPrivacyChip.setVisibility(View.VISIBLE);
-            if (mHasTopCutout) mBatteryMeterView.setVisibility(View.GONE);
         } else {
             mPrivacyChip.setVisibility(View.GONE);
         }
@@ -339,7 +322,6 @@
         // Update color schemes in landscape to use wallpaperTextColor
         boolean shouldUseWallpaperTextColor =
                 newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
-        mBatteryMeterView.useWallpaperTextColor(shouldUseWallpaperTextColor);
         mClockView.useWallpaperTextColor(shouldUseWallpaperTextColor);
     }
 
@@ -415,13 +397,6 @@
                 .build();
     }
 
-    private void updateBatteryRemainingText() {
-        if (!mShowBatteryPercentAndEstimate) {
-            return;
-        }
-        mBatteryRemainingText.setText(mBatteryController.getEstimatedTimeRemainingString());
-    }
-
     public void setExpanded(boolean expanded) {
         if (mExpanded == expanded) return;
         mExpanded = expanded;
@@ -519,7 +494,6 @@
             }
         }
         mSpace.setLayoutParams(lp);
-        // Decide whether to show BatteryMeterView
         setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE);
         return super.onApplyWindowInsets(insets);
     }
@@ -546,7 +520,6 @@
             mAlarmController.addCallback(this);
             mContext.registerReceiver(mRingerReceiver,
                     new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
-            updateBatteryRemainingText();
         } else {
             mZenController.removeCallback(this);
             mAlarmController.removeCallback(this);
@@ -559,9 +532,6 @@
         if (v == mClockView) {
             mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
                     AlarmClock.ACTION_SHOW_ALARMS),0);
-        } else if (v == mBatteryMeterView) {
-            mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
-                    Intent.ACTION_POWER_USAGE_SUMMARY),0);
         } else if (v == mPrivacyChip) {
             Handler mUiHandler = new Handler(Looper.getMainLooper());
             mUiHandler.post(() -> {
@@ -713,9 +683,6 @@
         mHeaderQsPanel.setQSPanelAndHeader(mQsPanel, this);
         mHeaderQsPanel.setHost(host, null /* No customization in header */);
 
-        // Use SystemUI context to get battery meter colors, and let it use the default tint (white)
-        mBatteryMeterView.setColorsFromContext(mHost.getContext());
-        mBatteryMeterView.onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
 
         Rect tintArea = new Rect(0, 0, 0, 0);
         int colorForeground = Utils.getColorAttrDefaultColor(getContext(),
@@ -763,22 +730,8 @@
                 .getIntForUser(getContext().getContentResolver(),
                         SHOW_BATTERY_PERCENT, 0, ActivityManager.getCurrentUser());
 
-        mShowBatteryPercentAndEstimate = systemSetting;
-
-        updateBatteryViews();
-    }
-
-    private void updateBatteryViews() {
-        if (mShowBatteryPercentAndEstimate) {
-            mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON);
-            mBatteryRemainingIcon.setVisibility(View.VISIBLE);
-            mBatteryRemainingText.setVisibility(View.VISIBLE);
-            updateBatteryRemainingText();
-        } else {
-            mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_OFF);
-            mBatteryRemainingIcon.setVisibility(View.GONE);
-            mBatteryRemainingText.setVisibility(View.GONE);
-        }
+        mBatteryRemainingIcon.setPercentShowMode(systemSetting
+                ? BatteryMeterView.MODE_ESTIMATE : BatteryMeterView.MODE_ON);
     }
 
     private final class PercentSettingObserver extends ContentObserver {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index f65f826..5e94152 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -48,18 +48,36 @@
     }
 
     /**
-     * A listener that will be notified whenever a change in battery level or power save mode
-     * has occurred.
+     * A listener that will be notified whenever a change in battery level or power save mode has
+     * occurred.
      */
     interface BatteryStateChangeCallback {
-        default void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {}
-        default void onPowerSaveChanged(boolean isPowerSave) {}
+
+        default void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+        }
+
+        default void onPowerSaveChanged(boolean isPowerSave) {
+        }
     }
 
     /**
-     * If available, get the estimated battery time remaining as a string
+     * If available, get the estimated battery time remaining as a string.
+     *
+     * @param completion A lambda that will be called with the result of fetching the estimate. The
+     * first time this method is called may need to be dispatched to a background thread. The
+     * completion is called on the main thread
      */
-    default String getEstimatedTimeRemainingString() {
-        return null;
+    default void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {}
+
+    /**
+     * Callback called when the estimated time remaining text is fetched.
+     */
+    public interface EstimateFetchCompletion {
+
+        /**
+         * The callback
+         * @param estimate the estimate
+         */
+        void onBatteryRemainingEstimateRetrieved(String estimate);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 6190c8f..af3c96f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -27,9 +27,12 @@
 import android.os.PowerSaveState;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.fuelgauge.BatterySaverUtils;
 import com.android.settingslib.utils.PowerUtil;
+import com.android.systemui.Dependency;
 import com.android.systemui.power.EnhancedEstimates;
 import com.android.systemui.power.Estimate;
 
@@ -56,6 +59,7 @@
 
     private final EnhancedEstimates mEstimates;
     private final ArrayList<BatteryController.BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
+    private final ArrayList<EstimateFetchCompletion> mFetchCallbacks = new ArrayList<>();
     private final PowerManager mPowerManager;
     private final Handler mHandler;
     private final Context mContext;
@@ -70,6 +74,7 @@
     private boolean mHasReceivedBattery = false;
     private Estimate mEstimate;
     private long mLastEstimateTimestamp = -1;
+    private boolean mFetchingEstimate = false;
 
     @Inject
     public BatteryControllerImpl(Context context, EnhancedEstimates enhancedEstimates) {
@@ -197,20 +202,61 @@
     }
 
     @Override
-    public String getEstimatedTimeRemainingString() {
-        if (mEstimate == null
-                || System.currentTimeMillis() > mLastEstimateTimestamp + UPDATE_GRANULARITY_MSEC) {
-            updateEstimate();
+    public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
+        if (mEstimate != null
+                && mLastEstimateTimestamp > System.currentTimeMillis() - UPDATE_GRANULARITY_MSEC) {
+            String percentage = generateTimeRemainingString();
+            completion.onBatteryRemainingEstimateRetrieved(percentage);
+            return;
         }
-        // Estimates may not exist yet even if we've checked
+
+        // Need to fetch or refresh the estimate, but it may involve binder calls so offload the
+        // work
+        synchronized (mFetchCallbacks) {
+            mFetchCallbacks.add(completion);
+        }
+        updateEstimateInBackground();
+    }
+
+    @Nullable
+    private String generateTimeRemainingString() {
         if (mEstimate == null) {
             return null;
         }
-        final String percentage = NumberFormat.getPercentInstance().format((double) mLevel / 100.0);
+
+        String percentage = NumberFormat.getPercentInstance().format((double) mLevel / 100.0);
         return PowerUtil.getBatteryRemainingShortStringFormatted(
                 mContext, mEstimate.estimateMillis);
     }
 
+    private void updateEstimateInBackground() {
+        if (mFetchingEstimate) {
+            // Already dispatched a fetch. It will notify all listeners when finished
+            return;
+        }
+
+        mFetchingEstimate = true;
+        Dependency.get(Dependency.BG_HANDLER).post(() -> {
+            mEstimate = mEstimates.getEstimate();
+            mLastEstimateTimestamp = System.currentTimeMillis();
+            mFetchingEstimate = false;
+
+            Dependency.get(Dependency.MAIN_HANDLER).post(this::notifyEstimateFetchCallbacks);
+        });
+    }
+
+    private void notifyEstimateFetchCallbacks() {
+        String estimate = generateTimeRemainingString();
+
+        synchronized (mFetchCallbacks) {
+            for (EstimateFetchCompletion completion : mFetchCallbacks) {
+                completion.onBatteryRemainingEstimateRetrieved(estimate);
+            }
+
+            mFetchCallbacks.clear();
+        }
+    }
+
     private void updateEstimate() {
         mEstimate = mEstimates.getEstimate();
         mLastEstimateTimestamp = System.currentTimeMillis();