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);
+ }
+}