Merge "[Settings] 1. Provision status checking of eSIM  2. Show SIM if isOpportunistic is false"
diff --git a/res/layout/user_credential.xml b/res/layout/user_credential.xml
index f441bda..fa7abb3 100644
--- a/res/layout/user_credential.xml
+++ b/res/layout/user_credential.xml
@@ -40,6 +40,22 @@
         android:paddingTop="10dp">
 
         <TextView
+            android:id="@+id/credential_being_used_by_title"
+            android:text="@string/credential_being_used_by"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?android:attr/textColorSecondary"/>
+
+        <TextView
+            android:id="@+id/credential_being_used_by_content"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?android:attr/textColorTertiary"
+            android:paddingStart="?android:attr/listPreferredItemPaddingStart"/>
+
+        <TextView
             android:id="@+id/contents_title"
             android:text="@string/credential_contains"
             android:layout_width="wrap_content"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4d5d898..e5ef8ea 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -5258,8 +5258,10 @@
     <string name="credentials_settings_not_available">Credentials are not available for this user</string>
     <!-- Sub-heading for a user credential installed to be used by apps and as part of VPN configurations. [CHAR LIMIT=NONE] -->
     <string name="credential_for_vpn_and_apps">Installed for VPN and apps</string>
+    <!-- Sub-heading for a user credential installed for Wi-Fi configuration. [CHAR LIMIT=NONE]. -->
+    <string name="credential_for_wifi">Installed for Wi\u2011Fi</string>
     <!-- Sub-heading for a user credential installed to be used as part of a Wi-Fi configuration. [CHAR LIMIT=NONE]. -->
-    <string name="credential_for_wifi">Installed for Wi-Fi</string>
+    <string name="credential_for_wifi_in_use">Installed for Wi\u2011Fi (In use)</string>
     <!-- Description of dialog to reset credential storage [CHAR LIMIT=NONE] -->
     <string name="credentials_reset_hint">Remove all the contents?</string>
     <!-- Toast message [CHAR LIMIT=30] -->
@@ -5809,14 +5811,16 @@
     <!-- Alert dialog confirmation when removing a user CA certificate. -->
     <string name="trusted_credentials_remove_confirmation">Permanently remove the user CA certificate?</string>
 
+    <!-- Header for a list of items that a credential entry is required. For example, a network uses this credential. [CHAR LIMIT=NONE] -->
+    <string name="credential_being_used_by">Being used by</string>
     <!-- Header for a list of items that a credential entry contains. For example, one private key and one certificate. [CHAR LIMIT=NONE] -->
-    <string name="credential_contains">This entry contains:</string>
+    <string name="credential_contains">This entry contains</string>
     <!-- Item found in the PKCS12 keystore being investigated [CHAR LIMIT=NONE] -->
-    <string name="one_userkey">one user key</string>
+    <string name="one_userkey">1 user key</string>
     <!-- Item found in the PKCS12 keystore being investigated [CHAR LIMIT=NONE] -->
-    <string name="one_usercrt">one user certificate</string>
+    <string name="one_usercrt">1 user certificate</string>
     <!-- Item found in the PKCS12 keystore being investigated [CHAR LIMIT=NONE] -->
-    <string name="one_cacrt">one CA certificate</string>
+    <string name="one_cacrt">1 CA certificate</string>
     <!-- Item found in thee PKCS12 keystore being investigated [CHAR LIMIT=NONE]-->
     <string name="n_cacrts">%d CA certificates</string>
     <!-- Alert dialog when viewing a set of user credentials. -->
diff --git a/res/xml/stylus_usi_details_fragment.xml b/res/xml/stylus_usi_details_fragment.xml
new file mode 100644
index 0000000..8a1d036
--- /dev/null
+++ b/res/xml/stylus_usi_details_fragment.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/stylus_device_details_title">
+
+    <com.android.settingslib.widget.LayoutPreference
+        android:key="stylus_usi_header"
+        android:layout="@layout/settings_entity_header"
+        android:selectable="false"
+        settings:allowDividerBelow="true"
+        settings:searchable="false"/>
+
+    <PreferenceCategory
+        android:key="device_stylus"/>
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/settings/UserCredentialsSettings.java b/src/com/android/settings/UserCredentialsSettings.java
index c45b5ef..73f1d9e 100644
--- a/src/com/android/settings/UserCredentialsSettings.java
+++ b/src/com/android/settings/UserCredentialsSettings.java
@@ -43,12 +43,14 @@
 import android.view.ViewGroup;
 import android.widget.TextView;
 
