Merge "Show other users in storage settings"
diff --git a/Android.bp b/Android.bp
index 9ddadaf..f980bfa 100644
--- a/Android.bp
+++ b/Android.bp
@@ -90,8 +90,11 @@
"WifiTrackerLib",
"SettingsLibActivityEmbedding",
"Settings-change-ids",
+ "androidx.room_room-runtime",
],
+ plugins: ["androidx.room_room-compiler-plugin"],
+
libs: [
"telephony-common",
"ims-common",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f5da15f..a3a6fcf 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -6,6 +6,11 @@
<original-package android:name="com.android.settings"/>
+ <!-- Permissions for reading or writing battery-related data. -->
+ <permission
+ android:name="com.android.settings.BATTERY_DATA"
+ android:protectionLevel="signature|privileged"/>
+
<uses-permission android:name="android.permission.REQUEST_NETWORK_SCORES" />
<uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
@@ -2961,6 +2966,21 @@
android:value="@string/menu_key_battery"/>
</activity>
+ <provider
+ android:name=".fuelgauge.batteryusage.BatteryUsageContentProvider"
+ android:enabled="true"
+ android:exported="true"
+ android:authorities="${applicationId}.battery.usage.provider"
+ android:permission="com.android.settings.BATTERY_DATA"/>
+
+ <receiver android:name=".fuelgauge.batteryusage.BatteryUsageBroadcastReceiver"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.settings.battery.action.FETCH_BATTERY_USAGE_DATA"/>
+ <action android:name="com.android.settings.battery.action.CLEAR_BATTERY_CACHE_DATA"/>
+ </intent-filter>
+ </receiver>
+
<activity
android:name="Settings$BatterySaverSettingsActivity"
android:label="@string/battery_saver"
diff --git a/res/layout/search_bar_two_pane_version.xml b/res/layout/search_bar_two_pane_version.xml
index 9ce220c..f98985c 100644
--- a/res/layout/search_bar_two_pane_version.xml
+++ b/res/layout/search_bar_two_pane_version.xml
@@ -38,6 +38,6 @@
android:layout_height="wrap_content"
android:paddingStart="@dimen/search_bar_title_padding_start_regular_two_pane"
android:layout_gravity="start"
- android:text="@string/search_menu"/>
+ android:text="@string/search_settings"/>
</Toolbar>
</com.google.android.material.card.MaterialCardView>
\ No newline at end of file
diff --git a/res/values-am/arrays.xml b/res/values-am/arrays.xml
index 6305184..9240b57 100644
--- a/res/values-am/arrays.xml
+++ b/res/values-am/arrays.xml
@@ -183,17 +183,17 @@
<item msgid="346101114322879720">"ማሳወቂያዎችን ድረስ"</item>
<item msgid="4760681822601767255">"ካሜራ"</item>
<item msgid="2172823594140104317">"ኦዲዮ ቅረጽ"</item>
- <item msgid="5612873260709742213">"ድምጽ አጫውት"</item>
+ <item msgid="5612873260709742213">"ድምፅ አጫውት"</item>
<item msgid="2027206403725749996">"ቅንጥብ መለያ አንብብ"</item>
<item msgid="5643742956725663156">"ቅንጥብ መለያ ቀይር"</item>
<item msgid="7362845549479684378">"የሚዲያ አዝራሮች"</item>
<item msgid="3843484466100107397">"የድምጽ ትኩረት"</item>
- <item msgid="617344340943430125">"ዋናው ድምጽ መቆጣጠሪያ"</item>
+ <item msgid="617344340943430125">"ዋናው ድምፅ መቆጣጠሪያ"</item>
<item msgid="1249691739381713634">"የድምጽ መጠን"</item>
- <item msgid="6485000384018554920">"የጥሪ ድምጽ መጠን"</item>
+ <item msgid="6485000384018554920">"የጥሪ ድምፅ መጠን"</item>
<item msgid="3378000878531336372">"የማህደረ መረጃ መጠን"</item>
<item msgid="5272927168355895681">"የማንቂያ ድምፅ መጠን"</item>
- <item msgid="4422070755065530548">"የማሳወቂያ ድምጽ መጠን"</item>
+ <item msgid="4422070755065530548">"የማሳወቂያ ድምፅ መጠን"</item>
<item msgid="3250654589277825306">"የብሉቱዝ ድምፅ መጠን"</item>
<item msgid="4212187233638382465">"እንደነቃ አቆይ"</item>
<item msgid="5099026183238335900">"አካባቢን ይቆጣጠሩ"</item>
@@ -250,7 +250,7 @@
<item msgid="8267704990417682222">"ማሳወቂያዎችን ይድረሱ"</item>
<item msgid="3180676986290096851">"ካሜራ"</item>
<item msgid="9174072114281872917">"ኦዲዮ ቅረጽ"</item>
- <item msgid="1444183972646890539">"ድምጽ አጫውት"</item>
+ <item msgid="1444183972646890539">"ድምፅ አጫውት"</item>
<item msgid="4337542044275236638">"ቅንጥብ ሰሌዳ አንብብ"</item>
<item msgid="2681224211796661809">"ቅንጥብ መለያ ቀይር"</item>
<item msgid="4479361062226474111">"የሚዲያ አዝራሮች"</item>
@@ -260,7 +260,7 @@
<item msgid="6749550886745567276">"የጥሪ መጠን"</item>
<item msgid="2218685029915863168">"የማህደረ መረጃ ክፍልፍል"</item>
<item msgid="4266577290496513640">"የማንቂያ ድምፅ መጠን"</item>
- <item msgid="8608084169623998854">"የማሳወቂያ ድምጽ መጠን"</item>
+ <item msgid="8608084169623998854">"የማሳወቂያ ድምፅ መጠን"</item>
<item msgid="7948784184567841794">"የብሉቱዝ ድምፅ መጠን"</item>
<item msgid="1148968792599973150">"እንደነቃ አቆይ"</item>
<item msgid="8482874682804856549">"አካባቢ"</item>
diff --git a/res/values/config.xml b/res/values/config.xml
index 29de56f..4b725b1 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -318,6 +318,9 @@
surface in search results or not.-->
<bool name="config_show_wifi_settings">true</bool>
+ <!-- Whether Wi-Fi hotspot settings should be shown or not. -->
+ <bool name="config_show_wifi_hotspot_settings">true</bool>
+
<!-- Whether toggle_airplane is available or not. -->
<bool name="config_show_toggle_airplane">true</bool>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c66c5b9..645a826 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2599,6 +2599,8 @@
<string name="screensaver_settings_title">Screen saver</string>
<!-- List of synonyms used in the settings search bar to find the “Screen saver”. [CHAR LIMIT=NONE] -->
<string name="keywords_screensaver">screensaver</string>
+ <!-- Summary for screensaver unavailable when Bedtime mode is on [CHAR LIMIT=50] -->
+ <string name="screensaver_settings_when_to_dream_bedtime">Unavailable because bedtime mode is on</string>
<!-- The title for the toggle which disables/enables screen savers [CHAR_LIMIT=30] -->
<string name="screensaver_settings_toggle_title">Use screen saver</string>
<!-- Display settings screen, summary fragment for screen saver options, activated when docked or asleep and charging [CHAR LIMIT=35] -->
@@ -8022,6 +8024,8 @@
<string name="notif_listener_excluded_app_summary">Change settings for each app that sends notifications</string>
<string name="notif_listener_excluded_app_screen_title">Apps shown on device</string>
<string name="notif_listener_not_migrated">This app doesn\u2019t support enhanced settings</string>
+ <string name="notif_listener_more_settings">More settings</string>
+ <string name="notif_listener_more_settings_desc">More settings are available inside this app</string>
<!-- Title for managing VR (virtual reality) helper services. [CHAR LIMIT=50] -->
<string name="vr_listeners_title">VR helper services</string>
diff --git a/res/xml/notification_access_permission_details.xml b/res/xml/notification_access_permission_details.xml
index 9867b6d..32a79e8 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" />
+ <Preference
+ android:key="more_settings"
+ android:title="@string/notif_listener_more_settings"
+ android:summary="@string/notif_listener_more_settings_desc"
+ settings:controller="com.android.settings.applications.specialaccess.notificationaccess.MoreSettingsPreferenceController" />
+
<com.android.settingslib.widget.FooterPreference
android:key="notif_listener_not_migrated"
android:title="@string/notif_listener_not_migrated"
diff --git a/res/xml/privacy_dashboard_settings.xml b/res/xml/privacy_dashboard_settings.xml
index f8f916e..4abdeda 100644
--- a/res/xml/privacy_dashboard_settings.xml
+++ b/res/xml/privacy_dashboard_settings.xml
@@ -86,8 +86,7 @@
<com.android.settings.RestrictedListPreference
android:key="privacy_lock_screen_notifications"
android:title="@string/lock_screen_notifs_title"
- android:summary="@string/summary_placeholder"
- settings:searchable="false"/>
+ android:summary="@string/summary_placeholder"/>
<!-- Show media on lock screen -->
<SwitchPreference
@@ -117,8 +116,7 @@
android:key="privacy_lock_screen_work_profile_notifications"
android:title="@string/locked_work_profile_notification_title"
android:summary="@string/summary_placeholder"
- android:order="999"
- settings:searchable="false"/>
+ android:order="999"/>
</PreferenceCategory>
<!-- Content Capture -->
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 6cdf43f..53960b4 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -158,6 +158,11 @@
/** Redirects to SafetyCenter if enabled. */
@VisibleForTesting
public void handleSafetyCenterRedirection() {
+ if (isFinishing()) {
+ // Don't trampoline if already exiting this activity.
+ return;
+ }
+
if (SafetyCenterManagerWrapper.get().isEnabled(this)) {
try {
startActivity(new Intent(Intent.ACTION_SAFETY_CENTER));
@@ -219,6 +224,11 @@
/** Redirects to SafetyCenter if enabled. */
@VisibleForTesting
public void handleSafetyCenterRedirection() {
+ if (isFinishing()) {
+ // Don't trampoline if already exiting this activity.
+ return;
+ }
+
if (ACTION_PRIVACY_SETTINGS.equals(getIntent().getAction())
&& SafetyCenterManagerWrapper.get().isEnabled(this)) {
try {
diff --git a/src/com/android/settings/applications/ApplicationFeatureProvider.java b/src/com/android/settings/applications/ApplicationFeatureProvider.java
index e9f877e..8a9f000 100644
--- a/src/com/android/settings/applications/ApplicationFeatureProvider.java
+++ b/src/com/android/settings/applications/ApplicationFeatureProvider.java
@@ -19,6 +19,8 @@
import android.annotation.UserIdInt;
import android.content.Intent;
+import androidx.annotation.NonNull;
+
import java.util.List;
import java.util.Set;
@@ -85,8 +87,9 @@
* Returns a user readable text explaining how much time user has spent in an app at a
* pre-specified duration.
*/
+ @NonNull
default CharSequence getTimeSpentInApp(String packageName) {
- return null;
+ return "";
}
/**
diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/MoreSettingsPreferenceController.java b/src/com/android/settings/applications/specialaccess/notificationaccess/MoreSettingsPreferenceController.java
new file mode 100644
index 0000000..a1ae3a4
--- /dev/null
+++ b/src/com/android/settings/applications/specialaccess/notificationaccess/MoreSettingsPreferenceController.java
@@ -0,0 +1,79 @@
+/*
+ * 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.applications.specialaccess.notificationaccess;
+
+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 com.android.settings.core.BasePreferenceController;
+
+import java.util.List;
+
+/**
+ * Controls link to reach more preference settings inside the app.
+ */
+public class MoreSettingsPreferenceController extends BasePreferenceController {
+
+ private static final String TAG = "MoreSettingsPrefContr";
+ private static final String KEY_MORE_SETTINGS = "more_settings";
+
+ PackageManager mPm;
+ String mPackage;
+ int mUserId;
+ Intent mIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(NotificationListenerService.INTENT_CATEGORY_SETTINGS_HOME);
+
+ public MoreSettingsPreferenceController(Context context) {
+ super(context, KEY_MORE_SETTINGS);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ final List<ResolveInfo> 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 e6feebb..531fb22 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 @@
.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/src/com/android/settings/bluetooth/BluetoothPairingRequest.java b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
index d5de41a..ed7a1fc 100644
--- a/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingRequest.java
@@ -87,9 +87,7 @@
return;
}
- if (mBluetoothManager.getCachedDeviceManager().shouldPairByCsip(device, groupId)) {
- device.createBond(BluetoothDevice.TRANSPORT_LE);
- }
+ mBluetoothManager.getCachedDeviceManager().pairDeviceByCsip(device, groupId);
}
}
}
diff --git a/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java b/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java
index 2ad9331..eb8add5 100644
--- a/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/PhoneNumberPreferenceController.java
@@ -88,6 +88,7 @@
for (int simSlotNumber = 1; simSlotNumber < mTelephonyManager.getPhoneCount();
simSlotNumber++) {
final Preference multiSimPreference = createNewPreference(screen.getContext());
+ multiSimPreference.setCopyingEnabled(true);
multiSimPreference.setOrder(phonePreferenceOrder + simSlotNumber);
multiSimPreference.setKey(KEY_PHONE_NUMBER + simSlotNumber);
category.addPreference(multiSimPreference);
diff --git a/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceController.java b/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceController.java
index 4204ec1..e392bd6 100644
--- a/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/simstatus/SimStatusPreferenceController.java
@@ -75,6 +75,7 @@
for (int simSlotNumber = 1; simSlotNumber < mTelephonyManager.getPhoneCount();
simSlotNumber++) {
final Preference multiSimPreference = createNewPreference(screen.getContext());
+ multiSimPreference.setCopyingEnabled(true);
multiSimPreference.setOrder(simStatusOrder + simSlotNumber);
multiSimPreference.setKey(KEY_SIM_STATUS + simSlotNumber);
category.addPreference(multiSimPreference);
diff --git a/src/com/android/settings/display/ScreenSaverPreferenceController.java b/src/com/android/settings/display/ScreenSaverPreferenceController.java
index 676a567..de1aaea 100644
--- a/src/com/android/settings/display/ScreenSaverPreferenceController.java
+++ b/src/com/android/settings/display/ScreenSaverPreferenceController.java
@@ -18,6 +18,7 @@
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 @@
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 @@
@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 4108e85..02ae6a7 100644
--- a/src/com/android/settings/dream/WhenToDreamPreferenceController.java
+++ b/src/com/android/settings/dream/WhenToDreamPreferenceController.java
@@ -20,7 +20,10 @@
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 @@
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/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java
new file mode 100644
index 0000000..5d8757d
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+/** A {@link BatteryUsageBroadcastReceiver} for battery usage data requesting. */
+public final class BatteryUsageBroadcastReceiver extends BroadcastReceiver {
+ private static final String TAG = "BatteryUsageBroadcastReceiver";
+ /** An intent action to request Settings to fetch usage data. */
+ public static final String ACTION_FETCH_BATTERY_USAGE_DATA =
+ "com.android.settings.battery.action.FETCH_BATTERY_USAGE_DATA";
+ /** An intent action to request Settings to clear cache data. */
+ public static final String ACTION_CLEAR_BATTERY_CACHE_DATA =
+ "com.android.settings.battery.action.CLEAR_BATTERY_CACHE_DATA";
+
+ @VisibleForTesting
+ static boolean sIsDebugMode = Build.TYPE.equals("userdebug");
+
+ @VisibleForTesting
+ boolean mFetchBatteryUsageData = false;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent == null || intent.getAction() == null) {
+ return;
+ }
+ Log.d(TAG, "onReceive:" + intent.getAction());
+ switch (intent.getAction()) {
+ case ACTION_FETCH_BATTERY_USAGE_DATA:
+ mFetchBatteryUsageData = true;
+ BatteryUsageDataLoader.enqueueWork(context);
+ break;
+ case ACTION_CLEAR_BATTERY_CACHE_DATA:
+ if (sIsDebugMode) {
+ BatteryDiffEntry.clearCache();
+ BatteryEntry.clearUidCache();
+ }
+ break;
+ }
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java
new file mode 100644
index 0000000..8d5ba33
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java
@@ -0,0 +1,168 @@
+/*
+ * 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;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+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 java.time.Clock;
+import java.time.Duration;
+
+/** {@link ContentProvider} class to fetch battery usage data. */
+public class BatteryUsageContentProvider extends ContentProvider {
+ private static final String TAG = "BatteryUsageContentProvider";
+
+ // TODO: Updates the duration to a more reasonable value for since-last-full-charge.
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ public static final Duration QUERY_DURATION_HOURS = Duration.ofHours(28);
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ public static final String QUERY_KEY_TIMESTAMP = "timestamp";
+
+ /** Codes */
+ private static final int BATTERY_STATE_CODE = 1;
+ private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+ static {
+ sUriMatcher.addURI(
+ DatabaseUtils.AUTHORITY,
+ /*path=*/ DatabaseUtils.BATTERY_STATE_TABLE,
+ /*code=*/ BATTERY_STATE_CODE);
+ }
+
+ private Clock mClock;
+ private BatteryStateDao mBatteryStateDao;
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ public void setClock(Clock clock) {
+ this.mClock = clock;
+ }
+
+ @Override
+ public boolean onCreate() {
+ if (DatabaseUtils.isWorkProfile(getContext())) {
+ Log.w(TAG, "do not create provider for work profile");
+ return false;
+ }
+ mClock = Clock.systemUTC();
+ mBatteryStateDao = BatteryStateDatabase.getInstance(getContext()).batteryStateDao();
+ Log.w(TAG, "create content provider from " + getCallingPackage());
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public Cursor query(
+ @NonNull Uri uri,
+ @Nullable String[] strings,
+ @Nullable String s,
+ @Nullable String[] strings1,
+ @Nullable String s1) {
+ switch (sUriMatcher.match(uri)) {
+ case BATTERY_STATE_CODE:
+ return getBatteryStates(uri);
+ default:
+ throw new IllegalArgumentException("unknown URI: " + uri);
+ }
+ }
+
+ @Nullable
+ @Override
+ public String getType(@NonNull Uri uri) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
+ switch (sUriMatcher.match(uri)) {
+ case BATTERY_STATE_CODE:
+ try {
+ mBatteryStateDao.insert(BatteryState.create(contentValues));
+ return uri;
+ } catch (RuntimeException e) {
+ Log.e(TAG, "insert() from:" + uri + " error:" + e);
+ return null;
+ }
+ default:
+ throw new IllegalArgumentException("unknown URI: " + uri);
+ }
+ }
+
+ @Override
+ public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
+ throw new UnsupportedOperationException("unsupported!");
+ }
+
+ @Override
+ public int update(
+ @NonNull Uri uri,
+ @Nullable ContentValues contentValues,
+ @Nullable String s,
+ @Nullable String[] strings) {
+ throw new UnsupportedOperationException("unsupported!");
+ }
+
+ private Cursor getBatteryStates(Uri uri) {
+ final long defaultTimestamp = mClock.millis() - QUERY_DURATION_HOURS.toMillis();
+ final long queryTimestamp = getQueryTimestamp(uri, defaultTimestamp);
+ return getBatteryStates(uri, queryTimestamp);
+ }
+
+ private Cursor getBatteryStates(Uri uri, long firstTimestamp) {
+ final long timestamp = mClock.millis();
+ Cursor cursor = null;
+ try {
+ cursor = mBatteryStateDao.getCursorAfter(firstTimestamp);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "query() from:" + uri + " error:" + e);
+ }
+ // TODO: Invokes hourly job recheck.
+ Log.w(TAG, "query battery states in " + (mClock.millis() - timestamp) + "/ms");
+ return cursor;
+ }
+
+ // If URI contains query parameter QUERY_KEY_TIMESTAMP, use the value directly.
+ // Otherwise, load the data for QUERY_DURATION_HOURS by default.
+ private long getQueryTimestamp(Uri uri, long defaultTimestamp) {
+ final String firstTimestampString = uri.getQueryParameter(QUERY_KEY_TIMESTAMP);
+ if (TextUtils.isEmpty(firstTimestampString)) {
+ Log.w(TAG, "empty query timestamp");
+ return defaultTimestamp;
+ }
+
+ try {
+ return Long.parseLong(firstTimestampString);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "invalid query timestamp: " + firstTimestampString, e);
+ return defaultTimestamp;
+ }
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
new file mode 100644
index 0000000..dc9c8b7
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.BatteryUsageStats;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.List;
+
+/** Load battery usage data in the background. */
+public final class BatteryUsageDataLoader {
+ private static final String TAG = "BatteryUsageDataLoader";
+
+ @VisibleForTesting
+ static BatteryAppListPreferenceController sController;
+
+ private BatteryUsageDataLoader() {
+ }
+
+ static void enqueueWork(Context context) {
+ AsyncTask.execute(() -> {
+ Log.d(TAG, "loadUsageDataSafely() in the AsyncTask");
+ loadUsageDataSafely(context.getApplicationContext());
+ });
+ }
+
+ @VisibleForTesting
+ static void loadUsageData(Context context) {
+ // Checks whether the battery content provider is available.
+ if (!DatabaseUtils.isContentProviderEnabled(context)) {
+ Log.w(TAG, "battery usage content provider is disabled!");
+ return;
+ }
+ final long start = System.currentTimeMillis();
+ final BatteryUsageStats batteryUsageStats = DataProcessor.getBatteryUsageStats(context);
+ final List<BatteryEntry> batteryEntryList =
+ DataProcessor.generateBatteryEntryListFromBatteryUsageStats(
+ context,
+ batteryUsageStats,
+ sController);
+ if (batteryEntryList == null || batteryEntryList.isEmpty()) {
+ Log.w(TAG, "getBatteryEntryList() returns null or empty content");
+ }
+ final long elapsedTime = System.currentTimeMillis() - start;
+ Log.d(TAG, String.format("getBatteryUsageStats() in %d/ms", elapsedTime));
+
+ // Uploads the BatteryEntry data into SettingsIntelligence.
+ DatabaseUtils.sendBatteryEntryData(
+ context, batteryEntryList, batteryUsageStats);
+ DataProcessor.closeBatteryUsageStats(batteryUsageStats);
+ }
+
+ private static void loadUsageDataSafely(Context context) {
+ try {
+ loadUsageData(context);
+ } catch (RuntimeException e) {
+ Log.e(TAG, "loadUsageData:" + e);
+ }
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
new file mode 100644
index 0000000..0e2a81f
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
@@ -0,0 +1,333 @@
+/*
+ * 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;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.BatteryManager;
+import android.os.BatteryUsageStats;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settingslib.fuelgauge.BatteryStatus;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** A utility class to operate battery usage database. */
+public final class DatabaseUtils {
+ private static final String TAG = "DatabaseUtils";
+ private static final String PREF_FILE_NAME = "battery_module_preference";
+ private static final String PREF_FULL_CHARGE_TIMESTAMP_KEY = "last_full_charge_timestamp_key";
+ /** Key for query parameter timestamp used in BATTERY_CONTENT_URI **/
+ private static final String QUERY_KEY_TIMESTAMP = "timestamp";
+ /** Clear memory threshold for device booting phase. **/
+ private static final long CLEAR_MEMORY_THRESHOLD_MS = Duration.ofMinutes(5).toMillis();
+ private static final long CLEAR_MEMORY_DELAYED_MS = Duration.ofSeconds(2).toMillis();
+
+ /** An authority name of the battery content provider. */
+ public static final String AUTHORITY = "com.android.settings.battery.usage.provider";
+ /** A table name for battery usage history. */
+ public static final String BATTERY_STATE_TABLE = "BatteryState";
+ /** A class name for battery usage data provider. */
+ public static final String SETTINGS_PACKAGE_PATH = "com.android.settings";
+ public static final String BATTERY_PROVIDER_CLASS_PATH =
+ "com.android.settings.fuelgauge.batteryusage.BatteryUsageContentProvider";
+
+ /** A content URI to access battery usage states data. */
+ public static final Uri BATTERY_CONTENT_URI =
+ new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(AUTHORITY)
+ .appendPath(BATTERY_STATE_TABLE)
+ .build();
+
+ private DatabaseUtils() {
+ }
+
+ /** Returns true if current user is a work profile user. */
+ public static boolean isWorkProfile(Context context) {
+ final UserManager userManager = context.getSystemService(UserManager.class);
+ return userManager.isManagedProfile() && !userManager.isSystemUser();
+ }
+
+ /** Returns true if the chart graph design is enabled. */
+ public static boolean isChartGraphEnabled(Context context) {
+ return isContentProviderEnabled(context);
+ }
+
+ /** Long: for timestamp and String: for BatteryHistEntry.getKey() */
+ public static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceLastFullCharge(
+ Context context, Calendar calendar) {
+ final long startTime = System.currentTimeMillis();
+ final long lastFullChargeTimestamp =
+ getStartTimestampForLastFullCharge(context, calendar);
+ // Builds the content uri everytime to avoid cache.
+ final Uri batteryStateUri =
+ new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(AUTHORITY)
+ .appendPath(BATTERY_STATE_TABLE)
+ .appendQueryParameter(
+ QUERY_KEY_TIMESTAMP, Long.toString(lastFullChargeTimestamp))
+ .build();
+
+ final Map<Long, Map<String, BatteryHistEntry>> resultMap =
+ loadHistoryMapFromContentProvider(context, batteryStateUri);
+ if (resultMap == null || resultMap.isEmpty()) {
+ Log.d(TAG, "getHistoryMapSinceLastFullCharge() returns empty or null");
+ } else {
+ Log.d(TAG, String.format("getHistoryMapSinceLastFullCharge() size=%d in %d/ms",
+ resultMap.size(), (System.currentTimeMillis() - startTime)));
+ }
+ return resultMap;
+ }
+
+ static boolean isContentProviderEnabled(Context context) {
+ return context.getPackageManager()
+ .getComponentEnabledSetting(
+ new ComponentName(SETTINGS_PACKAGE_PATH, BATTERY_PROVIDER_CLASS_PATH))
+ == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+ }
+
+ static List<ContentValues> sendBatteryEntryData(
+ Context context,
+ List<BatteryEntry> batteryEntryList,
+ BatteryUsageStats batteryUsageStats) {
+ final long startTime = System.currentTimeMillis();
+ final Intent intent = getBatteryIntent(context);
+ if (intent == null) {
+ Log.e(TAG, "sendBatteryEntryData(): cannot fetch battery intent");
+ clearMemory();
+ return null;
+ }
+ final int batteryLevel = getBatteryLevel(intent);
+ final int batteryStatus = intent.getIntExtra(
+ BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN);
+ final int batteryHealth = intent.getIntExtra(
+ BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN);
+ // We should use the same timestamp for each data snapshot.
+ final long snapshotTimestamp = Clock.systemUTC().millis();
+ final long snapshotBootTimestamp = SystemClock.elapsedRealtime();
+
+ // Creates the ContentValues list to insert them into provider.
+ final List<ContentValues> valuesList = new ArrayList<>();
+ if (batteryEntryList != null) {
+ batteryEntryList.stream()
+ .filter(entry -> {
+ final long foregroundMs = entry.getTimeInForegroundMs();
+ final long backgroundMs = entry.getTimeInBackgroundMs();
+ if (entry.getConsumedPower() == 0
+ && (foregroundMs != 0 || backgroundMs != 0)) {
+ Log.w(TAG, String.format(
+ "no consumed power but has running time for %s time=%d|%d",
+ entry.getLabel(), foregroundMs, backgroundMs));
+ }
+ return entry.getConsumedPower() != 0
+ || foregroundMs != 0
+ || backgroundMs != 0;
+ })
+ .forEach(entry -> valuesList.add(
+ ConvertUtils.convertToContentValues(
+ entry,
+ batteryUsageStats,
+ batteryLevel,
+ batteryStatus,
+ batteryHealth,
+ snapshotBootTimestamp,
+ snapshotTimestamp)));
+ }
+
+ int size = 1;
+ final ContentResolver resolver = context.getContentResolver();
+ // Inserts all ContentValues into battery provider.
+ if (!valuesList.isEmpty()) {
+ final ContentValues[] valuesArray = new ContentValues[valuesList.size()];
+ valuesList.toArray(valuesArray);
+ try {
+ size = resolver.bulkInsert(BATTERY_CONTENT_URI, valuesArray);
+ } catch (Exception e) {
+ Log.e(TAG, "bulkInsert() data into database error:\n" + e);
+ }
+ } else {
+ // Inserts one fake data into battery provider.
+ final ContentValues contentValues =
+ ConvertUtils.convertToContentValues(
+ /*entry=*/ null,
+ /*batteryUsageStats=*/ null,
+ batteryLevel,
+ batteryStatus,
+ batteryHealth,
+ snapshotBootTimestamp,
+ snapshotTimestamp);
+ try {
+ resolver.insert(BATTERY_CONTENT_URI, contentValues);
+ } catch (Exception e) {
+ Log.e(TAG, "insert() data into database error:\n" + e);
+ }
+ valuesList.add(contentValues);
+ }
+ saveLastFullChargeTimestampPref(context, batteryStatus, batteryLevel, snapshotTimestamp);
+ resolver.notifyChange(BATTERY_CONTENT_URI, /*observer=*/ null);
+ Log.d(TAG, String.format("sendBatteryEntryData() size=%d in %d/ms",
+ size, (System.currentTimeMillis() - startTime)));
+ clearMemory();
+ return valuesList;
+ }
+
+ @VisibleForTesting
+ static void saveLastFullChargeTimestampPref(
+ Context context, int batteryStatus, int batteryLevel, long timestamp) {
+ // Updates the SharedPreference only when timestamp is valid and phone is full charge.
+ if (!BatteryStatus.isCharged(batteryStatus, batteryLevel)) {
+ return;
+ }
+
+ final boolean success =
+ getSharedPreferences(context)
+ .edit()
+ .putLong(PREF_FULL_CHARGE_TIMESTAMP_KEY, timestamp)
+ .commit();
+ if (!success) {
+ Log.w(TAG, "saveLastFullChargeTimestampPref() fail: value=" + timestamp);
+ }
+ }
+
+ @VisibleForTesting
+ static long getLastFullChargeTimestampPref(Context context) {
+ return getSharedPreferences(context).getLong(PREF_FULL_CHARGE_TIMESTAMP_KEY, 0);
+ }
+
+ /**
+ * Returns the start timestamp for "since last full charge" battery usage chart.
+ * If the last full charge happens within the last 7 days, returns the timestamp of last full
+ * charge. Otherwise, returns the timestamp for 00:00 6 days before the calendar date.
+ */
+ @VisibleForTesting
+ static long getStartTimestampForLastFullCharge(
+ Context context, Calendar calendar) {
+ final long lastFullChargeTimestamp = getLastFullChargeTimestampPref(context);
+ final long sixDayAgoTimestamp = getTimestampSixDaysAgo(calendar);
+ return Math.max(lastFullChargeTimestamp, sixDayAgoTimestamp);
+ }
+
+ private static Map<Long, Map<String, BatteryHistEntry>> loadHistoryMapFromContentProvider(
+ Context context, Uri batteryStateUri) {
+ final boolean isWorkProfileUser = isWorkProfile(context);
+ Log.d(TAG, "loadHistoryMapFromContentProvider() isWorkProfileUser:" + isWorkProfileUser);
+ if (isWorkProfileUser) {
+ try {
+ context = context.createPackageContextAsUser(
+ /*packageName=*/ context.getPackageName(),
+ /*flags=*/ 0,
+ /*user=*/ UserHandle.OWNER);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "context.createPackageContextAsUser() fail:" + e);
+ return null;
+ }
+ }
+ if (!isContentProviderEnabled(context)) {
+ return null;
+ }
+ final Map<Long, Map<String, BatteryHistEntry>> resultMap = new HashMap();
+ try (Cursor cursor =
+ context.getContentResolver().query(batteryStateUri, null, null, null)) {
+ if (cursor == null || cursor.getCount() == 0) {
+ return resultMap;
+ }
+ // Loads and recovers all BatteryHistEntry data from cursor.
+ while (cursor.moveToNext()) {
+ final BatteryHistEntry entry = new BatteryHistEntry(cursor);
+ final long timestamp = entry.mTimestamp;
+ final String key = entry.getKey();
+ Map batteryHistEntryMap = resultMap.get(timestamp);
+ // Creates new one if there is no corresponding map.
+ if (batteryHistEntryMap == null) {
+ batteryHistEntryMap = new HashMap<>();
+ resultMap.put(timestamp, batteryHistEntryMap);
+ }
+ batteryHistEntryMap.put(key, entry);
+ }
+ }
+ return resultMap;
+ }
+
+ /** Gets the latest sticky battery intent from framework. */
+ private static Intent getBatteryIntent(Context context) {
+ return context.registerReceiver(
+ /*receiver=*/ null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ }
+
+ private static int getBatteryLevel(Intent intent) {
+ final int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+ final int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
+ return scale == 0
+ ? -1 /*invalid battery level*/
+ : Math.round((level / (float) scale) * 100f);
+ }
+
+ private static void clearMemory() {
+ if (SystemClock.uptimeMillis() > CLEAR_MEMORY_THRESHOLD_MS) {
+ return;
+ }
+ final Handler mainHandler = new Handler(Looper.getMainLooper());
+ mainHandler.postDelayed(() -> {
+ System.gc();
+ System.runFinalization();
+ System.gc();
+ Log.w(TAG, "invoke clearMemory()");
+ }, CLEAR_MEMORY_DELAYED_MS);
+ }
+
+ private static SharedPreferences getSharedPreferences(Context context) {
+ return context
+ .getApplicationContext() // ensures we bind it with application
+ .createDeviceProtectedStorageContext()
+ .getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE);
+ }
+
+ /** Returns the timestamp for 00:00 6 days before the calendar date. */
+ private static long getTimestampSixDaysAgo(Calendar calendar) {
+ Calendar startCalendar =
+ calendar == null ? Calendar.getInstance() : (Calendar) calendar.clone();
+ startCalendar.add(Calendar.DAY_OF_YEAR, -6);
+ startCalendar.set(Calendar.HOUR_OF_DAY, 0);
+ startCalendar.set(Calendar.MINUTE, 0);
+ startCalendar.set(Calendar.SECOND, 0);
+ startCalendar.set(Calendar.MILLISECOND, 0);
+ return startCalendar.getTimeInMillis();
+ }
+
+}
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 0000000..11db118
--- /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 0000000..b1afa6b
--- /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<BatteryState> states);
+
+ /** Lists all recorded data after a specific timestamp. */
+ @Query("SELECT * FROM BatteryState WHERE timestamp > :timestamp ORDER BY timestamp DESC")
+ List<BatteryState> 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<Long> 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 0000000..9396546
--- /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/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java
index 0da2d50..a94a1bf 100644
--- a/src/com/android/settings/network/NetworkDashboardFragment.java
+++ b/src/com/android/settings/network/NetworkDashboardFragment.java
@@ -121,7 +121,7 @@
controllers.add(internetPreferenceController);
}
controllers.add(privateDnsPreferenceController);
- controllers.add(new NetworkProviderCallsSmsController(context, lifecycle));
+ controllers.add(new NetworkProviderCallsSmsController(context, lifecycle, lifecycleOwner));
return controllers;
}
diff --git a/src/com/android/settings/network/NetworkProviderCallsSmsController.java b/src/com/android/settings/network/NetworkProviderCallsSmsController.java
index c8b1c49..bfe30f7 100644
--- a/src/com/android/settings/network/NetworkProviderCallsSmsController.java
+++ b/src/com/android/settings/network/NetworkProviderCallsSmsController.java
@@ -21,13 +21,13 @@
import android.content.Context;
import android.os.UserManager;
import android.telephony.ServiceState;
-import android.telephony.SubscriptionInfo;
-import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.util.Log;
import android.view.View;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
@@ -37,50 +37,55 @@
import com.android.settingslib.Utils;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.mobile.dataservice.MobileNetworkInfoEntity;
+import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
+import com.android.settingslib.mobile.dataservice.UiccInfoEntity;
import java.util.List;
public class NetworkProviderCallsSmsController extends AbstractPreferenceController implements
- SubscriptionsChangeListener.SubscriptionsChangeListenerClient, LifecycleObserver {
+ LifecycleObserver, MobileNetworkRepository.MobileNetworkCallback {
private static final String TAG = "NetworkProviderCallsSmsController";
private static final String KEY = "calls_and_sms";
private static final String RTL_MARK = "\u200F";
private UserManager mUserManager;
- private SubscriptionManager mSubscriptionManager;
- private SubscriptionsChangeListener mSubscriptionsChangeListener;
private TelephonyManager mTelephonyManager;
private RestrictedPreference mPreference;
private boolean mIsRtlMode;
+ private LifecycleOwner mLifecycleOwner;
+ private MobileNetworkRepository mMobileNetworkRepository;
+ private List<SubscriptionInfoEntity> mSubInfoEntityList;
/**
* The summary text and click behavior of the "Calls & SMS" item on the
* Network & internet page.
*/
- public NetworkProviderCallsSmsController(Context context, Lifecycle lifecycle) {
+ public NetworkProviderCallsSmsController(Context context, Lifecycle lifecycle,
+ LifecycleOwner lifecycleOwner) {
super(context);
mUserManager = context.getSystemService(UserManager.class);
- mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
mIsRtlMode = context.getResources().getConfiguration().getLayoutDirection()
== View.LAYOUT_DIRECTION_RTL;
+ mLifecycleOwner = lifecycleOwner;
+ mMobileNetworkRepository = new MobileNetworkRepository(context, this);
if (lifecycle != null) {
- mSubscriptionsChangeListener = new SubscriptionsChangeListener(context, this);
lifecycle.addObserver(this);
}
}
@OnLifecycleEvent(Event.ON_RESUME)
public void onResume() {
- mSubscriptionsChangeListener.start();
+ mMobileNetworkRepository.addRegister(mLifecycleOwner);
update();
}
@OnLifecycleEvent(Event.ON_PAUSE)
public void onPause() {
- mSubscriptionsChangeListener.stop();
+ mMobileNetworkRepository.removeRegister();
}
@Override
@@ -91,27 +96,24 @@
@Override
public CharSequence getSummary() {
- final List<SubscriptionInfo> subs = SubscriptionUtil.getActiveSubscriptions(
- mSubscriptionManager);
-
- if (subs.isEmpty()) {
+ List<SubscriptionInfoEntity> list = getSubscriptionInfoList();
+ if (list == null || list .isEmpty()) {
return setSummaryResId(R.string.calls_sms_no_sim);
} else {
final StringBuilder summary = new StringBuilder();
- for (SubscriptionInfo subInfo : subs) {
- int subsSize = subs.size();
- int subId = subInfo.getSubscriptionId();
- final CharSequence displayName = SubscriptionUtil.getUniqueSubscriptionDisplayName(
- subInfo, mContext);
+ for (SubscriptionInfoEntity subInfo : list) {
+ int subsSize = list.size();
+ int subId = Integer.parseInt(subInfo.subId);
+ final CharSequence displayName = subInfo.uniqueName;
// Set displayName as summary if there is only one valid SIM.
if (subsSize == 1
- && SubscriptionManager.isValidSubscriptionId(subId)
+ && list.get(0).isValidSubscription
&& isInService(subId)) {
return displayName;
}
- CharSequence status = getPreferredStatus(subsSize, subId);
+ CharSequence status = getPreferredStatus(subInfo, subsSize, subId);
if (status.toString().isEmpty()) {
// If there are 2 or more SIMs and one of these has no preferred status,
// set only its displayName as summary.
@@ -123,7 +125,7 @@
.append(")");
}
// Do not add ", " for the last subscription.
- if (subInfo != subs.get(subs.size() - 1)) {
+ if (!subInfo.equals(list.get(list.size() - 1))) {
summary.append(", ");
}
@@ -136,12 +138,13 @@
}
@VisibleForTesting
- protected CharSequence getPreferredStatus(int subsSize, int subId) {
+ protected CharSequence getPreferredStatus(SubscriptionInfoEntity subInfo, int subsSize,
+ int subId) {
String status = "";
- boolean isDataPreferred = subId == getDefaultVoiceSubscriptionId();
- boolean isSmsPreferred = subId == getDefaultSmsSubscriptionId();
+ boolean isDataPreferred = subInfo.isDefaultVoiceSubscription;
+ boolean isSmsPreferred = subInfo.isDefaultSmsSubscription;
- if (!SubscriptionManager.isValidSubscriptionId(subId) || !isInService(subId)) {
+ if (!subInfo.isValidSubscription || !isInService(subId)) {
status = setSummaryResId(subsSize > 1 ? R.string.calls_sms_unavailable :
R.string.calls_sms_temp_unavailable);
} else {
@@ -161,13 +164,8 @@
}
@VisibleForTesting
- protected int getDefaultVoiceSubscriptionId() {
- return SubscriptionManager.getDefaultVoiceSubscriptionId();
- }
-
- @VisibleForTesting
- protected int getDefaultSmsSubscriptionId() {
- return SubscriptionManager.getDefaultSmsSubscriptionId();
+ protected List<SubscriptionInfoEntity> getSubscriptionInfoList() {
+ return mSubInfoEntityList;
}
private void update() {
@@ -178,9 +176,7 @@
mPreference.setOnPreferenceClickListener(null);
mPreference.setFragment(null);
- final List<SubscriptionInfo> subs = SubscriptionUtil.getActiveSubscriptions(
- mSubscriptionManager);
- if (subs.isEmpty()) {
+ if (mSubInfoEntityList == null || mSubInfoEntityList.isEmpty()) {
mPreference.setEnabled(false);
} else {
mPreference.setEnabled(true);
@@ -213,16 +209,34 @@
update();
}
- @Override
- public void onSubscriptionsChanged() {
- refreshSummary(mPreference);
- update();
- }
-
@VisibleForTesting
protected boolean isInService(int subId) {
ServiceState serviceState =
mTelephonyManager.createForSubscriptionId(subId).getServiceState();
return Utils.isInService(serviceState);
}
+
+ @Override
+ public void onAvailableSubInfoChanged(List<SubscriptionInfoEntity> subInfoEntityList) {
+ }
+
+ @Override
+ public void onActiveSubInfoChanged(List<SubscriptionInfoEntity> activeSubInfoList) {
+ if ((mSubInfoEntityList != null &&
+ (activeSubInfoList.isEmpty() || !activeSubInfoList.equals(mSubInfoEntityList)))
+ || (!activeSubInfoList.isEmpty() && mSubInfoEntityList == null)) {
+ Log.d(TAG, "subInfo list from framework is changed, update the subInfo entity list.");
+ mSubInfoEntityList = activeSubInfoList;
+ update();
+ }
+ }
+
+ @Override
+ public void onAllUiccInfoChanged(List<UiccInfoEntity> uiccInfoEntityList) {
+ }
+
+ @Override
+ public void onAllMobileNetworkInfoChanged(
+ List<MobileNetworkInfoEntity> mobileNetworkInfoEntityList) {
+ }
}
diff --git a/src/com/android/settings/network/UiccSlotUtil.java b/src/com/android/settings/network/UiccSlotUtil.java
index 7b8d330..4c26995 100644
--- a/src/com/android/settings/network/UiccSlotUtil.java
+++ b/src/com/android/settings/network/UiccSlotUtil.java
@@ -28,7 +28,6 @@
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;
@@ -273,6 +272,7 @@
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) {
@@ -413,4 +413,29 @@
.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<UiccSlotInfo> 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 a878cb3..6fa803d 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.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.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 @@
}
private boolean isRemovableSimEnabled() {
- ImmutableList<UiccSlotInfo> 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/src/com/android/settings/privacy/PrivacyDashboardFragment.java b/src/com/android/settings/privacy/PrivacyDashboardFragment.java
index 75ed225..46a05b0 100644
--- a/src/com/android/settings/privacy/PrivacyDashboardFragment.java
+++ b/src/com/android/settings/privacy/PrivacyDashboardFragment.java
@@ -25,9 +25,12 @@
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 @@
Context context) {
return buildPreferenceControllers(context, null);
}
+
+ @Override
+ public List<String> getNonIndexableKeys(Context context) {
+ final List<String> 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;
+ }
};
}
diff --git a/src/com/android/settings/slices/CustomSliceRegistry.java b/src/com/android/settings/slices/CustomSliceRegistry.java
index 569a0ea..1cc3276 100644
--- a/src/com/android/settings/slices/CustomSliceRegistry.java
+++ b/src/com/android/settings/slices/CustomSliceRegistry.java
@@ -315,37 +315,28 @@
.appendPath("always_on_display")
.build();
- /**
- * Backing Uri for the Turn on Wi-Fi Slice.
- */
- public static final Uri TURN_ON_WIFI_SLICE_URI = new Uri.Builder()
- .scheme(ContentResolver.SCHEME_CONTENT)
- .authority(SettingsSliceProvider.SLICE_AUTHORITY)
- .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
- .appendPath("turn_on_wifi")
- .build();
-
@VisibleForTesting
static final Map<Uri, Class<? extends CustomSliceable>> sUriToSlice;
static {
sUriToSlice = new ArrayMap<>();
-
- sUriToSlice.put(BATTERY_FIX_SLICE_URI, BatteryFixSlice.class);
- sUriToSlice.put(BLUETOOTH_DEVICES_SLICE_URI, BluetoothDevicesSlice.class);
- sUriToSlice.put(CONTEXTUAL_ADAPTIVE_SLEEP_URI, ContextualAdaptiveSleepSlice.class);
- sUriToSlice.put(CONTEXTUAL_WIFI_SLICE_URI, ContextualWifiSlice.class);
- sUriToSlice.put(FACE_ENROLL_SLICE_URI, FaceSetupSlice.class);
sUriToSlice.put(FLASHLIGHT_SLICE_URI, FlashlightSlice.class);
sUriToSlice.put(LOCATION_SLICE_URI, LocationSlice.class);
- sUriToSlice.put(LOW_STORAGE_SLICE_URI, LowStorageSlice.class);
- sUriToSlice.put(MEDIA_OUTPUT_INDICATOR_SLICE_URI, MediaOutputIndicatorSlice.class);
sUriToSlice.put(MOBILE_DATA_SLICE_URI, MobileDataSlice.class);
sUriToSlice.put(PROVIDER_MODEL_SLICE_URI, ProviderModelSlice.class);
sUriToSlice.put(WIFI_SLICE_URI, WifiSlice.class);
sUriToSlice.put(DARK_THEME_SLICE_URI, DarkThemeSlice.class);
- sUriToSlice.put(REMOTE_MEDIA_SLICE_URI, RemoteMediaSlice.class);
sUriToSlice.put(ALWAYS_ON_SLICE_URI, AlwaysOnDisplaySlice.class);
+ sUriToSlice.put(MEDIA_OUTPUT_INDICATOR_SLICE_URI, MediaOutputIndicatorSlice.class);
+ sUriToSlice.put(REMOTE_MEDIA_SLICE_URI, RemoteMediaSlice.class);
+
+ // Slices for contextual card.
+ sUriToSlice.put(FACE_ENROLL_SLICE_URI, FaceSetupSlice.class);
+ sUriToSlice.put(BATTERY_FIX_SLICE_URI, BatteryFixSlice.class);
+ sUriToSlice.put(CONTEXTUAL_ADAPTIVE_SLEEP_URI, ContextualAdaptiveSleepSlice.class);
+ sUriToSlice.put(CONTEXTUAL_WIFI_SLICE_URI, ContextualWifiSlice.class);
+ sUriToSlice.put(LOW_STORAGE_SLICE_URI, LowStorageSlice.class);
+ sUriToSlice.put(BLUETOOTH_DEVICES_SLICE_URI, BluetoothDevicesSlice.class);
}
public static Class<? extends CustomSliceable> getSliceClassByUri(Uri uri) {
diff --git a/src/com/android/settings/spa/app/appinfo/AppButtonRepository.kt b/src/com/android/settings/spa/app/appinfo/AppButtonRepository.kt
index 3890e32..2383ddb 100644
--- a/src/com/android/settings/spa/app/appinfo/AppButtonRepository.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppButtonRepository.kt
@@ -60,13 +60,12 @@
val homePackages = mutableSetOf<String>()
val homeActivities = ArrayList<ResolveInfo>()
val currentDefaultHome = packageManager.getHomeActivities(homeActivities)
- homeActivities.map { it.activityInfo }.forEach {
- homePackages.add(it.packageName)
+ homeActivities.mapNotNull { it.activityInfo }.forEach { activityInfo ->
+ homePackages.add(activityInfo.packageName)
// Also make sure to include anything proxying for the home app
- val metaPackageName = it.metaData?.getString(ActivityManager.META_HOME_ALTERNATE)
- if (metaPackageName != null && signaturesMatch(metaPackageName, it.packageName)) {
- homePackages.add(metaPackageName)
- }
+ activityInfo.metaData?.getString(ActivityManager.META_HOME_ALTERNATE)
+ ?.takeIf { signaturesMatch(it, activityInfo.packageName) }
+ ?.let { homePackages.add(it) }
}
return HomePackages(homePackages, currentDefaultHome)
}
diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
index 8235101..82fdc84 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
@@ -92,7 +92,12 @@
AppPermissionPreference(app)
AppStoragePreference(app)
+ // TODO: instant_app_launch_supported_domain_urls
+ // TODO: data_settings
AppTimeSpentPreference(app)
+ // TODO: battery
+ // TODO: app_language_setting
+ AppOpenByDefaultPreference(app)
Category(title = stringResource(R.string.advanced_apps)) {
DisplayOverOtherAppsAppListProvider.InfoPageEntryItem(app)
diff --git a/src/com/android/settings/spa/app/appinfo/AppInstallButton.kt b/src/com/android/settings/spa/app/appinfo/AppInstallButton.kt
index a3ddfab..4ff2461 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInstallButton.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInstallButton.kt
@@ -33,7 +33,7 @@
val app = packageInfo.applicationInfo
if (!app.isInstantApp) return null
- return AppStoreUtil.getAppStoreLink(packageInfoPresenter.contextAsUser, app.packageName)
+ return AppStoreUtil.getAppStoreLink(packageInfoPresenter.userContext, app.packageName)
?.let { intent -> installButton(intent, app) }
}
diff --git a/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt b/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt
new file mode 100644
index 0000000..936dee6
--- /dev/null
+++ b/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.spa.app.appinfo
+
+import android.app.settings.SettingsEnums
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.liveData
+import com.android.settings.R
+import com.android.settings.applications.appinfo.AppInfoDashboardFragment
+import com.android.settings.applications.intentpicker.AppLaunchSettings
+import com.android.settings.applications.intentpicker.IntentPickerUtils
+import com.android.settingslib.applications.AppUtils
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spaprivileged.framework.common.asUser
+import com.android.settingslib.spaprivileged.framework.common.domainVerificationManager
+import com.android.settingslib.spaprivileged.model.app.hasFlag
+import com.android.settingslib.spaprivileged.model.app.userHandle
+import com.android.settingslib.spaprivileged.model.app.userId
+import kotlinx.coroutines.Dispatchers
+
+@Composable
+fun AppOpenByDefaultPreference(app: ApplicationInfo) {
+ val context = LocalContext.current
+ val presenter = remember { AppOpenByDefaultPresenter(context, app) }
+ if (!presenter.isAvailable()) return
+
+ Preference(object : PreferenceModel {
+ override val title = stringResource(R.string.launch_by_default)
+ override val summary = presenter.summaryLiveData.observeAsState(
+ initial = stringResource(R.string.summary_placeholder),
+ )
+ override val enabled = stateOf(presenter.isEnabled())
+ override val onClick = presenter::startActivity
+ })
+}
+
+private class AppOpenByDefaultPresenter(
+ private val context: Context,
+ private val app: ApplicationInfo,
+) {
+ private val domainVerificationManager = context.asUser(app.userHandle).domainVerificationManager
+
+ fun isAvailable() =
+ !app.isInstantApp && !AppUtils.isBrowserApp(context, app.packageName, app.userId)
+
+ fun isEnabled() = app.hasFlag(ApplicationInfo.FLAG_INSTALLED) && app.enabled
+
+ val summaryLiveData = liveData(Dispatchers.IO) {
+ emit(context.getString(when {
+ isLinkHandlingAllowed() -> R.string.app_link_open_always
+ else -> R.string.app_link_open_never
+ }))
+ }
+
+ fun isLinkHandlingAllowed(): Boolean {
+ val userState = IntentPickerUtils.getDomainVerificationUserState(
+ domainVerificationManager, app.packageName
+ )
+ return userState?.isLinkHandlingAllowed ?: false
+ }
+
+ fun startActivity() {
+ AppInfoDashboardFragment.startAppInfoFragment(
+ AppLaunchSettings::class.java,
+ app,
+ context,
+ SettingsEnums.APPLICATIONS_INSTALLED_APP_DETAILS,
+ )
+ }
+}
diff --git a/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt b/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt
index 9c5f673..f73c35a 100644
--- a/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt
@@ -24,6 +24,7 @@
import com.android.settings.R
import com.android.settingslib.applications.PermissionsSummaryHelper
import com.android.settingslib.applications.PermissionsSummaryHelper.PermissionsResultCallback
+import com.android.settingslib.spaprivileged.framework.common.asUser
import com.android.settingslib.spaprivileged.model.app.userHandle
data class AppPermissionSummaryState(
@@ -35,8 +36,8 @@
private val context: Context,
private val app: ApplicationInfo,
) : LiveData<AppPermissionSummaryState>() {
- private val contextAsUser = context.createContextAsUser(app.userHandle, 0)
- private val packageManager = contextAsUser.packageManager
+ private val userContext = context.asUser(app.userHandle)
+ private val packageManager = userContext.packageManager
private val onPermissionsChangedListener = OnPermissionsChangedListener { uid ->
if (uid == app.uid) update()
@@ -53,7 +54,7 @@
private fun update() {
PermissionsSummaryHelper.getPermissionSummary(
- contextAsUser, app.packageName, permissionsCallback
+ userContext, app.packageName, permissionsCallback
)
}
diff --git a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
index 2f5dda1..050c048 100644
--- a/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
+++ b/src/com/android/settings/spa/app/appinfo/PackageInfoPresenter.kt
@@ -29,6 +29,7 @@
import androidx.compose.runtime.Composable
import com.android.settings.overlay.FeatureFactory
import com.android.settingslib.spa.framework.compose.LocalNavController
+import com.android.settingslib.spaprivileged.framework.common.asUser
import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
import com.android.settingslib.spaprivileged.model.app.PackageManagers
import kotlinx.coroutines.CoroutineScope
@@ -49,8 +50,8 @@
private val coroutineScope: CoroutineScope,
) {
private val metricsFeatureProvider = FeatureFactory.getFactory(context).metricsFeatureProvider
- val contextAsUser by lazy { context.createContextAsUser(UserHandle.of(userId), 0) }
- val packageManagerAsUser: PackageManager by lazy { contextAsUser.packageManager }
+ val userContext by lazy { context.asUser(UserHandle.of(userId)) }
+ val packageManagerAsUser: PackageManager by lazy { userContext.packageManager }
private val _flow: MutableStateFlow<PackageInfo?> = MutableStateFlow(null)
val flow: StateFlow<PackageInfo?> = _flow
diff --git a/src/com/android/settings/vpn2/ConfigDialog.java b/src/com/android/settings/vpn2/ConfigDialog.java
index bf0dfc9..be2a68e 100644
--- a/src/com/android/settings/vpn2/ConfigDialog.java
+++ b/src/com/android/settings/vpn2/ConfigDialog.java
@@ -626,7 +626,14 @@
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;
diff --git a/src/com/android/settings/wifi/NetworkRequestDialogActivity.java b/src/com/android/settings/wifi/NetworkRequestDialogActivity.java
index 30f38d2..7b7d33f 100644
--- a/src/com/android/settings/wifi/NetworkRequestDialogActivity.java
+++ b/src/com/android/settings/wifi/NetworkRequestDialogActivity.java
@@ -27,12 +27,14 @@
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 @@
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 @@
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;
}
diff --git a/src/com/android/settings/wifi/WifiUtils.java b/src/com/android/settings/wifi/WifiUtils.java
index 4b94c81..a9010ac 100644
--- a/src/com/android/settings/wifi/WifiUtils.java
+++ b/src/com/android/settings/wifi/WifiUtils.java
@@ -22,14 +22,20 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.NetworkCapabilities;
+import android.net.TetheringManager;
import android.net.wifi.ScanResult;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.Log;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settings.R;
import com.android.settings.Utils;
import com.android.wifitrackerlib.WifiEntry;
@@ -38,12 +44,16 @@
/** A utility class for Wi-Fi functions. */
public class WifiUtils extends com.android.settingslib.wifi.WifiUtils {
+ static final String TAG = "WifiUtils";
+
private static final int SSID_ASCII_MIN_LENGTH = 1;
private static final int SSID_ASCII_MAX_LENGTH = 32;
private static final int PSK_PASSPHRASE_ASCII_MIN_LENGTH = 8;
private static final int PSK_PASSPHRASE_ASCII_MAX_LENGTH = 63;
+ private static Boolean sCanShowWifiHotspotCached;
+
public static boolean isSSIDTooLong(String ssid) {
if (TextUtils.isEmpty(ssid)) {
return false;
@@ -240,4 +250,62 @@
return WifiEntry.SECURITY_NONE;
}
+
+ /**
+ * Check if Wi-Fi hotspot settings can be displayed.
+ *
+ * @param context Context of caller
+ * @return true if Wi-Fi hotspot settings can be displayed
+ */
+ public static boolean checkShowWifiHotspot(Context context) {
+ if (context == null) return false;
+
+ boolean showWifiHotspotSettings =
+ context.getResources().getBoolean(R.bool.config_show_wifi_hotspot_settings);
+ if (!showWifiHotspotSettings) {
+ Log.w(TAG, "config_show_wifi_hotspot_settings:false");
+ return false;
+ }
+
+ WifiManager wifiManager = context.getSystemService(WifiManager.class);
+ if (wifiManager == null) {
+ Log.e(TAG, "WifiManager is null");
+ return false;
+ }
+
+ TetheringManager tetheringManager = context.getSystemService(TetheringManager.class);
+ if (tetheringManager == null) {
+ Log.e(TAG, "TetheringManager is null");
+ return false;
+ }
+ String[] wifiRegexs = tetheringManager.getTetherableWifiRegexs();
+ if (wifiRegexs == null || wifiRegexs.length == 0) {
+ Log.w(TAG, "TetherableWifiRegexs is empty");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Return the cached result to see if Wi-Fi hotspot settings can be displayed.
+ *
+ * @param context Context of caller
+ * @return true if Wi-Fi hotspot settings can be displayed
+ */
+ public static boolean canShowWifiHotspot(Context context) {
+ if (sCanShowWifiHotspotCached == null) {
+ sCanShowWifiHotspotCached = checkShowWifiHotspot(context);
+ }
+ return sCanShowWifiHotspotCached;
+ }
+
+ /**
+ * Sets the sCanShowWifiHotspotCached for testing purposes.
+ *
+ * @param cached Cached value for #canShowWifiHotspot()
+ */
+ @VisibleForTesting
+ public static void setCanShowWifiHotspotCached(Boolean cached) {
+ sCanShowWifiHotspotCached = cached;
+ }
}
diff --git a/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java b/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java
index c0ac159..49b437e 100644
--- a/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java
+++ b/src/com/android/settings/wifi/tether/WifiTetherPreferenceController.java
@@ -16,9 +16,10 @@
package com.android.settings.wifi.tether;
+import static com.android.settings.wifi.WifiUtils.canShowWifiHotspot;
+
import android.annotation.NonNull;
import android.content.Context;
-import android.net.TetheringManager;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiClient;
import android.net.wifi.WifiManager;
@@ -46,7 +47,6 @@
private static final String WIFI_TETHER_SETTINGS = "wifi_tether";
- private boolean mIsWifiTetherable;
private WifiManager mWifiManager;
private boolean mIsWifiTetheringAllow;
private int mSoftApState;
@@ -59,7 +59,6 @@
// TODO(b/246537032):Use fragment context to WifiManager service will caused memory leak
this(context, lifecycle,
context.getApplicationContext().getSystemService(WifiManager.class),
- context.getSystemService(TetheringManager.class),
true /* initSoftApManager */,
WifiEnterpriseRestrictionUtils.isWifiTetheringAllowed(context));
}
@@ -69,15 +68,9 @@
Context context,
Lifecycle lifecycle,
WifiManager wifiManager,
- TetheringManager tetheringManager,
boolean initSoftApManager,
boolean isWifiTetheringAllow) {
super(context);
- final String[] wifiRegexs = tetheringManager.getTetherableWifiRegexs();
- if (wifiRegexs != null && wifiRegexs.length != 0) {
- mIsWifiTetherable = true;
- }
-
mIsWifiTetheringAllow = isWifiTetheringAllow;
if (!isWifiTetheringAllow) return;
@@ -93,7 +86,7 @@
@Override
public boolean isAvailable() {
- return mIsWifiTetherable && !Utils.isMonkeyRunning();
+ return canShowWifiHotspot(mContext) && !Utils.isMonkeyRunning();
}
@Override
diff --git a/src/com/android/settings/wifi/tether/WifiTetherSettings.java b/src/com/android/settings/wifi/tether/WifiTetherSettings.java
index 5b9ce42..bee265e 100644
--- a/src/com/android/settings/wifi/tether/WifiTetherSettings.java
+++ b/src/com/android/settings/wifi/tether/WifiTetherSettings.java
@@ -18,6 +18,8 @@
import static android.net.wifi.WifiManager.WIFI_AP_STATE_CHANGED_ACTION;
+import static com.android.settings.wifi.WifiUtils.canShowWifiHotspot;
+
import android.app.settings.SettingsEnums;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -108,6 +110,13 @@
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ if (!canShowWifiHotspot(getContext())) {
+ Log.e(TAG, "can not launch Wi-Fi hotspot settings"
+ + " because the config is not set to show.");
+ finish();
+ return;
+ }
+
setIfOnlyAvailableForAdmins(true);
mUnavailable = isUiRestricted() || !mWifiRestriction.isHotspotAvailable(getContext());
}
diff --git a/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java
index 3305cde..458c5c6 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 @@
@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);
}
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java
new file mode 100644
index 0000000..c7e8322
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiverTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public final class BatteryUsageBroadcastReceiverTest {
+
+ private Context mContext;
+ private BatteryUsageBroadcastReceiver mBatteryUsageBroadcastReceiver;
+ @Mock
+ private PackageManager mPackageManager;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ mBatteryUsageBroadcastReceiver = new BatteryUsageBroadcastReceiver();
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+ }
+
+ @Test
+ public void onReceive_fetchUsageDataIntent_startService() {
+ setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ mBatteryUsageBroadcastReceiver.onReceive(mContext,
+ new Intent(BatteryUsageBroadcastReceiver.ACTION_FETCH_BATTERY_USAGE_DATA));
+
+ assertThat(mBatteryUsageBroadcastReceiver.mFetchBatteryUsageData).isTrue();
+ }
+
+ @Test
+ public void onReceive_invalidIntent_notStartService() {
+ setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ mBatteryUsageBroadcastReceiver.onReceive(mContext, new Intent("invalid intent"));
+
+ assertThat(mBatteryUsageBroadcastReceiver.mFetchBatteryUsageData).isFalse();
+ }
+
+ @Test
+ public void onReceive_clearCacheIntentInDebugMode_clearBatteryCacheData() {
+ BatteryUsageBroadcastReceiver.sIsDebugMode = true;
+ // Insert testing data first.
+ BatteryDiffEntry.sValidForRestriction.put(
+ /*packageName*/ "com.android.testing_package", Boolean.valueOf(true));
+ assertThat(BatteryDiffEntry.sValidForRestriction).isNotEmpty();
+
+ mBatteryUsageBroadcastReceiver.onReceive(mContext,
+ new Intent(BatteryUsageBroadcastReceiver.ACTION_CLEAR_BATTERY_CACHE_DATA));
+
+ assertThat(BatteryDiffEntry.sValidForRestriction).isEmpty();
+ }
+
+ @Test
+ public void onReceive_clearCacheIntentInNotDebugMode_notClearBatteryCacheData() {
+ BatteryUsageBroadcastReceiver.sIsDebugMode = false;
+ // Insert testing data first.
+ BatteryDiffEntry.sValidForRestriction.put(
+ /*packageName*/ "com.android.testing_package", Boolean.valueOf(true));
+ assertThat(BatteryDiffEntry.sValidForRestriction).isNotEmpty();
+
+ mBatteryUsageBroadcastReceiver.onReceive(mContext,
+ new Intent(BatteryUsageBroadcastReceiver.ACTION_CLEAR_BATTERY_CACHE_DATA));
+
+ assertThat(BatteryDiffEntry.sValidForRestriction).isNotEmpty();
+ }
+
+ private void setProviderSetting(int value) {
+ when(mPackageManager.getComponentEnabledSetting(
+ new ComponentName(
+ DatabaseUtils.SETTINGS_PACKAGE_PATH,
+ DatabaseUtils.BATTERY_PROVIDER_CLASS_PATH)))
+ .thenReturn(value);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
new file mode 100644
index 0000000..61d4efa
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
@@ -0,0 +1,306 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.fuelgauge.batteryusage.db.BatteryState;
+import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase;
+import com.android.settings.testutils.BatteryTestUtils;
+import com.android.settings.testutils.FakeClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.time.Duration;
+import java.util.List;
+
+/** Tests for {@link BatteryUsageContentProvider}. */
+@RunWith(RobolectricTestRunner.class)
+public final class BatteryUsageContentProviderTest {
+ private static final Uri VALID_BATTERY_STATE_CONTENT_URI = DatabaseUtils.BATTERY_CONTENT_URI;
+
+ private Context mContext;
+ private BatteryUsageContentProvider mProvider;
+
+ @Before
+ public void setUp() {
+ mContext = ApplicationProvider.getApplicationContext();
+ mProvider = new BatteryUsageContentProvider();
+ mProvider.attachInfo(mContext, /*info=*/ null);
+ BatteryTestUtils.setUpBatteryStateDatabase(mContext);
+ }
+
+ @Test
+ public void onCreate_withoutWorkProfileMode_returnsTrue() {
+ assertThat(mProvider.onCreate()).isTrue();
+ }
+
+ @Test
+ public void onCreate_withWorkProfileMode_returnsFalse() {
+ BatteryTestUtils.setWorkProfile(mContext);
+ assertThat(mProvider.onCreate()).isFalse();
+ }
+
+ @Test
+ public void queryAndInsert_incorrectContentUri_throwsIllegalArgumentException() {
+ final Uri.Builder builder =
+ new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(DatabaseUtils.AUTHORITY)
+ .appendPath(DatabaseUtils.BATTERY_STATE_TABLE + "/0");
+ 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 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<BatteryState> 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<BatteryState> 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/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java
new file mode 100644
index 0000000..4124e34
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoaderTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+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.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.BatteryStatsManager;
+import android.os.BatteryUsageStats;
+import android.os.BatteryUsageStatsQuery;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public final class BatteryUsageDataLoaderTest {
+
+ private Context mContext;
+ @Mock
+ private ContentResolver mMockContentResolver;
+ @Mock
+ private BatteryStatsManager mBatteryStatsManager;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private BatteryUsageStats mBatteryUsageStats;
+ @Mock
+ private BatteryAppListPreferenceController mMockBatteryAppListController;
+ @Mock
+ private BatteryEntry mMockBatteryEntry;
+ @Captor
+ private ArgumentCaptor<BatteryUsageStatsQuery> mStatsQueryCaptor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ BatteryUsageDataLoader.sController = mMockBatteryAppListController;
+ doReturn(mContext).when(mContext).getApplicationContext();
+ doReturn(mBatteryStatsManager).when(mContext).getSystemService(
+ Context.BATTERY_STATS_SERVICE);
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+ doReturn(mMockContentResolver).when(mContext).getContentResolver();
+ doReturn(new Intent()).when(mContext).registerReceiver(any(), any());
+ }
+
+ @Test
+ public void loadUsageData_loadUsageDataWithHistory() {
+ final List<BatteryEntry> batteryEntryList = new ArrayList<>();
+ batteryEntryList.add(mMockBatteryEntry);
+ setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ when(mBatteryStatsManager.getBatteryUsageStats(mStatsQueryCaptor.capture()))
+ .thenReturn(mBatteryUsageStats);
+ when(mMockBatteryAppListController.getBatteryEntryList(mBatteryUsageStats, true))
+ .thenReturn(batteryEntryList);
+
+ BatteryUsageDataLoader.loadUsageData(mContext);
+
+ final int queryFlags = mStatsQueryCaptor.getValue().getFlags();
+ assertThat(queryFlags
+ & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY)
+ .isNotEqualTo(0);
+ verify(mMockBatteryAppListController)
+ .getBatteryEntryList(mBatteryUsageStats, /*showAllApps=*/ true);
+ verify(mMockContentResolver).insert(any(), any());
+ }
+
+ @Test
+ public void loadUsageData_nullBatteryUsageStats_notLoadBatteryEntryData() {
+ setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ when(mBatteryStatsManager.getBatteryUsageStats(mStatsQueryCaptor.capture()))
+ .thenReturn(null);
+
+ BatteryUsageDataLoader.loadUsageData(mContext);
+
+ final int queryFlags = mStatsQueryCaptor.getValue().getFlags();
+ assertThat(queryFlags
+ & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY)
+ .isNotEqualTo(0);
+ verify(mMockBatteryAppListController, never())
+ .getBatteryEntryList(mBatteryUsageStats, /*showAllApps=*/ true);
+ verify(mMockContentResolver).insert(any(), any());
+ }
+
+ @Test
+ public void loadUsageData_nullBatteryEntryList_insertFakeDataIntoProvider() {
+ setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ when(mBatteryStatsManager.getBatteryUsageStats(mStatsQueryCaptor.capture()))
+ .thenReturn(mBatteryUsageStats);
+ when(mMockBatteryAppListController.getBatteryEntryList(mBatteryUsageStats, true))
+ .thenReturn(null);
+
+ BatteryUsageDataLoader.loadUsageData(mContext);
+
+ verify(mMockContentResolver).insert(any(), any());
+ }
+
+ @Test
+ public void loadUsageData_emptyBatteryEntryList_insertFakeDataIntoProvider() {
+ setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ when(mBatteryStatsManager.getBatteryUsageStats(mStatsQueryCaptor.capture()))
+ .thenReturn(mBatteryUsageStats);
+ when(mMockBatteryAppListController.getBatteryEntryList(mBatteryUsageStats, true))
+ .thenReturn(new ArrayList<BatteryEntry>());
+
+ BatteryUsageDataLoader.loadUsageData(mContext);
+
+ verify(mMockContentResolver).insert(any(), any());
+ }
+
+ @Test
+ public void loadUsageData_providerIsDisabled_notLoadHistory() {
+ setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+ when(mBatteryStatsManager.getBatteryUsageStats(mStatsQueryCaptor.capture()))
+ .thenReturn(mBatteryUsageStats);
+
+ BatteryUsageDataLoader.loadUsageData(mContext);
+
+ verify(mBatteryStatsManager, never()).getBatteryUsageStats(
+ mStatsQueryCaptor.capture());
+ }
+
+ private void setProviderSetting(int value) {
+ when(mPackageManager.getComponentEnabledSetting(
+ new ComponentName(
+ DatabaseUtils.SETTINGS_PACKAGE_PATH,
+ DatabaseUtils.BATTERY_PROVIDER_CLASS_PATH)))
+ .thenReturn(value);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
new file mode 100644
index 0000000..cb5255e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
@@ -0,0 +1,422 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.MatrixCursor;
+import android.os.BatteryManager;
+import android.os.BatteryUsageStats;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.settings.testutils.BatteryTestUtils;
+
+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;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Map;
+
+@RunWith(RobolectricTestRunner.class)
+public final class DatabaseUtilsTest {
+
+ private Context mContext;
+
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock private ContentResolver mMockContentResolver;
+ @Mock private ContentResolver mMockContentResolver2;
+ @Mock private BatteryUsageStats mBatteryUsageStats;
+ @Mock private BatteryEntry mMockBatteryEntry1;
+ @Mock private BatteryEntry mMockBatteryEntry2;
+ @Mock private BatteryEntry mMockBatteryEntry3;
+ @Mock private Context mMockContext;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ doReturn(mMockContentResolver2).when(mMockContext).getContentResolver();
+ doReturn(mMockContentResolver).when(mContext).getContentResolver();
+ doReturn(mPackageManager).when(mMockContext).getPackageManager();
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+ }
+
+ @Test
+ public void isWorkProfile_defaultValue_returnFalse() {
+ assertThat(DatabaseUtils.isWorkProfile(mContext)).isFalse();
+ }
+
+ @Test
+ public void isWorkProfile_withManagedUser_returnTrue() {
+ BatteryTestUtils.setWorkProfile(mContext);
+ assertThat(DatabaseUtils.isWorkProfile(mContext)).isTrue();
+ }
+
+ @Test
+ public void isWorkProfile_withSystemUser_returnFalse() {
+ BatteryTestUtils.setWorkProfile(mContext);
+ Shadows.shadowOf(mContext.getSystemService(UserManager.class)).setIsSystemUser(true);
+
+ assertThat(DatabaseUtils.isWorkProfile(mContext)).isFalse();
+ }
+
+ @Test
+ public void isChartGraphEnabled_providerIsEnabled_returnTrue() {
+ setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ assertThat(DatabaseUtils.isChartGraphEnabled(mContext)).isTrue();
+ }
+
+ @Test
+ public void isChartGraphEnabled_providerIsDisabled_returnFalse() {
+ setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+ assertThat(DatabaseUtils.isChartGraphEnabled(mContext)).isFalse();
+ }
+
+ @Test
+ public void isContentProviderEnabled_providerEnabled_returnsTrue() {
+ setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ assertThat(DatabaseUtils.isContentProviderEnabled(mContext)).isTrue();
+ }
+
+ @Test
+ public void isContentProviderEnabled_providerDisabled_returnsFalse() {
+ setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+ assertThat(DatabaseUtils.isContentProviderEnabled(mContext)).isFalse();
+ }
+
+ @Test
+ public void sendBatteryEntryData_nullBatteryIntent_returnsNullValue() {
+ doReturn(null).when(mContext).registerReceiver(any(), any());
+ assertThat(
+ DatabaseUtils.sendBatteryEntryData(
+ mContext, /*batteryEntryList=*/ null, mBatteryUsageStats))
+ .isNull();
+ }
+
+ @Test
+ public void sendBatteryEntryData_returnsExpectedList() {
+ doReturn(getBatteryIntent()).when(mContext).registerReceiver(any(), any());
+ // Configures the testing BatteryEntry data.
+ final List<BatteryEntry> batteryEntryList = new ArrayList<>();
+ batteryEntryList.add(mMockBatteryEntry1);
+ batteryEntryList.add(mMockBatteryEntry2);
+ batteryEntryList.add(mMockBatteryEntry3);
+ doReturn(0.0).when(mMockBatteryEntry1).getConsumedPower();
+ doReturn(0.5).when(mMockBatteryEntry2).getConsumedPower();
+ doReturn(0.0).when(mMockBatteryEntry3).getConsumedPower();
+ doReturn(1L).when(mMockBatteryEntry3).getTimeInForegroundMs();
+
+ final List<ContentValues> valuesList =
+ DatabaseUtils.sendBatteryEntryData(
+ mContext, batteryEntryList, mBatteryUsageStats);
+
+ assertThat(valuesList).hasSize(2);
+ // Verifies the ContentValues content.
+ verifyContentValues(0.5, valuesList.get(0));
+ verifyContentValues(0.0, valuesList.get(1));
+ // Verifies the inserted ContentValues into content provider.
+ final ContentValues[] valuesArray =
+ new ContentValues[] {valuesList.get(0), valuesList.get(1)};
+ verify(mMockContentResolver).bulkInsert(
+ DatabaseUtils.BATTERY_CONTENT_URI, valuesArray);
+ verify(mMockContentResolver).notifyChange(
+ DatabaseUtils.BATTERY_CONTENT_URI, /*observer=*/ null);
+ }
+
+ @Test
+ public void sendBatteryEntryData_emptyBatteryEntryList_sendFakeDataIntoProvider() {
+ doReturn(getBatteryIntent()).when(mContext).registerReceiver(any(), any());
+
+ final List<ContentValues> valuesList =
+ DatabaseUtils.sendBatteryEntryData(
+ mContext,
+ new ArrayList<>(),
+ mBatteryUsageStats);
+
+ assertThat(valuesList).hasSize(1);
+ verifyFakeContentValues(valuesList.get(0));
+ // Verifies the inserted ContentValues into content provider.
+ verify(mMockContentResolver).insert(any(), any());
+ verify(mMockContentResolver).notifyChange(
+ DatabaseUtils.BATTERY_CONTENT_URI, /*observer=*/ null);
+ }
+
+ @Test
+ public void sendBatteryEntryData_nullBatteryEntryList_sendFakeDataIntoProvider() {
+ doReturn(getBatteryIntent()).when(mContext).registerReceiver(any(), any());
+
+ final List<ContentValues> valuesList =
+ DatabaseUtils.sendBatteryEntryData(
+ mContext,
+ /*batteryEntryList=*/ null,
+ mBatteryUsageStats);
+
+ assertThat(valuesList).hasSize(1);
+ verifyFakeContentValues(valuesList.get(0));
+ // Verifies the inserted ContentValues into content provider.
+ verify(mMockContentResolver).insert(any(), any());
+ verify(mMockContentResolver).notifyChange(
+ DatabaseUtils.BATTERY_CONTENT_URI, /*observer=*/ null);
+ }
+
+ @Test
+ public void sendBatteryEntryData_nullBatteryUsageStats_sendFakeDataIntoProvider() {
+ doReturn(getBatteryIntent()).when(mContext).registerReceiver(any(), any());
+
+ final List<ContentValues> valuesList =
+ DatabaseUtils.sendBatteryEntryData(
+ mContext,
+ /*batteryEntryList=*/ null,
+ /*batteryUsageStats=*/ null);
+
+ assertThat(valuesList).hasSize(1);
+ verifyFakeContentValues(valuesList.get(0));
+ // Verifies the inserted ContentValues into content provider.
+ verify(mMockContentResolver).insert(any(), any());
+ verify(mMockContentResolver).notifyChange(
+ DatabaseUtils.BATTERY_CONTENT_URI, /*observer=*/ null);
+ }
+
+ @Test
+ public void getHistoryMapSinceLastFullCharge_providerIsDisabled_returnNull() {
+ setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+ assertThat(DatabaseUtils.getHistoryMapSinceLastFullCharge(
+ mContext, /*calendar=*/ null)).isNull();
+ }
+
+ @Test
+ public void getHistoryMapSinceLastFullCharge_emptyCursorContent_returnEmptyMap() {
+ final MatrixCursor cursor = new MatrixCursor(
+ new String[] {
+ BatteryHistEntry.KEY_UID,
+ BatteryHistEntry.KEY_USER_ID,
+ BatteryHistEntry.KEY_TIMESTAMP});
+ doReturn(cursor).when(mMockContentResolver).query(any(), any(), any(), any());
+
+ assertThat(DatabaseUtils.getHistoryMapSinceLastFullCharge(
+ mContext, /*calendar=*/ null)).isEmpty();
+ }
+
+ @Test
+ public void getHistoryMapSinceLastFullCharge_nullCursor_returnEmptyMap() {
+ doReturn(null).when(mMockContentResolver).query(any(), any(), any(), any());
+ assertThat(DatabaseUtils.getHistoryMapSinceLastFullCharge(
+ mContext, /*calendar=*/ null)).isEmpty();
+ }
+
+ @Test
+ public void getHistoryMapSinceLastFullCharge_returnExpectedMap() {
+ final Long timestamp1 = Long.valueOf(1001L);
+ final Long timestamp2 = Long.valueOf(1002L);
+ final MatrixCursor cursor = getMatrixCursor();
+ doReturn(cursor).when(mMockContentResolver).query(any(), any(), any(), any());
+ // Adds fake data into the cursor.
+ cursor.addRow(new Object[] {
+ "app name1", timestamp1, 1, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
+ cursor.addRow(new Object[] {
+ "app name2", timestamp2, 2, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
+ cursor.addRow(new Object[] {
+ "app name3", timestamp2, 3, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
+ cursor.addRow(new Object[] {
+ "app name4", timestamp2, 4, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
+
+ final Map<Long, Map<String, BatteryHistEntry>> batteryHistMap =
+ DatabaseUtils.getHistoryMapSinceLastFullCharge(
+ mContext, /*calendar=*/ null);
+
+ assertThat(batteryHistMap).hasSize(2);
+ // Verifies the BatteryHistEntry data for timestamp1.
+ Map<String, BatteryHistEntry> batteryMap = batteryHistMap.get(timestamp1);
+ assertThat(batteryMap).hasSize(1);
+ assertThat(batteryMap.get("1").mAppLabel).isEqualTo("app name1");
+ // Verifies the BatteryHistEntry data for timestamp2.
+ batteryMap = batteryHistMap.get(timestamp2);
+ assertThat(batteryMap).hasSize(3);
+ assertThat(batteryMap.get("2").mAppLabel).isEqualTo("app name2");
+ assertThat(batteryMap.get("3").mAppLabel).isEqualTo("app name3");
+ assertThat(batteryMap.get("4").mAppLabel).isEqualTo("app name4");
+ }
+
+ @Test
+ public void getHistoryMapSinceLastFullCharge_withWorkProfile_returnExpectedMap()
+ throws PackageManager.NameNotFoundException {
+ doReturn("com.fake.package").when(mContext).getPackageName();
+ doReturn(mMockContext).when(mContext).createPackageContextAsUser(
+ "com.fake.package", /*flags=*/ 0, UserHandle.OWNER);
+ BatteryTestUtils.setWorkProfile(mContext);
+ doReturn(getMatrixCursor()).when(mMockContentResolver2)
+ .query(any(), any(), any(), any());
+ doReturn(null).when(mMockContentResolver).query(any(), any(), any(), any());
+
+ final Map<Long, Map<String, BatteryHistEntry>> batteryHistMap =
+ DatabaseUtils.getHistoryMapSinceLastFullCharge(
+ mContext, /*calendar=*/ null);
+
+ assertThat(batteryHistMap).isEmpty();
+ }
+
+ @Test
+ public void saveLastFullChargeTimestampPref_notFullCharge_returnsFalse() {
+ DatabaseUtils.saveLastFullChargeTimestampPref(
+ mContext,
+ BatteryManager.BATTERY_STATUS_UNKNOWN,
+ /* level */ 10,
+ /* timestamp */ 1);
+ assertThat(DatabaseUtils.getLastFullChargeTimestampPref(mContext)).isEqualTo(0);
+ }
+
+ @Test
+ public void saveLastFullChargeTimestampPref_fullStatus_returnsTrue() {
+ long expectedTimestamp = 1;
+ DatabaseUtils.saveLastFullChargeTimestampPref(
+ mContext,
+ BatteryManager.BATTERY_STATUS_FULL,
+ /* level */ 10,
+ /* timestamp */ expectedTimestamp);
+ assertThat(DatabaseUtils.getLastFullChargeTimestampPref(mContext))
+ .isEqualTo(expectedTimestamp);
+ }
+
+ @Test
+ public void saveLastFullChargeTimestampPref_level100_returnsTrue() {
+ long expectedTimestamp = 1;
+ DatabaseUtils.saveLastFullChargeTimestampPref(
+ mContext,
+ BatteryManager.BATTERY_STATUS_UNKNOWN,
+ /* level */ 100,
+ /* timestamp */ expectedTimestamp);
+ assertThat(DatabaseUtils.getLastFullChargeTimestampPref(mContext))
+ .isEqualTo(expectedTimestamp);
+ }
+
+ @Test
+ public void getStartTimestampForLastFullCharge_noTimestampPreference_returnsSixDaysAgo() {
+ Calendar currentCalendar = Calendar.getInstance();
+ currentCalendar.set(2022, 6, 5, 6, 30, 50); // 2022-07-05 06:30:50
+ Calendar expectedCalendar = Calendar.getInstance();
+ expectedCalendar.set(2022, 5, 29, 0, 0, 0); // 2022-06-29 00:00:00
+ expectedCalendar.set(Calendar.MILLISECOND, 0);
+
+ assertThat(DatabaseUtils.getStartTimestampForLastFullCharge(mContext, currentCalendar))
+ .isEqualTo(expectedCalendar.getTimeInMillis());
+ }
+
+ @Test
+ public void getStartTimestampForLastFullCharge_lastFullChargeEarlier_returnsSixDaysAgo() {
+ Calendar lastFullCalendar = Calendar.getInstance();
+ lastFullCalendar.set(2021, 11, 25, 6, 30, 50); // 2021-12-25 06:30:50
+ DatabaseUtils.saveLastFullChargeTimestampPref(
+ mContext,
+ BatteryManager.BATTERY_STATUS_UNKNOWN,
+ /* level */ 100,
+ /* timestamp */ lastFullCalendar.getTimeInMillis());
+ Calendar currentCalendar = Calendar.getInstance();
+ currentCalendar.set(2022, 0, 2, 6, 30, 50); // 2022-01-02 06:30:50
+ Calendar expectedCalendar = Calendar.getInstance();
+ expectedCalendar.set(2021, 11, 27, 0, 0, 0); // 2021-12-27 00:00:00
+ expectedCalendar.set(Calendar.MILLISECOND, 0);
+
+ assertThat(DatabaseUtils.getStartTimestampForLastFullCharge(mContext, currentCalendar))
+ .isEqualTo(expectedCalendar.getTimeInMillis());
+ }
+
+ @Test
+ public void getStartTimestampForLastFullCharge_lastFullChargeLater_returnsLastFullCharge() {
+ Calendar lastFullCalendar = Calendar.getInstance();
+ lastFullCalendar.set(2022, 6, 1, 6, 30, 50); // 2022-07-01 06:30:50
+ long expectedTimestamp = lastFullCalendar.getTimeInMillis();
+ DatabaseUtils.saveLastFullChargeTimestampPref(
+ mContext,
+ BatteryManager.BATTERY_STATUS_UNKNOWN,
+ /* level */ 100,
+ /* timestamp */ expectedTimestamp);
+ Calendar currentCalendar = Calendar.getInstance();
+ currentCalendar.set(2022, 6, 5, 6, 30, 50); // 2022-07-05 06:30:50
+
+ assertThat(DatabaseUtils.getStartTimestampForLastFullCharge(mContext, currentCalendar))
+ .isEqualTo(expectedTimestamp);
+ }
+
+ private void setProviderSetting(int value) {
+ when(mPackageManager.getComponentEnabledSetting(
+ new ComponentName(
+ DatabaseUtils.SETTINGS_PACKAGE_PATH,
+ DatabaseUtils.BATTERY_PROVIDER_CLASS_PATH)))
+ .thenReturn(value);
+ }
+
+ private static void verifyContentValues(double consumedPower, ContentValues values) {
+ assertThat(values.getAsDouble(BatteryHistEntry.KEY_CONSUME_POWER))
+ .isEqualTo(consumedPower);
+ assertThat(values.getAsInteger(BatteryHistEntry.KEY_BATTERY_LEVEL)).isEqualTo(20);
+ assertThat(values.getAsInteger(BatteryHistEntry.KEY_BATTERY_STATUS))
+ .isEqualTo(BatteryManager.BATTERY_STATUS_FULL);
+ assertThat(values.getAsInteger(BatteryHistEntry.KEY_BATTERY_HEALTH))
+ .isEqualTo(BatteryManager.BATTERY_HEALTH_COLD);
+ }
+
+ private static void verifyFakeContentValues(ContentValues values) {
+ assertThat(values.getAsInteger("batteryLevel")).isEqualTo(20);
+ assertThat(values.getAsInteger("batteryStatus"))
+ .isEqualTo(BatteryManager.BATTERY_STATUS_FULL);
+ assertThat(values.getAsInteger("batteryHealth"))
+ .isEqualTo(BatteryManager.BATTERY_HEALTH_COLD);
+ assertThat(values.getAsString("packageName"))
+ .isEqualTo(ConvertUtils.FAKE_PACKAGE_NAME);
+ }
+
+ private static Intent getBatteryIntent() {
+ final Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ intent.putExtra(BatteryManager.EXTRA_LEVEL, 20);
+ intent.putExtra(BatteryManager.EXTRA_SCALE, 100);
+ intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL);
+ intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_COLD);
+ return intent;
+ }
+
+ private static MatrixCursor getMatrixCursor() {
+ return new MatrixCursor(
+ new String[] {
+ BatteryHistEntry.KEY_APP_LABEL,
+ BatteryHistEntry.KEY_TIMESTAMP,
+ BatteryHistEntry.KEY_UID,
+ BatteryHistEntry.KEY_CONSUMER_TYPE});
+ }
+}
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 0000000..41e3f4d
--- /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<BatteryState> 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<BatteryState> 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<Long> 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 0000000..ef23c41
--- /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/slices/SettingsSliceProviderTest.java b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java
index 9043974..c3b7821 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.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 @@
@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 @@
private Context mContext;
private SettingsSliceProvider mProvider;
private ShadowPackageManager mPackageManager;
+
@Mock
private SliceManager mManager;
@@ -573,14 +570,6 @@
}
@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(
mContext.getContentResolver(), Settings.Secure.FLASHLIGHT_AVAILABLE, 1);
@@ -604,32 +593,6 @@
}
@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() {
final List<Uri> uris = new ArrayList<>();
@@ -716,31 +679,6 @@
.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 {
diff --git a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java
index e4e26d2..fa3ee10 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 @@
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);
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 0000000..946d051
--- /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;
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/uwb/UwbPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/uwb/UwbPreferenceControllerTest.java
index 4183f29..94d797a 100644
--- a/tests/robotests/src/com/android/settings/uwb/UwbPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/uwb/UwbPreferenceControllerTest.java
@@ -31,7 +31,6 @@
import com.android.settings.core.BasePreferenceController;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -94,9 +93,11 @@
.isEqualTo(BasePreferenceController.UNSUPPORTED_ON_DEVICE);
}
- @Ignore
@Test
public void isChecked_uwbEnabled_shouldReturnTrue() {
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+ doReturn(true).when(mPackageManager)
+ .hasSystemFeature(PackageManager.FEATURE_UWB);
doReturn(mController.STATE_ENABLED_ACTIVE).when(mUwbManager).getAdapterState();
assertThat(mController.isChecked()).isTrue();
@@ -104,6 +105,9 @@
@Test
public void isChecked_uwbDisabled_shouldReturnFalse() {
+ doReturn(mPackageManager).when(mContext).getPackageManager();
+ doReturn(true).when(mPackageManager)
+ .hasSystemFeature(PackageManager.FEATURE_UWB);
doReturn(mController.STATE_DISABLED).when(mUwbManager).getAdapterState();
assertThat(mController.isChecked()).isFalse();
diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java
index e8ee7c3..bc6053b 100644
--- a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherPreferenceControllerTest.java
@@ -16,13 +16,14 @@
package com.android.settings.wifi.tether;
+import static com.android.settings.wifi.WifiUtils.setCanShowWifiHotspotCached;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import android.content.Context;
-import android.net.TetheringManager;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiManager;
@@ -62,8 +63,6 @@
@Mock
private Lifecycle mLifecycle;
@Mock
- private TetheringManager mTetheringManager;
- @Mock
private WifiManager mWifiManager;
@Mock
private PreferenceScreen mScreen;
@@ -74,38 +73,37 @@
@Before
public void setUp() {
+ setCanShowWifiHotspotCached(true);
FakeFeatureFactory.setupForTest();
mPreference = new PrimarySwitchPreference(mContext);
- when(mContext.getSystemService(Context.TETHERING_SERVICE)).thenReturn(mTetheringManager);
when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifiManager);
when(mScreen.findPreference(anyString())).thenReturn(mPreference);
mSoftApConfiguration = new SoftApConfiguration.Builder().setSsid(SSID).build();
when(mWifiManager.getSoftApConfiguration()).thenReturn(mSoftApConfiguration);
- when(mTetheringManager.getTetherableWifiRegexs()).thenReturn(new String[]{"1", "2"});
mController = new WifiTetherPreferenceController(mContext, mLifecycle, mWifiManager,
- mTetheringManager, false /* initSoftApManager */, true /* isWifiTetheringAllow */);
+ false /* initSoftApManager */, true /* isWifiTetheringAllow */);
mController.displayPreference(mScreen);
}
@Test
- public void isAvailable_noTetherRegex_shouldReturnFalse() {
- when(mTetheringManager.getTetherableWifiRegexs()).thenReturn(new String[]{});
- mController = new WifiTetherPreferenceController(mContext, mLifecycle, mWifiManager,
- mTetheringManager, false /* initSoftApManager */, true /* isWifiTetheringAllow */);
+ public void isAvailable_canNotShowWifiHotspot_shouldReturnFalse() {
+ setCanShowWifiHotspotCached(false);
assertThat(mController.isAvailable()).isFalse();
}
@Test
- public void isAvailable_hasTetherRegex_shouldReturnTrue() {
+ public void isAvailable_canShowWifiHostspot_shouldReturnTrue() {
+ setCanShowWifiHotspotCached(true);
+
assertThat(mController.isAvailable()).isTrue();
}
@Test
public void displayPreference_wifiTetheringNotAllowed_shouldDisable() {
mController = new WifiTetherPreferenceController(mContext, mLifecycle, mWifiManager,
- mTetheringManager, false /* initSoftApManager */, false /* isWifiTetheringAllow */);
+ false /* initSoftApManager */, false /* isWifiTetheringAllow */);
mController.displayPreference(mScreen);
@@ -116,7 +114,7 @@
@Test
public void displayPreference_wifiTetheringAllowed_shouldEnable() {
mController = new WifiTetherPreferenceController(mContext, mLifecycle, mWifiManager,
- mTetheringManager, false /* initSoftApManager */, true /* isWifiTetheringAllow */);
+ false /* initSoftApManager */, true /* isWifiTetheringAllow */);
mController.displayPreference(mScreen);
diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java
index d19bc90..6d3b879 100644
--- a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java
@@ -16,6 +16,8 @@
package com.android.settings.wifi.tether;
+import static com.android.settings.wifi.WifiUtils.setCanShowWifiHotspotCached;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -42,6 +44,7 @@
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
+import com.android.settings.dashboard.RestrictedDashboardFragment;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowFragment;
@@ -55,6 +58,8 @@
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
import org.robolectric.util.ReflectionHelpers;
import java.util.List;
@@ -88,6 +93,7 @@
@Before
public void setUp() {
+ setCanShowWifiHotspotCached(true);
doReturn(mWifiManager).when(mContext).getSystemService(WifiManager.class);
doReturn(mConnectivityManager)
.when(mContext).getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -101,6 +107,17 @@
}
@Test
+ @Config(shadows = ShadowRestrictedDashboardFragment.class)
+ public void onCreate_canNotShowWifiHotspot_shouldFinish() {
+ setCanShowWifiHotspotCached(false);
+ mWifiTetherSettings = spy(new WifiTetherSettings(mWifiRestriction));
+
+ mWifiTetherSettings.onCreate(null);
+
+ verify(mWifiTetherSettings).finish();
+ }
+
+ @Test
@Config(shadows = ShadowFragment.class)
public void onStart_uiIsRestricted_removeAllPreferences() {
spyWifiTetherSettings();
@@ -219,4 +236,13 @@
mWifiTetherSettings.onCreate(Bundle.EMPTY);
}
+
+ @Implements(RestrictedDashboardFragment.class)
+ public static final class ShadowRestrictedDashboardFragment {
+
+ @Implementation
+ public void onCreate(Bundle icicle) {
+ // do nothing
+ }
+ }
}
diff --git a/tests/spa_unit/Android.bp b/tests/spa_unit/Android.bp
index ed83ab2..9126c55 100644
--- a/tests/spa_unit/Android.bp
+++ b/tests/spa_unit/Android.bp
@@ -35,9 +35,12 @@
"androidx.compose.ui_ui-test-manifest",
"androidx.test.ext.junit",
"androidx.test.runner",
- "mockito-target-minus-junit4",
+ "mockito-target-inline-minus-junit4",
"truth-prebuilt",
],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ ],
kotlincflags: [
"-Xjvm-default=all",
"-opt-in=kotlin.RequiresOptIn",
diff --git a/tests/spa_unit/AndroidManifest.xml b/tests/spa_unit/AndroidManifest.xml
index 5cf8ffd..be16de3 100644
--- a/tests/spa_unit/AndroidManifest.xml
+++ b/tests/spa_unit/AndroidManifest.xml
@@ -19,7 +19,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.settings.tests.spa_unit">
- <application>
+ <application android:debuggable="true">
<provider android:name="com.android.settings.slices.SettingsSliceProvider"
android:authorities="${applicationId}.slices"
tools:replace="android:authorities"/>
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonRepositoryTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonRepositoryTest.kt
new file mode 100644
index 0000000..4e1b1b6
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonRepositoryTest.kt
@@ -0,0 +1,149 @@
+/*
+ * 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.spa.app.appinfo
+
+import android.app.ActivityManager
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import androidx.core.os.bundleOf
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class AppButtonRepositoryTest {
+
+ @JvmField
+ @Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @Spy
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Mock
+ private lateinit var packageManager: PackageManager
+
+ private lateinit var appButtonRepository: AppButtonRepository
+
+ @Before
+ fun setUp() {
+ whenever(context.packageManager).thenReturn(packageManager)
+
+ appButtonRepository = AppButtonRepository(context)
+ }
+
+ private fun mockGetHomeActivities(
+ homeActivities: List<ResolveInfo>,
+ currentDefaultHome: ComponentName? = null,
+ ) {
+ whenever(packageManager.getHomeActivities(any())).then {
+ @Suppress("UNCHECKED_CAST")
+ (it.arguments[0] as ArrayList<ResolveInfo>).addAll(homeActivities)
+ currentDefaultHome
+ }
+ }
+
+ @Test
+ fun getHomePackageInfo_empty() {
+ mockGetHomeActivities(homeActivities = emptyList())
+
+ val homePackageInfo = appButtonRepository.getHomePackageInfo()
+
+ assertThat(homePackageInfo.homePackages).isEmpty()
+ assertThat(homePackageInfo.currentDefaultHome).isNull()
+ }
+
+ @Test
+ fun getHomePackageInfo_noActivityInfo() {
+ mockGetHomeActivities(homeActivities = listOf(ResolveInfo()))
+
+ val homePackageInfo = appButtonRepository.getHomePackageInfo()
+
+ assertThat(homePackageInfo.homePackages).isEmpty()
+ assertThat(homePackageInfo.currentDefaultHome).isNull()
+ }
+
+ @Test
+ fun getHomePackageInfo_oneHome() {
+ mockGetHomeActivities(
+ homeActivities = listOf(RESOLVE_INFO),
+ currentDefaultHome = COMPONENT_NAME,
+ )
+
+ val homePackageInfo = appButtonRepository.getHomePackageInfo()
+
+ assertThat(homePackageInfo.homePackages).containsExactly(PACKAGE_NAME)
+ assertThat(homePackageInfo.currentDefaultHome).isSameInstanceAs(COMPONENT_NAME)
+ }
+
+ @Test
+ fun getHomePackageInfo_homeAlternateSignatureMatch() {
+ mockGetHomeActivities(homeActivities = listOf(RESOLVE_INFO_WITH_ALTERNATE))
+ whenever(packageManager.checkSignatures(PACKAGE_NAME_ALTERNATE, PACKAGE_NAME))
+ .thenReturn(PackageManager.SIGNATURE_MATCH)
+
+ val homePackageInfo = appButtonRepository.getHomePackageInfo()
+
+ assertThat(homePackageInfo.homePackages).containsExactly(
+ PACKAGE_NAME, PACKAGE_NAME_ALTERNATE
+ )
+ }
+
+ @Test
+ fun getHomePackageInfo_homeAlternateSignatureNoMatch() {
+ mockGetHomeActivities(homeActivities = listOf(RESOLVE_INFO_WITH_ALTERNATE))
+ whenever(packageManager.checkSignatures(PACKAGE_NAME_ALTERNATE, PACKAGE_NAME))
+ .thenReturn(PackageManager.SIGNATURE_NO_MATCH)
+
+ val homePackageInfo = appButtonRepository.getHomePackageInfo()
+
+ assertThat(homePackageInfo.homePackages).containsExactly(PACKAGE_NAME)
+ }
+
+ private companion object {
+ const val PACKAGE_NAME = "packageName"
+ const val PACKAGE_NAME_ALTERNATE = "packageName.alternate"
+ const val ACTIVITY_NAME = "activityName"
+ val COMPONENT_NAME = ComponentName(PACKAGE_NAME, ACTIVITY_NAME)
+ val RESOLVE_INFO = ResolveInfo().apply {
+ activityInfo = ActivityInfo().apply {
+ packageName = PACKAGE_NAME
+ }
+ }
+ val RESOLVE_INFO_WITH_ALTERNATE = ResolveInfo().apply {
+ activityInfo = ActivityInfo().apply {
+ packageName = PACKAGE_NAME
+ metaData = bundleOf(
+ ActivityManager.META_HOME_ALTERNATE to PACKAGE_NAME_ALTERNATE,
+ )
+ }
+ }
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreferenceTest.kt
new file mode 100644
index 0000000..a402a02
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreferenceTest.kt
@@ -0,0 +1,202 @@
+/*
+ * 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.spa.app.appinfo
+
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.content.pm.verify.domain.DomainVerificationManager
+import android.content.pm.verify.domain.DomainVerificationUserState
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.settingslib.spaprivileged.framework.common.domainVerificationManager
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.doReturn
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class AppOpenByDefaultPreferenceTest {
+ @JvmField
+ @Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Spy
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Mock
+ private lateinit var packageManager: PackageManager
+
+ @Mock
+ private lateinit var domainVerificationManager: DomainVerificationManager
+
+ @Mock
+ private lateinit var allowedUserState: DomainVerificationUserState
+
+ @Mock
+ private lateinit var notAllowedUserState: DomainVerificationUserState
+
+ @Before
+ fun setUp() {
+ whenever(context.packageManager).thenReturn(packageManager)
+ doReturn(context).`when`(context).createContextAsUser(any(), anyInt())
+ whenever(context.domainVerificationManager).thenReturn(domainVerificationManager)
+ whenever(allowedUserState.isLinkHandlingAllowed).thenReturn(true)
+ whenever(notAllowedUserState.isLinkHandlingAllowed).thenReturn(false)
+ }
+
+ @Test
+ fun instantApp_notDisplay() {
+ val instantApp = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
+ }
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ AppOpenByDefaultPreference(instantApp)
+ }
+ }
+
+ composeTestRule.onRoot().assertIsNotDisplayed()
+ }
+
+ @Test
+ fun browserApp_notDisplay() {
+ val browserApp = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
+ }
+ val resolveInfo = ResolveInfo().apply {
+ activityInfo = ActivityInfo()
+ handleAllWebDataURI = true
+ }
+ whenever(packageManager.queryIntentActivitiesAsUser(any(), anyInt(), anyInt()))
+ .thenReturn(listOf(resolveInfo))
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ AppOpenByDefaultPreference(browserApp)
+ }
+ }
+
+ composeTestRule.onRoot().assertIsNotDisplayed()
+ }
+
+ @Test
+ fun allowedUserState_alwaysOpen() {
+ whenever(domainVerificationManager.getDomainVerificationUserState(PACKAGE_NAME))
+ .thenReturn(allowedUserState)
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ AppOpenByDefaultPreference(INSTALLED_ENABLED_APP)
+ }
+ }
+
+ composeTestRule.onNodeWithText(context.getString(R.string.launch_by_default))
+ .assertIsDisplayed()
+ .assertIsEnabled()
+ composeTestRule.onNodeWithText(context.getString(R.string.app_link_open_always))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun notAllowedUserState_neverOpen() {
+ whenever(domainVerificationManager.getDomainVerificationUserState(PACKAGE_NAME))
+ .thenReturn(notAllowedUserState)
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ AppOpenByDefaultPreference(INSTALLED_ENABLED_APP)
+ }
+ }
+
+ composeTestRule.onNodeWithText(context.getString(R.string.launch_by_default))
+ .assertIsDisplayed()
+ .assertIsEnabled()
+ composeTestRule.onNodeWithText(context.getString(R.string.app_link_open_never))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun notInstalledApp_disabled() {
+ val notInstalledApp = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ }
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ AppOpenByDefaultPreference(notInstalledApp)
+ }
+ }
+
+ composeTestRule.onNodeWithText(context.getString(R.string.launch_by_default))
+ .assertIsNotEnabled()
+ }
+
+ @Test
+ fun notEnabledApp_disabled() {
+ val notEnabledApp = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ flags = ApplicationInfo.FLAG_INSTALLED
+ enabled = false
+ }
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ AppOpenByDefaultPreference(notEnabledApp)
+ }
+ }
+
+ composeTestRule.onNodeWithText(context.getString(R.string.launch_by_default))
+ .assertIsNotEnabled()
+ }
+
+ private companion object {
+ const val PACKAGE_NAME = "package name"
+
+ val INSTALLED_ENABLED_APP = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ flags = ApplicationInfo.FLAG_INSTALLED
+ enabled = true
+ }
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppStoragePreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppStoragePreferenceTest.kt
index 39c3413..47f553b 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppStoragePreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppStoragePreferenceTest.kt
@@ -68,12 +68,12 @@
}
@Test
- fun uninstalledApp_notDisplayed() {
- val uninstalledApp = ApplicationInfo()
+ fun notInstalledApp_notDisplayed() {
+ val notInstalledApp = ApplicationInfo()
composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) {
- AppStoragePreference(uninstalledApp)
+ AppStoragePreference(notInstalledApp)
}
}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreferenceTest.kt
index 56ba33a..e3fcdd9 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreferenceTest.kt
@@ -35,13 +35,11 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
import com.android.settings.testutils.FakeFeatureFactory
-import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Spy
@@ -122,15 +120,15 @@
}
@Test
- fun uninstalledApp_disabled() {
+ fun notInstalledApp_disabled() {
mockActivitiesQueryResult(listOf(MATCHED_RESOLVE_INFO))
- val uninstalledApp = ApplicationInfo().apply {
+ val notInstalledApp = ApplicationInfo().apply {
packageName = PACKAGE_NAME
}
composeTestRule.setContent {
CompositionLocalProvider(LocalContext provides context) {
- AppTimeSpentPreference(uninstalledApp)
+ AppTimeSpentPreference(notInstalledApp)
}
}
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 0000000..503af46
--- /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<Intent> 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<Intent> 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);
+ }
+}
diff --git a/tests/unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.java b/tests/unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.java
index eba45d4..88cf775 100644
--- a/tests/unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.java
+++ b/tests/unit/src/com/android/settings/network/NetworkProviderCallsSmsControllerTest.java
@@ -21,7 +21,6 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -29,6 +28,7 @@
import android.os.Looper;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import androidx.lifecycle.LifecycleOwner;
@@ -42,6 +42,7 @@
import com.android.settings.testutils.ResourcesUtils;
import com.android.settingslib.RestrictedPreference;
import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
import org.junit.Before;
import org.junit.Test;
@@ -50,58 +51,63 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
public class NetworkProviderCallsSmsControllerTest {
- private static final int SUB_ID_1 = 1;
- private static final int SUB_ID_2 = 2;
+ private static final String SUB_ID_1 = "1";
+ private static final String SUB_ID_2 = "2";
+ private static final String INVALID_SUB_ID = "-1";
private static final String KEY_PREFERENCE_CALLS_SMS = "calls_and_sms";
private static final String DISPLAY_NAME_1 = "Sub 1";
private static final String DISPLAY_NAME_2 = "Sub 2";
+ private static final String SUB_MCC_1 = "123";
+ private static final String SUB_MNC_1 = "456";
+ private static final String SUB_MCC_2 = "223";
+ private static final String SUB_MNC_2 = "456";
+ private static final String SUB_COUNTRY_ISO_1 = "Sub 1";
+ private static final String SUB_COUNTRY_ISO_2 = "Sub 2";
@Mock
- private SubscriptionManager mSubscriptionManager;
+ private SubscriptionInfoEntity mSubInfo1;
@Mock
- private SubscriptionInfo mSubscriptionInfo1;
- @Mock
- private SubscriptionInfo mSubscriptionInfo2;
+ private SubscriptionInfoEntity mSubInfo2;
@Mock
private Lifecycle mLifecycle;
@Mock
private LifecycleOwner mLifecycleOwner;
- private LifecycleRegistry mLifecycleRegistry;
+ private LifecycleRegistry mLifecycleRegistry;
private MockNetworkProviderCallsSmsController mController;
private PreferenceManager mPreferenceManager;
private PreferenceScreen mPreferenceScreen;
private RestrictedPreference mPreference;
-
private Context mContext;
+ private List<SubscriptionInfoEntity> mSubscriptionInfoEntityList = new ArrayList<>();
/**
- * Mock the NetworkProviderCallsSmsController that allows allows one to set a default voice
+ * Mock the NetworkProviderCallsSmsController that allows one to set a default voice
* and SMS subscription ID.
*/
private class MockNetworkProviderCallsSmsController extends
com.android.settings.network.NetworkProviderCallsSmsController {
- public MockNetworkProviderCallsSmsController(Context context, Lifecycle lifecycle) {
- super(context, lifecycle);
+ public MockNetworkProviderCallsSmsController(Context context, Lifecycle lifecycle,
+ LifecycleOwner lifecycleOwner) {
+ super(context, lifecycle, lifecycleOwner);
}
- private int mDefaultVoiceSubscriptionId;
- private int mDefaultSmsSubscriptionId;
+ private List<SubscriptionInfoEntity> mSubscriptionInfoEntity;
private boolean mIsInService;
- @Override
- protected int getDefaultVoiceSubscriptionId() {
- return mDefaultVoiceSubscriptionId;
- }
@Override
- protected int getDefaultSmsSubscriptionId() {
- return mDefaultSmsSubscriptionId;
+ protected List<SubscriptionInfoEntity> getSubscriptionInfoList() {
+ return mSubscriptionInfoEntity;
+ }
+
+ public void setSubscriptionInfoList(List<SubscriptionInfoEntity> list) {
+ mSubscriptionInfoEntity = list;
}
@Override
@@ -109,14 +115,6 @@
return mIsInService;
}
- public void setDefaultVoiceSubscriptionId(int subscriptionId) {
- mDefaultVoiceSubscriptionId = subscriptionId;
- }
-
- public void setDefaultSmsSubscriptionId(int subscriptionId) {
- mDefaultSmsSubscriptionId = subscriptionId;
- }
-
public void setInService(boolean inService) {
mIsInService = inService;
}
@@ -126,7 +124,6 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(ApplicationProvider.getApplicationContext());
- when(mContext.getSystemService(SubscriptionManager.class)).thenReturn(mSubscriptionManager);
if (Looper.myLooper() == null) {
Looper.prepare();
@@ -136,7 +133,8 @@
mPreferenceScreen = mPreferenceManager.createPreferenceScreen(mContext);
mPreference = new RestrictedPreference(mContext);
mPreference.setKey(KEY_PREFERENCE_CALLS_SMS);
- mController = new MockNetworkProviderCallsSmsController(mContext, mLifecycle);
+ mController = new MockNetworkProviderCallsSmsController(mContext, mLifecycle,
+ mLifecycleOwner);
mController.setInService(true);
mLifecycleRegistry = new LifecycleRegistry(mLifecycleOwner);
when(mLifecycleOwner.getLifecycle()).thenReturn(mLifecycleRegistry);
@@ -149,13 +147,6 @@
mLifecycleRegistry.handleLifecycleEvent(Event.ON_RESUME);
}
- private void setupSubscriptionInfoList(int subId, String displayName,
- SubscriptionInfo subscriptionInfo) {
- when(subscriptionInfo.getSubscriptionId()).thenReturn(subId);
- doReturn(subscriptionInfo).when(mSubscriptionManager).getActiveSubscriptionInfo(subId);
- when(subscriptionInfo.getDisplayName()).thenReturn(displayName);
- }
-
private String setSummaryResId(String resName) {
return ResourcesUtils.getResourcesString(mContext, resName);
}
@@ -163,23 +154,36 @@
@Test
@UiThreadTest
public void getSummary_noSim_returnNoSim() {
- when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(new ArrayList<>());
- when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(new ArrayList<>());
+ mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
displayPreferenceWithLifecycle();
assertTrue(TextUtils.equals(mController.getSummary(),
setSummaryResId("calls_sms_no_sim")));
}
+ private SubscriptionInfoEntity setupSubscriptionInfoEntity(String subId, int slotId,
+ int carrierId, String displayName, String mcc, String mnc, String countryIso,
+ int cardId, boolean isValid, boolean isActive, boolean isAvailable,
+ boolean isDefaultCall, boolean isDefaultSms) {
+ return new SubscriptionInfoEntity(subId, slotId, carrierId,
+ displayName, displayName, 0, mcc, mnc, countryIso, false, cardId,
+ TelephonyManager.DEFAULT_PORT_INDEX, false, null,
+ SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM, displayName, false,
+ "1234567890", true, "default", false, isValid,
+ true, isActive, isAvailable, isDefaultCall,
+ isDefaultSms, false, false);
+ }
+
@Test
@UiThreadTest
public void getSummary_invalidSubId_returnUnavailable() {
- setupSubscriptionInfoList(SubscriptionManager.INVALID_SUBSCRIPTION_ID, DISPLAY_NAME_1,
- mSubscriptionInfo1);
- when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
- Arrays.asList(mSubscriptionInfo1));
- when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
- Arrays.asList(mSubscriptionInfo1));
+
+ mSubInfo1 = setupSubscriptionInfoEntity(INVALID_SUB_ID,
+ SubscriptionManager.INVALID_SIM_SLOT_INDEX, TelephonyManager.UNKNOWN_CARRIER_ID,
+ DISPLAY_NAME_1, SUB_MCC_1, SUB_MNC_1, SUB_COUNTRY_ISO_1,
+ TelephonyManager.UNINITIALIZED_CARD_ID, false, true, true, false, false);
+ mSubscriptionInfoEntityList.add(mSubInfo1);
+ mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
displayPreferenceWithLifecycle();
final StringBuilder summary = new StringBuilder();
@@ -194,13 +198,16 @@
@Test
@UiThreadTest
public void getSummary_oneIsInvalidSubIdTwoIsValidSubId_returnOneIsUnavailable() {
- setupSubscriptionInfoList(SubscriptionManager.INVALID_SUBSCRIPTION_ID, DISPLAY_NAME_1,
- mSubscriptionInfo1);
- setupSubscriptionInfoList(SUB_ID_2, DISPLAY_NAME_2, mSubscriptionInfo2);
- when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
- Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
- when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
- Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
+
+ mSubInfo1 = setupSubscriptionInfoEntity(INVALID_SUB_ID,
+ SubscriptionManager.INVALID_SIM_SLOT_INDEX, TelephonyManager.UNKNOWN_CARRIER_ID,
+ DISPLAY_NAME_1, SUB_MCC_1, SUB_MNC_1, SUB_COUNTRY_ISO_1,
+ TelephonyManager.UNINITIALIZED_CARD_ID, false, true, true, false, false);
+ mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2,
+ SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true, false, false);
+ mSubscriptionInfoEntityList.add(mSubInfo1);
+ mSubscriptionInfoEntityList.add(mSubInfo2);
+ mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
displayPreferenceWithLifecycle();
final StringBuilder summary = new StringBuilder();
@@ -214,16 +221,14 @@
assertTrue(TextUtils.equals(mController.getSummary(), summary));
}
-
-
@Test
@UiThreadTest
public void getSummary_oneSubscription_returnDisplayName() {
- setupSubscriptionInfoList(SUB_ID_1, DISPLAY_NAME_1, mSubscriptionInfo1);
- when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
- Arrays.asList(mSubscriptionInfo1));
- when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
- Arrays.asList(mSubscriptionInfo1));
+
+ mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1,
+ SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true, false, false);
+ mSubscriptionInfoEntityList.add(mSubInfo1);
+ mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
displayPreferenceWithLifecycle();
assertThat(mPreference.getSummary()).isEqualTo(DISPLAY_NAME_1);
@@ -232,12 +237,14 @@
@Test
@UiThreadTest
public void getSummary_allSubscriptionsHaveNoPreferredStatus_returnDisplayName() {
- setupSubscriptionInfoList(SUB_ID_1, DISPLAY_NAME_1, mSubscriptionInfo1);
- setupSubscriptionInfoList(SUB_ID_2, DISPLAY_NAME_2, mSubscriptionInfo2);
- when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
- Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
- when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
- Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
+
+ mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1,
+ SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true, false, false);
+ mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2,
+ SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true, false, false);
+ mSubscriptionInfoEntityList.add(mSubInfo1);
+ mSubscriptionInfoEntityList.add(mSubInfo2);
+ mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
displayPreferenceWithLifecycle();
final StringBuilder summary = new StringBuilder();
@@ -250,15 +257,13 @@
@UiThreadTest
public void getSummary_oneSubscriptionsIsCallPreferredTwoIsSmsPreferred_returnStatus() {
- mController.setDefaultVoiceSubscriptionId(SUB_ID_1);
- mController.setDefaultSmsSubscriptionId(SUB_ID_2);
-
- setupSubscriptionInfoList(SUB_ID_1, DISPLAY_NAME_1, mSubscriptionInfo1);
- setupSubscriptionInfoList(SUB_ID_2, DISPLAY_NAME_2, mSubscriptionInfo2);
- when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
- Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
- when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
- Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
+ mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1,
+ SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true, true, false);
+ mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2,
+ SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true, false, true);
+ mSubscriptionInfoEntityList.add(mSubInfo1);
+ mSubscriptionInfoEntityList.add(mSubInfo2);
+ mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
displayPreferenceWithLifecycle();
final StringBuilder summary = new StringBuilder();
@@ -279,15 +284,13 @@
@UiThreadTest
public void getSummary_oneSubscriptionsIsSmsPreferredTwoIsCallPreferred_returnStatus() {
- mController.setDefaultVoiceSubscriptionId(SUB_ID_2);
- mController.setDefaultSmsSubscriptionId(SUB_ID_1);
-
- setupSubscriptionInfoList(SUB_ID_1, DISPLAY_NAME_1, mSubscriptionInfo1);
- setupSubscriptionInfoList(SUB_ID_2, DISPLAY_NAME_2, mSubscriptionInfo2);
- when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
- Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
- when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
- Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
+ mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1,
+ SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true, false, true);
+ mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2,
+ SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true, true, false);
+ mSubscriptionInfoEntityList.add(mSubInfo1);
+ mSubscriptionInfoEntityList.add(mSubInfo2);
+ mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
displayPreferenceWithLifecycle();
final StringBuilder summary = new StringBuilder();
@@ -308,15 +311,13 @@
@UiThreadTest
public void getSummary_oneSubscriptionsIsSmsPreferredAndIsCallPreferred_returnStatus() {
- mController.setDefaultVoiceSubscriptionId(SUB_ID_1);
- mController.setDefaultSmsSubscriptionId(SUB_ID_1);
-
- setupSubscriptionInfoList(SUB_ID_1, DISPLAY_NAME_1, mSubscriptionInfo1);
- setupSubscriptionInfoList(SUB_ID_2, DISPLAY_NAME_2, mSubscriptionInfo2);
- when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
- Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
- when(mSubscriptionManager.getAvailableSubscriptionInfoList()).thenReturn(
- Arrays.asList(mSubscriptionInfo1, mSubscriptionInfo2));
+ mSubInfo1 = setupSubscriptionInfoEntity(SUB_ID_1, 1, 1, DISPLAY_NAME_1, SUB_MCC_1,
+ SUB_MNC_1, SUB_COUNTRY_ISO_1, 1, true, true, true, true, true);
+ mSubInfo2 = setupSubscriptionInfoEntity(SUB_ID_2, 1, 1, DISPLAY_NAME_2, SUB_MCC_2,
+ SUB_MNC_2, SUB_COUNTRY_ISO_2, 1, true, true, true, false, false);
+ mSubscriptionInfoEntityList.add(mSubInfo1);
+ mSubscriptionInfoEntityList.add(mSubInfo2);
+ mController.setSubscriptionInfoList(mSubscriptionInfoEntityList);
displayPreferenceWithLifecycle();
final StringBuilder summary = new StringBuilder();
diff --git a/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java b/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java
index 4282b3e6..9a2c611 100644
--- a/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java
+++ b/tests/unit/src/com/android/settings/network/UiccSlotUtilTest.java
@@ -638,6 +638,106 @@
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<UiccSlotMapping> testUiccSlotMappings,
Collection<UiccSlotMapping> verifyUiccSlotMappings) {
assertThat(testUiccSlotMappings.size()).isEqualTo(verifyUiccSlotMappings.size());
@@ -792,6 +892,10 @@
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),
@@ -810,6 +914,30 @@
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),
@@ -872,4 +1000,20 @@
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 */)));
+ }
}
diff --git a/tests/unit/src/com/android/settings/wifi/WifiUtilsTest.java b/tests/unit/src/com/android/settings/wifi/WifiUtilsTest.java
index 1a5e852..2826310 100644
--- a/tests/unit/src/com/android/settings/wifi/WifiUtilsTest.java
+++ b/tests/unit/src/com/android/settings/wifi/WifiUtilsTest.java
@@ -21,19 +21,53 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.TetheringManager;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.android.settings.R;
import com.android.wifitrackerlib.WifiEntry;
+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;
@RunWith(AndroidJUnit4.class)
public class WifiUtilsTest {
+ static final String[] WIFI_REGEXS = {"wifi_regexs"};
+
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Spy
+ Context mContext = ApplicationProvider.getApplicationContext();
+ @Mock
+ Resources mResources;
+ @Mock
+ WifiManager mWifiManager;
+ @Mock
+ TetheringManager mTetheringManager;
+
+ @Before
+ public void setUp() {
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getBoolean(R.bool.config_show_wifi_hotspot_settings)).thenReturn(true);
+ when(mContext.getSystemService(WifiManager.class)).thenReturn(mWifiManager);
+ when(mContext.getSystemService(TetheringManager.class)).thenReturn(mTetheringManager);
+ when(mTetheringManager.getTetherableWifiRegexs()).thenReturn(WIFI_REGEXS);
+ }
+
@Test
public void testSSID() {
assertThat(WifiUtils.isSSIDTooLong("123")).isFalse();
@@ -108,4 +142,53 @@
WifiConfiguration config = WifiUtils.getWifiConfig(null /* wifiEntry */,
null /* scanResult */);
}
+
+ @Test
+ public void checkShowWifiHotspot_allReady_returnTrue() {
+ assertThat(WifiUtils.checkShowWifiHotspot(mContext)).isTrue();
+ }
+
+ @Test
+ public void checkShowWifiHotspot_contextIsNull_returnFalse() {
+ assertThat(WifiUtils.checkShowWifiHotspot(null)).isFalse();
+ }
+
+ @Test
+ public void checkShowWifiHotspot_configIsNotShow_returnFalse() {
+ when(mResources.getBoolean(R.bool.config_show_wifi_hotspot_settings)).thenReturn(false);
+
+ assertThat(WifiUtils.checkShowWifiHotspot(mContext)).isFalse();
+ }
+
+ @Test
+ public void checkShowWifiHotspot_wifiManagerIsNull_returnFalse() {
+ when(mContext.getSystemService(WifiManager.class)).thenReturn(null);
+
+ assertThat(WifiUtils.checkShowWifiHotspot(mContext)).isFalse();
+ }
+
+ @Test
+ public void checkShowWifiHotspot_tetheringManagerIsNull_returnFalse() {
+ when(mContext.getSystemService(TetheringManager.class)).thenReturn(null);
+
+ assertThat(WifiUtils.checkShowWifiHotspot(mContext)).isFalse();
+ }
+
+ @Test
+ public void checkShowWifiHotspot_wifiRegexsIsEmpty_returnFalse() {
+ when(mTetheringManager.getTetherableWifiRegexs()).thenReturn(null);
+
+ assertThat(WifiUtils.checkShowWifiHotspot(mContext)).isFalse();
+ }
+
+ @Test
+ public void canShowWifiHotspot_cachedIsReady_returnCached() {
+ WifiUtils.setCanShowWifiHotspotCached(true);
+
+ assertThat(WifiUtils.canShowWifiHotspot(null)).isTrue();
+
+ WifiUtils.setCanShowWifiHotspotCached(false);
+
+ assertThat(WifiUtils.canShowWifiHotspot(null)).isFalse();
+ }
}