From c27b674e3858f7032f4c5c77f682d44588f98881 Mon Sep 17 00:00:00 2001 From: SongFerngWang Date: Thu, 6 Oct 2022 17:15:04 +0800 Subject: [PATCH 01/10] [MEP] the user can't enable the psim when revmovable esim is enabled The isRemovableSimEnabled method at ToggleSubscriptionDialogActivity UI is for psim, not esim. It should skip the revmovable esim slot. Bug: 251384644 Test: build pass. atest UiccSlotUtilTest Change-Id: I11d0f9528961ec5e47cd8682cf9b6bb988a5700f --- .../settings/network/UiccSlotUtil.java | 27 +++- .../ToggleSubscriptionDialogActivity.java | 16 +- .../settings/network/UiccSlotUtilTest.java | 144 ++++++++++++++++++ 3 files changed, 171 insertions(+), 16 deletions(-) diff --git a/src/com/android/settings/network/UiccSlotUtil.java b/src/com/android/settings/network/UiccSlotUtil.java index 7ba2e0f8ed0..6c1dad7dab8 100644 --- a/src/com/android/settings/network/UiccSlotUtil.java +++ b/src/com/android/settings/network/UiccSlotUtil.java @@ -28,7 +28,6 @@ import android.telephony.UiccSlotMapping; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.telephony.uicc.UiccController; import com.android.settingslib.utils.ThreadUtils; import com.google.common.collect.ImmutableList; @@ -270,6 +269,7 @@ public class UiccSlotUtil { if (slotId == INVALID_PHYSICAL_SLOT_ID) { for (int i = 0; i < slots.length; i++) { if (slots[i].isRemovable() + && !slots[i].getIsEuicc() && !slots[i].getPorts().stream().findFirst().get().isActive() && slots[i].getCardStateInfo() != UiccSlotInfo.CARD_STATE_INFO_ERROR && slots[i].getCardStateInfo() != UiccSlotInfo.CARD_STATE_INFO_RESTRICTED) { @@ -410,4 +410,29 @@ public class UiccSlotUtil { .findFirst() .orElse(INVALID_LOGICAL_SLOT_ID); } + + /** + * Return whether the removable psim is enabled. + * + * @param telMgr is a TelephonyManager. + * @return whether the removable psim is enabled. + */ + public static boolean isRemovableSimEnabled(TelephonyManager telMgr) { + if (telMgr == null) { + return false; + } + ImmutableList slotInfos = UiccSlotUtil.getSlotInfos(telMgr); + boolean isRemovableSimEnabled = + slotInfos.stream() + .anyMatch( + slot -> slot != null + && slot.isRemovable() + && !slot.getIsEuicc() + && slot.getPorts().stream().anyMatch( + port -> port.isActive()) + && slot.getCardStateInfo() + == UiccSlotInfo.CARD_STATE_INFO_PRESENT); + Log.i(TAG, "isRemovableSimEnabled: " + isRemovableSimEnabled); + return isRemovableSimEnabled; + } } diff --git a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java index a878cb3ebd3..6fa803d0433 100644 --- a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java +++ b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java @@ -24,7 +24,6 @@ import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.UiccCardInfo; -import android.telephony.UiccSlotInfo; import android.text.TextUtils; import android.util.Log; import android.view.View; @@ -39,8 +38,6 @@ import com.android.settings.network.SwitchToRemovableSlotSidecar; import com.android.settings.network.UiccSlotUtil; import com.android.settings.sim.SimActivationNotifier; -import com.google.common.collect.ImmutableList; - import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -586,18 +583,7 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc } private boolean isRemovableSimEnabled() { - ImmutableList slotInfos = UiccSlotUtil.getSlotInfos(mTelMgr); - boolean isRemovableSimEnabled = - slotInfos.stream() - .anyMatch( - slot -> slot != null - && slot.isRemovable() - && slot.getPorts().stream().anyMatch( - port -> port.isActive()) - && slot.getCardStateInfo() - == UiccSlotInfo.CARD_STATE_INFO_PRESENT); - Log.i(TAG, "isRemovableSimEnabled: " + isRemovableSimEnabled); - return isRemovableSimEnabled; + return UiccSlotUtil.isRemovableSimEnabled(mTelMgr); } private boolean isMultipleEnabledProfilesSupported() { diff --git a/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java b/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java index 526fc0c8d18..887f3004618 100644 --- a/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java +++ b/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java @@ -632,6 +632,106 @@ public class UiccSlotUtilTest { assertThat(testExcludedLogicalSlotIndex).isEqualTo(verifyExcludedLogicalSlotIndex); } + @Test + public void isRemovableSimEnabled_noPsim_returnsFalse() { + when(mTelephonyManager.getUiccSlotsInfo()).thenReturn( + oneSimSlotDeviceActiveEsim()); + + boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager); + + assertThat(testSlot).isFalse(); + } + + @Test + public void isRemovableSimEnabled_activeRemovableEsimAndInactivePsim_returnsFalse() { + when(mTelephonyManager.getUiccSlotsInfo()).thenReturn( + twoSimSlotsDeviceActiveRemovableEsimInactivePsim()); + + boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager); + + assertThat(testSlot).isFalse(); + } + + @Test + public void isRemovableSimEnabled_activeRemovableEsimAndActivePsim_returnsTrue() { + when(mTelephonyManager.getUiccSlotsInfo()).thenReturn( + twoSimSlotsDeviceActivePsimActiveRemovableEsim()); + + boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager); + + assertThat(testSlot).isTrue(); + } + + @Test + public void isRemovableSimEnabled_inactiveRemovableEsimAndActivePsim_returnsTrue() { + when(mTelephonyManager.getUiccSlotsInfo()).thenReturn( + twoSimSlotsDeviceInactiveRemovableEsimActivePsim()); + + boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager); + + assertThat(testSlot).isTrue(); + } + + @Test + public void isRemovableSimEnabled_twoActiveRemovableEsimsAndInactivePsim_returnsFalse() { + when(mTelephonyManager.getUiccSlotsInfo()).thenReturn( + twoSimSlotsDeviceTwoActiveRemovableEsimsInactivePsim()); + + boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager); + + assertThat(testSlot).isFalse(); + } + + @Test + public void isRemovableSimEnabled_oneActiveOneInactiveRemovableEsimActivePsim_returnsTrue() { + when(mTelephonyManager.getUiccSlotsInfo()).thenReturn( + twoSimSlotsDeviceOneActiveOneInactiveRemovableEsimsActivePsim()); + + boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager); + + assertThat(testSlot).isTrue(); + } + + @Test + public void isRemovableSimEnabled_activePsim_returnsTrue() { + when(mTelephonyManager.getUiccSlotsInfo()).thenReturn( + oneSimSlotDeviceActivePsim()); + + boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager); + + assertThat(testSlot).isTrue(); + } + + @Test + public void isRemovableSimEnabled_inactivePsim_returnsFalse() { + when(mTelephonyManager.getUiccSlotsInfo()).thenReturn( + oneSimSlotDeviceinactivePsim()); + + boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager); + + assertThat(testSlot).isFalse(); + } + + @Test + public void isRemovableSimEnabled_activeEsimAndActivePsim_returnsTrue() { + when(mTelephonyManager.getUiccSlotsInfo()).thenReturn( + twoSimSlotsDeviceActivePsimActiveEsim()); + + boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager); + + assertThat(testSlot).isTrue(); + } + + @Test + public void isRemovableSimEnabled_activeEsimAndInactivePsim_returnsFalse() { + when(mTelephonyManager.getUiccSlotsInfo()).thenReturn( + twoSimSlotsDeviceInactivePsimActiveEsim()); + + boolean testSlot = UiccSlotUtil.isRemovableSimEnabled(mTelephonyManager); + + assertThat(testSlot).isFalse(); + } + private void compareTwoUiccSlotMappings(Collection testUiccSlotMappings, Collection verifyUiccSlotMappings) { assertThat(testUiccSlotMappings.size()).isEqualTo(verifyUiccSlotMappings.size()); @@ -786,6 +886,10 @@ public class UiccSlotUtilTest { return new UiccSlotInfo[]{createUiccSlotInfo(true, false, 1, true)}; } + private UiccSlotInfo[] oneSimSlotDeviceinactivePsim() { + return new UiccSlotInfo[]{createUiccSlotInfo(false, true, -1, false)}; + } + private UiccSlotInfo[] twoSimSlotsDeviceActivePsimActiveEsim() { return new UiccSlotInfo[]{ createUiccSlotInfo(false, true, 0, true), @@ -804,6 +908,30 @@ public class UiccSlotUtilTest { createUiccSlotInfo(true, true, 1, true)}; } + private UiccSlotInfo[] twoSimSlotsDeviceActiveRemovableEsimInactivePsim() { + return new UiccSlotInfo[]{ + createUiccSlotInfo(true, true, 0, true), + createUiccSlotInfo(false, true, -1, false)}; + } + + private UiccSlotInfo[] twoSimSlotsDeviceInactiveRemovableEsimActivePsim() { + return new UiccSlotInfo[]{ + createUiccSlotInfo(true, true, -1, false), + createUiccSlotInfo(false, true, 0, true)}; + } + + private UiccSlotInfo[] twoSimSlotsDeviceTwoActiveRemovableEsimsInactivePsim() { + return new UiccSlotInfo[]{ + createUiccSlotInfoForRemovableEsimMep(0, true, 1, true), + createUiccSlotInfo(false, true, -1, false)}; + } + + private UiccSlotInfo[] twoSimSlotsDeviceOneActiveOneInactiveRemovableEsimsActivePsim() { + return new UiccSlotInfo[]{ + createUiccSlotInfoForRemovableEsimMep(1, true, -1, false), + createUiccSlotInfo(false, true, 0, true)}; + } + private UiccSlotInfo[] twoSimSlotsDeviceActiveEsimActivePsim() { return new UiccSlotInfo[]{ createUiccSlotInfo(true, false, 0, true), @@ -866,4 +994,20 @@ public class UiccSlotUtilTest { logicalSlotIdx2 /* logicalSlotIdx */, isActiveEsim2 /* isActive */))); } + + private UiccSlotInfo createUiccSlotInfoForRemovableEsimMep(int logicalSlotIdx1, + boolean isActiveEsim1, int logicalSlotIdx2, boolean isActiveEsim2) { + return new UiccSlotInfo( + true, /* isEuicc */ + "123", /* cardId */ + CARD_STATE_INFO_PRESENT, /* cardStateInfo */ + true, /* isExtendApduSupported */ + true, /* isRemovable */ + Arrays.asList( + new UiccPortInfo("" /* iccId */, 0 /* portIdx */, + logicalSlotIdx1 /* logicalSlotIdx */, isActiveEsim1 /* isActive */), + new UiccPortInfo("" /* iccId */, 1 /* portIdx */, + logicalSlotIdx2 /* logicalSlotIdx */, + isActiveEsim2 /* isActive */))); + } } From e4026445ed116f54d6b0dfd2507552c8c3dd0b08 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Sat, 15 Oct 2022 15:44:31 -0700 Subject: [PATCH 02/10] Import translations. DO NOT MERGE ANYWHERE Auto-generated-cl: translation import Change-Id: I0049720788b756d0c4ae917806b14df933b8a34a --- res/values-am/arrays.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/res/values-am/arrays.xml b/res/values-am/arrays.xml index 63051841405..9240b575622 100644 --- a/res/values-am/arrays.xml +++ b/res/values-am/arrays.xml @@ -183,17 +183,17 @@ "ማሳወቂያዎችን ድረስ" "ካሜራ" "ኦዲዮ ቅረጽ" - "ድምጽ አጫውት" + "ድምፅ አጫውት" "ቅንጥብ መለያ አንብብ" "ቅንጥብ መለያ ቀይር" "የሚዲያ አዝራሮች" "የድምጽ ትኩረት" - "ዋናው ድምጽ መቆጣጠሪያ" + "ዋናው ድምፅ መቆጣጠሪያ" "የድምጽ መጠን" - "የጥሪ ድምጽ መጠን" + "የጥሪ ድምፅ መጠን" "የማህደረ መረጃ መጠን" "የማንቂያ ድምፅ መጠን" - "የማሳወቂያ ድምጽ መጠን" + "የማሳወቂያ ድምፅ መጠን" "የብሉቱዝ ድምፅ መጠን" "እንደነቃ አቆይ" "አካባቢን ይቆጣጠሩ" @@ -250,7 +250,7 @@ "ማሳወቂያዎችን ይድረሱ" "ካሜራ" "ኦዲዮ ቅረጽ" - "ድምጽ አጫውት" + "ድምፅ አጫውት" "ቅንጥብ ሰሌዳ አንብብ" "ቅንጥብ መለያ ቀይር" "የሚዲያ አዝራሮች" @@ -260,7 +260,7 @@ "የጥሪ መጠን" "የማህደረ መረጃ ክፍልፍል" "የማንቂያ ድምፅ መጠን" - "የማሳወቂያ ድምጽ መጠን" + "የማሳወቂያ ድምፅ መጠን" "የብሉቱዝ ድምፅ መጠን" "እንደነቃ አቆይ" "አካባቢ" From 72e5a7ea214b42e125821791a514e977b7ba9d5a Mon Sep 17 00:00:00 2001 From: Shen Lin Date: Mon, 17 Oct 2022 09:28:30 +0800 Subject: [PATCH 03/10] Fix crash when showing error dialog in NetworkRequestDialogActivity Fix: 253913148 Test: manual Change-Id: I23593da1ef6d72806f22d3501b06a8e7abadb100 --- .../settings/wifi/NetworkRequestDialogActivity.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/wifi/NetworkRequestDialogActivity.java b/src/com/android/settings/wifi/NetworkRequestDialogActivity.java index 30f38d299e5..7b7d33fdce3 100644 --- a/src/com/android/settings/wifi/NetworkRequestDialogActivity.java +++ b/src/com/android/settings/wifi/NetworkRequestDialogActivity.java @@ -27,12 +27,14 @@ import android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback; import android.os.Bundle; import android.os.Handler; import android.os.HandlerExecutor; +import android.os.Looper; import android.os.Message; import android.widget.Toast; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; import com.android.settings.R; import com.android.settings.wifi.NetworkRequestErrorDialogFragment.ERROR_DIALOG_TYPE; @@ -143,7 +145,7 @@ public class NetworkRequestDialogActivity extends FragmentActivity implements super.onPause(); } - private final Handler mHandler = new Handler() { + private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -162,13 +164,17 @@ public class NetworkRequestDialogActivity extends FragmentActivity implements dismissDialogs(); // Throws error dialog. + final FragmentManager fragmentManager = getSupportFragmentManager(); + if (fragmentManager.isDestroyed() || fragmentManager.isStateSaved()) { + return; + } final NetworkRequestErrorDialogFragment dialogFragment = NetworkRequestErrorDialogFragment.newInstance(); dialogFragment.setRejectCallback(mUserSelectionCallback); final Bundle bundle = new Bundle(); bundle.putSerializable(NetworkRequestErrorDialogFragment.DIALOG_TYPE, type); dialogFragment.setArguments(bundle); - dialogFragment.show(getSupportFragmentManager(), TAG); + dialogFragment.show(fragmentManager, TAG); mShowingErrorDialog = true; } From 9ef3cf257299c3dcd2b2b3305739790aaf65b0f5 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Thu, 29 Sep 2022 14:56:30 -0400 Subject: [PATCH 04/10] Add link to more app settings Test: MoreSettingsPreferenceControllerTest Test: test app that can handle the settings intent Bug: 236932102 Change-Id: Iae778c93057e47aa0f664d3faab21640b2cd5193 --- res/values/strings.xml | 2 + ...notification_access_permission_details.xml | 6 ++ .../MoreSettingsPreferenceController.java | 79 +++++++++++++++ .../NotificationAccessDetails.java | 3 + .../MoreSettingsPreferenceControllerTest.java | 99 +++++++++++++++++++ 5 files changed, 189 insertions(+) create mode 100644 src/com/android/settings/applications/specialaccess/notificationaccess/MoreSettingsPreferenceController.java create mode 100644 tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/MoreSettingsPreferenceControllerTest.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 6ac39b88f40..6124cf9fb91 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -8017,6 +8017,8 @@ Change settings for each app that sends notifications Apps shown on device This app doesn\u2019t support enhanced settings + More settings + More settings are available inside this app VR helper services diff --git a/res/xml/notification_access_permission_details.xml b/res/xml/notification_access_permission_details.xml index 9867b6d4c0a..32a79e8ee36 100644 --- a/res/xml/notification_access_permission_details.xml +++ b/res/xml/notification_access_permission_details.xml @@ -64,6 +64,12 @@ settings:searchable="false" settings:controller="com.android.settings.applications.specialaccess.notificationaccess.BridgedAppsLinkPreferenceController" /> + + resolveInfos = mPm.queryIntentActivities( + mIntent, + PackageManager.ResolveInfoFlags.of(0)); + if (resolveInfos == null || resolveInfos.isEmpty()) { + return CONDITIONALLY_UNAVAILABLE; + } + return AVAILABLE; + } + + @Override + public String getPreferenceKey() { + return KEY_MORE_SETTINGS; + } + + public MoreSettingsPreferenceController setPackageManager(PackageManager pm) { + mPm = pm; + return this; + } + + public MoreSettingsPreferenceController setPackage(String pkg) { + mPackage = pkg; + mIntent.setPackage(mPackage); + return this; + } + + public void updateState(Preference preference) { + preference.setIntent(mIntent); + } +} diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java index e6feebb92ab..531fb228dcd 100644 --- a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java @@ -124,6 +124,9 @@ public class NotificationAccessDetails extends DashboardFragment { .setCn(mComponentName) .setUserId(mUserId) .setTargetSdk(listenerTargetSdk); + use(MoreSettingsPreferenceController.class) + .setPackage(mComponentName.getPackageName()) + .setPackageManager(mPm); final int finalListenerTargetSdk = listenerTargetSdk; getPreferenceControllers().forEach(controllers -> { controllers.forEach(controller -> { diff --git a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/MoreSettingsPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/MoreSettingsPreferenceControllerTest.java new file mode 100644 index 00000000000..503af46846d --- /dev/null +++ b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/MoreSettingsPreferenceControllerTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2020 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.applications.specialaccess.notificationaccess; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.service.notification.NotificationListenerService; + +import androidx.preference.Preference; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.google.common.collect.ImmutableList; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +public class MoreSettingsPreferenceControllerTest { + + Context mContext; + private MoreSettingsPreferenceController mController; + @Mock + PackageManager mPm; + final String mPkg = "pkg"; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = ApplicationProvider.getApplicationContext(); + + mController = new MoreSettingsPreferenceController(mContext); + mController.setPackage(mPkg); + mController.setPackageManager(mPm); + + } + + @Test + public void getAvailabilityStatus_available() { + ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); + when(mPm.queryIntentActivities(captor.capture(), any())).thenReturn( + ImmutableList.of(mock(ResolveInfo.class))); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + assertThat(captor.getValue().getPackage()).isEqualTo(mPkg); + assertThat(captor.getValue().getAction()).isEqualTo(Intent.ACTION_MAIN); + assertThat(captor.getValue().getCategories()).contains( + NotificationListenerService.INTENT_CATEGORY_SETTINGS_HOME); + } + + @Test + public void getAvailabilityStatus_notAvailable() { + ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); + when(mPm.queryIntentActivities(captor.capture(), any())).thenReturn(ImmutableList.of()); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void updateState() { + Preference preference = new Preference(mContext); + mController.updateState(preference); + + assertThat(preference.getIntent().getPackage()).isEqualTo(mPkg); + assertThat(preference.getIntent().getAction()).isEqualTo(Intent.ACTION_MAIN); + assertThat(preference.getIntent().getCategories()).contains( + NotificationListenerService.INTENT_CATEGORY_SETTINGS_HOME); + } +} From a911e61ce7e0153cc77d881fbb16f1bf0c2ad091 Mon Sep 17 00:00:00 2001 From: Shen Lin Date: Mon, 17 Oct 2022 13:06:07 +0800 Subject: [PATCH 05/10] Fix crash when input illegal proxy port number Bug: 253951250 Test: manual and atest Change-Id: I80d2f0e3ce2df86a081b29d8633e06cfc648baeb --- src/com/android/settings/vpn2/ConfigDialog.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/vpn2/ConfigDialog.java b/src/com/android/settings/vpn2/ConfigDialog.java index bf0dfc9c5b8..be2a68e237a 100644 --- a/src/com/android/settings/vpn2/ConfigDialog.java +++ b/src/com/android/settings/vpn2/ConfigDialog.java @@ -626,7 +626,14 @@ class ConfigDialog extends AlertDialog implements TextWatcher, String proxyPort = mProxyPort.getText().toString().trim(); // 0 is a last resort default, but the interface validates that the proxy port is // present and non-zero. - int port = proxyPort.isEmpty() ? 0 : Integer.parseInt(proxyPort); + int port = 0; + if (!proxyPort.isEmpty()) { + try { + port = Integer.parseInt(proxyPort); + } catch (NumberFormatException e) { + Log.e(TAG, "Could not parse proxy port integer ", e); + } + } profile.proxy = ProxyInfo.buildDirectProxy(proxyHost, port); } else { profile.proxy = null; From f41ae57f1741b16524f7bce66322dddd7f33bc5f Mon Sep 17 00:00:00 2001 From: Kuan Wang Date: Thu, 13 Oct 2022 17:32:10 +0800 Subject: [PATCH 06/10] Copy Battery Usage Database from SettingsIntelligence to Settings. Bug: 253395332 Test: make RunSettingsRoboTests + manually Change-Id: Ibdd2ace10d9e0893b3d96b345d563307b1890df6 --- Android.bp | 3 + .../batteryusage/db/BatteryState.java | 397 ++++++++++++++++++ .../batteryusage/db/BatteryStateDao.java | 63 +++ .../batteryusage/db/BatteryStateDatabase.java | 57 +++ .../batteryusage/db/BatteryStateDaoTest.java | 135 ++++++ .../batteryusage/db/BatteryStateTest.java | 98 +++++ .../settings/testutils/BatteryTestUtils.java | 72 ++++ 7 files changed, 825 insertions(+) create mode 100644 src/com/android/settings/fuelgauge/batteryusage/db/BatteryState.java create mode 100644 src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java create mode 100644 src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateTest.java diff --git a/Android.bp b/Android.bp index 9ddadaf00b1..f980bfa6581 100644 --- a/Android.bp +++ b/Android.bp @@ -90,8 +90,11 @@ android_library { "WifiTrackerLib", "SettingsLibActivityEmbedding", "Settings-change-ids", + "androidx.room_room-runtime", ], + plugins: ["androidx.room_room-compiler-plugin"], + libs: [ "telephony-common", "ims-common", diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryState.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryState.java new file mode 100644 index 00000000000..11db1185398 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryState.java @@ -0,0 +1,397 @@ +/* + * 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.fuelgauge.batteryusage.db; + +import android.content.ContentValues; +import android.content.Intent; +import android.os.BatteryManager; + +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; + +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/** A {@link Entity} class to save battery states snapshot into database. */ +@Entity +public class BatteryState { + private static String sCacheZoneId; + private static SimpleDateFormat sCacheSimpleDateFormat; + + @PrimaryKey(autoGenerate = true) + private long mId; + + // Records the app relative information. + public final long uid; + public final long userId; + public final String appLabel; + public final String packageName; + // Whether the data is represented as system component or not? + public final boolean isHidden; + // Records the timestamp relative information. + public final long bootTimestamp; + public final long timestamp; + public final String zoneId; + // Records the battery usage relative information. + public final double totalPower; + public final double consumePower; + public final double percentOfTotal; + public final long foregroundUsageTimeInMs; + public final long backgroundUsageTimeInMs; + public final int drainType; + public final int consumerType; + // Records the battery intent relative information. + public final int batteryLevel; + public final int batteryStatus; + public final int batteryHealth; + + public BatteryState( + long uid, + long userId, + String appLabel, + String packageName, + boolean isHidden, + long bootTimestamp, + long timestamp, + String zoneId, + double totalPower, + double consumePower, + double percentOfTotal, + long foregroundUsageTimeInMs, + long backgroundUsageTimeInMs, + int drainType, + int consumerType, + int batteryLevel, + int batteryStatus, + int batteryHealth) { + // Records the app relative information. + this.uid = uid; + this.userId = userId; + this.appLabel = appLabel; + this.packageName = packageName; + this.isHidden = isHidden; + // Records the timestamp relative information. + this.bootTimestamp = bootTimestamp; + this.timestamp = timestamp; + this.zoneId = zoneId; + // Records the battery usage relative information. + this.totalPower = totalPower; + this.consumePower = consumePower; + this.percentOfTotal = percentOfTotal; + this.foregroundUsageTimeInMs = foregroundUsageTimeInMs; + this.backgroundUsageTimeInMs = backgroundUsageTimeInMs; + this.drainType = drainType; + this.consumerType = consumerType; + // Records the battery intent relative information. + this.batteryLevel = batteryLevel; + this.batteryStatus = batteryStatus; + this.batteryHealth = batteryHealth; + } + + /** Sets the auto-generated content ID. */ + public void setId(long id) { + this.mId = id; + } + + /** Gets the auto-generated content ID. */ + public long getId() { + return mId; + } + + @Override + @SuppressWarnings("JavaUtilDate") + public String toString() { + final String currentZoneId = TimeZone.getDefault().getID(); + if (!currentZoneId.equals(sCacheZoneId) || sCacheSimpleDateFormat == null) { + sCacheZoneId = currentZoneId; + sCacheSimpleDateFormat = new SimpleDateFormat("MMM dd,yyyy HH:mm:ss", Locale.US); + } + final String recordAtDateTime = sCacheSimpleDateFormat.format(new Date(timestamp)); + final StringBuilder builder = new StringBuilder() + .append("\nBatteryState{") + .append(String.format(Locale.US, + "\n\tpackage=%s|label=%s|uid=%d|userId=%d|isHidden=%b", + packageName, appLabel, uid, userId, isHidden)) + .append(String.format(Locale.US, "\n\ttimestamp=%s|zoneId=%s|bootTimestamp=%d", + recordAtDateTime, zoneId, Duration.ofMillis(bootTimestamp).getSeconds())) + .append(String.format(Locale.US, + "\n\tusage=%f|total=%f|consume=%f|elapsedTime=%d|%d", + percentOfTotal, totalPower, consumePower, + Duration.ofMillis(foregroundUsageTimeInMs).getSeconds(), + Duration.ofMillis(backgroundUsageTimeInMs).getSeconds())) + .append(String.format(Locale.US, + "\n\tdrain=%d|consumer=%d", drainType, consumerType)) + .append(String.format(Locale.US, "\n\tbattery=%d|status=%d|health=%d\n}", + batteryLevel, batteryStatus, batteryHealth)); + return builder.toString(); + } + + + /** Creates new {@link BatteryState} from {@link ContentValues}. */ + public static BatteryState create(ContentValues contentValues) { + Builder builder = BatteryState.newBuilder(); + if (contentValues.containsKey("uid")) { + builder.setUid(contentValues.getAsLong("uid")); + } + if (contentValues.containsKey("userId")) { + builder.setUserId(contentValues.getAsLong("userId")); + } + if (contentValues.containsKey("appLabel")) { + builder.setAppLabel(contentValues.getAsString("appLabel")); + } + if (contentValues.containsKey("packageName")) { + builder.setPackageName(contentValues.getAsString("packageName")); + } + if (contentValues.containsKey("isHidden")) { + builder.setIsHidden(contentValues.getAsBoolean("isHidden")); + } + if (contentValues.containsKey("bootTimestamp")) { + builder.setBootTimestamp(contentValues.getAsLong("bootTimestamp")); + } + if (contentValues.containsKey("timestamp")) { + builder.setTimestamp(contentValues.getAsLong("timestamp")); + } + if (contentValues.containsKey("consumePower")) { + builder.setConsumePower(contentValues.getAsDouble("consumePower")); + } + if (contentValues.containsKey("totalPower")) { + builder.setTotalPower(contentValues.getAsDouble("totalPower")); + } + if (contentValues.containsKey("percentOfTotal")) { + builder.setPercentOfTotal(contentValues.getAsDouble("percentOfTotal")); + } + if (contentValues.containsKey("foregroundUsageTimeInMs")) { + builder.setForegroundUsageTimeInMs( + contentValues.getAsLong("foregroundUsageTimeInMs")); + } + if (contentValues.containsKey("backgroundUsageTimeInMs")) { + builder.setBackgroundUsageTimeInMs( + contentValues.getAsLong("backgroundUsageTimeInMs")); + } + if (contentValues.containsKey("drainType")) { + builder.setDrainType(contentValues.getAsInteger("drainType")); + } + if (contentValues.containsKey("consumerType")) { + builder.setConsumerType(contentValues.getAsInteger("consumerType")); + } + if (contentValues.containsKey("batteryLevel")) { + builder.setBatteryLevel(contentValues.getAsInteger("batteryLevel")); + } + if (contentValues.containsKey("batteryStatus")) { + builder.setBatteryStatus(contentValues.getAsInteger("batteryStatus")); + } + if (contentValues.containsKey("batteryHealth")) { + builder.setBatteryHealth(contentValues.getAsInteger("batteryHealth")); + } + return builder.build(); + } + + /** Creates a new {@link Builder} instance. */ + public static Builder newBuilder() { + return new Builder(); + } + + /** A convenience builder class to improve readability. */ + public static class Builder { + private long mUid; + private long mUserId; + private String mAppLabel; + private String mPackageName; + private boolean mIsHidden; + private long mBootTimestamp; + private long mTimestamp; + private double mTotalPower; + private double mConsumePower; + private double mPercentOfTotal; + private long mForegroundUsageTimeInMs; + private long mBackgroundUsageTimeInMs; + private int mDrainType; + private int mConsumerType; + private int mBatteryLevel; + private int mBatteryStatus; + private int mBatteryHealth; + + /** Sets the uid. */ + @CanIgnoreReturnValue + public Builder setUid(long uid) { + this.mUid = uid; + return this; + } + + /** Sets the user ID. */ + @CanIgnoreReturnValue + public Builder setUserId(long userId) { + this.mUserId = userId; + return this; + } + + /** Sets the app label. */ + @CanIgnoreReturnValue + public Builder setAppLabel(String appLabel) { + this.mAppLabel = appLabel; + return this; + } + + /** Sets the package name. */ + @CanIgnoreReturnValue + public Builder setPackageName(String packageName) { + this.mPackageName = packageName; + return this; + } + + /** Sets the is hidden value. */ + @CanIgnoreReturnValue + public Builder setIsHidden(boolean isHidden) { + this.mIsHidden = isHidden; + return this; + } + + /** Sets the boot timestamp. */ + @CanIgnoreReturnValue + public Builder setBootTimestamp(long bootTimestamp) { + this.mBootTimestamp = bootTimestamp; + return this; + } + + /** Sets the timestamp. */ + @CanIgnoreReturnValue + public Builder setTimestamp(long timestamp) { + this.mTimestamp = timestamp; + return this; + } + + /** Sets the total power. */ + @CanIgnoreReturnValue + public Builder setTotalPower(double totalPower) { + this.mTotalPower = totalPower; + return this; + } + + /** Sets the consumed power. */ + @CanIgnoreReturnValue + public Builder setConsumePower(double consumePower) { + this.mConsumePower = consumePower; + return this; + } + + /** Sets the percentage of total. */ + @CanIgnoreReturnValue + public Builder setPercentOfTotal(double percentOfTotal) { + this.mPercentOfTotal = percentOfTotal; + return this; + } + + /** Sets the foreground usage time. */ + @CanIgnoreReturnValue + public Builder setForegroundUsageTimeInMs(long foregroundUsageTimeInMs) { + this.mForegroundUsageTimeInMs = foregroundUsageTimeInMs; + return this; + } + + /** Sets the background usage time. */ + @CanIgnoreReturnValue + public Builder setBackgroundUsageTimeInMs(long backgroundUsageTimeInMs) { + this.mBackgroundUsageTimeInMs = backgroundUsageTimeInMs; + return this; + } + + /** Sets the drain type. */ + @CanIgnoreReturnValue + public Builder setDrainType(int drainType) { + this.mDrainType = drainType; + return this; + } + + /** Sets the consumer type. */ + @CanIgnoreReturnValue + public Builder setConsumerType(int consumerType) { + this.mConsumerType = consumerType; + return this; + } + + /** Sets the battery level. */ + @CanIgnoreReturnValue + public Builder setBatteryLevel(int batteryLevel) { + this.mBatteryLevel = batteryLevel; + return this; + } + + /** Sets the battery status. */ + @CanIgnoreReturnValue + public Builder setBatteryStatus(int batteryStatus) { + this.mBatteryStatus = batteryStatus; + return this; + } + + /** Sets the battery health. */ + @CanIgnoreReturnValue + public Builder setBatteryHealth(int batteryHealth) { + this.mBatteryHealth = batteryHealth; + return this; + } + + /** Sets the battery intent. */ + @CanIgnoreReturnValue + public Builder setBatteryIntent(Intent batteryIntent) { + final int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); + final int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0); + this.mBatteryLevel = + scale == 0 + ? -1 /*invalid battery level*/ + : Math.round((level / (float) scale) * 100f); + this.mBatteryStatus = + batteryIntent.getIntExtra( + BatteryManager.EXTRA_STATUS, + BatteryManager.BATTERY_STATUS_UNKNOWN); + this.mBatteryHealth = + batteryIntent.getIntExtra( + BatteryManager.EXTRA_HEALTH, + BatteryManager.BATTERY_HEALTH_UNKNOWN); + return this; + } + + /** Builds the BatteryState. */ + public BatteryState build() { + return new BatteryState( + mUid, + mUserId, + mAppLabel, + mPackageName, + mIsHidden, + mBootTimestamp, + mTimestamp, + /*zoneId=*/ TimeZone.getDefault().getID(), + mTotalPower, + mConsumePower, + mPercentOfTotal, + mForegroundUsageTimeInMs, + mBackgroundUsageTimeInMs, + mDrainType, + mConsumerType, + mBatteryLevel, + mBatteryStatus, + mBatteryHealth); + } + + private Builder() {} + } +} diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java new file mode 100644 index 00000000000..b1afa6b6e9a --- /dev/null +++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java @@ -0,0 +1,63 @@ +/* + * 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.fuelgauge.batteryusage.db; + +import android.database.Cursor; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.OnConflictStrategy; +import androidx.room.Query; + +import java.util.List; + +/** Data access object for accessing {@link BatteryState} in the database. */ +@Dao +public interface BatteryStateDao { + + /** Inserts a {@link BatteryState} data into the database. */ + @Insert(onConflict = OnConflictStrategy.REPLACE) + void insert(BatteryState state); + + /** Inserts {@link BatteryState} data into the database. */ + @Insert(onConflict = OnConflictStrategy.REPLACE) + void insertAll(List states); + + /** Lists all recorded data after a specific timestamp. */ + @Query("SELECT * FROM BatteryState WHERE timestamp > :timestamp ORDER BY timestamp DESC") + List getAllAfter(long timestamp); + + /** Gets the {@link Cursor} of all recorded data from a specific timestamp. */ + @Query("SELECT * FROM BatteryState WHERE timestamp >= :timestamp ORDER BY timestamp DESC") + Cursor getCursorAfter(long timestamp); + + /** Get the count of distinct timestamp after a specific timestamp. */ + @Query("SELECT COUNT(DISTINCT timestamp) FROM BatteryState WHERE timestamp > :timestamp") + int getDistinctTimestampCount(long timestamp); + + /** Lists all distinct timestamps after a specific timestamp. */ + @Query("SELECT DISTINCT timestamp FROM BatteryState WHERE timestamp > :timestamp") + List getDistinctTimestamps(long timestamp); + + /** Deletes all recorded data before a specific timestamp. */ + @Query("DELETE FROM BatteryState WHERE timestamp <= :timestamp") + void clearAllBefore(long timestamp); + + /** Clears all recorded data in the database. */ + @Query("DELETE FROM BatteryState") + void clearAll(); +} diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java new file mode 100644 index 00000000000..939654639a0 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java @@ -0,0 +1,57 @@ +/* + * 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.fuelgauge.batteryusage.db; + +import android.content.Context; +import android.util.Log; + +import androidx.room.Database; +import androidx.room.Room; +import androidx.room.RoomDatabase; + +/** A {@link RoomDatabase} for battery usage states history. */ +@Database( + entities = {BatteryState.class}, + version = 1) +public abstract class BatteryStateDatabase extends RoomDatabase { + private static final String TAG = "BatteryStateDatabase"; + + private static BatteryStateDatabase sBatteryStateDatabase; + + /** Provides DAO for battery state table. */ + public abstract BatteryStateDao batteryStateDao(); + + /** Gets or creates an instance of {@link RoomDatabase}. */ + public static BatteryStateDatabase getInstance(Context context) { + if (sBatteryStateDatabase == null) { + sBatteryStateDatabase = + Room.databaseBuilder( + context, BatteryStateDatabase.class, "battery-usage-db-v1") + // Allows accessing data in the main thread for dumping bugreport. + .allowMainThreadQueries() + .fallbackToDestructiveMigration() + .build(); + Log.d(TAG, "initialize battery states database"); + } + return sBatteryStateDatabase; + } + + /** Sets the instance of {@link RoomDatabase}. */ + public static void setBatteryStateDatabase(BatteryStateDatabase database) { + BatteryStateDatabase.sBatteryStateDatabase = database; + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java new file mode 100644 index 00000000000..41e3f4df1c5 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java @@ -0,0 +1,135 @@ +/* + * 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.fuelgauge.batteryusage.db; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.database.Cursor; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.testutils.BatteryTestUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import java.util.List; + +/** Tests for {@link BatteryStateDao}. */ +@RunWith(RobolectricTestRunner.class) +public final class BatteryStateDaoTest { + private static final int CURSOR_COLUMN_SIZE = 19; + private static final long TIMESTAMP1 = System.currentTimeMillis(); + private static final long TIMESTAMP2 = System.currentTimeMillis() + 2; + private static final long TIMESTAMP3 = System.currentTimeMillis() + 4; + private static final String PACKAGE_NAME1 = "com.android.apps.settings"; + private static final String PACKAGE_NAME2 = "com.android.apps.calendar"; + private static final String PACKAGE_NAME3 = "com.android.apps.gmail"; + + private Context mContext; + private BatteryStateDatabase mDatabase; + private BatteryStateDao mBatteryStateDao; + + @Before + public void setUp() { + mContext = ApplicationProvider.getApplicationContext(); + mDatabase = BatteryTestUtils.setUpBatteryStateDatabase(mContext); + mBatteryStateDao = mDatabase.batteryStateDao(); + BatteryTestUtils.insertDataToBatteryStateDatabase(mContext, TIMESTAMP3, PACKAGE_NAME3); + BatteryTestUtils.insertDataToBatteryStateDatabase(mContext, TIMESTAMP2, PACKAGE_NAME2); + BatteryTestUtils.insertDataToBatteryStateDatabase( + mContext, TIMESTAMP1, PACKAGE_NAME1, /*multiple=*/ true); + } + + @After + public void closeDb() { + mDatabase.close(); + BatteryStateDatabase.setBatteryStateDatabase(/*database=*/ null); + } + + @Test + public void batteryStateDao_insertAll() throws Exception { + final List states = mBatteryStateDao.getAllAfter(TIMESTAMP1); + assertThat(states).hasSize(2); + // Verifies the queried battery states. + assertBatteryState(states.get(0), TIMESTAMP3, PACKAGE_NAME3); + assertBatteryState(states.get(1), TIMESTAMP2, PACKAGE_NAME2); + } + + @Test + public void batteryStateDao_getCursorAfter() throws Exception { + final Cursor cursor = mBatteryStateDao.getCursorAfter(TIMESTAMP2); + assertThat(cursor.getCount()).isEqualTo(2); + assertThat(cursor.getColumnCount()).isEqualTo(CURSOR_COLUMN_SIZE); + // Verifies the queried first battery state. + cursor.moveToFirst(); + assertThat(cursor.getString(4 /*packageName*/)).isEqualTo(PACKAGE_NAME3); + // Verifies the queried second battery state. + cursor.moveToNext(); + assertThat(cursor.getString(4 /*packageName*/)).isEqualTo(PACKAGE_NAME2); + } + + @Test + public void batteryStateDao_clearAllBefore() throws Exception { + mBatteryStateDao.clearAllBefore(TIMESTAMP2); + + final List states = mBatteryStateDao.getAllAfter(0); + assertThat(states).hasSize(1); + // Verifies the queried battery state. + assertBatteryState(states.get(0), TIMESTAMP3, PACKAGE_NAME3); + } + + @Test + public void batteryStateDao_clearAll() throws Exception { + assertThat(mBatteryStateDao.getAllAfter(0)).hasSize(3); + mBatteryStateDao.clearAll(); + assertThat(mBatteryStateDao.getAllAfter(0)).isEmpty(); + } + + @Test + public void getInstance_createNewInstance() throws Exception { + BatteryStateDatabase.setBatteryStateDatabase(/*database=*/ null); + assertThat(BatteryStateDatabase.getInstance(mContext)).isNotNull(); + } + + @Test + public void getDistinctTimestampCount_returnsExpectedResult() { + assertThat(mBatteryStateDao.getDistinctTimestampCount(/*timestamp=*/ 0)) + .isEqualTo(3); + assertThat(mBatteryStateDao.getDistinctTimestampCount(TIMESTAMP1)) + .isEqualTo(2); + } + + @Test + public void getDistinctTimestamps_returnsExpectedResult() { + final List timestamps = + mBatteryStateDao.getDistinctTimestamps(/*timestamp=*/ 0); + + assertThat(timestamps).hasSize(3); + assertThat(timestamps).containsExactly(TIMESTAMP1, TIMESTAMP2, TIMESTAMP3); + } + + private static void assertBatteryState( + BatteryState state, long timestamp, String packageName) { + assertThat(state.timestamp).isEqualTo(timestamp); + assertThat(state.packageName).isEqualTo(packageName); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateTest.java new file mode 100644 index 00000000000..ef23c410d0c --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateTest.java @@ -0,0 +1,98 @@ +/* + * 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.fuelgauge.batteryusage.db; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Intent; +import android.os.BatteryManager; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link BatteryState}. */ +@RunWith(RobolectricTestRunner.class) +public final class BatteryStateTest { + private static final int BATTERY_LEVEL = 45; + private static final int BATTERY_STATUS = BatteryManager.BATTERY_STATUS_FULL; + private static final int BATTERY_HEALTH = BatteryManager.BATTERY_HEALTH_COLD; + + private Intent mBatteryIntent; + + @Before + public void setUp() { + mBatteryIntent = new Intent(Intent.ACTION_BATTERY_CHANGED); + // Inserts the battery states into intent. + mBatteryIntent.putExtra(BatteryManager.EXTRA_LEVEL, BATTERY_LEVEL); + mBatteryIntent.putExtra(BatteryManager.EXTRA_STATUS, BATTERY_STATUS); + mBatteryIntent.putExtra(BatteryManager.EXTRA_HEALTH, BATTERY_HEALTH); + } + + @Test + public void testBuilder_returnsExpectedResult() { + mBatteryIntent.putExtra(BatteryManager.EXTRA_SCALE, 100); + BatteryState state = create(mBatteryIntent); + + // Verifies the app relative information. + assertThat(state.uid).isEqualTo(1001L); + assertThat(state.userId).isEqualTo(100L); + assertThat(state.appLabel).isEqualTo("Settings"); + assertThat(state.packageName).isEqualTo("com.android.settings"); + assertThat(state.isHidden).isTrue(); + assertThat(state.bootTimestamp).isEqualTo(101L); + assertThat(state.timestamp).isEqualTo(100001L); + // Verifies the battery relative information. + assertThat(state.totalPower).isEqualTo(100); + assertThat(state.consumePower).isEqualTo(3); + assertThat(state.percentOfTotal).isEqualTo(10); + assertThat(state.foregroundUsageTimeInMs).isEqualTo(60000); + assertThat(state.backgroundUsageTimeInMs).isEqualTo(10000); + assertThat(state.drainType).isEqualTo(1); + assertThat(state.consumerType).isEqualTo(2); + assertThat(state.batteryLevel).isEqualTo(BATTERY_LEVEL); + assertThat(state.batteryStatus).isEqualTo(BATTERY_STATUS); + assertThat(state.batteryHealth).isEqualTo(BATTERY_HEALTH); + } + + @Test + public void create_withoutBatteryScale_returnsStateWithInvalidLevel() { + BatteryState state = create(mBatteryIntent); + assertThat(state.batteryLevel).isEqualTo(-1); + } + + private static BatteryState create(Intent intent) { + return BatteryState.newBuilder() + .setUid(1001L) + .setUserId(100L) + .setAppLabel("Settings") + .setPackageName("com.android.settings") + .setIsHidden(true) + .setBootTimestamp(101L) + .setTimestamp(100001L) + .setTotalPower(100f) + .setConsumePower(3f) + .setPercentOfTotal(10f) + .setForegroundUsageTimeInMs(60000) + .setBackgroundUsageTimeInMs(10000) + .setDrainType(1) + .setConsumerType(2) + .setBatteryIntent(intent) + .build(); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java index e4e26d2c531..fa3ee10cc86 100644 --- a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java +++ b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java @@ -16,8 +16,21 @@ package com.android.settings.testutils; +import android.content.Context; import android.content.Intent; import android.os.BatteryManager; +import android.os.UserManager; + +import androidx.room.Room; + +import com.android.settings.fuelgauge.batteryusage.db.BatteryState; +import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDao; +import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase; + +import com.google.common.collect.ImmutableList; + +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowUserManager; public class BatteryTestUtils { @@ -37,6 +50,65 @@ public class BatteryTestUtils { BatteryManager.BATTERY_STATUS_DISCHARGING); } + /** Sets the work profile mode. */ + public static void setWorkProfile(Context context) { + final UserManager userManager = context.getSystemService(UserManager.class); + Shadows.shadowOf(userManager).setManagedProfile(true); + // Changes out of the default system user so isSystemUser() returns false. + final int userId = 1001; + Shadows.shadowOf(userManager) + .addUser(userId, "name", /*flags=*/ ShadowUserManager.FLAG_PRIMARY); + Shadows.shadowOf(userManager).switchUser(userId); + } + + /** Creates and sets up the in-memory {@link BatteryStateDatabase}. */ + public static BatteryStateDatabase setUpBatteryStateDatabase(Context context) { + final BatteryStateDatabase inMemoryDatabase = + Room.inMemoryDatabaseBuilder(context, BatteryStateDatabase.class) + .allowMainThreadQueries() + .build(); + BatteryStateDatabase.setBatteryStateDatabase(inMemoryDatabase); + return inMemoryDatabase; + } + + /** Inserts a fake data into the database for testing. */ + public static void insertDataToBatteryStateDatabase( + Context context, long timestamp, String packageName) { + insertDataToBatteryStateDatabase(context, timestamp, packageName, /*multiple=*/ false); + } + + /** Inserts a fake data into the database for testing. */ + public static void insertDataToBatteryStateDatabase( + Context context, long timestamp, String packageName, boolean multiple) { + final BatteryState state = + new BatteryState( + /*uid=*/ 1001L, + /*userId=*/ 100L, + /*appLabel=*/ "Settings", + packageName, + /*isHidden=*/ true, + /*bootTimestamp=*/ timestamp - 1, + timestamp, + /*zoneId=*/ "Europe/Paris", + /*totalPower=*/ 100f, + /*consumePower=*/ 0.3f, + /*percentOfTotal=*/ 10f, + /*foregroundUsageTimeInMs=*/ 60000, + /*backgroundUsageTimeInMs=*/ 10000, + /*drainType=*/ 1, + /*consumerType=*/ 2, + /*batteryLevel=*/ 31, + /*batteryStatus=*/ 0, + /*batteryHealth=*/ 0); + BatteryStateDao dao = + BatteryStateDatabase.getInstance(context).batteryStateDao(); + if (multiple) { + dao.insertAll(ImmutableList.of(state)); + } else { + dao.insert(state); + } + } + private static Intent getCustomBatteryIntent(int plugged, int level, int scale, int status) { Intent intent = new Intent(); intent.putExtra(BatteryManager.EXTRA_PLUGGED, plugged); From 0a0ba915ca67260d9d5162c6d776ca696b1bb3a5 Mon Sep 17 00:00:00 2001 From: Kuan Wang Date: Thu, 13 Oct 2022 18:07:54 +0800 Subject: [PATCH 07/10] Copy BatterySettingsContentProvider from SettingsIntelligence to Settings and rename it to BatteryUsageContentProvider. Bug: 253395332 Test: make RunSettingsRoboTests + manually Change-Id: Ib6c2240cd065f6e29393e13b7231bba7afd2d850 --- AndroidManifest.xml | 12 + .../BatteryUsageContentProvider.java | 168 ++++++++++ .../fuelgauge/batteryusage/DatabaseUtils.java | 36 ++ .../BatteryUsageContentProviderTest.java | 311 ++++++++++++++++++ .../android/settings/testutils/FakeClock.java | 54 +++ 5 files changed, 581 insertions(+) create mode 100644 src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java create mode 100644 src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java create mode 100644 tests/robotests/src/com/android/settings/testutils/FakeClock.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f5da15fe711..935c1a47f0d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -6,6 +6,11 @@ + + + @@ -2961,6 +2966,13 @@ android:value="@string/menu_key_battery"/> + + + mProvider.query( + uri, /*strings=*/ null, /*s=*/ null, /*strings1=*/ null, + /*s1=*/ null)); + assertThrows( + IllegalArgumentException.class, + () -> mProvider.insert(uri, /*contentValues=*/ null)); + } + + @Test + public void queryAndInsert_incorrectAuthority_throwsIllegalArgumentException() { + final Uri.Builder builder = + new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(DatabaseUtils.AUTHORITY + ".debug") + .appendPath(DatabaseUtils.BATTERY_STATE_TABLE); + final Uri uri = builder.build(); + mProvider.onCreate(); + + assertThrows( + IllegalArgumentException.class, + () -> + mProvider.query( + uri, /*strings=*/ null, /*s=*/ null, /*strings1=*/ null, + /*s1=*/ null)); + assertThrows( + IllegalArgumentException.class, + () -> mProvider.insert(uri, /*contentValues=*/ null)); + } + + @Test + public void query_batteryState_returnsExpectedResult() throws Exception { + mProvider.onCreate(); + final Duration currentTime = Duration.ofHours(52); + final long expiredTimeCutoff = currentTime.toMillis() + - BatteryUsageContentProvider.QUERY_DURATION_HOURS.toMillis(); + testQueryBatteryState(currentTime, expiredTimeCutoff, /*hasQueryTimestamp=*/ false); + } + + @Test + public void query_batteryStateTimestamp_returnsExpectedResult() throws Exception { + mProvider.onCreate(); + final Duration currentTime = Duration.ofHours(52); + final long expiredTimeCutoff = currentTime.toMillis() - Duration.ofHours(10).toMillis(); + testQueryBatteryState(currentTime, expiredTimeCutoff, /*hasQueryTimestamp=*/ true); + } + + @Test + public void query_incorrectParameterFormat_returnsExpectedResult() throws Exception { + mProvider.onCreate(); + final Duration currentTime = Duration.ofHours(52); + final long expiredTimeCutoff = + currentTime.toMillis() + - BatteryUsageContentProvider.QUERY_DURATION_HOURS.toMillis(); + testQueryBatteryState( + currentTime, + expiredTimeCutoff, + /*hasQueryTimestamp=*/ false, + /*customParameter=*/ "invalid number format"); + } + + @Test + public void insert_batteryState_returnsExpectedResult() { + mProvider.onCreate(); + ContentValues values = new ContentValues(); + values.put("uid", Long.valueOf(101L)); + values.put("userId", Long.valueOf(1001L)); + values.put("appLabel", new String("Settings")); + values.put("packageName", new String("com.android.settings")); + values.put("timestamp", Long.valueOf(2100021L)); + values.put("isHidden", Boolean.valueOf(true)); + values.put("totalPower", Double.valueOf(99.0)); + values.put("consumePower", Double.valueOf(9.0)); + values.put("percentOfTotal", Double.valueOf(0.9)); + values.put("foregroundUsageTimeInMs", Long.valueOf(1000)); + values.put("backgroundUsageTimeInMs", Long.valueOf(2000)); + values.put("drainType", Integer.valueOf(1)); + values.put("consumerType", Integer.valueOf(2)); + values.put("batteryLevel", Integer.valueOf(51)); + values.put("batteryStatus", Integer.valueOf(2)); + values.put("batteryHealth", Integer.valueOf(3)); + + final Uri uri = mProvider.insert(VALID_BATTERY_STATE_CONTENT_URI, values); + + assertThat(uri).isEqualTo(VALID_BATTERY_STATE_CONTENT_URI); + // Verifies the BatteryState content. + final List states = + BatteryStateDatabase.getInstance(mContext).batteryStateDao().getAllAfter(0); + assertThat(states).hasSize(1); + assertThat(states.get(0).uid).isEqualTo(101L); + assertThat(states.get(0).userId).isEqualTo(1001L); + assertThat(states.get(0).appLabel).isEqualTo("Settings"); + assertThat(states.get(0).packageName).isEqualTo("com.android.settings"); + assertThat(states.get(0).isHidden).isTrue(); + assertThat(states.get(0).timestamp).isEqualTo(2100021L); + assertThat(states.get(0).totalPower).isEqualTo(99.0); + assertThat(states.get(0).consumePower).isEqualTo(9.0); + assertThat(states.get(0).percentOfTotal).isEqualTo(0.9); + assertThat(states.get(0).foregroundUsageTimeInMs).isEqualTo(1000); + assertThat(states.get(0).backgroundUsageTimeInMs).isEqualTo(2000); + assertThat(states.get(0).drainType).isEqualTo(1); + assertThat(states.get(0).consumerType).isEqualTo(2); + assertThat(states.get(0).batteryLevel).isEqualTo(51); + assertThat(states.get(0).batteryStatus).isEqualTo(2); + assertThat(states.get(0).batteryHealth).isEqualTo(3); + } + + @Test + public void insert_partialFieldsContentValues_returnsExpectedResult() { + mProvider.onCreate(); + final ContentValues values = new ContentValues(); + values.put("packageName", new String("fake_data")); + values.put("timestamp", Long.valueOf(2100022L)); + values.put("batteryLevel", Integer.valueOf(52)); + values.put("batteryStatus", Integer.valueOf(3)); + values.put("batteryHealth", Integer.valueOf(2)); + + final Uri uri = mProvider.insert(VALID_BATTERY_STATE_CONTENT_URI, values); + + assertThat(uri).isEqualTo(VALID_BATTERY_STATE_CONTENT_URI); + // Verifies the BatteryState content. + final List states = + BatteryStateDatabase.getInstance(mContext).batteryStateDao().getAllAfter(0); + assertThat(states).hasSize(1); + assertThat(states.get(0).packageName).isEqualTo("fake_data"); + assertThat(states.get(0).timestamp).isEqualTo(2100022L); + assertThat(states.get(0).batteryLevel).isEqualTo(52); + assertThat(states.get(0).batteryStatus).isEqualTo(3); + assertThat(states.get(0).batteryHealth).isEqualTo(2); + } + + @Test + public void delete_throwsUnsupportedOperationException() { + assertThrows( + UnsupportedOperationException.class, + () -> mProvider.delete(/*uri=*/ null, /*s=*/ null, /*strings=*/ null)); + } + + @Test + public void update_throwsUnsupportedOperationException() { + assertThrows( + UnsupportedOperationException.class, + () -> + mProvider.update( + /*uri=*/ null, /*contentValues=*/ null, /*s=*/ null, + /*strings=*/ null)); + } + + private void testQueryBatteryState( + Duration currentTime, long expiredTimeCutoff, boolean hasQueryTimestamp) + throws Exception { + testQueryBatteryState(currentTime, expiredTimeCutoff, hasQueryTimestamp, null); + } + + private void testQueryBatteryState( + Duration currentTime, + long expiredTimeCutoff, + boolean hasQueryTimestamp, + String customParameter) + throws Exception { + mProvider.onCreate(); + final FakeClock fakeClock = new FakeClock(); + fakeClock.setCurrentTime(currentTime); + mProvider.setClock(fakeClock); + // Inserts some expired testing data. + BatteryTestUtils.insertDataToBatteryStateDatabase( + mContext, expiredTimeCutoff - 1, "com.android.sysui1"); + BatteryTestUtils.insertDataToBatteryStateDatabase( + mContext, expiredTimeCutoff - 2, "com.android.sysui2"); + BatteryTestUtils.insertDataToBatteryStateDatabase( + mContext, expiredTimeCutoff - 3, "com.android.sysui3"); + // Inserts some valid testing data. + final String packageName1 = "com.android.settings1"; + final String packageName2 = "com.android.settings2"; + final String packageName3 = "com.android.settings3"; + BatteryTestUtils.insertDataToBatteryStateDatabase( + mContext, currentTime.toMillis(), packageName1); + BatteryTestUtils.insertDataToBatteryStateDatabase( + mContext, expiredTimeCutoff + 2, packageName2); + BatteryTestUtils.insertDataToBatteryStateDatabase( + mContext, expiredTimeCutoff, packageName3); + + final Uri.Builder builder = + new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(DatabaseUtils.AUTHORITY) + .appendPath(DatabaseUtils.BATTERY_STATE_TABLE); + if (customParameter != null) { + builder.appendQueryParameter( + BatteryUsageContentProvider.QUERY_KEY_TIMESTAMP, customParameter); + } else if (hasQueryTimestamp) { + builder.appendQueryParameter( + BatteryUsageContentProvider.QUERY_KEY_TIMESTAMP, + Long.toString(expiredTimeCutoff)); + } + final Uri batteryStateQueryContentUri = builder.build(); + + final Cursor cursor = + mProvider.query( + batteryStateQueryContentUri, + /*strings=*/ null, + /*s=*/ null, + /*strings1=*/ null, + /*s1=*/ null); + + // Verifies the result not include expired data. + assertThat(cursor.getCount()).isEqualTo(3); + final int packageNameIndex = cursor.getColumnIndex("packageName"); + // Verifies the first data package name. + cursor.moveToFirst(); + final String actualPackageName1 = cursor.getString(packageNameIndex); + assertThat(actualPackageName1).isEqualTo(packageName1); + // Verifies the second data package name. + cursor.moveToNext(); + final String actualPackageName2 = cursor.getString(packageNameIndex); + assertThat(actualPackageName2).isEqualTo(packageName2); + // Verifies the third data package name. + cursor.moveToNext(); + final String actualPackageName3 = cursor.getString(packageNameIndex); + assertThat(actualPackageName3).isEqualTo(packageName3); + cursor.close(); + // TODO: add verification for recheck broadcast. + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/FakeClock.java b/tests/robotests/src/com/android/settings/testutils/FakeClock.java new file mode 100644 index 00000000000..946d0515340 --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/FakeClock.java @@ -0,0 +1,54 @@ +/* + * 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.testutils; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; + +/** A fake {@link Clock} class for testing. */ +public final class FakeClock extends Clock { + private long mCurrentTimeMillis; + + public FakeClock() {} + + /** Sets the time in millis for {@link Clock#millis()} method. */ + public void setCurrentTime(Duration duration) { + mCurrentTimeMillis = duration.toMillis(); + } + + @Override + public ZoneId getZone() { + throw new UnsupportedOperationException("unsupported!"); + } + + @Override + public Clock withZone(ZoneId zone) { + throw new UnsupportedOperationException("unsupported!"); + } + + @Override + public Instant instant() { + throw new UnsupportedOperationException("unsupported!"); + } + + @Override + public long millis() { + return mCurrentTimeMillis; + } +} From 50f229903e87277e8d3e35f28559333773df023a Mon Sep 17 00:00:00 2001 From: Xuanang Zhao Date: Tue, 18 Oct 2022 14:20:57 +0800 Subject: [PATCH 08/10] Clean up ignored tests in Settings Slices provider. Change-Id: Iad9102a5d23333d64976e0697f9438bccc77af0a Bug: 253386051 --- .../slices/SettingsSliceProviderTest.java | 64 +------------------ 1 file changed, 1 insertion(+), 63 deletions(-) diff --git a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java index 9043974d72f..c3b7821a07d 100644 --- a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java +++ b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java @@ -61,12 +61,9 @@ import com.android.settings.testutils.shadow.ShadowLockPatternUtils; import com.android.settings.testutils.shadow.ShadowThreadUtils; import com.android.settings.testutils.shadow.ShadowUserManager; import com.android.settings.testutils.shadow.ShadowUtils; -import com.android.settings.wifi.slice.WifiScanWorker; -import com.android.settingslib.wifi.WifiTracker; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -100,7 +97,6 @@ import java.util.Set; @Config(shadows = {ShadowUserManager.class, ShadowUtils.class, SlicesDatabaseAccessorTest.ShadowApplicationPackageManager.class, ShadowBluetoothAdapter.class, ShadowLockPatternUtils.class, - SettingsSliceProviderTest.ShadowWifiScanWorker.class, SettingsSliceProviderTest.ShadowTheme.class}) public class SettingsSliceProviderTest { @@ -123,6 +119,7 @@ public class SettingsSliceProviderTest { private Context mContext; private SettingsSliceProvider mProvider; private ShadowPackageManager mPackageManager; + @Mock private SliceManager mManager; @@ -572,14 +569,6 @@ public class SettingsSliceProviderTest { assertThat(pendingIntent).isEqualTo(settingsPendingIntent); } - @Test - @Ignore - public void bindSlice_wifiSlice_returnsWifiSlice() { - final Slice wifiSlice = mProvider.onBindSlice(CustomSliceRegistry.WIFI_SLICE_URI); - - assertThat(wifiSlice.getUri()).isEqualTo(CustomSliceRegistry.WIFI_SLICE_URI); - } - @Test public void bindSlice_flashlightSlice_returnsFlashlightSlice() { Settings.Secure.putInt( @@ -603,32 +592,6 @@ public class SettingsSliceProviderTest { mProvider.onSlicePinned(uri); } - @Test - @Ignore - public void onSlicePinned_backgroundWorker_started() { - mProvider.onSlicePinned(CustomSliceRegistry.WIFI_SLICE_URI); - - verify(ShadowWifiScanWorker.getWifiTracker()).onStart(); - } - - @Test - @Ignore - public void onSlicePinned_backgroundWorker_stopped() { - mProvider.onSlicePinned(CustomSliceRegistry.WIFI_SLICE_URI); - mProvider.onSliceUnpinned(CustomSliceRegistry.WIFI_SLICE_URI); - - verify(ShadowWifiScanWorker.getWifiTracker()).onStop(); - } - - @Test - @Ignore - public void shutdown_backgroundWorker_closed() { - mProvider.onSlicePinned(CustomSliceRegistry.WIFI_SLICE_URI); - mProvider.shutdown(); - - verify(ShadowWifiScanWorker.getWifiTracker()).onDestroy(); - } - @Test @Config(qualifiers = "mcc998") public void grantAllowlistedPackagePermissions_noAllowlist_shouldNotGrant() { @@ -716,31 +679,6 @@ public class SettingsSliceProviderTest { .build(); } - @Implements(WifiScanWorker.class) - public static class ShadowWifiScanWorker { - private static WifiTracker mWifiTracker; - - @Implementation - protected void onSlicePinned() { - mWifiTracker = mock(WifiTracker.class); - mWifiTracker.onStart(); - } - - @Implementation - protected void onSliceUnpinned() { - mWifiTracker.onStop(); - } - - @Implementation - protected void close() { - mWifiTracker.onDestroy(); - } - - static WifiTracker getWifiTracker() { - return mWifiTracker; - } - } - @Implements(value = StrictMode.class) public static class ShadowStrictMode { From bd2ee1e20ad6db9351129eb4bbdf99f0ca67d16a Mon Sep 17 00:00:00 2001 From: Tsung-Mao Fang Date: Tue, 18 Oct 2022 16:01:16 +0800 Subject: [PATCH 09/10] Add indexable setting items When work profile is supported on the device, we should support work profile releated setting items. Fix: 238055360 Test: Manual Change-Id: I5b952f958d0c5fc0f7b22838e1f7cfc36239a22e --- res/xml/privacy_dashboard_settings.xml | 6 ++---- .../privacy/PrivacyDashboardFragment.java | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/res/xml/privacy_dashboard_settings.xml b/res/xml/privacy_dashboard_settings.xml index f8f916e916a..4abdeda79ea 100644 --- a/res/xml/privacy_dashboard_settings.xml +++ b/res/xml/privacy_dashboard_settings.xml @@ -86,8 +86,7 @@ + android:summary="@string/summary_placeholder"/> + android:order="999"/> diff --git a/src/com/android/settings/privacy/PrivacyDashboardFragment.java b/src/com/android/settings/privacy/PrivacyDashboardFragment.java index 75ed225e3a6..46a05b06109 100644 --- a/src/com/android/settings/privacy/PrivacyDashboardFragment.java +++ b/src/com/android/settings/privacy/PrivacyDashboardFragment.java @@ -25,9 +25,12 @@ import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROF import android.app.settings.SettingsEnums; import android.content.Context; import android.os.Bundle; +import android.os.UserHandle; +import android.os.UserManager; import android.provider.SearchIndexableResource; import com.android.settings.R; +import com.android.settings.Utils; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.notification.LockScreenNotificationPreferenceController; import com.android.settings.safetycenter.SafetyCenterManagerWrapper; @@ -131,5 +134,21 @@ public class PrivacyDashboardFragment extends DashboardFragment { Context context) { return buildPreferenceControllers(context, null); } + + @Override + public List getNonIndexableKeys(Context context) { + final List keys = super.getNonIndexableKeys(context); + final int profileUserId = + Utils.getManagedProfileId( + UserManager.get(context), UserHandle.myUserId()); + // If work profile is supported, we should keep the search result. + if (profileUserId != UserHandle.USER_NULL) { + return keys; + } + + // Otherwise, we should hide the search result. + keys.add(KEY_NOTIFICATION_WORK_PROFILE_NOTIFICATIONS); + return keys; + } }; } From 51f838ba4bc4bdac61419c71fc9e6ff2f470341e Mon Sep 17 00:00:00 2001 From: Lucas Silva Date: Mon, 17 Oct 2022 22:14:34 -0400 Subject: [PATCH 10/10] Update dream summary when dream suppressed by bedtime mode Bug: 246472225 Test: enabled bedtime mode, verified settings summary updated Test: make -j64 RunSettingsRoboTests ROBOTEST_FILTER="com.android.settings.dream.WhenToDreamPreferenceControllerTest" Change-Id: I4de12f636975b1bdf36898f7a530fde2f55644d4 --- res/values/strings.xml | 2 + .../ScreenSaverPreferenceController.java | 11 +++- .../WhenToDreamPreferenceController.java | 22 +++++++- .../WhenToDreamPreferenceControllerTest.java | 55 ++++++++++++++++--- 4 files changed, 80 insertions(+), 10 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 458f6fc40d1..818689d0fc4 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -3359,6 +3359,8 @@ Screen saver screensaver + + Unavailable because bedtime mode is on Use screen saver diff --git a/src/com/android/settings/display/ScreenSaverPreferenceController.java b/src/com/android/settings/display/ScreenSaverPreferenceController.java index 676a567f202..de1aaea0bd8 100644 --- a/src/com/android/settings/display/ScreenSaverPreferenceController.java +++ b/src/com/android/settings/display/ScreenSaverPreferenceController.java @@ -18,6 +18,7 @@ import android.os.UserManager; import androidx.preference.Preference; +import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.dream.DreamSettings; import com.android.settingslib.core.AbstractPreferenceController; @@ -26,9 +27,12 @@ public class ScreenSaverPreferenceController extends AbstractPreferenceControlle PreferenceControllerMixin { private static final String KEY_SCREEN_SAVER = "screensaver"; + private final boolean mDreamsDisabledByAmbientModeSuppression; public ScreenSaverPreferenceController(Context context) { super(context); + mDreamsDisabledByAmbientModeSuppression = context.getResources().getBoolean( + com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig); } @Override @@ -47,7 +51,12 @@ public class ScreenSaverPreferenceController extends AbstractPreferenceControlle @Override public void updateState(Preference preference) { - preference.setSummary(DreamSettings.getSummaryTextWithDreamName(mContext)); + if (mDreamsDisabledByAmbientModeSuppression + && AmbientDisplayAlwaysOnPreferenceController.isAodSuppressedByBedtime(mContext)) { + preference.setSummary(R.string.screensaver_settings_when_to_dream_bedtime); + } else { + preference.setSummary(DreamSettings.getSummaryTextWithDreamName(mContext)); + } } private boolean isSystemUser() { diff --git a/src/com/android/settings/dream/WhenToDreamPreferenceController.java b/src/com/android/settings/dream/WhenToDreamPreferenceController.java index 4108e850538..02ae6a75f39 100644 --- a/src/com/android/settings/dream/WhenToDreamPreferenceController.java +++ b/src/com/android/settings/dream/WhenToDreamPreferenceController.java @@ -20,7 +20,10 @@ import android.content.Context; import androidx.preference.Preference; +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.display.AmbientDisplayAlwaysOnPreferenceController; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.dream.DreamBackend; @@ -29,19 +32,34 @@ public class WhenToDreamPreferenceController extends AbstractPreferenceControlle private static final String WHEN_TO_START = "when_to_start"; private final DreamBackend mBackend; + private final boolean mDreamsDisabledByAmbientModeSuppression; WhenToDreamPreferenceController(Context context) { + this(context, context.getResources().getBoolean( + com.android.internal.R.bool.config_dreamsDisabledByAmbientModeSuppressionConfig)); + } + + @VisibleForTesting + WhenToDreamPreferenceController(Context context, + boolean dreamsDisabledByAmbientModeSuppression) { super(context); mBackend = DreamBackend.getInstance(context); + mDreamsDisabledByAmbientModeSuppression = dreamsDisabledByAmbientModeSuppression; } @Override public void updateState(Preference preference) { super.updateState(preference); - int resId = DreamSettings.getDreamSettingDescriptionResId(mBackend.getWhenToDreamSetting()); - preference.setSummary(preference.getContext().getString(resId)); + if (mDreamsDisabledByAmbientModeSuppression + && AmbientDisplayAlwaysOnPreferenceController.isAodSuppressedByBedtime(mContext)) { + preference.setSummary(R.string.screensaver_settings_when_to_dream_bedtime); + } else { + final int resId = DreamSettings.getDreamSettingDescriptionResId( + mBackend.getWhenToDreamSetting()); + preference.setSummary(resId); + } } @Override diff --git a/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java index 3305cded8ad..458c5c6ceee 100644 --- a/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java @@ -16,15 +16,24 @@ package com.android.settings.dream; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.PowerManager; import androidx.preference.Preference; import androidx.test.core.app.ApplicationProvider; +import com.android.settings.R; +import com.android.settings.display.AmbientDisplayAlwaysOnPreferenceController; import com.android.settingslib.dream.DreamBackend; import com.android.settingslib.dream.DreamBackend.WhenToDream; @@ -38,32 +47,64 @@ import org.robolectric.util.ReflectionHelpers; @RunWith(RobolectricTestRunner.class) public class WhenToDreamPreferenceControllerTest { + private static final String TEST_PACKAGE = "com.android.test"; private WhenToDreamPreferenceController mController; private Context mContext; @Mock private DreamBackend mBackend; + @Mock + private PowerManager mPowerManager; + @Mock + private PackageManager mPackageManager; + @Mock + private ApplicationInfo mApplicationInfo; @Before - public void setup() { + public void setup() throws Exception { MockitoAnnotations.initMocks(this); - mContext = ApplicationProvider.getApplicationContext(); - mController = new WhenToDreamPreferenceController(mContext); + mContext = spy(ApplicationProvider.getApplicationContext()); + mController = new WhenToDreamPreferenceController(mContext, true); ReflectionHelpers.setField(mController, "mBackend", mBackend); + when(mContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager); + when(mPowerManager.isAmbientDisplaySuppressedForTokenByApp(anyString(), anyInt())) + .thenReturn(false); + + mApplicationInfo.uid = 1; + when(mContext.getString( + com.android.internal.R.string.config_defaultWellbeingPackage)).thenReturn( + TEST_PACKAGE); + + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.getApplicationInfo(TEST_PACKAGE, /* flag= */ 0)).thenReturn( + mApplicationInfo); } @Test - public void updateSummary() { + public void testUpdateSummary() { // Don't have to test the other settings because DreamSettings tests that all // @WhenToDream values map to the correct ResId final @WhenToDream int testSetting = DreamBackend.WHILE_CHARGING; final Preference mockPref = mock(Preference.class); when(mockPref.getContext()).thenReturn(mContext); when(mBackend.getWhenToDreamSetting()).thenReturn(testSetting); - final String expectedString = - mContext.getString(DreamSettings.getDreamSettingDescriptionResId(testSetting)); + final int expectedResId = DreamSettings.getDreamSettingDescriptionResId(testSetting); mController.updateState(mockPref); - verify(mockPref).setSummary(expectedString); + verify(mockPref).setSummary(expectedResId); + } + + @Test + public void testBedtimeModeSuppression() { + final Preference mockPref = mock(Preference.class); + when(mockPref.getContext()).thenReturn(mContext); + when(mBackend.getWhenToDreamSetting()).thenReturn(DreamBackend.WHILE_CHARGING); + when(mPowerManager.isAmbientDisplaySuppressedForTokenByApp(anyString(), anyInt())) + .thenReturn(true); + + assertTrue(AmbientDisplayAlwaysOnPreferenceController.isAodSuppressedByBedtime(mContext)); + + mController.updateState(mockPref); + verify(mockPref).setSummary(R.string.screensaver_settings_when_to_dream_bedtime); } }