+import androidx.annotation.VisibleForTesting;
 import androidx.appcompat.app.AlertDialog;
 import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.Fragment;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.wifi.helper.SavedWifiHelper;
 import com.android.settingslib.RestrictedLockUtils;
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 import com.android.settingslib.RestrictedLockUtilsInternal;
@@ -74,6 +76,9 @@
 
     private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
 
+    @VisibleForTesting
+    protected SavedWifiHelper mSavedWifiHelper;
+
     @Override
     public int getMetricsCategory() {
         return SettingsEnums.USER_CREDENTIALS;
@@ -88,15 +93,23 @@
     @Override
     public void onClick(final View view) {
         final Credential item = (Credential) view.getTag();
-        if (item != null) {
-            CredentialDialogFragment.show(this, item);
+        if (item == null) return;
+        if (item.isInUse()) {
+            item.setUsedByNames(mSavedWifiHelper.getCertificateNetworkNames(item.alias));
         }
+        showCredentialDialogFragment(item);
     }
 
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         getActivity().setTitle(R.string.user_credentials);
+        mSavedWifiHelper = SavedWifiHelper.getInstance(getContext(), getSettingsLifecycle());
+    }
+
+    @VisibleForTesting
+    protected void showCredentialDialogFragment(Credential item) {
+        CredentialDialogFragment.show(this, item);
     }
 
     protected void announceRemoval(String alias) {
@@ -112,7 +125,9 @@
         }
     }
 
-    public static class CredentialDialogFragment extends InstrumentedDialogFragment {
+    /** The fragment to show the credential information. */
+    public static class CredentialDialogFragment extends InstrumentedDialogFragment
+            implements DialogInterface.OnShowListener {
         private static final String TAG = "CredentialDialogFragment";
         private static final String ARG_CREDENTIAL = "credential";
 
@@ -162,17 +177,23 @@
                         dialog.dismiss();
                     }
                 };
-                // TODO: b/127865361
-                //       a safe means of clearing wifi certificates. Configs refer to aliases
-                //       directly so deleting certs will break dependent access points.
-                //       However, Wi-Fi used to remove this certificate from storage if the network
-                //       was removed, regardless if it is used in more than one network.
-                //       It has been decided to allow removing certificates from this menu, as we
-                //       assume that the user who manually adds certificates must have a way to
-                //       manually remove them.
                 builder.setNegativeButton(R.string.trusted_credentials_remove_label, listener);
             }
-            return builder.create();
+            AlertDialog dialog = builder.create();
+            dialog.setOnShowListener(this);
+            return dialog;
+        }
+
+        /**
+         * Override for the negative button enablement on demand.
+         */
+        @Override
+        public void onShow(DialogInterface dialogInterface) {
+            final Credential item = (Credential) getArguments().getParcelable(ARG_CREDENTIAL);
+            if (item.isInUse()) {
+                ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_NEGATIVE)
+                        .setEnabled(false);
+            }
         }
 
         @Override
