Merge "[Audiosharing] Set visibility by active device." into main
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2b49148..19927a2 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -138,6 +138,7 @@
<uses-permission android:name="android.permission.CUSTOMIZE_SYSTEM_UI" />
<uses-permission android:name="android.permission.REMAP_MODIFIER_KEYS" />
<uses-permission android:name="android.permission.ACCESS_GPU_SERVICE" />
+ <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:name=".SettingsApplication"
@@ -491,6 +492,17 @@
android:value="@string/menu_key_display"/>
</activity>
+ <activity android:name=".Settings$ScreenTimeoutActivity"
+ android:label="@string/screen_timeout"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.settings.SCREEN_TIMEOUT_SETTINGS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+ android:value="com.android.settings.display.ScreenTimeoutSettings"/>
+ </activity>
+
<activity
android:name="Settings$ConfigureWifiSettingsActivity"
android:label="@string/wifi_configure_settings_preference_title"
diff --git a/aconfig/settings_telephony_flag_declarations.aconfig b/aconfig/settings_telephony_flag_declarations.aconfig
index 68e313b..0999a7b 100644
--- a/aconfig/settings_telephony_flag_declarations.aconfig
+++ b/aconfig/settings_telephony_flag_declarations.aconfig
@@ -6,3 +6,10 @@
description: "Stop honoring CarrierConfigManager.KEY_HIDE_ENABLE_2G. Allow 2G toggle cannot be hidden from users by carriers."
bug: "300248708"
}
+
+flag {
+ name: "is_dual_sim_onboarding_enabled"
+ namespace: "settings_experience"
+ description: "Control the Dual SIM onobarding feature"
+ bug: "298898436"
+}
diff --git a/res/values/config.xml b/res/values/config.xml
index c30a047..d084ff9 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -258,9 +258,6 @@
com.android.settings.intelligence
</string>
- <!-- Whether the confirmation for sim deletion is defaulted to be on or off-->
- <bool name="config_sim_deletion_confirmation_default_on">false</bool>
-
<!-- Package Installer package name -->
<string name="config_package_installer_package_name" translatable="false">
com.android.packageinstaller
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 33cae6d..01f2525 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3898,6 +3898,8 @@
<string name="force_stop">Force stop</string>
<!-- Manage applications, text label for button to archive an application. Archiving means uninstalling the app without deleting user's personal data and replacing the app with a stub app with minimum size. So, the user can unarchive the app later and not lose any personal data. -->
<string name="archive">Archive</string>
+ <!-- Manage applications, text label for button to restore an application. Restoring means installing the archived app. -->
+ <string name="restore">Restore</string>
<!-- Manage applications, individual application info screen,label under Storage heading. The total storage space taken up by this app. -->
<string name="total_size_label">Total</string>
<!-- Manage applications, individual application info screen, label under Storage heading. The amount of space taken up by the application itself (for example, the java compield files and things like that) -->
@@ -4012,6 +4014,12 @@
<string name="archiving_failed">Archiving failed</string>
<!-- Toast message when archiving an app succeeded. -->
<string name="archiving_succeeded">Archived <xliff:g id="package_label" example="Translate">%1$s</xliff:g></string>
+ <!-- Toast message when restoring an app failed. -->
+ <string name="restoring_failed">Restoring failed</string>
+ <!-- Toast message when restoring an app succeeded. -->
+ <string name="restoring_succeeded">Restored <xliff:g id="package_label" example="Translate">%1$s</xliff:g></string>
+ <!-- Toast message when restoring an app has started. -->
+ <string name="restoring_in_progress">Restoring <xliff:g id="package_label" example="Translate">%1$s</xliff:g></string>
<!-- Text of pop up message if the request for a "migrate primary storage" operation
(see storage_menu_migrate) is denied as another is already in progress. [CHAR LIMIT=75] -->
@@ -9107,6 +9115,12 @@
<!-- Summary of the switch preference that controls whether the system will pause app activity when the app has not been used for months [CHAR LIMIT=NONE]-->
<string name="unused_apps_switch_summary">Remove permissions, delete temporary files, and stop notifications</string>
+ <!-- Label of a switch preference that controls whether the system will pause app activity when the app has not been used for a while [CHAR LIMIT=40]-->
+ <string name="unused_apps_switch_v2">Manage app if unused</string>
+
+ <!-- Summary of the switch preference that controls whether the system will pause app activity when the app has not been used for a while [CHAR LIMIT=NONE]-->
+ <string name="unused_apps_switch_summary_v2">Remove permissions, delete temporary files, stop notifications, and archive the app</string>
+
<!-- Label for showing all apps in list [CHAR LIMIT=30] -->
<string name="filter_all_apps">All apps</string>
<!-- Label for showing enabled apps in list [CHAR LIMIT=30] -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 2c928ff..8df990b 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -438,11 +438,10 @@
</style>
<style name="DeviceAudioSharingText">
- <item name="android:textAlignment">viewStart</item>
+ <item name="android:textAlignment">center</item>
<item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
- <item name="android:paddingBottom">24dp</item>
</style>
<style name="ContextualCardStyle">
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index f0a2881..e1ccad8 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -727,10 +727,11 @@
android:title="@string/enable_non_resizable_multi_window"
android:summary="@string/enable_non_resizable_multi_window_summary" />
- <SwitchPreferenceCompat
- android:key="back_navigation_animation"
- android:title="@string/back_navigation_animation"
- android:summary="@string/back_navigation_animation_summary" />
+ // TODO(b/315859328): Temporally removed since causing search indexing failure.
+<!-- <SwitchPreferenceCompat-->
+<!-- android:key="back_navigation_animation"-->
+<!-- android:title="@string/back_navigation_animation"-->
+<!-- android:summary="@string/back_navigation_animation_summary" />-->
<Preference
android:key="reset_shortcut_manager_throttling"
diff --git a/res/xml/more_security_privacy_settings.xml b/res/xml/more_security_privacy_settings.xml
index 822b03d..5cd60f0 100644
--- a/res/xml/more_security_privacy_settings.xml
+++ b/res/xml/more_security_privacy_settings.xml
@@ -204,14 +204,6 @@
settings:keywords="@string/keywords_app_pinning"
settings:controller="com.android.settings.security.ScreenPinningPreferenceController" />
- <SwitchPreferenceCompat
- android:order="290"
- android:key="confirm_sim_deletion"
- android:title="@string/confirm_sim_deletion_title"
- android:summary="@string/confirm_sim_deletion_description"
- settings:isPreferenceVisible="@bool/config_show_sim_info"
- settings:controller="com.android.settings.security.ConfirmSimDeletionPreferenceController" />
-
<Preference
android:order="300"
android:id="@+id/memtag_page"
diff --git a/res/xml/security_advanced_settings.xml b/res/xml/security_advanced_settings.xml
index 89834b4..c220e99 100644
--- a/res/xml/security_advanced_settings.xml
+++ b/res/xml/security_advanced_settings.xml
@@ -106,14 +106,6 @@
settings:keywords="@string/keywords_app_pinning"
settings:controller="com.android.settings.security.ScreenPinningPreferenceController" />
- <SwitchPreferenceCompat
- android:order="290"
- android:key="confirm_sim_deletion"
- android:title="@string/confirm_sim_deletion_title"
- android:summary="@string/confirm_sim_deletion_description"
- settings:isPreferenceVisible="@bool/config_show_sim_info"
- settings:controller="com.android.settings.security.ConfirmSimDeletionPreferenceController" />
-
<com.android.settingslib.RestrictedPreference
android:order="300"
android:id="@+id/memtag_page"
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 68ae0ae..86baba4 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -478,4 +478,6 @@
public static class OneHandedSettingsActivity extends SettingsActivity { /* empty */ }
public static class PreviouslyConnectedDeviceActivity extends SettingsActivity { /* empty */ }
+
+ public static class ScreenTimeoutActivity extends SettingsActivity { /* empty */ }
}
diff --git a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java
index dfa2f33..6f21fb8 100644
--- a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java
+++ b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java
@@ -234,6 +234,32 @@
// permittedServices null means all accessibility services are allowed.
boolean serviceAllowed = permittedServices == null || permittedServices.contains(
preference.getPackageName());
+
+ if (android.security.Flags.extendEcmToAllSettings()) {
+ preference.checkEcmRestrictionAndSetDisabled(
+ AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE,
+ preference.getPackageName(), preference.getUid());
+ if (preference.isDisabledByEcm()) {
+ serviceAllowed = false;
+ }
+
+ if (serviceAllowed || serviceEnabled) {
+ preference.setEnabled(true);
+ } else {
+ // Disable accessibility service that are not permitted.
+ final RestrictedLockUtils.EnforcedAdmin admin =
+ RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed(
+ mContext, preference.getPackageName(), UserHandle.myUserId());
+
+ if (admin != null) {
+ preference.setDisabledByAdmin(admin);
+ } else if (!preference.isDisabledByEcm()) {
+ preference.setEnabled(false);
+ }
+ }
+ return;
+ }
+
boolean appOpsAllowed;
if (serviceAllowed) {
try {
diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java
index e3816bf..fb78e3e 100644
--- a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java
+++ b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java
@@ -41,6 +41,8 @@
private PreferenceFragmentCompat mParent;
private NotificationManager mNm;
private PackageManager mPm;
+ // The appOp representing this preference
+ private String mAppOpStr;
public ApprovalPreferenceController(Context context, String key) {
super(context, key);
@@ -71,6 +73,14 @@
return this;
}
+ /**
+ * Set the associated appOp for the Setting
+ */
+ public ApprovalPreferenceController setAppOpStr(String appOpStr) {
+ mAppOpStr = appOpStr;
+ return this;
+ }
+
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
@@ -107,8 +117,20 @@
return false;
}
});
- preference.updateState(
- mCn.getPackageName(), mPkgInfo.applicationInfo.uid, isAllowedCn, isEnabled);
+
+ if (android.security.Flags.extendEcmToAllSettings()) {
+ if (!isAllowedCn && !isEnabled) {
+ preference.setEnabled(false);
+ } else if (isEnabled) {
+ preference.setEnabled(true);
+ } else {
+ preference.checkEcmRestrictionAndSetDisabled(mAppOpStr,
+ mCn.getPackageName(), mPkgInfo.applicationInfo.uid);
+ }
+ } else {
+ preference.updateState(
+ mCn.getPackageName(), mPkgInfo.applicationInfo.uid, isAllowedCn, isEnabled);
+ }
}
public void disable(final ComponentName cn) {
diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
index 17dabe4..89767dd 100644
--- a/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
+++ b/src/com/android/settings/applications/specialaccess/notificationaccess/NotificationAccessDetails.java
@@ -21,6 +21,7 @@
import static com.android.settings.applications.AppInfoBase.ARG_PACKAGE_NAME;
import android.Manifest;
+import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.settings.SettingsEnums;
import android.companion.ICompanionDeviceManager;
@@ -102,6 +103,7 @@
.setCn(mComponentName)
.setNm(context.getSystemService(NotificationManager.class))
.setPm(mPm)
+ .setAppOpStr(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS)
.setParent(this);
use(HeaderPreferenceController.class)
.setFragment(this)
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
index b71330a..059173c 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java
@@ -290,6 +290,17 @@
}
@Override
+ protected Intent getFingerprintEnrollingIntent() {
+ final Intent ret = super.getFingerprintEnrollingIntent();
+ if (Flags.udfpsEnrollCalibration()) {
+ if (mCalibrator != null) {
+ ret.putExtras(mCalibrator.getExtrasForNextIntent(true));
+ }
+ }
+ return ret;
+ }
+
+ @Override
public void onBackPressed() {
stopLookingForFingerprint();
super.onBackPressed();
diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
index bd52b64..aef3c06 100644
--- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
+++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java
@@ -385,7 +385,7 @@
}
if (Flags.udfpsEnrollCalibration()) {
if (mCalibrator != null) {
- intent.putExtras(mCalibrator.getExtrasForNextIntent());
+ intent.putExtras(mCalibrator.getExtrasForNextIntent(false));
}
}
return intent;
diff --git a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt
index 9809bcc..c54c6b5 100644
--- a/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt
+++ b/src/com/android/settings/biometrics/fingerprint/UdfpsEnrollCalibrator.kt
@@ -6,7 +6,7 @@
interface UdfpsEnrollCalibrator {
- val extrasForNextIntent: Bundle
+ fun getExtrasForNextIntent(isEnrolling: Boolean): Bundle
fun onSaveInstanceState(outState: Bundle)
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java
index e60eabd..5c0a90a 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java
@@ -51,9 +51,10 @@
public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) {
boolean isFilterMatched = false;
if (isDeviceConnected(cachedDevice) && isDeviceInCachedDevicesList(cachedDevice)) {
- // If device is LE audio device and has a broadcast source,
- // it would show in audio sharing devices group.
+ // If device is LE audio device and in a sharing session on current sharing device,
+ // it would show in volume control group.
if (cachedDevice.isConnectedLeAudioDevice()
+ && AudioSharingUtils.isBroadcasting(mLocalBtManager)
&& AudioSharingUtils.hasBroadcastSource(cachedDevice, mLocalBtManager)) {
isFilterMatched = true;
}
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index 6d1d4e8..d68f2c8 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -100,6 +100,7 @@
import com.android.settings.deviceinfo.legal.ModuleLicensesDashboard;
import com.android.settings.display.AutoBrightnessSettings;
import com.android.settings.display.NightDisplaySettings;
+import com.android.settings.display.ScreenTimeoutSettings;
import com.android.settings.display.SmartAutoRotatePreferenceFragment;
import com.android.settings.display.darkmode.DarkModeSettingsFragment;
import com.android.settings.dream.DreamSettings;
@@ -369,7 +370,8 @@
LongBackgroundTasksDetails.class.getName(),
RegionalPreferencesEntriesFragment.class.getName(),
BatteryInfoFragment.class.getName(),
- UserAspectRatioDetails.class.getName()
+ UserAspectRatioDetails.class.getName(),
+ ScreenTimeoutSettings.class.getName(),
};
public static final String[] SETTINGS_FOR_RESTRICTED = {
diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java
index 3321d50..0348e11 100644
--- a/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java
+++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectFragment.java
@@ -130,7 +130,6 @@
if (titleResId > 0) {
activity.setTitle(titleResId);
}
- final int selectedTab = getTabId(activity, getArguments());
final View tabContainer = mContentView.findViewById(R.id.tab_container);
mViewPager = tabContainer.findViewById(R.id.view_pager);
@@ -149,6 +148,7 @@
}
);
tabContainer.setVisibility(View.VISIBLE);
+ final int selectedTab = getTabId(activity, getArguments());
final TabLayout.Tab tab = tabs.getTabAt(selectedTab);
tab.select();
@@ -228,7 +228,7 @@
if (bundle != null) {
final int extraTab = bundle.getInt(SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB, -1);
if (extraTab != -1) {
- return extraTab;
+ return ((ViewPagerAdapter) mViewPager.getAdapter()).getTabForPosition(extraTab);
}
final int userId = bundle.getInt(EXTRA_USER_ID, UserHandle.SYSTEM.getIdentifier());
final boolean isWorkProfile = UserManager.get(activity).isManagedProfile(userId);
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 4c8b2dc..a483f9f 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -49,6 +49,7 @@
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Toast;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
@@ -606,8 +607,9 @@
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
- Activity activity, Lifecycle lifecycle, DevelopmentSettingsDashboardFragment fragment,
- BluetoothA2dpConfigStore bluetoothA2dpConfigStore) {
+ @Nullable Activity activity, @Nullable Lifecycle lifecycle,
+ @Nullable DevelopmentSettingsDashboardFragment fragment,
+ @Nullable BluetoothA2dpConfigStore bluetoothA2dpConfigStore) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new MemoryUsagePreferenceController(context));
controllers.add(new BugReportPreferenceController(context));
@@ -735,7 +737,7 @@
controllers.add(new OverlaySettingsPreferenceController(context));
controllers.add(new StylusHandwritingPreferenceController(context));
controllers.add(new IngressRateLimitPreferenceController((context)));
- controllers.add(new BackAnimationPreferenceController(context, fragment));
+ // controllers.add(new BackAnimationPreferenceController(context, fragment));
controllers.add(new PhantomProcessPreferenceController(context));
controllers.add(new ContrastPreferenceController(
context, context.getSystemService(UiModeManager.class)));
diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
index a2ee3e4..0bc6176 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
@@ -22,6 +22,7 @@
import android.util.ArrayMap;
import android.util.SparseIntArray;
+import com.android.settings.fuelgauge.batteryusage.DetectRequestSourceType;
import com.android.settings.fuelgauge.batteryusage.PowerAnomalyEventList;
import com.android.settingslib.fuelgauge.Estimate;
@@ -103,11 +104,9 @@
/** Returns {@code true} if delay the hourly job when device is booting */
boolean delayHourlyJobWhenBooting();
- /** Insert settings configuration data for anomaly detection */
- void insertSettingsData(Context context, double displayDrain);
-
/** Returns {@link Bundle} for settings anomaly detection result */
- PowerAnomalyEventList detectSettingsAnomaly(Context context, double displayDrain);
+ PowerAnomalyEventList detectSettingsAnomaly(
+ Context context, double displayDrain, DetectRequestSourceType detectRequestSourceType);
/** Gets an intent for one time bypass charge limited to resume charging. */
Intent getResumeChargeIntent(boolean isDockDefender);
diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
index 2e2cf12..a8a2f75 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
@@ -27,6 +27,7 @@
import android.util.SparseIntArray;
import com.android.internal.util.ArrayUtils;
+import com.android.settings.fuelgauge.batteryusage.DetectRequestSourceType;
import com.android.settings.fuelgauge.batteryusage.PowerAnomalyEventList;
import com.android.settingslib.fuelgauge.Estimate;
@@ -168,10 +169,8 @@
}
@Override
- public void insertSettingsData(Context context, double displayDrain) {}
-
- @Override
- public PowerAnomalyEventList detectSettingsAnomaly(Context context, double displayDrain) {
+ public PowerAnomalyEventList detectSettingsAnomaly(
+ Context context, double displayDrain, DetectRequestSourceType detectRequestSourceType) {
return null;
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
index ead580b..fb5b9a1 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
@@ -135,7 +135,10 @@
.isEmpty()))) {
FeatureFactory.getFeatureFactory()
.getPowerUsageFeatureProvider()
- .detectSettingsAnomaly(context, /* displayDrain= */ 0);
+ .detectSettingsAnomaly(
+ context,
+ /* displayDrain= */ 0,
+ DetectRequestSourceType.TYPE_DATA_LOADER);
}
});
if (batteryLevelData == null) {
diff --git a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
index 072040d..1482117 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/PowerUsageAdvanced.java
@@ -263,7 +263,9 @@
FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider();
final PowerAnomalyEventList anomalyEventList =
powerUsageFeatureProvider.detectSettingsAnomaly(
- getContext(), /* displayDrain= */ 0);
+ getContext(),
+ /* displayDrain= */ 0,
+ DetectRequestSourceType.TYPE_USAGE_UI);
mHandler.post(() -> onAnomalyDetected(anomalyEventList));
});
}
diff --git a/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto b/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
index 930a21b..3c0705f 100644
--- a/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
+++ b/src/com/android/settings/fuelgauge/protos/power_anomaly_event.proto
@@ -21,18 +21,12 @@
optional string dismiss_record_key = 8;
}
-// NOTE: Please DO NOT delete enum items or change enum values. Use [deprecated = true] instead.
-// The enum value will be used to decide the tips card style like icons and colors.
-//
// Next id: 2
enum PowerAnomalyType{
TYPE_SETTINGS_BANNER = 0;
TYPE_APPS_ITEM = 1;
}
-// NOTE: Please DO NOT delete enum items or change enum values. Use [deprecated = true] instead.
-// The enum value will be used to decide pre-defined title and button labels.
-//
// Next id: 8
enum PowerAnomalyKey{
KEY_BRIGHTNESS = 0;
@@ -45,6 +39,13 @@
KEY_APP_FOREGROUND_HIGHER_THAN_USUAL = 7;
}
+// Next id: 3
+enum DetectRequestSourceType{
+ TYPE_UNKNOWN_SOURCE = 0;
+ TYPE_USAGE_UI = 1;
+ TYPE_DATA_LOADER = 2;
+}
+
message WarningBannerInfo {
optional string title_string = 1;
optional string description_string = 2;
diff --git a/src/com/android/settings/network/EraseEuiccDataDialogFragment.java b/src/com/android/settings/network/EraseEuiccDataDialogFragment.java
index 0200e52..3bf9db3 100644
--- a/src/com/android/settings/network/EraseEuiccDataDialogFragment.java
+++ b/src/com/android/settings/network/EraseEuiccDataDialogFragment.java
@@ -32,7 +32,6 @@
import com.android.settings.R;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
-import com.android.settings.network.helper.ConfirmationSimDeletionPredicate;
import com.android.settings.system.ResetDashboardFragment;
import com.android.settings.wifi.dpp.WifiDppUtils;
@@ -76,14 +75,7 @@
if (which == DialogInterface.BUTTON_POSITIVE) {
Context context = getContext();
- if (ConfirmationSimDeletionPredicate.getSingleton().test(context)) {
- // Create a "verify it's you" verification over keyguard
- // when "erase" button been pressed.
- // This might protect from erasing by some automation process.
- WifiDppUtils.showLockScreen(context, () -> runAsyncWipe(context));
- } else {
- runAsyncWipe(context);
- }
+ WifiDppUtils.showLockScreen(context, () -> runAsyncWipe(context));
}
}
diff --git a/src/com/android/settings/network/helper/ConfirmationSimDeletionPredicate.java b/src/com/android/settings/network/helper/ConfirmationSimDeletionPredicate.java
deleted file mode 100644
index 420f6db..0000000
--- a/src/com/android/settings/network/helper/ConfirmationSimDeletionPredicate.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2021 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.network.helper;
-
-import android.app.KeyguardManager;
-import android.content.Context;
-import android.provider.Settings;
-
-import com.android.settings.R;
-
-import java.util.function.Predicate;
-
-/**
- * {@link Predicate} for detecting the configuration of confirm SIM deletion.
- */
-public class ConfirmationSimDeletionPredicate implements Predicate<Context> {
-
- public static final String KEY_CONFIRM_SIM_DELETION = "confirm_sim_deletion";
-
- private static final ConfirmationSimDeletionPredicate sSingleton =
- new ConfirmationSimDeletionPredicate();
-
- // Get singleton of this predicate
- public static final ConfirmationSimDeletionPredicate getSingleton() {
- return sSingleton;
- }
-
- /**
- * Get default configuration of confirm SIM deletion.
- *
- * @param Context context
- * @return the configuration of confirm SIM deletion
- */
- private static boolean getDefaultValue(Context context) {
- return context.getResources()
- .getBoolean(R.bool.config_sim_deletion_confirmation_default_on);
- }
-
- /**
- * Get the configuration of confirm SIM deletion.
- *
- * @param Context context
- * @return the configuration of confirm SIM deletion
- */
- public boolean test(Context context) {
- final KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
- if ((keyguardManager != null) && !keyguardManager.isKeyguardSecure()) {
- return false;
- }
- return Settings.Global.getInt(context.getContentResolver(), KEY_CONFIRM_SIM_DELETION,
- getDefaultValue(context) ? 1 : 0) == 1;
- }
-}
diff --git a/src/com/android/settings/network/telephony/ConvertToEsimPreferenceController.java b/src/com/android/settings/network/telephony/ConvertToEsimPreferenceController.java
index a17144f..799543f 100644
--- a/src/com/android/settings/network/telephony/ConvertToEsimPreferenceController.java
+++ b/src/com/android/settings/network/telephony/ConvertToEsimPreferenceController.java
@@ -43,6 +43,7 @@
import com.android.internal.telephony.util.TelephonyUtils;
import com.android.settings.network.MobileNetworkRepository;
+import com.android.settings.network.SubscriptionUtil;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.mobile.dataservice.SubscriptionInfoEntity;
@@ -98,10 +99,21 @@
@Override
public int getAvailabilityStatus(int subId) {
- // TODO(b/262195754): Need the intent to enabled the feature.
+ // TODO(b/315073761) : Add a new API to set whether the profile has been
+ // converted/transferred. Remove any confusion to the user according to the set value.
+
+ /*
+ * If pSIM is set to preferred SIM and there is an active eSIM, convert the pSIM to eSIM
+ * and then disable the pSIM.
+ * This causes a dialog to switch the preferred SIM to downloaded new eSIM.
+ * This may cause confusion for the user about the seamless conversion.
+ * To avoid showing users dialogs that can cause confusion,
+ * add conditions to allow conversion in the absence of active eSIM.
+ */
if (findConversionSupportComponent()) {
return mSubscriptionInfoEntity != null && mSubscriptionInfoEntity.isActiveSubscriptionId
&& !mSubscriptionInfoEntity.isEmbedded && isActiveSubscription(subId)
+ && !hasActiveEsimProfiles()
? AVAILABLE
: CONDITIONALLY_UNAVAILABLE;
}
@@ -135,7 +147,6 @@
@Override
public void onActiveSubInfoChanged(List<SubscriptionInfoEntity> subInfoEntityList) {
- // TODO(b/262195754): Need the intent to enabled the feature.
mSubscriptionInfoEntityList = subInfoEntityList;
mSubscriptionInfoEntityList.forEach(entity -> {
if (Integer.parseInt(entity.subId) == mSubId) {
@@ -155,6 +166,24 @@
return true;
}
+ private boolean hasActiveEsimProfiles() {
+ SubscriptionManager subscriptionManager = mContext.getSystemService(
+ SubscriptionManager.class);
+ List<SubscriptionInfo> subscriptionInfoList =
+ SubscriptionUtil.getActiveSubscriptions(subscriptionManager);
+ if (subscriptionInfoList == null || subscriptionInfoList.isEmpty()) {
+ return false;
+ }
+ int activatedEsimCount = (int) subscriptionInfoList
+ .stream()
+ .filter(SubscriptionInfo::isEmbedded)
+ .count();
+ if (activatedEsimCount > 0) {
+ return true;
+ }
+ return false;
+ }
+
private boolean findConversionSupportComponent() {
Intent intent = new Intent(EuiccService.ACTION_CONVERT_TO_EMBEDDED_SUBSCRIPTION);
PackageManager packageManager = mContext.getPackageManager();
diff --git a/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt b/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
index 093c4bf..64f9730 100644
--- a/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
+++ b/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceController.kt
@@ -26,10 +26,8 @@
import com.android.settings.R
import com.android.settings.core.BasePreferenceController
import com.android.settings.network.SubscriptionUtil
-import com.android.settings.security.ConfirmSimDeletionPreferenceController.KEY_CONFIRM_SIM_DELETION
import com.android.settings.wifi.dpp.WifiDppUtils
import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle
-import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBoolean
/** This controls a preference allowing the user to delete the profile for an eSIM. */
class DeleteSimProfilePreferenceController(context: Context, preferenceKey: String) :
@@ -63,16 +61,8 @@
override fun handlePreferenceTreeClick(preference: Preference): Boolean {
if (preference.key != preferenceKey) return false
- val confirmDeletion by mContext.settingsGlobalBoolean(
- name = KEY_CONFIRM_SIM_DELETION,
- defaultValue = mContext.resources
- .getBoolean(R.bool.config_sim_deletion_confirmation_default_on),
- )
- if (confirmDeletion) {
- WifiDppUtils.showLockScreen(mContext) { deleteSim() }
- } else {
- deleteSim()
- }
+ WifiDppUtils.showLockScreen(mContext) { deleteSim() }
+
return true
}
diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
index a0db4ce..2a299c5 100644
--- a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
+++ b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java
@@ -43,6 +43,7 @@
import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.StorageManager;
import android.util.Log;
import android.view.WindowManager;
@@ -292,7 +293,7 @@
// use, which optionally accepts a challenge.
mForceVerifyPath = true;
if (isBiometricAllowed(effectiveUserId, mUserId)) {
- showBiometricPrompt(promptInfo);
+ showBiometricPrompt(promptInfo, mUserId);
launchedBiometric = true;
} else {
showConfirmCredentials();
@@ -302,19 +303,25 @@
&& userProperties != null
&& userProperties.isAuthAlwaysRequiredToDisableQuietMode()
&& isInternalActivity()) {
- // Force verification path is required to be invoked as we might need to verify the tied
- // profile challenge if the profile is using the unified challenge mode. This would
- // result in ConfirmLockPassword.startVerifyPassword/
+ // Force verification path is required to be invoked as we might need to verify the
+ // tied profile challenge if the profile is using the unified challenge mode. This
+ // would result in ConfirmLockPassword.startVerifyPassword/
// ConfirmLockPattern.startVerifyPattern being called instead of the
// startCheckPassword/startCheckPattern
mForceVerifyPath = userProperties.isCredentialShareableWithParent();
- showConfirmCredentials();
- launchedCDC = true;
+ if (android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+ && isBiometricAllowed(effectiveUserId, mUserId)) {
+ showBiometricPrompt(promptInfo, effectiveUserId);
+ launchedBiometric = true;
+ } else {
+ showConfirmCredentials();
+ launchedCDC = true;
+ }
} else {
if (isBiometricAllowed(effectiveUserId, mUserId)) {
// Don't need to check if biometrics / pin/pattern/pass are enrolled. It will go to
// onAuthenticationError and do the right thing automatically.
- showBiometricPrompt(promptInfo);
+ showBiometricPrompt(promptInfo, mUserId);
launchedBiometric = true;
} else {
showConfirmCredentials();
@@ -400,7 +407,19 @@
// biometric is disabled due to device restart.
private boolean isStrongAuthRequired(int effectiveUserId) {
return !mLockPatternUtils.isBiometricAllowedForUser(effectiveUserId)
- || !mUserManager.isUserUnlocked(mUserId);
+ || doesUserStateEnforceStrongAuth(mUserId);
+ }
+
+ private boolean doesUserStateEnforceStrongAuth(int userId) {
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+ // Check if CE storage for user is locked since biometrics can't unlock fbe/keystore of
+ // the profile user using verifyTiedProfileChallenge. Biometrics can still be used if
+ // the user is stopped with delayed locking (i.e., with storage unlocked), so the user
+ // state (whether the user is in the RUNNING_UNLOCKED state) should not be relied upon.
+ return !StorageManager.isUserKeyUnlocked(userId);
+ }
+ return !mUserManager.isUserUnlocked(userId);
}
private boolean isBiometricAllowed(int effectiveUserId, int realUserId) {
@@ -408,7 +427,7 @@
.hasPendingEscrowToken(realUserId);
}
- private void showBiometricPrompt(PromptInfo promptInfo) {
+ private void showBiometricPrompt(PromptInfo promptInfo, int userId) {
mBiometricFragment = (BiometricFragment) getSupportFragmentManager()
.findFragmentByTag(TAG_BIOMETRIC_FRAGMENT);
boolean newFragment = false;
@@ -418,7 +437,9 @@
newFragment = true;
}
mBiometricFragment.setCallbacks(mExecutor, mAuthenticationCallback);
- mBiometricFragment.setUser(mUserId);
+ // TODO(b/315864564): Move the logic of choosing the user id against which the
+ // authentication needs to happen to the BiometricPrompt API
+ mBiometricFragment.setUser(userId);
if (newFragment) {
getSupportFragmentManager().beginTransaction()
diff --git a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java
index b5e76920..b8f140f 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceMaintainer.java
@@ -18,6 +18,7 @@
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static android.provider.Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT;
+import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import android.app.ActivityManager;
import android.app.IActivityManager;
@@ -96,6 +97,7 @@
IActivityManager am = ActivityManager.getService();
try {
+ //TODO(b/313926659): To check and handle failure of startProfile
am.startProfile(mUserHandle.getIdentifier());
} catch (RemoteException e) {
Log.e(TAG, "Failed to start private profile");
@@ -104,6 +106,7 @@
Log.i(TAG, "Private space created with id: " + mUserHandle.getIdentifier());
resetPrivateSpaceSettings();
+ setUserSetupComplete();
}
return true;
}
@@ -250,4 +253,14 @@
private void resetPrivateSpaceSettings() {
setHidePrivateSpaceEntryPointSetting(HIDE_PRIVATE_SPACE_ENTRY_POINT_DISABLED_VAL);
}
+
+ /**
+ * Sets the USER_SETUP_COMPLETE for private profile on which device theme is applied to the
+ * profile.
+ */
+ @GuardedBy("this")
+ private void setUserSetupComplete() {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(), USER_SETUP_COMPLETE,
+ 1, mUserHandle.getIdentifier());
+ }
}
diff --git a/src/com/android/settings/search/BaseSearchIndexProvider.java b/src/com/android/settings/search/BaseSearchIndexProvider.java
index 581eb2e..d21d983 100644
--- a/src/com/android/settings/search/BaseSearchIndexProvider.java
+++ b/src/com/android/settings/search/BaseSearchIndexProvider.java
@@ -136,7 +136,7 @@
try {
controllersFromCode = createPreferenceControllers(context);
} catch (Exception e) {
- Log.w(TAG, "Error initial controller");
+ Log.w(TAG, "Error initializing controller in fragment: " + this + ", e: " + e);
}
final List<SearchIndexableResource> res = getXmlResourcesToIndex(context, true);
diff --git a/src/com/android/settings/security/ConfirmSimDeletionPreferenceController.java b/src/com/android/settings/security/ConfirmSimDeletionPreferenceController.java
deleted file mode 100644
index b6b3608..0000000
--- a/src/com/android/settings/security/ConfirmSimDeletionPreferenceController.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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.security;
-
-import android.app.KeyguardManager;
-import android.app.settings.SettingsEnums;
-import android.content.Context;
-import android.os.UserManager;
-import android.provider.Settings;
-
-import androidx.preference.Preference;
-import androidx.preference.TwoStatePreference;
-
-import com.android.settings.R;
-import com.android.settings.core.BasePreferenceController;
-import com.android.settings.network.helper.ConfirmationSimDeletionPredicate;
-import com.android.settings.network.telephony.MobileNetworkUtils;
-import com.android.settings.overlay.FeatureFactory;
-import com.android.settings.wifi.dpp.WifiDppUtils;
-import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
-
-/** Enable/disable user confirmation before deleting an eSim */
-public class ConfirmSimDeletionPreferenceController extends BasePreferenceController implements
- Preference.OnPreferenceChangeListener{
- public static final String KEY_CONFIRM_SIM_DELETION =
- ConfirmationSimDeletionPredicate.KEY_CONFIRM_SIM_DELETION;
- private boolean mConfirmationDefaultOn;
- private MetricsFeatureProvider mMetricsFeatureProvider;
- private UserManager mUserManager;
- private KeyguardManager mKeyguardManager;
-
- public ConfirmSimDeletionPreferenceController(Context context, String key) {
- super(context, key);
- mConfirmationDefaultOn =
- context.getResources()
- .getBoolean(R.bool.config_sim_deletion_confirmation_default_on);
- mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
-
- mUserManager = context.getSystemService(UserManager.class);
- mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
- }
-
- @Override
- public int getAvailabilityStatus() {
- // hide if eSim is not supported on the device
- return (!MobileNetworkUtils.isMobileNetworkUserRestricted(mContext)) &&
- MobileNetworkUtils.showEuiccSettings(mContext) ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
- }
-
- private boolean getGlobalState() {
- return Settings.Global.getInt(
- mContext.getContentResolver(),
- KEY_CONFIRM_SIM_DELETION,
- mConfirmationDefaultOn ? 1 : 0)
- == 1;
- }
-
- public boolean isChecked() {
- return getGlobalState();
- }
-
- public boolean setChecked(boolean isChecked) {
- Settings.Global.putInt(
- mContext.getContentResolver(), KEY_CONFIRM_SIM_DELETION, isChecked ? 1 : 0);
- return true;
- }
-
- // handle UI change
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- if (!preference.getKey().equals(getPreferenceKey())) {
- return false;
- }
- if (!isChecked()) {
- mMetricsFeatureProvider.action(mContext,
- SettingsEnums.ACTION_CONFIRM_SIM_DELETION_ON);
- setChecked(true);
- return true;
- } else {
- // prevent disabling the feature until authorized
- WifiDppUtils.showLockScreen(mContext, () -> {
- mMetricsFeatureProvider.action(mContext,
- SettingsEnums.ACTION_CONFIRM_SIM_DELETION_OFF);
- // set data
- setChecked(false);
- // set UI
- ((TwoStatePreference) preference).setChecked(false);
- });
- return false;
- }
- }
-
- @Override
- public void updateState(Preference preference) {
- if (!mKeyguardManager.isKeyguardSecure() && mUserManager.isGuestUser()) {
- preference.setEnabled(false);
- if (preference instanceof TwoStatePreference) {
- ((TwoStatePreference) preference).setChecked(false);
- }
- preference.setSummary(R.string.disabled_because_no_backup_security);
- } else {
- preference.setEnabled(true);
- if (preference instanceof TwoStatePreference) {
- ((TwoStatePreference) preference).setChecked(getGlobalState());
- }
- preference.setSummary(R.string.confirm_sim_deletion_description);
- }
- }
-}
diff --git a/src/com/android/settings/spa/app/appinfo/AppBatteryPreference.kt b/src/com/android/settings/spa/app/appinfo/AppBatteryPreference.kt
index c707b44b..af4fc17 100644
--- a/src/com/android/settings/spa/app/appinfo/AppBatteryPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppBatteryPreference.kt
@@ -49,7 +49,7 @@
@Composable
fun AppBatteryPreference(app: ApplicationInfo) {
val context = LocalContext.current
- val presenter = remember { AppBatteryPresenter(context, app) }
+ val presenter = remember(app) { AppBatteryPresenter(context, app) }
if (!presenter.isAvailable()) return
Preference(object : PreferenceModel {
diff --git a/src/com/android/settings/spa/app/appinfo/AppButtons.kt b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
index f6fafd7..403263c 100644
--- a/src/com/android/settings/spa/app/appinfo/AppButtons.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppButtons.kt
@@ -53,6 +53,7 @@
private val appClearButton = AppClearButton(packageInfoPresenter)
private val appForceStopButton = AppForceStopButton(packageInfoPresenter)
private val appArchiveButton = AppArchiveButton(packageInfoPresenter)
+ private val appRestoreButton = AppRestoreButton(packageInfoPresenter)
@Composable
fun getActionButtons() =
@@ -63,7 +64,11 @@
@Composable
private fun getActionButtons(app: ApplicationInfo): List<ActionButton> = listOfNotNull(
if (featureFlags.archiving()) {
- appArchiveButton.getActionButton(app)
+ if (app.isArchived) {
+ appRestoreButton.getActionButton(app)
+ } else {
+ appArchiveButton.getActionButton(app)
+ }
} else {
appLaunchButton.getActionButton(app)
},
diff --git a/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt b/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt
index 057f911..7e6e726 100644
--- a/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt
@@ -59,7 +59,7 @@
) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
- val presenter = remember {
+ val presenter = remember(app) {
AppDataUsagePresenter(context, app, coroutineScope, networkTemplates, repositoryFactory)
}
if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return
diff --git a/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt b/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt
index de6bd10..91c3887 100644
--- a/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppPermissionSummary.kt
@@ -39,7 +39,7 @@
@Composable
fun rememberAppPermissionSummary(app: ApplicationInfo): AppPermissionSummaryLiveData {
val context = LocalContext.current
- return remember { AppPermissionSummaryLiveData(context, app) }
+ return remember(app) { AppPermissionSummaryLiveData(context, app) }
}
class AppPermissionSummaryLiveData(
@@ -55,7 +55,11 @@
override fun onActive() {
userPackageManager.addOnPermissionsChangeListener(onPermissionsChangedListener)
- update()
+ if (app.isArchived) {
+ postValue(noPermissionRequestedState())
+ } else {
+ update()
+ }
}
override fun onInactive() {
diff --git a/src/com/android/settings/spa/app/appinfo/AppRestoreButton.kt b/src/com/android/settings/spa/app/appinfo/AppRestoreButton.kt
new file mode 100644
index 0000000..c47fdac
--- /dev/null
+++ b/src/com/android/settings/spa/app/appinfo/AppRestoreButton.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2023 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.PendingIntent
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInstaller
+import android.os.UserHandle
+import android.util.Log
+import android.widget.Toast
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.CloudDownload
+import androidx.compose.runtime.Composable
+import com.android.settings.R
+import com.android.settingslib.spa.widget.button.ActionButton
+import com.android.settingslib.spaprivileged.framework.compose.DisposableBroadcastReceiverAsUser
+
+class AppRestoreButton(packageInfoPresenter: PackageInfoPresenter) {
+ private companion object {
+ private const val LOG_TAG = "AppRestoreButton"
+ private const val INTENT_ACTION = "com.android.settings.unarchive.action"
+ }
+
+ private val context = packageInfoPresenter.context
+ private val userPackageManager = packageInfoPresenter.userPackageManager
+ private val packageInstaller = userPackageManager.packageInstaller
+ private val packageName = packageInfoPresenter.packageName
+ private val userHandle = UserHandle.of(packageInfoPresenter.userId)
+ private var broadcastReceiverIsCreated = false
+
+ @Composable
+ fun getActionButton(app: ApplicationInfo): ActionButton {
+ if (!broadcastReceiverIsCreated) {
+ val intentFilter = IntentFilter(INTENT_ACTION)
+ DisposableBroadcastReceiverAsUser(intentFilter, userHandle) { intent ->
+ if (app.packageName == intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)) {
+ onReceive(intent, app)
+ }
+ }
+ broadcastReceiverIsCreated = true
+ }
+ return ActionButton(
+ text = context.getString(R.string.restore),
+ imageVector = Icons.Outlined.CloudDownload,
+ enabled = app.isArchived
+ ) { onRestoreClicked(app) }
+ }
+
+ private fun onRestoreClicked(app: ApplicationInfo) {
+ val intent = Intent(INTENT_ACTION)
+ intent.setPackage(context.packageName)
+ val pendingIntent = PendingIntent.getBroadcastAsUser(
+ context, 0, intent,
+ // FLAG_MUTABLE is required by PackageInstaller#requestUnarchive
+ PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_MUTABLE,
+ userHandle
+ )
+ try {
+ packageInstaller.requestUnarchive(app.packageName, pendingIntent.intentSender)
+ val appLabel = userPackageManager.getApplicationLabel(app)
+ Toast.makeText(
+ context,
+ context.getString(R.string.restoring_in_progress, appLabel),
+ Toast.LENGTH_SHORT
+ ).show()
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "Request unarchive failed", e)
+ Toast.makeText(
+ context,
+ context.getString(R.string.restoring_failed),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+
+ private fun onReceive(intent: Intent, app: ApplicationInfo) {
+ when (val unarchiveStatus =
+ intent.getIntExtra(PackageInstaller.EXTRA_UNARCHIVE_STATUS, Int.MIN_VALUE)) {
+ PackageInstaller.STATUS_PENDING_USER_ACTION -> {
+ Log.e(
+ LOG_TAG,
+ "Request unarchiving failed for $packageName with code $unarchiveStatus"
+ )
+ Toast.makeText(
+ context,
+ context.getString(R.string.restoring_failed),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+
+ PackageInstaller.STATUS_SUCCESS -> {
+ val appLabel = userPackageManager.getApplicationLabel(app)
+ Toast.makeText(
+ context,
+ context.getString(R.string.restoring_succeeded, appLabel),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+
+ else -> {
+ Log.e(
+ LOG_TAG,
+ "Request unarchiving failed for $packageName with code $unarchiveStatus"
+ )
+ val errorDialogIntent =
+ intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+ if (errorDialogIntent != null) {
+ context.startActivityAsUser(errorDialogIntent, userHandle)
+ } else {
+ Toast.makeText(
+ context,
+ context.getString(R.string.restoring_failed),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+ }
+}
diff --git a/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt b/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt
index 7ba61dc..837df67 100644
--- a/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt
@@ -40,7 +40,7 @@
@Composable
fun AppTimeSpentPreference(app: ApplicationInfo) {
val context = LocalContext.current
- val presenter = remember { AppTimeSpentPresenter(context, app) }
+ val presenter = remember(app) { AppTimeSpentPresenter(context, app) }
if (!presenter.isAvailable()) return
val summary by presenter.summaryLiveData.observeAsState(
diff --git a/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt b/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
index 78ca15b..324fa06 100644
--- a/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
@@ -22,6 +22,7 @@
import android.app.AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
import android.content.Context
import android.content.pm.ApplicationInfo
+import android.content.pm.Flags
import android.os.Build
import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM
import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_UNKNOWN
@@ -53,15 +54,24 @@
@Composable
fun HibernationSwitchPreference(app: ApplicationInfo) {
val context = LocalContext.current
- val presenter = remember { HibernationSwitchPresenter(context, app) }
+ val presenter = remember(app) { HibernationSwitchPresenter(context, app) }
if (!presenter.isAvailable()) return
val isEligibleState by presenter.isEligibleFlow.collectAsStateWithLifecycle(initialValue = false)
val isCheckedState = presenter.isCheckedFlow.collectAsStateWithLifecycle(initialValue = null)
SwitchPreference(remember {
object : SwitchPreferenceModel {
- override val title = context.getString(R.string.unused_apps_switch)
- override val summary = { context.getString(R.string.unused_apps_switch_summary) }
+ override val title =
+ if (isArchivingEnabled())
+ context.getString(R.string.unused_apps_switch_v2)
+ else
+ context.getString(R.string.unused_apps_switch)
+ override val summary = {
+ if (isArchivingEnabled())
+ context.getString(R.string.unused_apps_switch_summary_v2)
+ else
+ context.getString(R.string.unused_apps_switch_summary)
+ }
override val changeable = { isEligibleState }
override val checked = { if (changeable()) isCheckedState.value else false }
override val onCheckedChange = presenter::onCheckedChange
@@ -69,6 +79,9 @@
})
}
+private fun isArchivingEnabled() =
+ Flags.archiving() || "true" == System.getProperty("pm.archiving.enabled")
+
private class HibernationSwitchPresenter(context: Context, private val app: ApplicationInfo) {
private val appOpsManager = context.appOpsManager
private val permissionControllerManager =
@@ -80,6 +93,10 @@
DeviceConfig.getBoolean(NAMESPACE_APP_HIBERNATION, PROPERTY_APP_HIBERNATION_ENABLED, true)
val isEligibleFlow = flow {
+ if (app.isArchived) {
+ emit(false)
+ return@flow
+ }
val eligibility = getEligibility()
emit(
eligibility != HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM &&
diff --git a/tests/spa_unit/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceControllerTest.kt
index 7285ff8..c132273 100644
--- a/tests/spa_unit/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceControllerTest.kt
+++ b/tests/spa_unit/src/com/android/settings/network/telephony/DeleteSimProfilePreferenceControllerTest.kt
@@ -16,15 +16,15 @@
package com.android.settings.network.telephony
+import android.app.KeyguardManager
import android.content.Context
+import android.os.UserManager
import android.telephony.SubscriptionInfo
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.network.SubscriptionUtil
-import com.android.settings.security.ConfirmSimDeletionPreferenceController
-import com.android.settingslib.spaprivileged.settingsprovider.settingsGlobalBoolean
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -46,8 +46,13 @@
on { isEmbedded } doReturn true
}
+ private val mockKeyguardManager = mock<KeyguardManager>() {
+ on { isKeyguardSecure() } doReturn false
+ }
+
private var context: Context = spy(ApplicationProvider.getApplicationContext()) {
doNothing().whenever(mock).startActivity(any())
+ on { getSystemService(Context.KEYGUARD_SERVICE) } doReturn mockKeyguardManager
}
private val preference = Preference(context).apply { key = PREF_KEY }
@@ -103,11 +108,6 @@
fun onPreferenceClick_startsIntent() {
controller.init(SUB_ID)
controller.displayPreference(preferenceScreen)
- // turn off confirmation before click
- var confirmDeletion by context.settingsGlobalBoolean(
- name = ConfirmSimDeletionPreferenceController.KEY_CONFIRM_SIM_DELETION,
- )
- confirmDeletion = false
controller.handlePreferenceTreeClick(preference)
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
index 50094f2..6d22c92 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppButtonsTest.kt
@@ -140,6 +140,38 @@
composeTestRule.onNodeWithText(context.getString(R.string.uninstall_text)).assertIsEnabled()
}
+ @Test
+ fun archiveButton_displayed_whenAppIsNotArchived() {
+ featureFlags.setFlag(Flags.FLAG_ARCHIVING, true)
+ val packageInfo = PackageInfo().apply {
+ applicationInfo = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ isArchived = false
+ }
+ packageName = PACKAGE_NAME
+ }
+ setContent(packageInfo)
+
+ composeTestRule.onNodeWithText(context.getString(R.string.archive)).assertIsDisplayed()
+ composeTestRule.onNodeWithText(context.getString(R.string.restore)).assertIsNotDisplayed()
+ }
+
+ @Test
+ fun restoreButton_displayed_whenAppIsArchived() {
+ featureFlags.setFlag(Flags.FLAG_ARCHIVING, true)
+ val packageInfo = PackageInfo().apply {
+ applicationInfo = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ isArchived = true
+ }
+ packageName = PACKAGE_NAME
+ }
+ setContent(packageInfo)
+
+ composeTestRule.onNodeWithText(context.getString(R.string.restore)).assertIsDisplayed()
+ composeTestRule.onNodeWithText(context.getString(R.string.archive)).assertIsNotDisplayed()
+ }
+
private fun setContent(packageInfo: PackageInfo = PACKAGE_INFO) {
whenever(packageInfoPresenter.flow).thenReturn(MutableStateFlow(packageInfo))
composeTestRule.setContent {
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppRestoreButtonTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppRestoreButtonTest.kt
new file mode 100644
index 0000000..9d30521
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/AppRestoreButtonTest.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2023 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.ApplicationInfo
+import android.content.pm.PackageInstaller
+import android.content.pm.PackageManager
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.CloudDownload
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.annotation.UiThreadTest
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.android.settingslib.spa.widget.button.ActionButton
+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.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class AppRestoreButtonTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {}
+
+ private val packageInfoPresenter = mock<PackageInfoPresenter>()
+
+ private val userPackageManager = mock<PackageManager>()
+
+ private val packageInstaller = mock<PackageInstaller>()
+
+ private lateinit var appRestoreButton: AppRestoreButton
+
+ @Before
+ fun setUp() {
+ whenever(packageInfoPresenter.context).thenReturn(context)
+ whenever(packageInfoPresenter.userPackageManager).thenReturn(userPackageManager)
+ whenever(userPackageManager.packageInstaller).thenReturn(packageInstaller)
+ whenever(packageInfoPresenter.packageName).thenReturn(PACKAGE_NAME)
+ appRestoreButton = AppRestoreButton(packageInfoPresenter)
+ }
+
+ @Test
+ fun appRestoreButton_whenIsNotArchived_isDisabled() {
+ val app = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ isArchived = false
+ }
+
+ val actionButton = setContent(app)
+
+ assertThat(actionButton.enabled).isFalse()
+ }
+
+ @Test
+ fun appRestoreButton_whenIsArchived_isEnabled() {
+ val app = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ isArchived = true
+ }
+
+ val actionButton = setContent(app)
+
+ assertThat(actionButton.enabled).isTrue()
+ }
+
+ @Test
+ fun appRestoreButton_displaysRightTextAndIcon() {
+ val app = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ isArchived = false
+ }
+
+ val actionButton = setContent(app)
+
+ assertThat(actionButton.text).isEqualTo(context.getString(R.string.restore))
+ assertThat(actionButton.imageVector).isEqualTo(Icons.Outlined.CloudDownload)
+ }
+
+ @Test
+ @UiThreadTest
+ fun appRestoreButton_clicked() {
+ val app = ApplicationInfo().apply {
+ packageName = PACKAGE_NAME
+ isArchived = true
+ }
+
+ val actionButton = setContent(app)
+ actionButton.onClick()
+
+ verify(packageInstaller).requestUnarchive(
+ eq(PACKAGE_NAME),
+ any()
+ )
+ }
+
+ private fun setContent(app: ApplicationInfo): ActionButton {
+ lateinit var actionButton: ActionButton
+ composeTestRule.setContent {
+ actionButton = appRestoreButton.getActionButton(app)
+ }
+ return actionButton
+ }
+
+ private companion object {
+ const val PACKAGE_NAME = "package.name"
+ }
+}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreferenceTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreferenceTest.kt
index 6acdcf5..57e31da 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreferenceTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreferenceTest.kt
@@ -24,8 +24,8 @@
import android.apphibernation.AppHibernationManager
import android.content.Context
import android.content.pm.ApplicationInfo
+import android.content.pm.Flags
import android.os.Build
-import android.os.UserHandle
import android.permission.PermissionControllerManager
import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_ELIGIBLE
import android.permission.PermissionControllerManager.HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM
@@ -63,7 +63,6 @@
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyString
import org.mockito.Mockito.doAnswer
-import org.mockito.Mockito.doReturn
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@@ -147,12 +146,20 @@
setContent()
- composeTestRule.onNodeWithText(context.getString(R.string.unused_apps_switch))
+ val text = if (isArchivingEnabled()) {
+ context.getString(R.string.unused_apps_switch_v2)
+ } else {
+ context.getString(R.string.unused_apps_switch)
+ }
+ composeTestRule.onNodeWithText(text)
.assertIsDisplayed()
.assertIsNotEnabled()
.assertIsOff()
}
+ private fun isArchivingEnabled() =
+ Flags.archiving() || "true" == System.getProperty("pm.archiving.enabled")
+
@Test
fun `An app targets Q with ops mode default when hibernation targets pre S - not exempted`() {
mockOpsMode(MODE_DEFAULT)
diff --git a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java
index 1d27326..1605ae6 100644
--- a/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java
+++ b/tests/unit/src/com/android/settings/privatespace/PrivateSpaceMaintainerTest.java
@@ -198,4 +198,40 @@
assertThat(privateSpaceMaintainer.doesPrivateSpaceExist()).isFalse();
assertThat(privateSpaceMaintainer.lockPrivateSpace()).isFalse();
}
+
+ /**
+ * Tests that {@link PrivateSpaceMaintainer#createPrivateSpace()} when no PS exists sets
+ * USER_SETUP_COMPLETE setting.
+ */
+ @Test
+ public void createPrivateSpace_psDoesNotExist_setsUserSetupComplete() {
+ PrivateSpaceMaintainer privateSpaceMaintainer =
+ PrivateSpaceMaintainer.getInstance(mContext);
+ privateSpaceMaintainer.createPrivateSpace();
+ assertThat(getSecureUserSetupComplete()).isEqualTo(1);
+ }
+
+ /**
+ * Tests that {@link PrivateSpaceMaintainer#createPrivateSpace()} when PS exists does not
+ * change USER_SETUP_COMPLETE setting.
+ */
+ @Test
+ public void createPrivateSpace_pSExists_doesNotChangeUserSetupSetting() {
+ PrivateSpaceMaintainer privateSpaceMaintainer =
+ PrivateSpaceMaintainer.getInstance(mContext);
+ privateSpaceMaintainer.createPrivateSpace();
+ assertThat(getSecureUserSetupComplete()).isEqualTo(1);
+ privateSpaceMaintainer.createPrivateSpace();
+ assertThat(getSecureUserSetupComplete()).isEqualTo(1);
+ }
+
+ private int getSecureUserSetupComplete() {
+ PrivateSpaceMaintainer privateSpaceMaintainer =
+ PrivateSpaceMaintainer.getInstance(mContext);
+ return Settings.Secure.getIntForUser(
+ mContentResolver,
+ Settings.Secure.USER_SETUP_COMPLETE,
+ 0,
+ privateSpaceMaintainer.getPrivateProfileHandle().getIdentifier());
+ }
}