@@ -300,6 +321,9 @@
                 while (aliases.hasMoreElements()) {
                     String alias = aliases.nextElement();
                     Credential c = new Credential(alias, uid);
+                    if (!c.isSystem()) {
+                        c.setInUse(mSavedWifiHelper.isCertificateInUse(alias));
+                    }
                     Key key = null;
                     try {
                         key = keyStore.getKey(alias, null);
@@ -423,12 +447,13 @@
         }
 
         ((TextView) view.findViewById(R.id.alias)).setText(item.alias);
-        ((TextView) view.findViewById(R.id.purpose)).setText(item.isSystem()
-                ? R.string.credential_for_vpn_and_apps
-                : R.string.credential_for_wifi);
+        updatePurposeView(view.findViewById(R.id.purpose), item);
 
         view.findViewById(R.id.contents).setVisibility(expanded ? View.VISIBLE : View.GONE);
         if (expanded) {
+            updateUsedByViews(view.findViewById(R.id.credential_being_used_by_title),
+                    view.findViewById(R.id.credential_being_used_by_content), item);
+
             for (int i = 0; i < credentialViewTypes.size(); i++) {
                 final View detail = view.findViewById(credentialViewTypes.keyAt(i));
                 detail.setVisibility(item.storedTypes.contains(credentialViewTypes.valueAt(i))
@@ -438,6 +463,30 @@
         return view;
     }
 
+    @VisibleForTesting
+    protected static void updatePurposeView(TextView purpose, Credential item) {
+        int subTextResId = R.string.credential_for_vpn_and_apps;
+        if (!item.isSystem()) {
+            subTextResId = (item.isInUse())
+                    ? R.string.credential_for_wifi_in_use
+                    : R.string.credential_for_wifi;
+        }
+        purpose.setText(subTextResId);
+    }
+
+    @VisibleForTesting
+    protected static void updateUsedByViews(TextView title, TextView content, Credential item) {
+        List<String> usedByNames = item.getUsedByNames();
+        if (usedByNames.size() > 0) {
+            title.setVisibility(View.VISIBLE);
+            content.setText(String.join("\n", usedByNames));
+            content.setVisibility(View.VISIBLE);
+        } else {
+            title.setVisibility(View.GONE);
+            content.setVisibility(View.GONE);
+        }
+    }
+
     static class AliasEntry {
         public String alias;
         public int uid;
@@ -469,6 +518,16 @@
         final int uid;
 
         /**
+         * Indicate whether or not this credential is in use.
+         */
+        boolean mIsInUse;
+
+        /**
+         * The list of networks which use this credential.
+         */
+        List<String> mUsedByNames = new ArrayList<>();
+
+        /**
          * Should contain some non-empty subset of:
          * <ul>
          *   <li>{@link Credentials.CA_CERTIFICATE}</li>
@@ -524,10 +583,28 @@
             return UserHandle.getAppId(uid) == Process.SYSTEM_UID;
         }
 
-        public String getAlias() { return alias; }
+        public String getAlias() {
+            return alias;
+        }
 
         public EnumSet<Type> getStoredTypes() {
             return storedTypes;
         }
+
+        public void setInUse(boolean inUse) {
+            mIsInUse = inUse;
+        }
+
+        public boolean isInUse() {
+            return mIsInUse;
+        }
+
+        public void setUsedByNames(List<String> names) {
+            mUsedByNames = new ArrayList<>(names);
+        }
+
+        public List<String> getUsedByNames() {
+            return new ArrayList<String>(mUsedByNames);
+        }
     }
 }
diff --git a/src/com/android/settings/connecteddevice/stylus/StylusUsiDetailsFragment.java b/src/com/android/settings/connecteddevice/stylus/StylusUsiDetailsFragment.java
new file mode 100644
index 0000000..4691a5b
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/stylus/StylusUsiDetailsFragment.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 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.connecteddevice.stylus;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.view.InputDevice;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settings.R;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Controls the USI stylus details and provides updates to individual controllers. */
+public class StylusUsiDetailsFragment extends DashboardFragment {
+    private static final String TAG = StylusUsiDetailsFragment.class.getSimpleName();
+    private static final String KEY_DEVICE_INPUT_ID = "device_input_id";
+
+    @VisibleForTesting
+    @Nullable
+    InputDevice mInputDevice;
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        int inputDeviceId = getArguments().getInt(KEY_DEVICE_INPUT_ID);
+        InputManager im = context.getSystemService(InputManager.class);
+        mInputDevice = im.getInputDevice(inputDeviceId);
+
+        super.onAttach(context);
+        if (mInputDevice == null) {
+            finish();
+        }
+    }
+
+
+    @Override
+    public int getMetricsCategory() {
+        // TODO(b/261988317): for new SettingsEnum for this page
+        return SettingsEnums.BLUETOOTH_DEVICE_DETAILS;
+    }
+
+    @Override
+    protected String getLogTag() {
+        return TAG;
+    }
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.stylus_usi_details_fragment;
+    }
+
+    @Override
+    protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+        ArrayList<AbstractPreferenceController> controllers = new ArrayList<>();
+        if (mInputDevice != null) {
+            Lifecycle lifecycle = getSettingsLifecycle();
+            controllers.add(new StylusUsiHeaderController(context, mInputDevice));
+            controllers.add(new StylusDevicesController(context, mInputDevice, lifecycle));
+        }
+        return controllers;
+    }
+}
diff --git a/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderController.java b/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderController.java
new file mode 100644
index 0000000..826cc1f
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderController.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2022 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.connecteddevice.stylus;
+
+import android.content.Context;
+import android.hardware.BatteryState;
+import android.hardware.input.InputManager;
+import android.os.Bundle;
+import android.view.InputDevice;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnCreate;
+import com.android.settingslib.core.lifecycle.events.OnDestroy;
+import com.android.settingslib.widget.LayoutPreference;
+
+import java.text.NumberFormat;
+
+/**
+ * This class adds a header for USI stylus devices with a heading, icon, and battery level.
+ * As opposed to the bluetooth device headers, this USI header gets its battery values
+ * from {@link InputManager} APIs, rather than the bluetooth battery levels.
+ */
+public class StylusUsiHeaderController extends BasePreferenceController implements
+        InputManager.InputDeviceBatteryListener, LifecycleObserver, OnCreate, OnDestroy {
+
+    private static final String KEY_STYLUS_USI_HEADER = "stylus_usi_header";
+    private static final String TAG = StylusUsiHeaderController.class.getSimpleName();
+
+    private final InputManager mInputManager;
+    private final InputDevice mInputDevice;
+
+    private LayoutPreference mHeaderPreference;
+
+
+    public StylusUsiHeaderController(Context context, InputDevice inputDevice) {
+        super(context, KEY_STYLUS_USI_HEADER);
+        mInputDevice = inputDevice;
+        mInputManager = context.getSystemService(InputManager.class);
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        mHeaderPreference = screen.findPreference(getPreferenceKey());
+        View view = mHeaderPreference.findViewById(R.id.entity_header);
+        TextView titleView = view.findViewById(R.id.entity_header_title);
+        titleView.setText(R.string.stylus_usi_header_title);
+
+        ImageView iconView = mHeaderPreference.findViewById(R.id.entity_header_icon);
+        if (iconView != null) {
+            // TODO(b/250909304): get proper icon once VisD ready
+            iconView.setImageResource(R.drawable.circle);
+            iconView.setContentDescription("Icon for stylus");
+        }
+        refresh();
+        super.displayPreference(screen);
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        refresh();
+    }
+
+    private void refresh() {
+        BatteryState batteryState = mInputDevice.getBatteryState();
+        View view = mHeaderPreference.findViewById(R.id.entity_header);
+        TextView summaryView = view.findViewById(R.id.entity_header_summary);
+
+        if (isValidBatteryState(batteryState)) {
+            summaryView.setVisibility(View.VISIBLE);
+            summaryView.setText(
+                    NumberFormat.getPercentInstance().format(batteryState.getCapacity()));
+        } else {
+            summaryView.setVisibility(View.INVISIBLE);
+        }
+    }
+
+    /**
+     * This determines if a battery state is 'stale', as indicated by the presence of
+     * battery values.
+     *
+     * A USI battery state is valid (and present) if a USI battery value has been pulled
+     * within the last 1 hour of a stylus touching/hovering on the screen. The header shows
+     * battery values in this case, Conversely, a stale battery state means no USI battery
+     * value has been detected within the last 1 hour. Thus, the USI stylus preference will
+     * not be shown in Settings, and accordingly, the USI battery state won't surface.
+     *
+     * @param batteryState Latest battery state pulled from the kernel
+     */
+    private boolean isValidBatteryState(BatteryState batteryState) {
+        return batteryState != null
+                && batteryState.isPresent()
+                && batteryState.getCapacity() > 0f;
+    }
+
+    @Override
+    public int getAvailabilityStatus() {
+        return AVAILABLE;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_STYLUS_USI_HEADER;
+    }
+
+    @Override
+    public void onBatteryStateChanged(int deviceId, long eventTimeMillis,
+            @NonNull BatteryState batteryState) {
+        refresh();
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        mInputManager.addInputDeviceBatteryListener(mInputDevice.getId(),
+                mContext.getMainExecutor(), this);
+    }
+
+    @Override
+    public void onDestroy() {
+        mInputManager.removeInputDeviceBatteryListener(mInputDevice.getId(),
+                this);
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java
index caf2cf3..59903a0 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryDiffData.java
@@ -32,9 +32,6 @@
             @NonNull List<BatteryDiffEntry> systemDiffEntries) {
         mAppEntries = appDiffEntries;
         mSystemEntries = systemDiffEntries;
-        setTotalConsumePowerForAllEntries(mAppEntries);
-        setTotalConsumePowerForAllEntries(mSystemEntries);
-        sortEntries();
     }
 
     public List<BatteryDiffEntry> getAppDiffEntryList() {
@@ -45,6 +42,18 @@
         return mSystemEntries;
     }
 
+    // Sorts entries based on consumed percentage.
+    void sortEntries() {
+        Collections.sort(mAppEntries, BatteryDiffEntry.COMPARATOR);
+        Collections.sort(mSystemEntries, BatteryDiffEntry.COMPARATOR);
+    }
+
+    // Sets total consume power for app and system entries separately.
+    void setTotalConsumePower() {
+        setTotalConsumePowerForAllEntries(mAppEntries);
+        setTotalConsumePowerForAllEntries(mSystemEntries);
+    }
+
     // Sets total consume power for each entry.
     private void setTotalConsumePowerForAllEntries(List<BatteryDiffEntry> batteryDiffEntries) {
         double totalConsumePower = 0.0;
@@ -55,10 +64,4 @@
             batteryDiffEntry.setTotalConsumePower(totalConsumePower);
         }
     }
-
-    // Sorts entries based on consumed percentage.
-    private void sortEntries() {
-        Collections.sort(mAppEntries, BatteryDiffEntry.COMPARATOR);
-        Collections.sort(mSystemEntries, BatteryDiffEntry.COMPARATOR);
-    }
 }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
index e6b3750..2db6849 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
@@ -546,7 +546,7 @@
         insertDailyUsageDiffData(hourlyBatteryLevelsPerDay, resultMap);
         // Insert diff data [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL].
         insertAllUsageDiffData(resultMap);
-        purgeFakeAndHiddenPackages(context, resultMap);
+        processBatteryDiffData(context, resultMap);
         if (!isUsageMapValid(resultMap, hourlyBatteryLevelsPerDay)) {
             return null;
         }
@@ -651,7 +651,7 @@
         allUsageMap.put(SELECTED_INDEX_ALL,
                 generateBatteryDiffData(context, getBatteryHistListFromFromStatsService(context)));
         resultMap.put(SELECTED_INDEX_ALL, allUsageMap);
-        purgeFakeAndHiddenPackages(context, resultMap);
+        processBatteryDiffData(context, resultMap);
         return resultMap;
     }
 
@@ -1232,8 +1232,11 @@
         }
     }
 
-    // Removes low percentage data and fake usage data, which will be zero value.
-    private static void purgeFakeAndHiddenPackages(
+    // Process every battery diff data in the battery usage result map.
+    // (1) Removes low percentage data and fake usage data, which will be zero value.
+    // (2) Sets total consume power, so the usage percentage is updated.
+    // (3) Sorts the result.
+    private static void processBatteryDiffData(
             final Context context,
             final Map<Integer, Map<Integer, BatteryDiffData>> resultMap) {
         final Set<CharSequence> hideBackgroundUsageTimeSet =
@@ -1246,16 +1249,18 @@
                         .getHideApplicationSet(context);
         resultMap.keySet().forEach(dailyKey -> {
             final Map<Integer, BatteryDiffData> dailyUsageMap = resultMap.get(dailyKey);
-            dailyUsageMap.values().forEach(diffEntryLists -> {
-                if (diffEntryLists == null) {
+            dailyUsageMap.values().forEach(batteryDiffData -> {
+                if (batteryDiffData == null) {
                     return;
                 }
                 purgeFakeAndHiddenPackages(
-                        diffEntryLists.getAppDiffEntryList(), hideBackgroundUsageTimeSet,
+                        batteryDiffData.getAppDiffEntryList(), hideBackgroundUsageTimeSet,
                         hideApplicationSet);
                 purgeFakeAndHiddenPackages(
-                        diffEntryLists.getSystemDiffEntryList(), hideBackgroundUsageTimeSet,
+                        batteryDiffData.getSystemDiffEntryList(), hideBackgroundUsageTimeSet,
                         hideApplicationSet);
+                batteryDiffData.setTotalConsumePower();
+                batteryDiffData.sortEntries();
             });
         });
     }
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderControllerTest.java
new file mode 100644
index 0000000..27b1de5
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusUsiHeaderControllerTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 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.connecteddevice.stylus;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.BatteryState;
+import android.hardware.input.InputManager;
+import android.os.Bundle;
+import android.view.InputDevice;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.R;
+import com.android.settingslib.widget.LayoutPreference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class StylusUsiHeaderControllerTest {
+
+    private Context mContext;
+    private StylusUsiHeaderController mController;
+    private LayoutPreference mLayoutPreference;
+    private PreferenceScreen mScreen;
+    private InputDevice mInputDevice;
+
+    @Mock
+    private InputManager mInputManager;
+    @Mock
+    private BatteryState mBatteryState;
+    @Mock
+    private Bundle mBundle;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        InputDevice device = new InputDevice.Builder().setId(1).setSources(
+                InputDevice.SOURCE_BLUETOOTH_STYLUS).build();
+        mInputDevice = spy(device);
+        when(mInputDevice.getBatteryState()).thenReturn(mBatteryState);
+        when(mBatteryState.getCapacity()).thenReturn(1f);
+        when(mBatteryState.isPresent()).thenReturn(true);
+
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(InputManager.class)).thenReturn(mInputManager);
+        mController = new StylusUsiHeaderController(mContext, mInputDevice);
+
+        PreferenceManager preferenceManager = new PreferenceManager(mContext);
+        mLayoutPreference = new LayoutPreference(mContext,
+                LayoutInflater.from(mContext).inflate(R.layout.advanced_bt_entity_header, null));
+        mLayoutPreference.setKey(mController.getPreferenceKey());
+
+        mScreen = preferenceManager.createPreferenceScreen(mContext);
+        mScreen.addPreference(mLayoutPreference);
+
+    }
+
+    @Test
+    public void onCreate_registersBatteryListener() {
+        mController.onCreate(mBundle);
+
+        verify(mInputManager).addInputDeviceBatteryListener(mInputDevice.getId(),
+                mContext.getMainExecutor(),
+                mController);
+    }
+
+    @Test
+    public void onDestroy_unregistersBatteryListener() {
+        mController.onDestroy();
+
+        verify(mInputManager).removeInputDeviceBatteryListener(mInputDevice.getId(),
+                mController);
+    }
+
+    @Test
+    public void displayPreference_showsCorrectTitle() {
+        mController.displayPreference(mScreen);
+
+        assertThat(((TextView) mLayoutPreference.findViewById(
+                R.id.entity_header_title)).getText().toString()).isEqualTo(
+                mContext.getString(R.string.stylus_usi_header_title));
+    }
+
+    @Test
+    public void displayPreference_hasBattery_showsCorrectBatterySummary() {
+        mController.displayPreference(mScreen);
+
+        assertThat(mLayoutPreference.findViewById(
+                R.id.entity_header_summary).getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(((TextView) mLayoutPreference.findViewById(
+                R.id.entity_header_summary)).getText().toString()).isEqualTo(
+                "100%");
+    }
+
+    @Test
+    public void displayPreference_noBattery_showsEmptySummary() {
+        when(mBatteryState.isPresent()).thenReturn(false);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mLayoutPreference.findViewById(
+                R.id.entity_header_summary).getVisibility()).isEqualTo(View.INVISIBLE);
+    }
+
+    @Test
+    public void displayPreference_invalidCapacity_showsEmptySummary() {
+        when(mBatteryState.getCapacity()).thenReturn(-1f);
+
+        mController.displayPreference(mScreen);
+
+        assertThat(mLayoutPreference.findViewById(
+                R.id.entity_header_summary).getVisibility()).isEqualTo(View.INVISIBLE);
+    }
+
+    @Test
+    public void onBatteryStateChanged_updatesSummary() {
+        mController.displayPreference(mScreen);
+
+        when(mBatteryState.getCapacity()).thenReturn(0.2f);
+        mController.onBatteryStateChanged(mInputDevice.getId(),
+                System.currentTimeMillis(), mBatteryState);
+
+        assertThat(((TextView) mLayoutPreference.findViewById(
+                R.id.entity_header_summary)).getText().toString()).isEqualTo(
+                "20%");
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
index 5c3da4a..1d06e60 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownControllerTest.java
@@ -105,6 +105,8 @@
         mBatteryDiffEntry = spy(mBatteryDiffEntry);
         mBatteryUsageBreakdownController.mBatteryDiffData =
                 new BatteryDiffData(Arrays.asList(mBatteryDiffEntry), Arrays.asList());
+        mBatteryUsageBreakdownController.mBatteryDiffData.setTotalConsumePower();
+        mBatteryUsageBreakdownController.mBatteryDiffData.sortEntries();
         // Adds fake testing data.
         BatteryDiffEntry.sResourceCache.put(
                 "fakeBatteryDiffEntryKey",
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
index a44768e..b1695eb 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
@@ -1076,7 +1076,7 @@
                         .get(DataProcessor.SELECTED_INDEX_ALL);
         assertBatteryDiffEntry(
                 resultDiffData.getAppDiffEntryList().get(0), currentUserId, /*uid=*/ 2L,
-                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 50.0,
+                ConvertUtils.CONSUMER_TYPE_UID_BATTERY, /*consumePercentage=*/ 100.0,
                 /*foregroundUsageConsumePower=*/ 0, /*foregroundServiceUsageConsumePower=*/ 0,
                 /*backgroundUsageConsumePower=*/ 5, /*cachedUsageConsumePower=*/ 5,
                 /*foregroundUsageTimeInMs=*/ 10, /*backgroundUsageTimeInMs=*/ 20);
@@ -1226,6 +1226,8 @@
 
         final BatteryDiffData batteryDiffData = DataProcessor.generateBatteryDiffData(mContext,
                 DataProcessor.convertToBatteryHistEntry(batteryEntryList, mBatteryUsageStats));
+        batteryDiffData.setTotalConsumePower();
+        batteryDiffData.sortEntries();
 
         assertBatteryDiffEntry(
                 batteryDiffData.getAppDiffEntryList().get(0), 0, /*uid=*/ 2L,
diff --git a/tests/unit/src/com/android/settings/UserCredentialsSettingsTest.java b/tests/unit/src/com/android/settings/UserCredentialsSettingsTest.java
new file mode 100644
index 0000000..c4cdf80
--- /dev/null
+++ b/tests/unit/src/com/android/settings/UserCredentialsSettingsTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2022 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Looper;
+import android.os.Process;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.settings.testutils.ResourcesUtils;
+import com.android.settings.wifi.helper.SavedWifiHelper;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class UserCredentialsSettingsTest {
+    static final String TEST_ALIAS = "test_alias";
+    static final String TEST_USER_BY_NAME = "test_used_by_name";
+
+    static final String TEXT_PURPOSE_SYSTEM = "credential_for_vpn_and_apps";
+    static final String TEXT_PURPOSE_WIFI = "credential_for_wifi";
+    static final String TEXT_PURPOSE_WIFI_IN_USE = "credential_for_wifi_in_use";
+
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Spy
+    final Context mContext = ApplicationProvider.getApplicationContext();
+    @Mock
+    SavedWifiHelper mSavedWifiHelper;
+    @Mock
+    View mView;
+
+    UserCredentialsSettings mSettings;
+    UserCredentialsSettings.Credential mSysCredential =
+            new UserCredentialsSettings.Credential(TEST_ALIAS, Process.SYSTEM_UID);
+    UserCredentialsSettings.Credential mWifiCredential =
+            new UserCredentialsSettings.Credential(TEST_ALIAS, Process.WIFI_UID);
+    List<String> mUsedByNames = Arrays.asList(TEST_USER_BY_NAME);
+    TextView mPurposeView = new TextView(ApplicationProvider.getApplicationContext());
+    TextView mUsedByTitleView = new TextView(ApplicationProvider.getApplicationContext());
+    TextView mUsedByContentView = new TextView(ApplicationProvider.getApplicationContext());
+
+    @Before
+    @UiThreadTest
+    public void setUp() {
+        when(mSavedWifiHelper.isCertificateInUse(any(String.class))).thenReturn(false);
+        when(mSavedWifiHelper.getCertificateNetworkNames(any(String.class)))
+                .thenReturn(new ArrayList<>());
+        when(mView.getTag()).thenReturn(mWifiCredential);
+
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+        mSettings = spy(new UserCredentialsSettings());
+        when(mSettings.getContext()).thenReturn(mContext);
+        mSettings.mSavedWifiHelper = mSavedWifiHelper;
+        doNothing().when(mSettings)
+                .showCredentialDialogFragment(any(UserCredentialsSettings.Credential.class));
+    }
+
+    @Test
+    @UiThreadTest
+    public void onClick_noCredentialInTag_doNothing() {
+        when(mView.getTag()).thenReturn(null);
+
+        mSettings.onClick(mView);
+
+        verify(mSavedWifiHelper, never()).getCertificateNetworkNames(any(String.class));
+        verify(mSettings, never())
+                .showCredentialDialogFragment(any(UserCredentialsSettings.Credential.class));
+    }
+
+    @Test
+    @UiThreadTest
+    public void onClick_credentialInNotUse_notSetUsedByNamesThenShowDialog() {
+        mWifiCredential.setInUse(false);
+        when(mView.getTag()).thenReturn(mWifiCredential);
+
+        mSettings.onClick(mView);
+
+        verify(mSavedWifiHelper, never()).getCertificateNetworkNames(any(String.class));
+        verify(mSettings)
+                .showCredentialDialogFragment(any(UserCredentialsSettings.Credential.class));
+    }
+
+    @Test
+    @UiThreadTest
+    public void onClick_credentialInUse_setUsedByNamesThenShowDialog() {
+        mWifiCredential.setInUse(true);
+        when(mView.getTag()).thenReturn(mWifiCredential);
+        when(mSavedWifiHelper.getCertificateNetworkNames(any(String.class)))
+                .thenReturn(mUsedByNames);
+
+        mSettings.onClick(mView);
+
+        verify(mSavedWifiHelper).getCertificateNetworkNames(any(String.class));
+        assertThat(mWifiCredential.getUsedByNames()).isEqualTo(mUsedByNames);
+        verify(mSettings)
+                .showCredentialDialogFragment(any(UserCredentialsSettings.Credential.class));
+    }
+
+    @Test
+    @UiThreadTest
+    public void updatePurposeView_getSystemCert_setTextCorrectly() {
+        mSettings.updatePurposeView(mPurposeView, mSysCredential);
+
+        assertThat(mPurposeView.getText()).isEqualTo(getResString(TEXT_PURPOSE_SYSTEM));
+    }
+
+    @Test
+    @UiThreadTest
+    public void updatePurposeView_getWifiCert_setTextCorrectly() {
+        mWifiCredential.setInUse(false);
+
+        mSettings.updatePurposeView(mPurposeView, mWifiCredential);
+
+        assertThat(mPurposeView.getText()).isEqualTo(getResString(TEXT_PURPOSE_WIFI));
+    }
+
+    @Test
+    @UiThreadTest
+    public void updatePurposeView_isWifiCertInUse_setTextCorrectly() {
+        mWifiCredential.setInUse(true);
+
+        mSettings.updatePurposeView(mPurposeView, mWifiCredential);
+
+        assertThat(mPurposeView.getText()).isEqualTo(getResString(TEXT_PURPOSE_WIFI_IN_USE));
+    }
+
+    @Test
+    @UiThreadTest
+    public void updateUsedByViews_noUsedByName_hideViews() {
+        mWifiCredential.setUsedByNames(new ArrayList<>());
+
+        mSettings.updateUsedByViews(mUsedByTitleView, mUsedByContentView, mWifiCredential);
+
+        assertThat(mUsedByTitleView.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mUsedByContentView.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    @UiThreadTest
+    public void updateUsedByViews_hasUsedByName_showViews() {
+        mWifiCredential.setUsedByNames(mUsedByNames);
+
+        mSettings.updateUsedByViews(mUsedByTitleView, mUsedByContentView, mWifiCredential);
+
+        assertThat(mUsedByTitleView.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mUsedByContentView.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mUsedByContentView.getText().toString().contains(TEST_USER_BY_NAME)).isTrue();
+    }
+
+    static String getResString(String name) {
+        return ResourcesUtils.getResourcesString(ApplicationProvider.getApplicationContext(), name);
+    }
+}