Merge "Settings: add a new developer menu entry to show hdr/sdr ratio overlay." into main
diff --git a/aconfig/Android.bp b/aconfig/Android.bp
index 5c6c8c3..f485869 100644
--- a/aconfig/Android.bp
+++ b/aconfig/Android.bp
@@ -11,6 +11,7 @@
"settings_development_flag_declarations.aconfig",
"settings_globalintl_flag_declarations.aconfig",
"settings_experience_flag_declarations.aconfig",
+ "settings_notification_flag_declarations.aconfig",
"settings_onboarding_experience_flag_declarations.aconfig",
"settings_telephony_flag_declarations.aconfig",
"settings_biometrics_integration_declarations.aconfig",
diff --git a/aconfig/settings_notification_flag_declarations.aconfig b/aconfig/settings_notification_flag_declarations.aconfig
new file mode 100644
index 0000000..f2bf1c8
--- /dev/null
+++ b/aconfig/settings_notification_flag_declarations.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.settings.flags"
+
+flag {
+ name: "dedupe_dnd_settings_channels"
+ namespace: "systemui"
+ description: "Controls adding group names to channel names in the DND>Apps settings page"
+ bug: "294333850"
+}
+
diff --git a/protos/fuelgauge_log.proto b/protos/fuelgauge_log.proto
index e75ca48..36126a5 100644
--- a/protos/fuelgauge_log.proto
+++ b/protos/fuelgauge_log.proto
@@ -43,6 +43,7 @@
RECHECK_JOB = 3;
FETCH_USAGE_DATA = 4;
INSERT_USAGE_DATA = 5;
+ TIME_UPDATED = 6;
}
optional int64 timestamp = 1;
diff --git a/res/drawable/ic_error_red.xml b/res/drawable/ic_error_red.xml
new file mode 100644
index 0000000..d17c85b
--- /dev/null
+++ b/res/drawable/ic_error_red.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?android:attr/colorError">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M480,680Q497,680 508.5,668.5Q520,657 520,640Q520,623 508.5,611.5Q497,600 480,600Q463,600 451.5,611.5Q440,623 440,640Q440,657 451.5,668.5Q463,680 480,680ZM440,520L520,520L520,280L440,280L440,520ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
+</vector>
diff --git a/res/layout/privatespace_account_login_error.xml b/res/layout/privatespace_account_login_error.xml
new file mode 100644
index 0000000..a38dd50
--- /dev/null
+++ b/res/layout/privatespace_account_login_error.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<com.google.android.setupdesign.GlifLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/ps_error_page_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:filterTouchesWhenObscured="true"
+ app:sucHeaderText="@string/privatespace_retry_signin_title"
+ app:sudDescriptionText="@string/privatespace_retry_summary"
+ android:icon="@drawable/ic_error_red">
+</com.google.android.setupdesign.GlifLayout>
diff --git a/res/navigation/privatespace_main_context_nav.xml b/res/navigation/privatespace_main_context_nav.xml
index 5b2552a..ffc63ec 100644
--- a/res/navigation/privatespace_main_context_nav.xml
+++ b/res/navigation/privatespace_main_context_nav.xml
@@ -33,8 +33,8 @@
android:id="@+id/action_advance_profile_error"
app:destination="@id/ps_profile_error_fragment"/>
<action
- android:id="@+id/action_advance_to_success"
- app:destination="@id/ps_profile_success_fragment"/>
+ android:id="@+id/action_advance_login_error"
+ app:destination="@id/ps_account_error_fragment"/>
</fragment>
<fragment android:id="@+id/ps_profile_error_fragment"
android:name="com.android.settings.privatespace.PrivateProfileCreationError"
@@ -46,4 +46,18 @@
<fragment android:id="@+id/ps_profile_success_fragment"
android:name="com.android.settings.privatespace.SetupSuccessFragment"
android:label="fragment_ps_success"/>
-</navigation>
\ No newline at end of file
+ <fragment android:id="@+id/ps_account_error_fragment"
+ android:name="com.android.settings.privatespace.PrivateSpaceAccountLoginError"
+ android:label="fragment_account_error">
+ <action
+ android:id="@+id/action_advance_login_error"
+ app:destination="@id/ps_account_error_fragment"/>
+ </fragment>
+ <fragment android:id="@+id/ps_profile_lock_fragment"
+ android:name="com.android.settings.privatespace.PrivateSpaceSetLockFragment"
+ android:label="fragment_ps_lock"/>
+ <action android:id="@+id/action_success_fragment"
+ app:destination="@id/ps_profile_success_fragment"/>
+ <action android:id="@+id/action_set_lock_fragment"
+ app:destination="@id/ps_profile_lock_fragment"/>
+</navigation>
diff --git a/res/navigation/privatespace_private_context_nav.xml b/res/navigation/privatespace_private_context_nav.xml
deleted file mode 100644
index 3df8fa5..0000000
--- a/res/navigation/privatespace_private_context_nav.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
- ~ 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.
- -->
-
-<navigation xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/private_space_navigation"
- app:startDestination="@id/ps_profile_lock_fragment">
- <fragment android:id="@+id/ps_profile_lock_fragment"
- android:name="com.android.settings.privatespace.PrivateSpaceSetLockFragment"
- android:label="fragment_ps_lock"/>
-</navigation>
diff --git a/res/values/config.xml b/res/values/config.xml
index 99052ca..f50e918 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -753,6 +753,9 @@
<!-- Whether to display Cloned Apps page in Settings (Settings > Apps > Cloned Apps).-->
<bool name="config_cloned_apps_page_enabled">false</bool>
+ <!-- Whether to initiate Account login during Private Space setup.-->
+ <bool name="config_privatespace_account_login_enabled">false</bool>
+
<!-- Certificates of apps which are allowed to use activity embedding with Settings.-->
<string-array name="config_known_host_certs" translatable="false">
<item></item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a503710..7ed9ade 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1298,6 +1298,10 @@
<string name="privatespace_done_label">Done</string>
<!-- Toast to show on private space setup completion informing user to scroll down All apps to access private space. [CHAR LIMIT=60] -->
<string name="scrolldown_to_access">Scroll down to access Private Space</string>
+ <!-- Title for Private Space account login error screen. [CHAR LIMIT=60] -->
+ <string name="privatespace_retry_signin_title">Sign in to set up Private Space</string>
+ <!-- Summary for the Private Space account login error screen. [CHAR LIMIT=NONE] -->
+ <string name="privatespace_retry_summary">You need to sign in to a Account to set up Private Space</string>
<!-- Text shown when "Add fingerprint" button is disabled -->
<string name="fingerprint_add_max">You can add up to <xliff:g id="count" example="5">%d</xliff:g> fingerprints</string>
@@ -9525,6 +9529,13 @@
<!-- Label for showing apps that can manage external storage[CHAR LIMIT=45] -->
<string name="filter_manage_external_storage">Can access all files</string>
+ <!-- Voice Activation apps settings title [CHAR LIMIT=40] -->
+ <string name="voice_activation_apps_title">Voice activation apps</string>
+ <!-- Label for a setting which controls whether an app can be voice activated [CHAR LIMIT=NONE] -->
+ <string name="permit_voice_activation_apps">Allow voice activation</string>
+ <!-- Description for a setting which controls whether an app can be voice activated [CHAR LIMIT=NONE] -->
+ <string name ="allow_voice_activation_apps_description">Voice activation turns-on approved apps, hands-free, using voice command. Built-in adaptive sensing ensures data stays private only to you.\n\n<a href="">More about protected adaptive sensing</a></string>
+
<!-- Manage full screen intent permission title [CHAR LIMIT=40] -->
<string name="full_screen_intent_title">Full screen notifications</string>
diff --git a/res/xml/special_access.xml b/res/xml/special_access.xml
index b3f3f7d..3f3d75d 100644
--- a/res/xml/special_access.xml
+++ b/res/xml/special_access.xml
@@ -100,6 +100,11 @@
settings:controller="com.android.settings.spa.app.specialaccess.UseFullScreenIntentPreferenceController" />
<Preference
+ android:key="voice_activation_apps"
+ android:title="@string/voice_activation_apps_title"
+ settings:controller="com.android.settings.spa.app.specialaccess.VoiceActivationAppsPreferenceController" />
+
+ <Preference
android:key="picture_in_picture"
android:title="@string/picture_in_picture_title"
android:order="-1100"
diff --git a/src/com/android/settings/SettingsActivityUtil.kt b/src/com/android/settings/SettingsActivityUtil.kt
index 65d26de..c23bc18 100644
--- a/src/com/android/settings/SettingsActivityUtil.kt
+++ b/src/com/android/settings/SettingsActivityUtil.kt
@@ -37,6 +37,7 @@
import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListProvider
import com.android.settings.spa.app.specialaccess.NfcTagAppsSettingsProvider
import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider
+import com.android.settings.spa.app.specialaccess.VoiceActivationAppsListProvider
import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
import com.android.settings.wifi.ChangeWifiStateDetails
@@ -65,6 +66,8 @@
WifiControlAppListProvider.getAppInfoRoutePrefix(),
NfcTagAppsSettingsProvider::class.qualifiedName to
NfcTagAppsSettingsProvider.getAppInfoRoutePrefix(),
+ VoiceActivationAppsListProvider::class.qualifiedName to
+ VoiceActivationAppsListProvider.getAppInfoRoutePrefix(),
)
@JvmStatic
diff --git a/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java b/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java
index 076d37c..02d5c27 100644
--- a/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java
+++ b/src/com/android/settings/applications/appcompat/UserAspectRatioDetails.java
@@ -28,6 +28,7 @@
import android.app.ActivityManager;
import android.app.IActivityManager;
+import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
@@ -44,8 +45,10 @@
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.applications.AppInfoBase;
+import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.applications.AppUtils;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.widget.ActionButtonsPreference;
import java.util.ArrayList;
@@ -104,6 +107,7 @@
Log.e(TAG, "Unable to set user min aspect ratio");
return;
}
+ logActionMetrics(selectedKey, mSelectedKey);
// Only update to selected aspect ratio if nothing goes wrong
mSelectedKey = selectedKey;
updateAllPreferences(mSelectedKey);
@@ -118,8 +122,7 @@
@Override
public int getMetricsCategory() {
- // TODO(b/292566895): add metrics for logging
- return 0;
+ return SettingsEnums.USER_ASPECT_RATIO_APP_INFO_SETTINGS;
}
@Override
@@ -244,6 +247,68 @@
}
}
+ private void logActionMetrics(@NonNull String selectedKey, @NonNull String unselectedKey) {
+ final MetricsFeatureProvider metricsFeatureProvider =
+ FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
+ final int attribution = metricsFeatureProvider.getAttribution(getActivity());
+ metricsFeatureProvider.action(
+ attribution,
+ getUnselectedAspectRatioAction(unselectedKey),
+ getMetricsCategory(),
+ mPackageName,
+ mUserId
+ );
+ metricsFeatureProvider.action(
+ attribution,
+ getSelectedAspectRatioAction(selectedKey),
+ getMetricsCategory(),
+ mPackageName,
+ mUserId
+ );
+ }
+
+ private static int getSelectedAspectRatioAction(@NonNull String selectedKey) {
+ switch (selectedKey) {
+ case KEY_PREF_DEFAULT:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_APP_DEFAULT_SELECTED;
+ case KEY_PREF_FULLSCREEN:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_FULL_SCREEN_SELECTED;
+ case KEY_PREF_HALF_SCREEN:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_HALF_SCREEN_SELECTED;
+ case KEY_PREF_4_3:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_4_3_SELECTED;
+ case KEY_PREF_16_9:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_16_9_SELECTED;
+ case KEY_PREF_3_2:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_3_2_SELECTED;
+ case KEY_PREF_DISPLAY_SIZE:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_DISPLAY_SIZE_SELECTED;
+ default:
+ return SettingsEnums.ACTION_UNKNOWN;
+ }
+ }
+
+ private static int getUnselectedAspectRatioAction(@NonNull String unselectedKey) {
+ switch (unselectedKey) {
+ case KEY_PREF_DEFAULT:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_APP_DEFAULT_UNSELECTED;
+ case KEY_PREF_FULLSCREEN:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_FULL_SCREEN_UNSELECTED;
+ case KEY_PREF_HALF_SCREEN:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_HALF_SCREEN_UNSELECTED;
+ case KEY_PREF_4_3:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_4_3_UNSELECTED;
+ case KEY_PREF_16_9:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_16_9_UNSELECTED;
+ case KEY_PREF_3_2:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_3_2_UNSELECTED;
+ case KEY_PREF_DISPLAY_SIZE:
+ return SettingsEnums.ACTION_USER_ASPECT_RATIO_DISPLAY_SIZE_UNSELECTED;
+ default:
+ return SettingsEnums.ACTION_UNKNOWN;
+ }
+ }
+
@VisibleForTesting
UserAspectRatioManager getAspectRatioManager() {
return mUserAspectRatioManager;
diff --git a/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java b/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java
index c94edc6..1d96688 100644
--- a/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java
+++ b/src/com/android/settings/applications/manageapplications/ApplicationViewHolder.java
@@ -46,14 +46,14 @@
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.applications.ApplicationsState.AppEntry;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
-import com.android.settingslib.spaprivileged.template.app.AppListItemKt;
+import com.android.settingslib.spaprivileged.template.app.AppListItemModelKt;
import com.android.settingslib.spaprivileged.template.app.AppListPageKt;
import com.android.settingslib.widget.LottieColorUtils;
import com.airbnb.lottie.LottieAnimationView;
/**
- * @deprecated Will be removed, use {@link AppListItemKt} {@link AppListPageKt} instead.
+ * @deprecated Will be removed, use {@link AppListItemModelKt} {@link AppListPageKt} instead.
*/
@Deprecated(forRemoval = true)
public class ApplicationViewHolder extends RecyclerView.ViewHolder {
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt b/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
index 216ce47..82e987e 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
+++ b/src/com/android/settings/applications/manageapplications/ManageApplicationsUtil.kt
@@ -64,9 +64,11 @@
import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider
import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider
import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider
+import com.android.settings.spa.app.specialaccess.LongBackgroundTasksAppListProvider
import com.android.settings.spa.app.specialaccess.MediaManagementAppsAppListProvider
import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListProvider
import com.android.settings.spa.app.specialaccess.NfcTagAppsSettingsProvider
+import com.android.settings.spa.app.specialaccess.TurnScreenOnAppsAppListProvider
import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
import com.android.settings.spa.app.storage.StorageAppListPageProvider
import com.android.settings.spa.notification.AppListNotificationsPageProvider
@@ -120,6 +122,8 @@
LIST_TYPE_MAIN -> AllAppListPageProvider.name
LIST_TYPE_NFC_TAG_APPS -> NfcTagAppsSettingsProvider.getAppListRoute()
LIST_TYPE_USER_ASPECT_RATIO_APPS -> UserAspectRatioAppsPageProvider.name
+ LIST_TYPE_LONG_BACKGROUND_TASKS -> LongBackgroundTasksAppListProvider.getAppListRoute()
+ LIST_TYPE_TURN_SCREEN_ON -> TurnScreenOnAppsAppListProvider.getAppListRoute()
// TODO(b/292165031) enable once sorting is supported
//LIST_TYPE_STORAGE -> StorageAppListPageProvider.Apps.name
//LIST_TYPE_GAMES -> StorageAppListPageProvider.Games.name
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
index 5b99907..1fd0b87 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
@@ -38,6 +38,26 @@
public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
private static final String TAG = "AudioSharingDialog";
+ private static final String BUNDLE_KEY_DEVICE_NAMES = "bundle_key_device_names";
+
+ // The host creates an instance of this dialog fragment must implement this interface to receive
+ // event callbacks.
+ public interface DialogEventListener {
+ /**
+ * Called when users click the device item for sharing in the dialog.
+ *
+ * @param position The position of the item clicked.
+ */
+ void onItemClick(int position);
+
+ /**
+ * Called when users click the cancel button in the dialog.
+ */
+ void onCancelClick();
+ }
+
+ private static DialogEventListener sListener;
+
private View mRootView;
@Override
@@ -50,40 +70,59 @@
*
* @param host The Fragment this dialog will be hosted.
*/
- public static void show(Fragment host) {
+ public static void show(
+ Fragment host, ArrayList<String> deviceNames, DialogEventListener listener) {
if (!Flags.enableLeAudioSharing()) return;
final FragmentManager manager = host.getChildFragmentManager();
+ sListener = listener;
if (manager.findFragmentByTag(TAG) == null) {
- final AudioSharingDialogFragment dialog = new AudioSharingDialogFragment();
+ final Bundle bundle = new Bundle();
+ bundle.putStringArrayList(BUNDLE_KEY_DEVICE_NAMES, deviceNames);
+ AudioSharingDialogFragment dialog = new AudioSharingDialogFragment();
+ dialog.setArguments(bundle);
dialog.show(manager, TAG);
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Bundle arguments = requireArguments();
+ ArrayList<String> deviceNames = arguments.getStringArrayList(BUNDLE_KEY_DEVICE_NAMES);
final AlertDialog.Builder builder =
- new AlertDialog.Builder(getActivity()).setTitle("Share audio");
+ new AlertDialog.Builder(getActivity()).setTitle("Share audio").setCancelable(false);
mRootView =
LayoutInflater.from(builder.getContext())
.inflate(R.layout.dialog_audio_sharing, /* parent= */ null);
- // TODO: use real subtitle according to device count.
TextView subTitle1 = mRootView.findViewById(R.id.share_audio_subtitle1);
TextView subTitle2 = mRootView.findViewById(R.id.share_audio_subtitle2);
- subTitle1.setText("2 devices connected");
- subTitle2.setText("placeholder");
+ if (deviceNames.isEmpty()) {
+ subTitle1.setVisibility(View.INVISIBLE);
+ subTitle2.setText("To start sharing audio, connect headphones that support LE audio");
+ builder.setNegativeButton(
+ "Close",
+ (dialog, which) -> {
+ sListener.onCancelClick();
+ });
+ } else if (deviceNames.size() == 1) {
+ // TODO: add real impl
+ subTitle1.setText("1 devices connected");
+ subTitle2.setText("placeholder");
+ } else {
+ // TODO: add real impl
+ subTitle1.setText("2 devices connected");
+ subTitle2.setText("placeholder");
+ }
RecyclerView recyclerView = mRootView.findViewById(R.id.btn_list);
- // TODO: use real audio sharing device list.
- ArrayList<String> devices = new ArrayList<>();
- devices.add("Buds 1");
- devices.add("Buds 2");
recyclerView.setAdapter(
new AudioSharingDeviceAdapter(
- devices,
+ deviceNames,
(int position) -> {
- // TODO: add on click callback.
+ sListener.onItemClick(position);
}));
recyclerView.setLayoutManager(
new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
- return builder.setView(mRootView).create();
+ AlertDialog dialog = builder.setView(mRootView).create();
+ dialog.setCanceledOnTouchOutside(false);
+ return dialog;
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
index a375a3c..bd8027c 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing;
+import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.content.Context;
import android.util.Log;
import android.widget.Switch;
@@ -24,36 +26,116 @@
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
+import com.android.settings.bluetooth.Utils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.flags.Flags;
import com.android.settings.widget.SettingsMainSwitchBar;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.OnMainSwitchChangeListener;
+import java.util.ArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
public class AudioSharingSwitchBarController extends BasePreferenceController
implements DefaultLifecycleObserver, OnMainSwitchChangeListener {
private static final String TAG = "AudioSharingSwitchBarCtl";
private static final String PREF_KEY = "audio_sharing_main_switch";
-
- private final Context mContext;
private final SettingsMainSwitchBar mSwitchBar;
+ private final LocalBluetoothManager mBtManager;
+ private final LocalBluetoothLeBroadcast mBroadcast;
+ private final Executor mExecutor;
private DashboardFragment mFragment;
+ private final BluetoothLeBroadcast.Callback mBroadcastCallback =
+ new BluetoothLeBroadcast.Callback() {
+ @Override
+ public void onBroadcastStarted(int reason, int broadcastId) {
+ Log.d(
+ TAG,
+ "onBroadcastStarted(), reason = "
+ + reason
+ + ", broadcastId = "
+ + broadcastId);
+ updateSwitch();
+ }
+
+ @Override
+ public void onBroadcastStartFailed(int reason) {
+ Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason);
+ // TODO: handle broadcast start fail
+ updateSwitch();
+ }
+
+ @Override
+ public void onBroadcastMetadataChanged(
+ int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) {
+ Log.d(
+ TAG,
+ "onBroadcastMetadataChanged(), broadcastId = "
+ + broadcastId
+ + ", metadata = "
+ + metadata);
+ // TODO: handle add sink if there are connected lea devices.
+ }
+
+ @Override
+ public void onBroadcastStopped(int reason, int broadcastId) {
+ Log.d(
+ TAG,
+ "onBroadcastStopped(), reason = "
+ + reason
+ + ", broadcastId = "
+ + broadcastId);
+ updateSwitch();
+ }
+
+ @Override
+ public void onBroadcastStopFailed(int reason) {
+ Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason);
+ // TODO: handle broadcast stop fail
+ updateSwitch();
+ }
+
+ @Override
+ public void onBroadcastUpdated(int reason, int broadcastId) {}
+
+ @Override
+ public void onBroadcastUpdateFailed(int reason, int broadcastId) {}
+
+ @Override
+ public void onPlaybackStarted(int reason, int broadcastId) {}
+
+ @Override
+ public void onPlaybackStopped(int reason, int broadcastId) {}
+ };
+
AudioSharingSwitchBarController(Context context, SettingsMainSwitchBar switchBar) {
super(context, PREF_KEY);
- mContext = context;
mSwitchBar = switchBar;
- mSwitchBar.setChecked(false);
+ mBtManager = Utils.getLocalBtManager(context);
+ mBroadcast = mBtManager.getProfileManager().getLeAudioBroadcastProfile();
+ mExecutor = Executors.newSingleThreadExecutor();
+ mSwitchBar.setChecked(isBroadcasting());
}
@Override
public void onStart(@NonNull LifecycleOwner owner) {
mSwitchBar.addOnSwitchChangeListener(this);
+ if (mBroadcast != null) {
+ mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
+ }
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
mSwitchBar.removeOnSwitchChangeListener(this);
+ if (mBroadcast != null) {
+ mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
+ }
}
@Override
@@ -63,7 +145,7 @@
if (isChecked) {
startAudioSharing();
} else {
- // TODO: stop sharing
+ stopAudioSharing();
}
}
@@ -82,10 +164,53 @@
}
private void startAudioSharing() {
- if (mFragment != null) {
- AudioSharingDialogFragment.show(mFragment);
- } else {
- Log.w(TAG, "Dialog fail to show due to null fragment.");
+ mSwitchBar.setEnabled(false);
+ if (mBroadcast == null || isBroadcasting()) {
+ Log.d(TAG, "Already in broadcasting or broadcast not support, ignore!");
+ mSwitchBar.setEnabled(true);
+ return;
}
+ if (mFragment == null) {
+ Log.w(TAG, "Dialog fail to show due to null fragment.");
+ mSwitchBar.setEnabled(true);
+ return;
+ }
+ ArrayList<String> deviceNames = new ArrayList<>();
+ AudioSharingDialogFragment.show(
+ mFragment,
+ deviceNames,
+ new AudioSharingDialogFragment.DialogEventListener() {
+ @Override
+ public void onItemClick(int position) {
+ // TODO: handle broadcast based on the dialog device item clicked
+ }
+
+ @Override
+ public void onCancelClick() {
+ mBroadcast.startBroadcast("test", /* language= */ null);
+ }
+ });
+ }
+
+ private void stopAudioSharing() {
+ mSwitchBar.setEnabled(false);
+ if (mBroadcast == null || !isBroadcasting()) {
+ Log.d(TAG, "Already not broadcasting or broadcast not support, ignore!");
+ mSwitchBar.setEnabled(true);
+ return;
+ }
+ mBroadcast.stopBroadcast(mBroadcast.getLatestBroadcastId());
+ }
+
+ private void updateSwitch() {
+ ThreadUtils.postOnMainThread(
+ () -> {
+ mSwitchBar.setChecked(isBroadcasting());
+ mSwitchBar.setEnabled(true);
+ });
+ }
+
+ private boolean isBroadcasting() {
+ return mBroadcast != null && mBroadcast.isEnabled(null);
}
}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
index ebf1543..dc8dfb6 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
@@ -70,8 +70,7 @@
break;
case Intent.ACTION_TIME_CHANGED:
Log.d(TAG, "refresh job and clear all data from action=" + action);
- DatabaseUtils.clearAll(context);
- PeriodicJobManager.getInstance(context).refreshJob(/*fromBoot=*/ false);
+ DatabaseUtils.clearDataAfterTimeChangedIfNeeded(context);
break;
default:
Log.w(TAG, "receive unsupported action=" + action);
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java
index 1a226fd..c298afa 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java
@@ -72,8 +72,6 @@
private static final String TAG = "DataProcessManager";
private static final List<BatteryEventType> POWER_CONNECTION_EVENTS =
List.of(BatteryEventType.POWER_CONNECTED, BatteryEventType.POWER_DISCONNECTED);
- private static final List<BatteryEventType> BATTERY_LEVEL_RECORD_EVENTS =
- List.of(BatteryEventType.FULL_CHARGED, BatteryEventType.EVEN_HOUR);
// For testing only.
@VisibleForTesting
@@ -575,7 +573,7 @@
final List<BatteryEvent> batteryLevelRecordEvents =
DatabaseUtils.getBatteryEvents(
context, Calendar.getInstance(), lastFullChargeTime,
- BATTERY_LEVEL_RECORD_EVENTS);
+ DatabaseUtils.BATTERY_LEVEL_RECORD_EVENTS);
final long startTimestamp = batteryLevelRecordEvents.isEmpty()
? lastFullChargeTime : batteryLevelRecordEvents.get(0).getTimestamp();
final BatteryLevelData batteryLevelData = getPeriodBatteryLevelData(context, handler,
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
index e78d25c..d099843 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
@@ -53,6 +53,7 @@
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
@@ -133,6 +134,9 @@
.authority(AUTHORITY)
.appendPath(BATTERY_USAGE_SLOT_TABLE)
.build();
+ /** A list of level record event types to access battery usage data. */
+ public static final List<BatteryEventType> BATTERY_LEVEL_RECORD_EVENTS =
+ List.of(BatteryEventType.FULL_CHARGED, BatteryEventType.EVEN_HOUR);
// For testing only.
@VisibleForTesting
@@ -408,6 +412,35 @@
});
}
+ /** Clears all data and jobs if current timestamp is out of the range of last recorded job. */
+ public static void clearDataAfterTimeChangedIfNeeded(Context context) {
+ AsyncTask.execute(() -> {
+ try {
+ final List<BatteryEvent> batteryLevelRecordEvents =
+ DatabaseUtils.getBatteryEvents(context, Calendar.getInstance(),
+ getLastFullChargeTime(context), BATTERY_LEVEL_RECORD_EVENTS);
+ final long lastRecordTimestamp = batteryLevelRecordEvents.isEmpty()
+ ? INVALID_TIMESTAMP : batteryLevelRecordEvents.get(0).getTimestamp();
+ final long nextRecordTimestamp =
+ TimestampUtils.getNextEvenHourTimestamp(lastRecordTimestamp);
+ final long currentTime = System.currentTimeMillis();
+ final boolean isOutOfTimeRange = lastRecordTimestamp == INVALID_TIMESTAMP
+ || currentTime < lastRecordTimestamp || currentTime > nextRecordTimestamp;
+ final String logInfo = String.format(Locale.ENGLISH,
+ "clear database = %b, current time = %d, last record time = %d",
+ isOutOfTimeRange, currentTime, lastRecordTimestamp);
+ Log.d(TAG, logInfo);
+ BatteryUsageLogUtils.writeLog(context, Action.TIME_UPDATED, logInfo);
+ if (isOutOfTimeRange) {
+ DatabaseUtils.clearAll(context);
+ PeriodicJobManager.getInstance(context).refreshJob(/* fromBoot= */ false);
+ }
+ } catch (RuntimeException e) {
+ Log.e(TAG, "refreshDataAndJobIfNeededAfterTimeChanged() failed", e);
+ }
+ });
+ }
+
/** Returns the timestamp for 00:00 6 days before the calendar date. */
public static long getTimestampSixDaysAgo(Calendar calendar) {
Calendar startCalendar =
diff --git a/src/com/android/settings/localepicker/AppLocalePickerActivity.java b/src/com/android/settings/localepicker/AppLocalePickerActivity.java
index 194a08f..6706c6d 100644
--- a/src/com/android/settings/localepicker/AppLocalePickerActivity.java
+++ b/src/com/android/settings/localepicker/AppLocalePickerActivity.java
@@ -23,6 +23,7 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
@@ -45,12 +46,18 @@
import com.android.settings.applications.AppLocaleUtil;
import com.android.settings.applications.appinfo.AppLocaleDetails;
import com.android.settings.core.SettingsBaseActivity;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
public class AppLocalePickerActivity extends SettingsBaseActivity
implements LocalePickerWithRegion.LocaleSelectedListener, MenuItem.OnActionExpandListener {
private static final String TAG = AppLocalePickerActivity.class.getSimpleName();
private static final String CHANNEL_ID_SUGGESTION = "suggestion";
private static final String CHANNEL_ID_SUGGESTION_TO_USER = "Locale suggestion";
+ private static final int SIM_LOCALE = 1 << 0;
+ private static final int SYSTEM_LOCALE = 1 << 1;
+ private static final int APP_LOCALE = 1 << 2;
+ private static final int IME_LOCALE = 1 << 3;
static final String EXTRA_APP_LOCALE = "app_locale";
static final String EXTRA_NOTIFICATION_ID = "notification_id";
@@ -59,6 +66,7 @@
private AppLocaleDetails mAppLocaleDetails;
private View mAppLocaleDetailContainer;
private NotificationController mNotificationController;
+ private MetricsFeatureProvider mMetricsFeatureProvider;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -84,6 +92,7 @@
setTitle(R.string.app_locale_picker_title);
getActionBar().setDisplayHomeAsUpEnabled(true);
+ mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
mNotificationController = NotificationController.getInstance(this);
mLocalePickerWithRegion = LocalePickerWithRegion.createLanguagePicker(
@@ -113,6 +122,7 @@
if (localeInfo == null || localeInfo.getLocale() == null || localeInfo.isSystemLocale()) {
setAppDefaultLocale("");
} else {
+ logLocaleSource(localeInfo);
setAppDefaultLocale(localeInfo.getLocale().toLanguageTag());
broadcastAppLocaleChange(localeInfo);
}
@@ -268,4 +278,32 @@
return false;
}
+
+ private void logLocaleSource(LocaleStore.LocaleInfo localeInfo) {
+ if (!localeInfo.isSuggested() || localeInfo.isAppCurrentLocale()) {
+ return;
+ }
+ int localeSource = 0;
+ if (hasSuggestionType(localeInfo,
+ LocaleStore.LocaleInfo.SUGGESTION_TYPE_SYSTEM_AVAILABLE_LANGUAGE)) {
+ localeSource |= SYSTEM_LOCALE;
+ }
+ if (hasSuggestionType(localeInfo,
+ LocaleStore.LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE)) {
+ localeSource |= APP_LOCALE;
+ }
+ if (hasSuggestionType(localeInfo, LocaleStore.LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE)) {
+ localeSource |= IME_LOCALE;
+ }
+ if (hasSuggestionType(localeInfo, LocaleStore.LocaleInfo.SUGGESTION_TYPE_SIM)) {
+ localeSource |= SIM_LOCALE;
+ }
+ mMetricsFeatureProvider.action(this,
+ SettingsEnums.ACTION_CHANGE_APP_LANGUAGE_FROM_SUGGESTED, localeSource);
+ }
+
+ private static boolean hasSuggestionType(LocaleStore.LocaleInfo localeInfo,
+ int suggestionType) {
+ return localeInfo.isSuggestionOfType(suggestionType);
+ }
}
diff --git a/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java b/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java
index 2830024..200a47b 100644
--- a/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java
+++ b/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceController.java
@@ -39,13 +39,18 @@
import com.android.settings.applications.AppInfoBase;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.flags.Flags;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.PrimarySwitchPreference;
import com.android.settingslib.RestrictedSwitchPreference;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* Populates the PreferenceCategory with notification channels associated with the given app.
@@ -61,6 +66,9 @@
private RestrictedSwitchPreference mAllNotificationsToggle;
private PreferenceCategory mPreferenceCategory;
private List<NotificationChannel> mChannels = new ArrayList<>();
+ private Set<String> mDuplicateChannelNames = new HashSet<>();
+ private Map<NotificationChannel, String> mChannelGroupNames =
+ new HashMap<NotificationChannel, String>();
public AppChannelsBypassingDndPreferenceController(
Context context,
@@ -135,10 +143,20 @@
List<NotificationChannel> newChannelList = new ArrayList<>();
List<NotificationChannelGroup> mChannelGroupList = mBackend.getGroups(mAppRow.pkg,
mAppRow.uid).getList();
+ Set<String> allChannelNames = new HashSet<>();
for (NotificationChannelGroup channelGroup : mChannelGroupList) {
for (NotificationChannel channel : channelGroup.getChannels()) {
if (!isConversation(channel)) {
newChannelList.add(channel);
+ if (Flags.dedupeDndSettingsChannels()) {
+ mChannelGroupNames.put(channel, channelGroup.getName().toString());
+ // Check if channel name is unique on this page; if not, save it.
+ if (allChannelNames.contains(channel.getName())) {
+ mDuplicateChannelNames.add(channel.getName().toString());
+ } else {
+ allChannelNames.add(channel.getName().toString());
+ }
+ }
}
}
}
@@ -172,6 +190,17 @@
&& isChannelConfigurable(channel)
&& showNotification(channel));
channelPreference.setTitle(BidiFormatter.getInstance().unicodeWrap(channel.getName()));
+ if (Flags.dedupeDndSettingsChannels()) {
+ // If the channel shares its name with another channel, set group name as summary
+ // to disambiguate in the list.
+ if (mDuplicateChannelNames.contains(channel.getName().toString())
+ && mChannelGroupNames.containsKey(channel)
+ && mChannelGroupNames.get(channel) != null
+ && !mChannelGroupNames.get(channel).isEmpty()) {
+ channelPreference.setSummary(BidiFormatter.getInstance().unicodeWrap(
+ mChannelGroupNames.get(channel)));
+ }
+ }
channelPreference.setChecked(showNotificationInDnd(channel));
channelPreference.setOnPreferenceChangeListener(
new Preference.OnPreferenceChangeListener() {
diff --git a/src/com/android/settings/overlay/FeatureFactory.kt b/src/com/android/settings/overlay/FeatureFactory.kt
index ac689d9..bc0cf1f 100644
--- a/src/com/android/settings/overlay/FeatureFactory.kt
+++ b/src/com/android/settings/overlay/FeatureFactory.kt
@@ -39,6 +39,7 @@
import com.android.settings.onboarding.OnboardingFeatureProvider
import com.android.settings.overlay.FeatureFactory.Companion.setFactory
import com.android.settings.panel.PanelFeatureProvider
+import com.android.settings.privatespace.PrivateSpaceLoginFeatureProvider
import com.android.settings.search.SearchFeatureProvider
import com.android.settings.security.SecurityFeatureProvider
import com.android.settings.security.SecuritySettingsFeatureProvider
@@ -170,6 +171,11 @@
*/
abstract val fastPairFeatureProvider: FastPairFeatureProvider
+ /**
+ * Gets implementation for Private Space account login feature.
+ */
+ abstract val privateSpaceLoginFeatureProvider: PrivateSpaceLoginFeatureProvider
+
companion object {
private var _factory: FeatureFactory? = null
diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.kt b/src/com/android/settings/overlay/FeatureFactoryImpl.kt
index 7f991b7..28dbb23 100644
--- a/src/com/android/settings/overlay/FeatureFactoryImpl.kt
+++ b/src/com/android/settings/overlay/FeatureFactoryImpl.kt
@@ -59,6 +59,8 @@
import com.android.settings.security.SecurityFeatureProviderImpl
import com.android.settings.security.SecuritySettingsFeatureProvider
import com.android.settings.security.SecuritySettingsFeatureProviderImpl
+import com.android.settings.privatespace.PrivateSpaceLoginFeatureProvider
+import com.android.settings.privatespace.PrivateSpaceLoginFeatureProviderImpl
import com.android.settings.slices.SlicesFeatureProviderImpl
import com.android.settings.users.UserFeatureProviderImpl
import com.android.settings.vpn2.AdvancedVpnFeatureProviderImpl
@@ -184,4 +186,8 @@
override val fastPairFeatureProvider: FastPairFeatureProvider by lazy {
FastPairFeatureProviderImpl()
}
+
+ override val privateSpaceLoginFeatureProvider: PrivateSpaceLoginFeatureProvider by lazy {
+ PrivateSpaceLoginFeatureProviderImpl()
+ }
}
diff --git a/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java b/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java
index 5456c01..3b59166 100644
--- a/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java
+++ b/src/com/android/settings/privatespace/AutoAdvanceSetupFragment.java
@@ -16,7 +16,8 @@
package com.android.settings.privatespace;
-import static com.android.settings.privatespace.PrivateSpaceSetupActivity.SET_LOCK_ACTION;
+import static com.android.settings.privatespace.PrivateSpaceSetupActivity.ACCOUNT_LOGIN_ACTION;
+import static com.android.settings.privatespace.PrivateSpaceSetupActivity.EXTRA_ACTION_TYPE;
import android.annotation.SuppressLint;
import android.content.Intent;
@@ -134,7 +135,8 @@
private void startActivityInPrivateUser(UserHandle userHandle) {
/* Start new activity in private profile which is needed to set private profile lock */
Intent intent = new Intent(getContext(), PrivateProfileContextHelperActivity.class);
- getActivity().startActivityForResultAsUser(intent, SET_LOCK_ACTION, userHandle);
+ intent.putExtra(EXTRA_ACTION_TYPE, ACCOUNT_LOGIN_ACTION);
+ getActivity().startActivityForResultAsUser(intent, ACCOUNT_LOGIN_ACTION, userHandle);
}
private void showPrivateSpaceErrorScreen() {
diff --git a/src/com/android/settings/privatespace/PrivateProfileContextHelperActivity.java b/src/com/android/settings/privatespace/PrivateProfileContextHelperActivity.java
index c0d762a..0539f60 100644
--- a/src/com/android/settings/privatespace/PrivateProfileContextHelperActivity.java
+++ b/src/com/android/settings/privatespace/PrivateProfileContextHelperActivity.java
@@ -16,27 +16,84 @@
package com.android.settings.privatespace;
+import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD;
+import static android.app.admin.DevicePolicyManager.EXTRA_PASSWORD_COMPLEXITY;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
+
+import static com.android.settings.privatespace.PrivateSpaceSetupActivity.ACCOUNT_LOGIN_ACTION;
+import static com.android.settings.privatespace.PrivateSpaceSetupActivity.EXTRA_ACTION_TYPE;
+import static com.android.settings.privatespace.PrivateSpaceSetupActivity.SET_LOCK_ACTION;
+
+import android.app.KeyguardManager;
+import android.content.Intent;
import android.os.Bundle;
+import androidx.activity.result.ActivityResult;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
-import androidx.navigation.fragment.NavHostFragment;
-import com.android.settings.R;
import com.android.settings.SetupWizardUtils;
+import com.android.settings.overlay.FeatureFactory;
import com.google.android.setupdesign.util.ThemeHelper;
-/** Activity that is started as private profile user that helps to set private profile lock. */
+/** Activity that is started as private profile user that helps to set private profile lock or
+ * add an account on the private profile. */
public class PrivateProfileContextHelperActivity extends FragmentActivity {
private static final String TAG = "PrivateProfileHelper";
+ private final ActivityResultLauncher<Intent> mAddAccountToPrivateProfile =
+ registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
+ this::onAccountAdded);
+ private final ActivityResultLauncher<Intent> mVerifyDeviceLock =
+ registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
+ this::onSetDeviceNewLock);
+
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
ThemeHelper.trySetDynamicColor(this);
super.onCreate(savedInstanceState);
- setContentView(R.layout.privatespace_setup_root);
- NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
- .findFragmentById(R.id.ps_nav_host_fragment);
- navHostFragment.getNavController().setGraph(R.navigation.privatespace_private_context_nav);
+ if (savedInstanceState == null) {
+ int action = getIntent().getIntExtra(EXTRA_ACTION_TYPE, -1);
+ if (action == ACCOUNT_LOGIN_ACTION) {
+ PrivateSpaceLoginFeatureProvider privateSpaceLoginFeatureProvider =
+ FeatureFactory.getFeatureFactory().getPrivateSpaceLoginFeatureProvider();
+ if (!privateSpaceLoginFeatureProvider.initiateAccountLogin(this,
+ mAddAccountToPrivateProfile)) {
+ setResult(RESULT_OK);
+ finish();
+ }
+ } else if (action == SET_LOCK_ACTION) {
+ createPrivateSpaceLock();
+ }
+ }
+ }
+
+ private void createPrivateSpaceLock() {
+ final Intent intent = new Intent(ACTION_SET_NEW_PASSWORD);
+ intent.putExtra(EXTRA_PASSWORD_COMPLEXITY, PASSWORD_COMPLEXITY_LOW);
+ mVerifyDeviceLock.launch(intent);
+ }
+
+ private void onAccountAdded(@Nullable ActivityResult result) {
+ if (result != null && result.getResultCode() == RESULT_OK) {
+ setResult(RESULT_OK);
+ } else {
+ setResult(RESULT_CANCELED);
+ }
+ finish();
+ }
+
+ private void onSetDeviceNewLock(@Nullable ActivityResult result) {
+ // TODO(b/307281644) : Verify this for biometrics and check result code after new
+ // Authentication changes are merged.
+ if (result != null && getSystemService(KeyguardManager.class).isDeviceSecure()) {
+ setResult(RESULT_OK);
+ } else {
+ setResult(RESULT_CANCELED);
+ }
+ finish();
}
}
diff --git a/src/com/android/settings/privatespace/PrivateSpaceAccountLoginError.java b/src/com/android/settings/privatespace/PrivateSpaceAccountLoginError.java
new file mode 100644
index 0000000..2d263d7
--- /dev/null
+++ b/src/com/android/settings/privatespace/PrivateSpaceAccountLoginError.java
@@ -0,0 +1,85 @@
+/*
+ * 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.privatespace;
+
+import static com.android.settings.privatespace.PrivateSpaceSetupActivity.ACCOUNT_LOGIN_ACTION;
+import static com.android.settings.privatespace.PrivateSpaceSetupActivity.EXTRA_ACTION_TYPE;
+
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.activity.OnBackPressedCallback;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.android.settings.R;
+
+import com.google.android.setupcompat.template.FooterBarMixin;
+import com.google.android.setupcompat.template.FooterButton;
+import com.google.android.setupdesign.GlifLayout;
+
+/** Fragment to display error screen if the profile is not signed in with a Google account. */
+public class PrivateSpaceAccountLoginError extends Fragment {
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater,
+ @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ GlifLayout rootView =
+ (GlifLayout) inflater
+ .inflate(R.layout.privatespace_account_login_error, container, false);
+ final FooterBarMixin mixin = rootView.getMixin(FooterBarMixin.class);
+ mixin.setPrimaryButton(
+ new FooterButton.Builder(getContext())
+ .setText(R.string.privatespace_tryagain_label)
+ .setListener(nextScreen())
+ .setButtonType(FooterButton.ButtonType.NEXT)
+ .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
+ .build());
+ OnBackPressedCallback callback =
+ new OnBackPressedCallback(true /* enabled by default */) {
+ @Override
+ public void handleOnBackPressed() {
+ // Handle the back button event
+ }
+ };
+ requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
+
+ return rootView;
+ }
+
+ @SuppressLint("MissingPermission")
+ private View.OnClickListener nextScreen() {
+ return v -> {
+ PrivateSpaceMaintainer privateSpaceMaintainer = PrivateSpaceMaintainer
+ .getInstance(getActivity());
+ UserHandle userHandle;
+ if (privateSpaceMaintainer.doesPrivateSpaceExist() && (userHandle =
+ privateSpaceMaintainer.getPrivateProfileHandle()) != null) {
+ Intent intent = new Intent(getContext(), PrivateProfileContextHelperActivity.class);
+ intent.putExtra(EXTRA_ACTION_TYPE, ACCOUNT_LOGIN_ACTION);
+ getActivity().startActivityForResultAsUser(intent, ACCOUNT_LOGIN_ACTION,
+ userHandle);
+ }
+ };
+ }
+}
diff --git a/src/com/android/settings/privatespace/PrivateSpaceLoginFeatureProvider.java b/src/com/android/settings/privatespace/PrivateSpaceLoginFeatureProvider.java
new file mode 100644
index 0000000..76ea9ac
--- /dev/null
+++ b/src/com/android/settings/privatespace/PrivateSpaceLoginFeatureProvider.java
@@ -0,0 +1,30 @@
+/*
+ * 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.privatespace;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.annotation.NonNull;
+
+/** Feature provider for account login during Private Space setup. */
+public interface PrivateSpaceLoginFeatureProvider {
+ /** Returns true if login to an account is enabled during Private Space setup. */
+ boolean initiateAccountLogin(@NonNull Context context,
+ @NonNull ActivityResultLauncher<Intent> resultLauncher);
+}
diff --git a/src/com/android/settings/privatespace/PrivateSpaceLoginFeatureProviderImpl.java b/src/com/android/settings/privatespace/PrivateSpaceLoginFeatureProviderImpl.java
new file mode 100644
index 0000000..7fca2a4
--- /dev/null
+++ b/src/com/android/settings/privatespace/PrivateSpaceLoginFeatureProviderImpl.java
@@ -0,0 +1,32 @@
+/*
+ * 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.privatespace;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.annotation.NonNull;
+
+/** Stub class for Private space to not initiate account login during setup */
+public class PrivateSpaceLoginFeatureProviderImpl implements PrivateSpaceLoginFeatureProvider {
+ @Override
+ public boolean initiateAccountLogin(@NonNull Context context,
+ @NonNull ActivityResultLauncher<Intent> resultLauncher) {
+ return false;
+ }
+}
diff --git a/src/com/android/settings/privatespace/PrivateSpaceSetLockFragment.java b/src/com/android/settings/privatespace/PrivateSpaceSetLockFragment.java
index 3d17638..93dc43b 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceSetLockFragment.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceSetLockFragment.java
@@ -16,25 +16,21 @@
package com.android.settings.privatespace;
-import static android.app.Activity.RESULT_OK;
-import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD;
-import static android.app.admin.DevicePolicyManager.EXTRA_PASSWORD_COMPLEXITY;
-import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
+import static com.android.settings.privatespace.PrivateSpaceSetupActivity.EXTRA_ACTION_TYPE;
+import static com.android.settings.privatespace.PrivateSpaceSetupActivity.SET_LOCK_ACTION;
-import android.app.Activity;
-import android.app.KeyguardManager;
+import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
+import android.os.UserHandle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.activity.OnBackPressedCallback;
-import androidx.activity.result.ActivityResult;
-import androidx.activity.result.ActivityResultLauncher;
-import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
+import androidx.navigation.fragment.NavHostFragment;
import com.android.settings.R;
@@ -45,9 +41,6 @@
/** Fragment that provides an option to user to choose between the existing screen lock or set a
* separate private profile lock. */
public class PrivateSpaceSetLockFragment extends Fragment {
- private final ActivityResultLauncher<Intent> mVerifyDeviceLock =
- registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
- this::onSetDeviceNewLock);
@Override
public View onCreateView(
@@ -90,11 +83,8 @@
private View.OnClickListener onClickUse() {
return v -> {
// Simply Use default screen lock. No need to handle
- Activity activity = getActivity();
- if (activity != null) {
- activity.setResult(RESULT_OK);
- activity.finish();
- }
+ NavHostFragment.findNavController(PrivateSpaceSetLockFragment.this)
+ .navigate(R.id.action_success_fragment);
};
}
@@ -104,22 +94,17 @@
};
}
+ @SuppressLint("MissingPermission")
private void createPrivateSpaceLock() {
- final Intent intent = new Intent(ACTION_SET_NEW_PASSWORD);
- intent.putExtra(EXTRA_PASSWORD_COMPLEXITY, PASSWORD_COMPLEXITY_LOW);
- mVerifyDeviceLock.launch(intent);
- }
-
- private void onSetDeviceNewLock(@Nullable ActivityResult result) {
- // TODO(b/307281644) : Verify this for biometrics and check result code after new
- // Authentication changes are merged.
- if (result != null) {
- Activity profileContextHelperActivity = getActivity();
- if (profileContextHelperActivity != null && profileContextHelperActivity
- .getSystemService(KeyguardManager.class).isDeviceSecure()) {
- profileContextHelperActivity.setResult(RESULT_OK);
- profileContextHelperActivity.finish();
- }
+ PrivateSpaceMaintainer privateSpaceMaintainer = PrivateSpaceMaintainer
+ .getInstance(getActivity());
+ UserHandle userHandle;
+ if (privateSpaceMaintainer.doesPrivateSpaceExist() && (userHandle =
+ privateSpaceMaintainer.getPrivateProfileHandle()) != null) {
+ Intent intent = new Intent(getContext(), PrivateProfileContextHelperActivity.class);
+ intent.putExtra(EXTRA_ACTION_TYPE, SET_LOCK_ACTION);
+ getActivity().startActivityForResultAsUser(intent, SET_LOCK_ACTION,
+ userHandle);
}
}
}
diff --git a/src/com/android/settings/privatespace/PrivateSpaceSetupActivity.java b/src/com/android/settings/privatespace/PrivateSpaceSetupActivity.java
index 3a58e9e..a5628c8 100644
--- a/src/com/android/settings/privatespace/PrivateSpaceSetupActivity.java
+++ b/src/com/android/settings/privatespace/PrivateSpaceSetupActivity.java
@@ -31,6 +31,8 @@
/** Activity class that helps in setting up of private space */
public class PrivateSpaceSetupActivity extends FragmentActivity {
public static final int SET_LOCK_ACTION = 1;
+ public static final int ACCOUNT_LOGIN_ACTION = 2;
+ public static final String EXTRA_ACTION_TYPE = "action_type";
private NavHostFragment mNavHostFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -46,7 +48,13 @@
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == SET_LOCK_ACTION && resultCode == RESULT_OK) {
- mNavHostFragment.getNavController().navigate(R.id.action_advance_to_success);
+ mNavHostFragment.getNavController().navigate(R.id.action_success_fragment);
+ } else if (requestCode == ACCOUNT_LOGIN_ACTION) {
+ if (resultCode == RESULT_OK) {
+ mNavHostFragment.getNavController().navigate(R.id.action_set_lock_fragment);
+ } else {
+ mNavHostFragment.getNavController().navigate(R.id.action_advance_login_error);
+ }
}
super.onActivityResult(requestCode, resultCode, data);
}
diff --git a/src/com/android/settings/privatespace/SetupSuccessFragment.java b/src/com/android/settings/privatespace/SetupSuccessFragment.java
index a8ca3f1..b761da7 100644
--- a/src/com/android/settings/privatespace/SetupSuccessFragment.java
+++ b/src/com/android/settings/privatespace/SetupSuccessFragment.java
@@ -70,7 +70,7 @@
private View.OnClickListener onClickNext() {
return v -> {
accessPrivateSpaceToast();
- // TODO: Replace with the intent to launch PS/PS Launch Settings
+ // TODO(b/306228087): Replace with the intent to launch All Apps once it is working.
Intent startMain = new Intent(Intent.ACTION_MAIN);
startMain.addCategory(Intent.CATEGORY_HOME);
startActivity(startMain);
diff --git a/src/com/android/settings/spa/SettingsSpaEnvironment.kt b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
index 40cc9a2..6b96460 100644
--- a/src/com/android/settings/spa/SettingsSpaEnvironment.kt
+++ b/src/com/android/settings/spa/SettingsSpaEnvironment.kt
@@ -30,12 +30,15 @@
import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider
import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider
import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider
+import com.android.settings.spa.app.specialaccess.LongBackgroundTasksAppListProvider
import com.android.settings.spa.app.specialaccess.MediaManagementAppsAppListProvider
import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListProvider
import com.android.settings.spa.app.specialaccess.NfcTagAppsSettingsProvider
import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider
import com.android.settings.spa.app.specialaccess.SpecialAppAccessPageProvider
+import com.android.settings.spa.app.specialaccess.TurnScreenOnAppsAppListProvider
import com.android.settings.spa.app.specialaccess.UseFullScreenIntentAppListProvider
+import com.android.settings.spa.app.specialaccess.VoiceActivationAppsListProvider
import com.android.settings.spa.app.specialaccess.WifiControlAppListProvider
import com.android.settings.spa.app.storage.StorageAppListPageProvider
import com.android.settings.spa.core.instrumentation.SpaLogProvider
@@ -66,8 +69,11 @@
PictureInPictureListProvider,
InstallUnknownAppsListProvider,
AlarmsAndRemindersAppListProvider,
+ VoiceActivationAppsListProvider,
WifiControlAppListProvider,
NfcTagAppsSettingsProvider,
+ LongBackgroundTasksAppListProvider,
+ TurnScreenOnAppsAppListProvider,
)
}
diff --git a/src/com/android/settings/spa/about/AboutPhone.kt b/src/com/android/settings/spa/about/AboutPhone.kt
index 7343da0..5f9aa97 100644
--- a/src/com/android/settings/spa/about/AboutPhone.kt
+++ b/src/com/android/settings/spa/about/AboutPhone.kt
@@ -29,7 +29,6 @@
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
@@ -56,7 +55,7 @@
val deviceNamePresenter = remember { DeviceNamePresenter(context) }
Preference(object : PreferenceModel {
override val title = stringResource(R.string.about_settings)
- override val summary = deviceNamePresenter.deviceName.toState()
+ override val summary = { deviceNamePresenter.deviceName }
override val onClick = navigator(name)
override val icon = @Composable {
SettingsIcon(imageVector = Icons.Outlined.PermDeviceInformation)
diff --git a/src/com/android/settings/spa/about/DeviceName.kt b/src/com/android/settings/spa/about/DeviceName.kt
index c481e32..86a9512 100644
--- a/src/com/android/settings/spa/about/DeviceName.kt
+++ b/src/com/android/settings/spa/about/DeviceName.kt
@@ -24,7 +24,6 @@
import androidx.compose.ui.res.stringResource
import com.android.settings.R
import com.android.settings.deviceinfo.DeviceNamePreferenceController
-import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.widget.dialog.AlertDialogButton
import com.android.settingslib.spa.widget.dialog.rememberAlertDialogPresenter
import com.android.settingslib.spa.widget.preference.Preference
@@ -48,7 +47,7 @@
Preference(object : PreferenceModel {
override val title =
stringResource(R.string.my_device_info_device_name_preference_title)
- override val summary = deviceNamePresenter.deviceName.toState()
+ override val summary = { deviceNamePresenter.deviceName }
override val onClick = dialogPresenter::open
})
diff --git a/src/com/android/settings/spa/app/AllAppList.kt b/src/com/android/settings/spa/app/AllAppList.kt
index 383a0e8..5b13211 100644
--- a/src/com/android/settings/spa/app/AllAppList.kt
+++ b/src/com/android/settings/spa/app/AllAppList.kt
@@ -21,8 +21,6 @@
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource
import com.android.settings.R
import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider
@@ -116,7 +114,7 @@
option: Int,
recordListFlow: Flow<List<AppRecordWithSize>>,
): Flow<List<AppRecordWithSize>> = recordListFlow.filterItem(
- when (SpinnerItem.values().getOrNull(option)) {
+ when (SpinnerItem.entries.getOrNull(option)) {
SpinnerItem.Enabled -> ({ it.app.enabled && !it.app.isInstantApp })
SpinnerItem.Disabled -> isDisabled
SpinnerItem.Instant -> isInstant
@@ -130,22 +128,20 @@
private val isInstant: (AppRecordWithSize) -> Boolean = { it.app.isInstantApp }
@Composable
- override fun getSummary(option: Int, record: AppRecordWithSize): State<String> {
+ override fun getSummary(option: Int, record: AppRecordWithSize): () -> String {
val storageSummary = record.app.getStorageSummary()
- return remember {
- derivedStateOf {
- storageSummary.value +
- when {
- !record.app.installed && !record.app.isArchived -> {
- System.lineSeparator() + context.getString(R.string.not_installed)
- }
- isDisabled(record) -> {
- System.lineSeparator() +
- context.getString(com.android.settingslib.R.string.disabled)
- }
- else -> ""
- }
+ return {
+ val summaryList = mutableListOf(storageSummary.value)
+ when {
+ !record.app.installed && !record.app.isArchived -> {
+ summaryList += context.getString(R.string.not_installed)
+ }
+
+ isDisabled(record) -> {
+ summaryList += context.getString(com.android.settingslib.R.string.disabled)
+ }
}
+ summaryList.joinToString(separator = System.lineSeparator())
}
}
diff --git a/src/com/android/settings/spa/app/AppsMain.kt b/src/com/android/settings/spa/app/AppsMain.kt
index 2dea9c5..83b3080 100644
--- a/src/com/android/settings/spa/app/AppsMain.kt
+++ b/src/com/android/settings/spa/app/AppsMain.kt
@@ -30,7 +30,6 @@
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
@@ -54,10 +53,10 @@
fun buildInjectEntry() =
SettingsEntryBuilder.createInject(owner = owner)
.setUiLayoutFn {
+ val summary = stringResource(R.string.app_and_notification_dashboard_summary)
Preference(object : PreferenceModel {
override val title = stringResource(R.string.apps_dashboard_title)
- override val summary =
- stringResource(R.string.app_and_notification_dashboard_summary).toState()
+ override val summary = { summary }
override val onClick = navigator(name)
override val icon = @Composable {
SettingsIcon(imageVector = Icons.Outlined.Apps)
diff --git a/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreference.kt b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreference.kt
index 61098e8..96884be 100644
--- a/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreference.kt
+++ b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppPreference.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -40,11 +41,12 @@
val presenter = remember { UserAspectRatioAppPresenter(context, app) }
if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return
+ val summary by presenter.summaryFlow.collectAsStateWithLifecycle(
+ initialValue = stringResource(R.string.summary_placeholder),
+ )
Preference(object : PreferenceModel {
override val title = stringResource(R.string.aspect_ratio_experimental_title)
- override val summary = presenter.summaryFlow.collectAsStateWithLifecycle(
- initialValue = stringResource(R.string.summary_placeholder),
- )
+ override val summary = { summary }
override val onClick = presenter::startActivity
})
}
@@ -75,4 +77,4 @@
context,
AppInfoSettingsProvider.METRICS_CATEGORY,
)
-}
\ No newline at end of file
+}
diff --git a/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt
index deea745..5af29ef 100644
--- a/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt
+++ b/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProvider.kt
@@ -28,7 +28,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@@ -41,15 +41,14 @@
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.rememberContext
-import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.util.asyncMap
import com.android.settingslib.spa.framework.util.filterItem
-import com.android.settingslib.spa.widget.preference.Preference
-import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.illustration.Illustration
import com.android.settingslib.spa.widget.illustration.IllustrationModel
import com.android.settingslib.spa.widget.illustration.ResourceType
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.ui.SettingsBody
import com.android.settingslib.spa.widget.ui.SpinnerOption
import com.android.settingslib.spaprivileged.model.app.AppListModel
@@ -80,12 +79,14 @@
@Composable
@VisibleForTesting
- fun EntryItem() =
+ fun EntryItem() {
+ val summary = getSummary()
Preference(object : PreferenceModel {
override val title = stringResource(R.string.aspect_ratio_experimental_title)
- override val summary = getSummary().toState()
+ override val summary = { summary }
override val onClick = navigator(name)
})
+ }
@VisibleForTesting
fun buildInjectEntry() = SettingsEntryBuilder
@@ -177,7 +178,7 @@
option: Int,
recordListFlow: Flow<List<UserAspectRatioAppListItemModel>>
): Flow<List<UserAspectRatioAppListItemModel>> = recordListFlow.filterItem(
- when (SpinnerItem.values().getOrNull(option)) {
+ when (SpinnerItem.entries.getOrNull(option)) {
SpinnerItem.Suggested -> ({ it.canDisplay && it.suggested })
SpinnerItem.Overridden -> ({ it.userOverride != USER_MIN_ASPECT_RATIO_UNSET })
else -> ({ it.canDisplay })
@@ -185,13 +186,15 @@
)
@Composable
- override fun getSummary(option: Int, record: UserAspectRatioAppListItemModel) : State<String> =
- remember(record.userOverride) {
+ override fun getSummary(option: Int, record: UserAspectRatioAppListItemModel): () -> String {
+ val summary by remember(record.userOverride) {
flow {
emit(userAspectRatioManager.getUserMinAspectRatioEntry(record.userOverride,
record.app.packageName))
}.flowOn(Dispatchers.IO)
}.collectAsStateWithLifecycle(initialValue = stringResource(R.string.summary_placeholder))
+ return { summary }
+ }
private fun getPackageAndActivityInfo(app: ApplicationInfo): PackageInfo? = try {
packageManager.getPackageInfoAsUser(app.packageName, GET_ACTIVITIES_FLAGS, app.userId)
diff --git a/src/com/android/settings/spa/app/appinfo/AppAllServicesPreference.kt b/src/com/android/settings/spa/app/appinfo/AppAllServicesPreference.kt
index 34272d4..31e068c 100644
--- a/src/com/android/settings/spa/app/appinfo/AppAllServicesPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppAllServicesPreference.kt
@@ -24,6 +24,7 @@
import android.os.Bundle
import android.util.Log
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
@@ -52,11 +53,12 @@
val presenter = remember { AppAllServicesPresenter(context, app, coroutineScope) }
if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return
+ val summary by presenter.summaryFlow.collectAsStateWithLifecycle(
+ initialValue = stringResource(R.string.summary_placeholder),
+ )
Preference(object : PreferenceModel {
override val title = stringResource(R.string.app_info_all_services_label)
- override val summary = presenter.summaryFlow.collectAsStateWithLifecycle(
- initialValue = stringResource(R.string.summary_placeholder),
- )
+ override val summary = { summary }
override val onClick = presenter::startActivity
})
}
diff --git a/src/com/android/settings/spa/app/appinfo/AppBatteryPreference.kt b/src/com/android/settings/spa/app/appinfo/AppBatteryPreference.kt
index 7dd78a9..c707b44b 100644
--- a/src/com/android/settings/spa/app/appinfo/AppBatteryPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppBatteryPreference.kt
@@ -21,7 +21,6 @@
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -91,16 +90,17 @@
}
}
- val enabled = derivedStateOf { batteryDiffEntryState is LoadingState.Done }
+ val enabled = { batteryDiffEntryState is LoadingState.Done }
- val summary = derivedStateOf<String> {
- if (!app.installed) return@derivedStateOf ""
- batteryDiffEntryState.let { batteryDiffEntryState ->
- when (batteryDiffEntryState) {
- is LoadingState.Loading -> context.getString(R.string.summary_placeholder)
- is LoadingState.Done -> batteryDiffEntryState.result.getSummary()
+ val summary = {
+ if (app.installed) {
+ batteryDiffEntryState.let { batteryDiffEntryState ->
+ when (batteryDiffEntryState) {
+ is LoadingState.Loading -> context.getString(R.string.summary_placeholder)
+ is LoadingState.Done -> batteryDiffEntryState.result.getSummary()
+ }
}
- }
+ } else ""
}
private fun BatteryDiffEntry?.getSummary(): String =
@@ -155,7 +155,7 @@
}
private sealed class LoadingState<out T> {
- object Loading : LoadingState<Nothing>()
+ data object Loading : LoadingState<Nothing>()
data class Done<T>(val result: T) : LoadingState<T>()
diff --git a/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt b/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt
index ceb3986..057f911 100644
--- a/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppDataUsagePreference.kt
@@ -20,6 +20,7 @@
import android.content.pm.ApplicationInfo
import android.net.NetworkTemplate
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
@@ -34,7 +35,6 @@
import com.android.settings.datausage.lib.INetworkTemplates
import com.android.settings.datausage.lib.NetworkTemplates
import com.android.settings.datausage.lib.NetworkTemplates.getTitleResId
-import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spaprivileged.model.app.hasFlag
@@ -64,16 +64,17 @@
}
if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return
+ val summary by presenter.summaryFlow.collectAsStateWithLifecycle(
+ initialValue = stringResource(R.string.computing_size),
+ )
Preference(object : PreferenceModel {
override val title = stringResource(
presenter.titleResIdFlow.collectAsStateWithLifecycle(
initialValue = R.string.summary_placeholder,
).value
)
- override val summary = presenter.summaryFlow.collectAsStateWithLifecycle(
- initialValue = stringResource(R.string.computing_size),
- )
- override val enabled = presenter.isEnabled().toState()
+ override val summary = { summary }
+ override val enabled = { presenter.isEnabled() }
override val onClick = presenter::startActivity
})
}
diff --git a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
index ed912c3..3b7f579 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInfoSettings.kt
@@ -32,6 +32,7 @@
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavType
import androidx.navigation.navArgument
+import com.android.settings.flags.Flags
import com.android.settings.R
import com.android.settings.applications.AppInfoBase
import com.android.settings.applications.appinfo.AppInfoDashboardFragment
@@ -42,6 +43,7 @@
import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider
import com.android.settings.spa.app.specialaccess.ModifySystemSettingsAppListProvider
import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider
+import com.android.settings.spa.app.specialaccess.VoiceActivationAppsListProvider
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.LifecycleEffect
import com.android.settingslib.spa.framework.compose.navigator
@@ -160,6 +162,9 @@
InstallUnknownAppsListProvider.InfoPageEntryItem(app)
InteractAcrossProfilesDetailsPreference(app)
AlarmsAndRemindersAppListProvider.InfoPageEntryItem(app)
+ if (Flags.enableVoiceActivationAppsInSettings()) {
+ VoiceActivationAppsListProvider.InfoPageEntryItem(app)
+ }
}
Category(title = stringResource(R.string.app_install_details_group_title)) {
diff --git a/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt b/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt
index 5a348f7..62e714a 100644
--- a/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppInstallerInfoPreference.kt
@@ -19,15 +19,16 @@
import android.content.Context
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R
import com.android.settings.Utils
import com.android.settings.applications.AppStoreUtil
import com.android.settingslib.applications.AppUtils
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spaprivileged.framework.common.asUser
@@ -49,13 +50,14 @@
val presenter = remember { AppInstallerInfoPresenter(context, app, coroutineScope) }
if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return
+ val summary by presenter.summaryFlow.collectAsStateWithLifecycle(
+ initialValue = stringResource(R.string.summary_placeholder),
+ )
+ val enabled by presenter.enabledFlow.collectAsStateWithLifecycle(initialValue = false)
Preference(object : PreferenceModel {
override val title = stringResource(R.string.app_install_details_title)
- override val summary = presenter.summaryFlow.collectAsStateWithLifecycle(
- initialValue = stringResource(R.string.summary_placeholder),
- )
- override val enabled =
- presenter.enabledFlow.collectAsStateWithLifecycle(initialValue = false)
+ override val summary = { summary }
+ override val enabled = { enabled }
override val onClick = presenter::startActivity
})
}
diff --git a/src/com/android/settings/spa/app/appinfo/AppLocalePreference.kt b/src/com/android/settings/spa/app/appinfo/AppLocalePreference.kt
index 2d6fbb6..3b2aace 100644
--- a/src/com/android/settings/spa/app/appinfo/AppLocalePreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppLocalePreference.kt
@@ -23,15 +23,16 @@
import android.content.pm.PackageManager.ResolveInfoFlags
import android.net.Uri
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R
import com.android.settings.applications.AppInfoBase
import com.android.settings.applications.AppLocaleUtil
import com.android.settings.applications.appinfo.AppLocaleDetails
import com.android.settings.localepicker.AppLocalePickerActivity
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spaprivileged.model.app.userHandle
@@ -46,11 +47,12 @@
val presenter = remember { AppLocalePresenter(context, app) }
if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return
+ val summary by presenter.summaryFlow.collectAsStateWithLifecycle(
+ initialValue = stringResource(R.string.summary_placeholder),
+ )
Preference(object : PreferenceModel {
override val title = stringResource(R.string.app_locale_preference_title)
- override val summary = presenter.summaryFlow.collectAsStateWithLifecycle(
- initialValue = stringResource(R.string.summary_placeholder),
- )
+ override val summary = { summary }
override val onClick = presenter::startActivity
})
}
diff --git a/src/com/android/settings/spa/app/appinfo/AppNotificationPreference.kt b/src/com/android/settings/spa/app/appinfo/AppNotificationPreference.kt
index 45033e7..28527c1 100644
--- a/src/com/android/settings/spa/app/appinfo/AppNotificationPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppNotificationPreference.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -29,7 +30,6 @@
import com.android.settings.spa.notification.AppNotificationRepository
import com.android.settings.spa.notification.IAppNotificationRepository
import com.android.settingslib.spa.framework.compose.rememberContext
-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.model.app.installed
@@ -43,17 +43,17 @@
repository: IAppNotificationRepository = rememberContext(::AppNotificationRepository),
) {
val context = LocalContext.current
- val summaryFlow = remember(app) {
+ val summary by remember(app) {
flow {
emit(repository.getNotificationSummary(app))
- }.flowOn(Dispatchers.IO)
- }
+ }.flowOn(Dispatchers.Default)
+ }.collectAsStateWithLifecycle(
+ initialValue = stringResource(R.string.summary_placeholder)
+ )
Preference(object : PreferenceModel {
override val title = stringResource(R.string.notifications_label)
- override val summary = summaryFlow.collectAsStateWithLifecycle(
- initialValue = stringResource(R.string.summary_placeholder)
- )
- override val enabled = stateOf(app.installed)
+ override val summary = { summary }
+ override val enabled = { app.installed }
override val onClick = { navigateToAppNotificationSettings(context, app) }
})
}
@@ -65,4 +65,4 @@
context,
AppInfoSettingsProvider.METRICS_CATEGORY,
)
-}
\ No newline at end of file
+}
diff --git a/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt b/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt
index 757ddc2..aae9569 100644
--- a/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppOpenByDefaultPreference.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -28,7 +29,6 @@
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
@@ -46,12 +46,13 @@
val presenter = remember(app) { AppOpenByDefaultPresenter(context, app) }
if (remember(presenter) { !presenter.isAvailable() }) return
+ val summary by presenter.summaryFlow.collectAsStateWithLifecycle(
+ initialValue = stringResource(R.string.summary_placeholder),
+ )
Preference(object : PreferenceModel {
override val title = stringResource(R.string.launch_by_default)
- override val summary = presenter.summaryFlow.collectAsStateWithLifecycle(
- initialValue = stringResource(R.string.summary_placeholder),
- )
- override val enabled = stateOf(presenter.isEnabled())
+ override val summary = { summary }
+ override val enabled = { presenter.isEnabled() }
override val onClick = presenter::startActivity
})
}
diff --git a/src/com/android/settings/spa/app/appinfo/AppPermissionPreference.kt b/src/com/android/settings/spa/app/appinfo/AppPermissionPreference.kt
index ad666dc..ec1780f 100644
--- a/src/com/android/settings/spa/app/appinfo/AppPermissionPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppPermissionPreference.kt
@@ -22,7 +22,6 @@
import android.content.pm.ApplicationInfo
import android.util.Log
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
@@ -52,8 +51,8 @@
model = remember {
object : PreferenceModel {
override val title = context.getString(R.string.permissions_label)
- override val summary = derivedStateOf { summaryState.value.summary }
- override val enabled = derivedStateOf { summaryState.value.enabled }
+ override val summary = { summaryState.value.summary }
+ override val enabled = { summaryState.value.enabled }
override val onClick = { startManagePermissionsActivity(context, app) }
}
},
diff --git a/src/com/android/settings/spa/app/appinfo/AppStoragePreference.kt b/src/com/android/settings/spa/app/appinfo/AppStoragePreference.kt
index e8b1018..2b96454 100644
--- a/src/com/android/settings/spa/app/appinfo/AppStoragePreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppStoragePreference.kt
@@ -19,9 +19,6 @@
import android.content.Context
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import com.android.settings.R
@@ -47,12 +44,13 @@
}
@Composable
-private fun getSummary(context: Context, app: ApplicationInfo): State<String> {
+private fun getSummary(context: Context, app: ApplicationInfo): () -> String {
val sizeState = app.getStorageSize()
- return remember {
- derivedStateOf {
- val size = sizeState.value
- if (size.isBlank()) return@derivedStateOf context.getString(R.string.computing_size)
+ return {
+ val size = sizeState.value
+ if (size.isBlank()) {
+ context.getString(R.string.computing_size)
+ } else {
val storageType = context.getString(
when (app.hasFlag(ApplicationInfo.FLAG_EXTERNAL_STORAGE)) {
true -> R.string.storage_type_external
diff --git a/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt b/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt
index 21b3d73..7ba61dc 100644
--- a/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/AppTimeSpentPreference.kt
@@ -22,6 +22,7 @@
import android.content.pm.PackageManager.ResolveInfoFlags
import android.provider.Settings
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
@@ -29,7 +30,6 @@
import androidx.lifecycle.liveData
import com.android.settings.R
import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
-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.model.app.hasFlag
@@ -43,12 +43,13 @@
val presenter = remember { AppTimeSpentPresenter(context, app) }
if (!presenter.isAvailable()) return
+ val summary by presenter.summaryLiveData.observeAsState(
+ initial = stringResource(R.string.summary_placeholder),
+ )
Preference(object : PreferenceModel {
override val title = stringResource(R.string.time_spent_in_app_pref_title)
- override val summary = presenter.summaryLiveData.observeAsState(
- initial = stringResource(R.string.summary_placeholder),
- )
- override val enabled = stateOf(presenter.isEnabled())
+ override val summary = { summary }
+ override val enabled = { presenter.isEnabled() }
override val onClick = presenter::startActivity
})
}
diff --git a/src/com/android/settings/spa/app/appinfo/DefaultAppShortcutPreference.kt b/src/com/android/settings/spa/app/appinfo/DefaultAppShortcutPreference.kt
index 74c0aa4..51f6845 100644
--- a/src/com/android/settings/spa/app/appinfo/DefaultAppShortcutPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/DefaultAppShortcutPreference.kt
@@ -22,6 +22,7 @@
import android.content.pm.ApplicationInfo
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
@@ -57,11 +58,12 @@
if (remember(presenter) { !presenter.isAvailable() }) return
if (presenter.isVisible().observeAsState().value != true) return
+ val summary by presenter.summaryFlow.collectAsStateWithLifecycle(
+ initialValue = stringResource(R.string.summary_placeholder),
+ )
Preference(object : PreferenceModel {
override val title = stringResource(shortcut.titleResId)
- override val summary = presenter.summaryFlow.collectAsStateWithLifecycle(
- initialValue = stringResource(R.string.summary_placeholder),
- )
+ override val summary = { summary }
override val onClick = presenter::startActivity
})
}
diff --git a/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt b/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
index f62a3be..77d68b5 100644
--- a/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/HibernationSwitchPreference.kt
@@ -31,12 +31,11 @@
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R
import com.android.settings.Utils.PROPERTY_APP_HIBERNATION_ENABLED
import com.android.settings.Utils.PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS
import com.android.settingslib.spa.framework.compose.OverridableFlow
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.settingslib.spaprivileged.framework.common.appHibernationManager
@@ -44,12 +43,12 @@
import com.android.settingslib.spaprivileged.framework.common.asUser
import com.android.settingslib.spaprivileged.framework.common.permissionControllerManager
import com.android.settingslib.spaprivileged.model.app.userHandle
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
-import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
@Composable
fun HibernationSwitchPreference(app: ApplicationInfo) {
@@ -62,7 +61,7 @@
SwitchPreference(remember {
object : SwitchPreferenceModel {
override val title = context.getString(R.string.unused_apps_switch)
- override val summary = stateOf(context.getString(R.string.unused_apps_switch_summary))
+ override val summary = { context.getString(R.string.unused_apps_switch_summary) }
override val changeable = isEligibleState
override val checked = derivedStateOf {
diff --git a/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreference.kt b/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreference.kt
index 7b9480d..9c3ec97 100644
--- a/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/InstantAppDomainsPreference.kt
@@ -32,9 +32,9 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R
import com.android.settings.Utils
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -53,11 +53,12 @@
val presenter = remember { InstantAppDomainsPresenter(context, app) }
var openDialog by rememberSaveable { mutableStateOf(false) }
+ val summary by presenter.summaryFlow.collectAsStateWithLifecycle(
+ initialValue = stringResource(R.string.summary_placeholder),
+ )
Preference(object : PreferenceModel {
override val title = stringResource(R.string.app_launch_supported_domain_urls_title)
- override val summary = presenter.summaryFlow.collectAsStateWithLifecycle(
- initialValue = stringResource(R.string.summary_placeholder),
- )
+ override val summary = { summary }
override val onClick = { openDialog = true }
})
diff --git a/src/com/android/settings/spa/app/appinfo/InteractAcrossProfilesDetailsPreference.kt b/src/com/android/settings/spa/app/appinfo/InteractAcrossProfilesDetailsPreference.kt
index 12f6907..905e057 100644
--- a/src/com/android/settings/spa/app/appinfo/InteractAcrossProfilesDetailsPreference.kt
+++ b/src/com/android/settings/spa/app/appinfo/InteractAcrossProfilesDetailsPreference.kt
@@ -19,13 +19,14 @@
import android.content.Context
import android.content.pm.ApplicationInfo
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settings.R
import com.android.settings.applications.appinfo.AppInfoDashboardFragment
import com.android.settings.applications.specialaccess.interactacrossprofiles.InteractAcrossProfilesDetails
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spaprivileged.framework.common.crossProfileApps
@@ -39,11 +40,12 @@
val presenter = remember { InteractAcrossProfilesDetailsPresenter(context, app) }
if (!presenter.isAvailableFlow.collectAsStateWithLifecycle(initialValue = false).value) return
+ val summary by presenter.summaryFlow.collectAsStateWithLifecycle(
+ initialValue = stringResource(R.string.summary_placeholder),
+ )
Preference(object : PreferenceModel {
override val title = stringResource(R.string.interact_across_profiles_title)
- override val summary = presenter.summaryFlow.collectAsStateWithLifecycle(
- initialValue = stringResource(R.string.summary_placeholder),
- )
+ override val summary = { summary }
override val onClick = presenter::startActivity
})
}
diff --git a/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProvider.kt b/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProvider.kt
index 6e0643b..89f473b 100644
--- a/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProvider.kt
+++ b/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProvider.kt
@@ -32,6 +32,7 @@
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -88,9 +89,10 @@
@Composable
fun EntryItem() {
if(featureIsDisabled) return
+ val summary by generatePreferenceSummary()
Preference(object : PreferenceModel {
override val title = stringResource(R.string.background_install_title)
- override val summary = generatePreferenceSummary()
+ override val summary = { summary }
override val onClick = navigator(name)
})
}
diff --git a/src/com/android/settings/spa/app/specialaccess/LongBackgroundTasksApps.kt b/src/com/android/settings/spa/app/specialaccess/LongBackgroundTasksApps.kt
new file mode 100644
index 0000000..3ba9b08
--- /dev/null
+++ b/src/com/android/settings/spa/app/specialaccess/LongBackgroundTasksApps.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.specialaccess
+
+import android.Manifest
+import android.app.AppOpsManager
+import android.app.settings.SettingsEnums
+import android.content.Context
+import com.android.settings.R
+import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
+import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel
+import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
+
+object LongBackgroundTasksAppListProvider : TogglePermissionAppListProvider {
+ override val permissionType = "LongBackgroundTasksApps"
+ override fun createModel(context: Context) = LongBackgroundTasksAppsListModel(context)
+}
+
+class LongBackgroundTasksAppsListModel(context: Context) : AppOpPermissionListModel(context) {
+ override val pageTitleResId = R.string.long_background_tasks_title
+ override val switchTitleResId = R.string.long_background_tasks_switch_title
+ override val footerResId = R.string.long_background_tasks_footer_title
+ override val appOp = AppOpsManager.OP_RUN_USER_INITIATED_JOBS
+ override val permission = Manifest.permission.RUN_USER_INITIATED_JOBS
+ override val setModeByUid = true
+
+ override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
+ super.setAllowed(record, newAllowed)
+ logPermissionChange(newAllowed)
+ }
+
+ private fun logPermissionChange(newAllowed: Boolean) {
+ featureFactory.metricsFeatureProvider.action(
+ context,
+ SettingsEnums.ACTION_LONG_BACKGROUND_TASKS_TOGGLE,
+ if (newAllowed) 1 else 0
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt b/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt
index b40e32b..fb05a38 100644
--- a/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt
+++ b/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt
@@ -66,7 +66,10 @@
PictureInPictureListProvider,
InstallUnknownAppsListProvider,
AlarmsAndRemindersAppListProvider,
+ VoiceActivationAppsListProvider,
WifiControlAppListProvider,
+ LongBackgroundTasksAppListProvider,
+ TurnScreenOnAppsAppListProvider,
)
.map { it.buildAppListInjectEntry().setLink(fromPage = owner).build() }
}
diff --git a/src/com/android/settings/spa/app/specialaccess/TurnScreenOnApps.kt b/src/com/android/settings/spa/app/specialaccess/TurnScreenOnApps.kt
new file mode 100644
index 0000000..262acb7
--- /dev/null
+++ b/src/com/android/settings/spa/app/specialaccess/TurnScreenOnApps.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.specialaccess
+
+import android.Manifest
+import android.app.AppOpsManager
+import android.app.settings.SettingsEnums
+import android.content.Context
+import com.android.settings.R
+import com.android.settings.overlay.FeatureFactory.Companion.featureFactory
+import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel
+import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
+
+object TurnScreenOnAppsAppListProvider : TogglePermissionAppListProvider {
+ override val permissionType = "TurnScreenOnApps"
+ override fun createModel(context: Context) = TurnScreenOnAppsListModel(context)
+}
+
+class TurnScreenOnAppsListModel(context: Context) : AppOpPermissionListModel(context) {
+ override val pageTitleResId = com.android.settingslib.R.string.turn_screen_on_title
+ override val switchTitleResId = com.android.settingslib.R.string.allow_turn_screen_on
+ override val footerResId = com.android.settingslib.R.string.allow_turn_screen_on_description
+ override val appOp = AppOpsManager.OP_TURN_SCREEN_ON
+ override val permission = Manifest.permission.TURN_SCREEN_ON
+ override val setModeByUid = true
+
+ override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
+ super.setAllowed(record, newAllowed)
+ logPermissionChange(newAllowed)
+ }
+
+ private fun logPermissionChange(newAllowed: Boolean) {
+ featureFactory.metricsFeatureProvider.action(
+ context,
+ SettingsEnums.SETTINGS_MANAGE_TURN_SCREEN_ON,
+ if (newAllowed) 1 else 0
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/spa/app/specialaccess/VoiceActivationApps.kt b/src/com/android/settings/spa/app/specialaccess/VoiceActivationApps.kt
new file mode 100644
index 0000000..de5f3b7
--- /dev/null
+++ b/src/com/android/settings/spa/app/specialaccess/VoiceActivationApps.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.specialaccess
+
+import android.Manifest
+import android.app.AppOpsManager
+import android.app.settings.SettingsEnums
+import android.content.Context
+import android.content.res.Resources
+import com.android.settings.R
+import com.android.settings.overlay.FeatureFactory
+import com.android.settingslib.spaprivileged.template.app.AppOpPermissionListModel
+import com.android.settingslib.spaprivileged.template.app.AppOpPermissionRecord
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
+
+
+object VoiceActivationAppsListProvider : TogglePermissionAppListProvider {
+ override val permissionType = "VoiceActivationApps"
+ override fun createModel(context: Context) = VoiceActivationAppsListModel(context)
+}
+
+class VoiceActivationAppsListModel(context: Context) : AppOpPermissionListModel(context) {
+ override val pageTitleResId = R.string.voice_activation_apps_title
+ override val switchTitleResId = R.string.permit_voice_activation_apps
+ override val footerResId = R.string.allow_voice_activation_apps_description
+ override val appOp = AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
+ override val permission = Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO
+ override val setModeByUid = true
+
+ override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) {
+ super.setAllowed(record, newAllowed)
+ logPermissionChange(newAllowed)
+ }
+
+ private fun logPermissionChange(newAllowed: Boolean) {
+ val category = when {
+ newAllowed -> SettingsEnums.APP_SPECIAL_PERMISSION_RECEIVE_SANDBOX_TRIGGER_AUDIO_ALLOW
+ else -> SettingsEnums.APP_SPECIAL_PERMISSION_RECEIVE_SANDBOX_TRIGGER_AUDIO_DENY
+ }
+ /**
+ * Leave the package string empty as we should not log the package names for the collected
+ * metrics.
+ */
+ FeatureFactory.featureFactory.metricsFeatureProvider.action(context, category, "")
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsPreferenceController.kt b/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsPreferenceController.kt
new file mode 100644
index 0000000..27d4b4b
--- /dev/null
+++ b/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsPreferenceController.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.specialaccess
+
+import android.content.Context
+import androidx.preference.Preference
+import com.android.settings.core.BasePreferenceController
+import com.android.settings.flags.Flags
+import com.android.settings.spa.SpaActivity.Companion.startSpaActivity
+
+class VoiceActivationAppsPreferenceController(context: Context, preferenceKey: String) :
+ BasePreferenceController(context, preferenceKey) {
+ override fun getAvailabilityStatus() =
+ if (Flags.enableVoiceActivationAppsInSettings()) AVAILABLE
+ else CONDITIONALLY_UNAVAILABLE
+
+ override fun handlePreferenceTreeClick(preference: Preference): Boolean {
+ if (preference.key == mPreferenceKey) {
+ mContext.startSpaActivity(VoiceActivationAppsListProvider.getAppListRoute())
+ return true
+ }
+ return false
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/spa/app/storage/StorageAppList.kt b/src/com/android/settings/spa/app/storage/StorageAppList.kt
index 8fc3eb5..c33de33 100644
--- a/src/com/android/settings/spa/app/storage/StorageAppList.kt
+++ b/src/com/android/settings/spa/app/storage/StorageAppList.kt
@@ -22,7 +22,7 @@
import androidx.annotation.StringRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -121,13 +121,9 @@
): Flow<List<AppRecordWithSize>> = recordListFlow.filterItem { type.filter(it) }
@Composable
- override fun getSummary(option: Int, record: AppRecordWithSize): State<String> {
- val storageSummary = record.app.getStorageSummary()
- return remember {
- derivedStateOf {
- storageSummary.value
- }
- }
+ override fun getSummary(option: Int, record: AppRecordWithSize): () -> String {
+ val storageSummary by record.app.getStorageSummary()
+ return { storageSummary }
}
@Composable
diff --git a/src/com/android/settings/spa/development/UsageStatsListModel.kt b/src/com/android/settings/spa/development/UsageStatsListModel.kt
index 61c24ac..d27796d 100644
--- a/src/com/android/settings/spa/development/UsageStatsListModel.kt
+++ b/src/com/android/settings/spa/development/UsageStatsListModel.kt
@@ -22,10 +22,8 @@
import android.content.pm.ApplicationInfo
import android.text.format.DateUtils
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
import com.android.settings.R
import com.android.settings.spa.development.UsageStatsListModel.SpinnerItem.Companion.toSpinnerItem
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.widget.ui.SpinnerOption
import com.android.settingslib.spaprivileged.model.app.AppEntry
import com.android.settingslib.spaprivileged.model.app.AppListModel
@@ -55,7 +53,7 @@
}
override fun getSpinnerOptions(recordList: List<UsageStatsAppRecord>): List<SpinnerOption> =
- SpinnerItem.values().map {
+ SpinnerItem.entries.map {
SpinnerOption(
id = it.ordinal,
text = context.getString(it.stringResId),
@@ -77,7 +75,7 @@
}.then(super.getComparator(option))
@Composable
- override fun getSummary(option: Int, record: UsageStatsAppRecord): State<String>? {
+ override fun getSummary(option: Int, record: UsageStatsAppRecord): (() -> String)? {
val usageStats = record.usageStats ?: return null
val lastTimeUsed = DateUtils.formatSameDayTime(
usageStats.lastTimeUsed, now, DateFormat.MEDIUM, DateFormat.MEDIUM
@@ -85,7 +83,7 @@
val lastTimeUsedLine = "${context.getString(R.string.last_time_used_label)}: $lastTimeUsed"
val usageTime = DateUtils.formatElapsedTime(usageStats.totalTimeInForeground / 1000)
val usageTimeLine = "${context.getString(R.string.usage_time_label)}: $usageTime"
- return stateOf("$lastTimeUsedLine\n$usageTimeLine")
+ return { "$lastTimeUsedLine\n$usageTimeLine" }
}
private fun getUsageStats(): Map<String, UsageStats> {
@@ -101,7 +99,7 @@
AppName(R.string.usage_stats_sort_by_app_name);
companion object {
- fun Int.toSpinnerItem(): SpinnerItem = values()[this]
+ fun Int.toSpinnerItem(): SpinnerItem = entries[this]
}
}
}
diff --git a/src/com/android/settings/spa/development/compat/PlatformCompatAppListModel.kt b/src/com/android/settings/spa/development/compat/PlatformCompatAppListModel.kt
index c6752b9..8f53698 100644
--- a/src/com/android/settings/spa/development/compat/PlatformCompatAppListModel.kt
+++ b/src/com/android/settings/spa/development/compat/PlatformCompatAppListModel.kt
@@ -24,7 +24,6 @@
import androidx.core.os.bundleOf
import com.android.settings.core.SubSettingLauncher
import com.android.settings.development.compat.PlatformCompatDashboard
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.util.filterItem
import com.android.settingslib.spa.framework.util.mapItem
import com.android.settingslib.spaprivileged.model.app.AppListModel
@@ -53,8 +52,9 @@
}
@Composable
- override fun getSummary(option: Int, record: PlatformCompatAppRecord) =
- stateOf(record.app.packageName)
+ override fun getSummary(option: Int, record: PlatformCompatAppRecord): () -> String = {
+ record.app.packageName
+ }
@Composable
override fun AppListItemModel<PlatformCompatAppRecord>.AppItem() {
diff --git a/src/com/android/settings/spa/network/NetworkAndInternet.kt b/src/com/android/settings/spa/network/NetworkAndInternet.kt
index 777133e..f985237 100644
--- a/src/com/android/settings/spa/network/NetworkAndInternet.kt
+++ b/src/com/android/settings/spa/network/NetworkAndInternet.kt
@@ -34,7 +34,6 @@
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
@@ -60,9 +59,10 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = owner)
.setUiLayoutFn {
+ val summary = stringResource(getSummaryResId())
Preference(object : PreferenceModel {
override val title = stringResource(R.string.network_dashboard_title)
- override val summary = stringResource(getSummaryResId()).toState()
+ override val summary = { summary }
override val onClick = navigator(name)
override val icon = @Composable {
SettingsIcon(imageVector = Icons.Outlined.Wifi)
diff --git a/src/com/android/settings/spa/notification/AppListNotifications.kt b/src/com/android/settings/spa/notification/AppListNotifications.kt
index c1e5d64..00e4394 100644
--- a/src/com/android/settings/spa/notification/AppListNotifications.kt
+++ b/src/com/android/settings/spa/notification/AppListNotifications.kt
@@ -23,7 +23,6 @@
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.rememberContext
-import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spaprivileged.template.app.AppListPage
@@ -41,9 +40,10 @@
@Composable
fun EntryItem() {
+ val summary = stringResource(R.string.app_notification_field_summary)
Preference(object : PreferenceModel {
override val title = stringResource(R.string.app_notifications_title)
- override val summary = stringResource(R.string.app_notification_field_summary).toState()
+ override val summary = { summary }
override val onClick = navigator(name)
})
}
diff --git a/src/com/android/settings/spa/notification/AppNotificationsListModel.kt b/src/com/android/settings/spa/notification/AppNotificationsListModel.kt
index 0b9b676..692ffcb 100644
--- a/src/com/android/settings/spa/notification/AppNotificationsListModel.kt
+++ b/src/com/android/settings/spa/notification/AppNotificationsListModel.kt
@@ -27,7 +27,6 @@
import com.android.settings.applications.AppInfoBase
import com.android.settings.notification.app.AppNotificationSettings
import com.android.settings.spa.notification.SpinnerItem.Companion.toSpinnerItem
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.util.asyncFilter
import com.android.settingslib.spa.framework.util.asyncForEach
import com.android.settingslib.spa.widget.ui.SpinnerOption
@@ -91,16 +90,17 @@
}.then(super.getComparator(option))
@Composable
- override fun getSummary(option: Int, record: AppNotificationsRecord) = record.sentState?.let {
- when (option.toSpinnerItem()) {
- SpinnerItem.MostRecent -> stateOf(formatLastSent(it.lastSent))
- SpinnerItem.MostFrequent -> stateOf(repository.calculateFrequencySummary(it.sentCount))
- else -> null
+ override fun getSummary(option: Int, record: AppNotificationsRecord): (() -> String)? =
+ record.sentState?.let {
+ when (option.toSpinnerItem()) {
+ SpinnerItem.MostRecent -> ({ formatLastSent(it.lastSent) })
+ SpinnerItem.MostFrequent -> ({ repository.calculateFrequencySummary(it.sentCount) })
+ else -> null
+ }
}
- }
override fun getSpinnerOptions(recordList: List<AppNotificationsRecord>): List<SpinnerOption> =
- SpinnerItem.values().map {
+ SpinnerItem.entries.map {
SpinnerOption(
id = it.ordinal,
text = context.getString(it.stringResId),
@@ -145,6 +145,6 @@
TurnedOff(R.string.filter_notif_blocked_apps);
companion object {
- fun Int.toSpinnerItem(): SpinnerItem = values()[this]
+ fun Int.toSpinnerItem(): SpinnerItem = entries[this]
}
}
diff --git a/src/com/android/settings/spa/notification/NotificationMain.kt b/src/com/android/settings/spa/notification/NotificationMain.kt
index 305f201..b3c7a55 100644
--- a/src/com/android/settings/spa/notification/NotificationMain.kt
+++ b/src/com/android/settings/spa/notification/NotificationMain.kt
@@ -25,13 +25,12 @@
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.SettingsIcon
-import com.android.settingslib.spa.framework.common.createSettingsPage
object NotificationMainPageProvider : SettingsPageProvider {
override val name = "NotificationMain"
@@ -53,9 +52,10 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = owner)
.setUiLayoutFn {
+ val summary = stringResource(R.string.notification_dashboard_summary)
Preference(object : PreferenceModel {
override val title = stringResource(R.string.configure_notification_settings)
- override val summary = stringResource(R.string.notification_dashboard_summary).toState()
+ override val summary = { summary }
override val onClick = navigator(name)
override val icon = @Composable {
SettingsIcon(imageVector = Icons.Outlined.Notifications)
diff --git a/src/com/android/settings/spa/system/AppLanguages.kt b/src/com/android/settings/spa/system/AppLanguages.kt
index b878aa7..d836a32 100644
--- a/src/com/android/settings/spa/system/AppLanguages.kt
+++ b/src/com/android/settings/spa/system/AppLanguages.kt
@@ -26,7 +26,6 @@
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.rememberContext
-import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -52,9 +51,10 @@
@Composable
fun EntryItem() {
+ val summary = stringResource(R.string.app_locale_picker_summary)
Preference(object : PreferenceModel {
override val title = stringResource(R.string.app_locales_picker_menu_title)
- override val summary = stringResource(R.string.app_locale_picker_summary).toState()
+ override val summary = { summary }
override val onClick = navigator(name)
})
}
diff --git a/src/com/android/settings/spa/system/AppLanguagesListModel.kt b/src/com/android/settings/spa/system/AppLanguagesListModel.kt
index 3413ff0..3573e25 100644
--- a/src/com/android/settings/spa/system/AppLanguagesListModel.kt
+++ b/src/com/android/settings/spa/system/AppLanguagesListModel.kt
@@ -23,7 +23,7 @@
import android.net.Uri
import android.os.UserHandle
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -79,12 +79,14 @@
) = recordListFlow.filterItem { it.isAppLocaleSupported }
@Composable
- override fun getSummary(option: Int, record: AppLanguagesRecord): State<String> =
- remember(record.app) {
+ override fun getSummary(option: Int, record: AppLanguagesRecord): () -> String {
+ val summary by remember(record.app) {
flow {
emit(getSummary(record.app))
}.flowOn(Dispatchers.IO)
}.collectAsStateWithLifecycle(initialValue = stringResource(R.string.summary_placeholder))
+ return { summary }
+ }
private fun getSummary(app: ApplicationInfo): String =
AppLocaleDetails.getSummary(context, app).toString()
diff --git a/src/com/android/settings/spa/system/LanguageAndInputPageProvider.kt b/src/com/android/settings/spa/system/LanguageAndInputPageProvider.kt
index b5cd299..5c1038d 100644
--- a/src/com/android/settings/spa/system/LanguageAndInputPageProvider.kt
+++ b/src/com/android/settings/spa/system/LanguageAndInputPageProvider.kt
@@ -24,7 +24,6 @@
import com.android.settings.R
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
@@ -42,9 +41,10 @@
@Composable
fun EntryItem() {
+ val summary = stringResource(R.string.language_settings)
Preference(object : PreferenceModel {
override val title = stringResource(R.string.language_settings)
- override val summary = stringResource(R.string.language_settings).toState()
+ override val summary = { summary }
override val onClick = navigator(name)
override val icon = @Composable {
SettingsIcon(imageVector = Icons.Outlined.Language)
diff --git a/src/com/android/settings/spa/system/SystemMain.kt b/src/com/android/settings/spa/system/SystemMain.kt
index 04ae512..c9aa8cc 100644
--- a/src/com/android/settings/spa/system/SystemMain.kt
+++ b/src/com/android/settings/spa/system/SystemMain.kt
@@ -27,7 +27,6 @@
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
@@ -53,9 +52,10 @@
fun buildInjectEntry(): SettingsEntryBuilder {
return SettingsEntryBuilder.createInject(owner = owner)
.setUiLayoutFn {
+ val summary = stringResource(R.string.system_dashboard_summary)
Preference(object : PreferenceModel {
override val title = stringResource(R.string.header_category_system)
- override val summary = stringResource(R.string.system_dashboard_summary).toState()
+ override val summary = { summary }
override val onClick = navigator(name)
override val icon = @Composable {
SettingsIcon(imageVector = Icons.Outlined.Info)
diff --git a/tests/robotests/src/com/android/settings/applications/appcompat/UserAspectRatioDetailsTest.java b/tests/robotests/src/com/android/settings/applications/appcompat/UserAspectRatioDetailsTest.java
index d98b0e7..b615163 100644
--- a/tests/robotests/src/com/android/settings/applications/appcompat/UserAspectRatioDetailsTest.java
+++ b/tests/robotests/src/com/android/settings/applications/appcompat/UserAspectRatioDetailsTest.java
@@ -21,21 +21,27 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.IActivityManager;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.RemoteException;
import androidx.test.core.app.ApplicationProvider;
+import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowActivityManager;
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
@@ -56,6 +62,7 @@
private RadioWithImagePreference mRadioButtonPref;
private Context mContext;
private UserAspectRatioDetails mFragment;
+ private MetricsFeatureProvider mMetricsFeatureProvider;
@Before
public void setUp() {
@@ -67,6 +74,8 @@
when(mFragment.getAspectRatioManager()).thenReturn(mUserAspectRatioManager);
ShadowActivityManager.setService(mAm);
mRadioButtonPref = new RadioWithImagePreference(mContext);
+ final FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest();
+ mMetricsFeatureProvider = featureFactory.metricsFeatureProvider;
}
@Test
@@ -93,4 +102,31 @@
verify(mUserAspectRatioManager).setUserMinAspectRatio(
any(), anyInt(), anyInt());
}
+
+ @Test
+ public void onRadioButtonClicked_prefChange_logMetrics() throws NullPointerException {
+ // Default was already selected
+ mRadioButtonPref.setKey(KEY_PREF_DEFAULT);
+ mFragment.onRadioButtonClicked(mRadioButtonPref);
+ // Preference changed
+ mRadioButtonPref.setKey(KEY_PREF_3_2);
+ mFragment.onRadioButtonClicked(mRadioButtonPref);
+ InOrder inOrder = inOrder(mMetricsFeatureProvider);
+ // Check the old aspect ratio value is logged as having been unselected
+ inOrder.verify(mMetricsFeatureProvider)
+ .action(
+ eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(SettingsEnums.ACTION_USER_ASPECT_RATIO_APP_DEFAULT_UNSELECTED),
+ eq(SettingsEnums.USER_ASPECT_RATIO_APP_INFO_SETTINGS),
+ any(),
+ anyInt());
+ // Check the new aspect ratio value is logged as having been selected
+ inOrder.verify(mMetricsFeatureProvider)
+ .action(
+ eq(SettingsEnums.PAGE_UNKNOWN),
+ eq(SettingsEnums.ACTION_USER_ASPECT_RATIO_3_2_SELECTED),
+ eq(SettingsEnums.USER_ASPECT_RATIO_APP_INFO_SETTINGS),
+ any(),
+ anyInt());
+ }
}
diff --git a/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java
index 72b01f8..817df4c 100644
--- a/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java
+++ b/tests/robotests/src/com/android/settings/localepicker/AppLocalePickerActivityTest.java
@@ -18,6 +18,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -27,6 +29,7 @@
import android.app.Activity;
import android.app.ApplicationPackageManager;
import android.app.LocaleConfig;
+import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -51,6 +54,7 @@
import com.android.settings.applications.AppInfoBase;
import com.android.settings.applications.AppLocaleUtil;
import com.android.settings.flags.Flags;
+import com.android.settings.testutils.FakeFeatureFactory;
import org.junit.After;
import org.junit.Before;
@@ -91,6 +95,7 @@
private static final String EN_US = "en-US";
private static int sUid;
+ private FakeFeatureFactory mFeatureFactory;
private LocaleNotificationDataManager mDataManager;
private AppLocalePickerActivity mActivity;
@@ -117,6 +122,7 @@
when(mLocaleConfig.getSupportedLocales()).thenReturn(LocaleList.forLanguageTags("en-US"));
ReflectionHelpers.setStaticField(AppLocaleUtil.class, "sLocaleConfig", mLocaleConfig);
sUid = Process.myUid();
+ mFeatureFactory = FakeFeatureFactory.setupForTest();
}
@After
@@ -229,6 +235,37 @@
}
@Test
+ public void onLocaleSelected_logLocaleSource() {
+ ActivityController<TestAppLocalePickerActivity> controller =
+ initActivityController(true);
+ LocaleList.setDefault(LocaleList.forLanguageTags("ja-JP,en-CA,en-US"));
+ Locale locale = new Locale("en", "US");
+ when(mLocaleInfo.getLocale()).thenReturn(locale);
+ when(mLocaleInfo.isSystemLocale()).thenReturn(false);
+ when(mLocaleInfo.isSuggested()).thenReturn(true);
+ when(mLocaleInfo.isSuggestionOfType(LocaleStore.LocaleInfo.SUGGESTION_TYPE_SIM)).thenReturn(
+ true);
+ when(mLocaleInfo.isSuggestionOfType(
+ LocaleStore.LocaleInfo.SUGGESTION_TYPE_SYSTEM_AVAILABLE_LANGUAGE)).thenReturn(
+ true);
+ when(mLocaleInfo.isSuggestionOfType(
+ LocaleStore.LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE)).thenReturn(
+ true);
+ when(mLocaleInfo.isSuggestionOfType(
+ LocaleStore.LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE)).thenReturn(
+ true);
+
+ controller.create();
+ AppLocalePickerActivity mActivity = controller.get();
+ mActivity.onLocaleSelected(mLocaleInfo);
+
+ int localeSource = 15; // SIM_LOCALE | SYSTEM_LOCALE |IME_LOCALE|APP_LOCALE
+ verify(mFeatureFactory.metricsFeatureProvider).action(
+ any(), eq(SettingsEnums.ACTION_CHANGE_APP_LANGUAGE_FROM_SUGGESTED),
+ eq(localeSource));
+ }
+
+ @Test
@RequiresFlagsEnabled(Flags.FLAG_LOCALE_NOTIFICATION_ENABLED)
public void onLocaleSelected_evaluateNotification_simpleLocaleUpdate_localeCreatedWithUid()
throws Exception {
diff --git a/tests/robotests/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceControllerTest.java
index 13528b4..053b352 100644
--- a/tests/robotests/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/app/AppChannelsBypassingDndPreferenceControllerTest.java
@@ -26,16 +26,19 @@
import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.ParceledListSlice;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.test.core.app.ApplicationProvider;
+import com.android.settings.flags.Flags;
import com.android.settings.notification.NotificationBackend;
import com.android.settingslib.PrimarySwitchPreference;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -51,6 +54,9 @@
@LooperMode(LooperMode.Mode.LEGACY)
public class AppChannelsBypassingDndPreferenceControllerTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private NotificationBackend mBackend;
@@ -150,4 +156,44 @@
}
return new ParceledListSlice<>(Collections.singletonList(group));
}
+
+ @Test
+ public void displayPreference_duplicateChannelName_AddsGroupNameAsSummary() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DEDUPE_DND_SETTINGS_CHANNELS);
+ NotificationChannelGroup group1 = new NotificationChannelGroup("group1_id", "Group1");
+ NotificationChannelGroup group2 = new NotificationChannelGroup("group2_id", "Group2");
+
+ group1.addChannel(new NotificationChannel("mail_group1_id", "Mail",
+ NotificationManager.IMPORTANCE_DEFAULT));
+ group1.addChannel(new NotificationChannel("other_group1_id", "Other",
+ NotificationManager.IMPORTANCE_DEFAULT));
+
+ group2.addChannel(new NotificationChannel("music_group2_id", "Music",
+ NotificationManager.IMPORTANCE_DEFAULT));
+ // This channel has the same name as a channel in group1.
+ group2.addChannel(new NotificationChannel("mail_group2_id", "Mail",
+ NotificationManager.IMPORTANCE_DEFAULT));
+
+ ParceledListSlice<NotificationChannelGroup> groups = new ParceledListSlice<>(
+ new ArrayList<NotificationChannelGroup>() {
+ {
+ add(group1);
+ add(group2);
+ }
+ }
+ );
+
+ when(mBackend.getGroups(eq(mAppRow.pkg), eq(mAppRow.uid))).thenReturn(groups);
+ mController.displayPreference(mPreferenceScreen);
+ ShadowApplication.runBackgroundTasks();
+ // Check that we've added the group name as a summary to channels that have identical names.
+ // Channels are also alphabetized.
+ assertThat(mCategory.getPreference(1).getTitle().toString()).isEqualTo("Mail");
+ assertThat(mCategory.getPreference(1).getSummary().toString()).isEqualTo("Group1");
+ assertThat(mCategory.getPreference(2).getTitle().toString()).isEqualTo("Mail");
+ assertThat(mCategory.getPreference(2).getSummary().toString()).isEqualTo("Group2");
+ assertThat(mCategory.getPreference(3).getTitle().toString()).isEqualTo("Music");
+ assertThat(mCategory.getPreference(4).getTitle().toString()).isEqualTo("Other");
+
+ }
}
diff --git a/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java b/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
index 9156cae..5a5008c 100644
--- a/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/robotests/testutils/com/android/settings/testutils/FakeFeatureFactory.java
@@ -46,6 +46,7 @@
import com.android.settings.overlay.SupportFeatureProvider;
import com.android.settings.overlay.SurveyFeatureProvider;
import com.android.settings.panel.PanelFeatureProvider;
+import com.android.settings.privatespace.PrivateSpaceLoginFeatureProvider;
import com.android.settings.search.SearchFeatureProvider;
import com.android.settings.security.SecurityFeatureProvider;
import com.android.settings.security.SecuritySettingsFeatureProvider;
@@ -99,6 +100,7 @@
public StylusFeatureProvider mStylusFeatureProvider;
public OnboardingFeatureProvider mOnboardingFeatureProvider;
public FastPairFeatureProvider mFastPairFeatureProvider;
+ public PrivateSpaceLoginFeatureProvider mPrivateSpaceLoginFeatureProvider;
/**
* Call this in {@code @Before} method of the test class to use fake factory.
@@ -146,6 +148,7 @@
mStylusFeatureProvider = mock(StylusFeatureProvider.class);
mOnboardingFeatureProvider = mock(OnboardingFeatureProvider.class);
mFastPairFeatureProvider = mock(FastPairFeatureProvider.class);
+ mPrivateSpaceLoginFeatureProvider = mock(PrivateSpaceLoginFeatureProvider.class);
}
@Override
@@ -323,5 +326,10 @@
public FastPairFeatureProvider getFastPairFeatureProvider() {
return mFastPairFeatureProvider;
}
+
+ @Override
+ public PrivateSpaceLoginFeatureProvider getPrivateSpaceLoginFeatureProvider() {
+ return mPrivateSpaceLoginFeatureProvider;
+ }
}
diff --git a/tests/spa_unit/Android.bp b/tests/spa_unit/Android.bp
index 28a2667..c3e99f7 100644
--- a/tests/spa_unit/Android.bp
+++ b/tests/spa_unit/Android.bp
@@ -34,6 +34,7 @@
"androidx.compose.runtime_runtime",
"androidx.test.ext.junit",
"androidx.test.runner",
+ "flag-junit",
"mockito-target-extended-minus-junit4",
],
jni_libs: [
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/AllAppListTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/AllAppListTest.kt
index 53ed4f0..1a05479 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/AllAppListTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/AllAppListTest.kt
@@ -21,7 +21,6 @@
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.State
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
@@ -146,12 +145,12 @@
fun allAppListModel_getSummary() {
val listModel = AllAppListModel(context) { stateOf(SUMMARY) }
- lateinit var summaryState: State<String>
+ lateinit var summary: () -> String
composeTestRule.setContent {
- summaryState = listModel.getSummary(option = 0, record = AppRecordWithSize(app = APP))
+ summary = listModel.getSummary(option = 0, record = AppRecordWithSize(app = APP))
}
- assertThat(summaryState.value).isEqualTo(SUMMARY)
+ assertThat(summary()).isEqualTo(SUMMARY)
}
@Test
@@ -163,13 +162,13 @@
enabled = false
}
- lateinit var summaryState: State<String>
+ lateinit var summary: () -> String
composeTestRule.setContent {
- summaryState =
+ summary =
listModel.getSummary(option = 0, record = AppRecordWithSize(app = disabledApp))
}
- assertThat(summaryState.value).isEqualTo("$SUMMARY${System.lineSeparator()}Disabled")
+ assertThat(summary()).isEqualTo("$SUMMARY${System.lineSeparator()}Disabled")
}
@Test
@@ -179,13 +178,13 @@
packageName = PACKAGE_NAME
}
- lateinit var summaryState: State<String>
+ lateinit var summary: () -> String
composeTestRule.setContent {
- summaryState =
+ summary =
listModel.getSummary(option = 0, record = AppRecordWithSize(app = notInstalledApp))
}
- assertThat(summaryState.value)
+ assertThat(summary())
.isEqualTo("$SUMMARY${System.lineSeparator()}Not installed for this user")
}
@@ -207,7 +206,7 @@
AppListItemModel(
record = AppRecordWithSize(app = app),
label = LABEL,
- summary = stateOf(SUMMARY),
+ summary = { SUMMARY },
).AppItem()
}
}
@@ -224,13 +223,13 @@
isArchived = true
}
- lateinit var summaryState: State<String>
+ lateinit var summary: () -> String
composeTestRule.setContent {
- summaryState =
+ summary =
listModel.getSummary(option = 0, record = AppRecordWithSize(app = archivedApp))
}
- assertThat(summaryState.value).isEqualTo(SUMMARY)
+ assertThat(summary()).isEqualTo(SUMMARY)
}
private fun getAppListInput(): AppListInput<AppRecordWithSize> {
@@ -252,7 +251,7 @@
AppListItemModel(
record = AppRecordWithSize(app = APP),
label = LABEL,
- summary = stateOf(SUMMARY),
+ summary = { SUMMARY },
).AppItem()
}
}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt
index 4f372e2..dfacca8 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/appcompat/UserAspectRatioAppsPageProviderTest.kt
@@ -21,7 +21,6 @@
import android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN
import android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET
import android.os.Build
-import androidx.compose.runtime.State
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
@@ -29,7 +28,6 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.testutils.FakeNavControllerWrapper
import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
import com.android.settingslib.spaprivileged.template.app.AppListItemModel
@@ -132,7 +130,7 @@
AppListItemModel(
record = APP_RECORD_SUGGESTED,
label = LABEL,
- summary = stateOf(SUMMARY)
+ summary = { SUMMARY }
).AppItem()
}
}
@@ -141,23 +139,23 @@
@Test
fun aspectRatioAppListModel_getSummaryDefault() {
- val summaryState = setSummaryState(USER_MIN_ASPECT_RATIO_UNSET)
- assertThat(summaryState.value)
- .isEqualTo(context.getString(R.string.user_aspect_ratio_app_default))
+ val summary = getSummary(USER_MIN_ASPECT_RATIO_UNSET)
+
+ assertThat(summary).isEqualTo(context.getString(R.string.user_aspect_ratio_app_default))
}
@Test
fun aspectRatioAppListModel_getSummaryWhenSplitScreen() {
- val summaryState = setSummaryState(USER_MIN_ASPECT_RATIO_SPLIT_SCREEN)
- assertThat(summaryState.value)
- .isEqualTo(context.getString(R.string.user_aspect_ratio_half_screen))
+ val summary = getSummary(USER_MIN_ASPECT_RATIO_SPLIT_SCREEN)
+
+ assertThat(summary).isEqualTo(context.getString(R.string.user_aspect_ratio_half_screen))
}
- private fun setSummaryState(userOverride: Int): State<String> {
+ private fun getSummary(userOverride: Int): String {
val listModel = UserAspectRatioAppListModel(context)
- lateinit var summaryState: State<String>
+ lateinit var summary: () -> String
composeTestRule.setContent {
- summaryState = listModel.getSummary(option = 0,
+ summary = listModel.getSummary(option = 0,
record = UserAspectRatioAppListItemModel(
app = APP,
userOverride = userOverride,
@@ -165,7 +163,7 @@
canDisplay = true,
))
}
- return summaryState
+ return summary()
}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProviderTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProviderTest.kt
index ccd385f..5c65da1 100644
--- a/tests/spa_unit/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProviderTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/app/backgroundinstall/BackgroundInstalledAppsPageProviderTest.kt
@@ -29,7 +29,6 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R
-import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.testutils.FakeNavControllerWrapper
import com.android.settingslib.spaprivileged.template.app.AppListItemModel
import com.google.common.truth.Truth.assertThat
@@ -244,7 +243,7 @@
app = APP,
dateOfInstall = TEST_FIRST_INSTALL_TIME),
label = TEST_LABEL,
- summary = stateOf(TEST_SUMMARY),
+ summary = { TEST_SUMMARY },
).AppItem()
}
}
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/LongBackgroundTasksAppsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/LongBackgroundTasksAppsTest.kt
new file mode 100644
index 0000000..579c6c9
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/LongBackgroundTasksAppsTest.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.specialaccess
+
+import android.Manifest
+import android.app.AppOpsManager
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import com.android.settings.R
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class LongBackgroundTasksAppsTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val listModel = LongBackgroundTasksAppsListModel(context)
+
+ @Test
+ fun modelResourceIdAndProperties() {
+ assertThat(listModel.pageTitleResId).isEqualTo(R.string.long_background_tasks_title)
+ assertThat(listModel.switchTitleResId).isEqualTo(R.string.long_background_tasks_switch_title)
+ assertThat(listModel.footerResId).isEqualTo(R.string.long_background_tasks_footer_title)
+ assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_RUN_USER_INITIATED_JOBS)
+ assertThat(listModel.permission).isEqualTo(Manifest.permission.RUN_USER_INITIATED_JOBS)
+ assertThat(listModel.setModeByUid).isTrue()
+ }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/TurnScreenOnAppsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/TurnScreenOnAppsTest.kt
new file mode 100644
index 0000000..54ae6c6
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/TurnScreenOnAppsTest.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.specialaccess
+
+import android.Manifest
+import android.app.AppOpsManager
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TurnScreenOnAppsTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val listModel = TurnScreenOnAppsListModel(context)
+
+ @Test
+ fun pageTitleResId() {
+ assertThat(listModel.pageTitleResId).isEqualTo(com.android.settingslib.R.string.turn_screen_on_title)
+ }
+
+ @Test
+ fun switchTitleResId() {
+ assertThat(listModel.switchTitleResId).isEqualTo(com.android.settingslib.R.string.allow_turn_screen_on)
+ }
+
+ @Test
+ fun footerResId() {
+ assertThat(listModel.footerResId).isEqualTo(com.android.settingslib.R.string.allow_turn_screen_on_description)
+ }
+
+ @Test
+ fun appOp() {
+ assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_TURN_SCREEN_ON)
+ }
+
+ @Test
+ fun permission() {
+ assertThat(listModel.permission).isEqualTo(Manifest.permission.TURN_SCREEN_ON)
+ }
+
+ @Test
+ fun setModeByUid() {
+ assertThat(listModel.setModeByUid).isTrue()
+ }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsPreferenceControllerTest.kt
new file mode 100644
index 0000000..2127497
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsPreferenceControllerTest.kt
@@ -0,0 +1,65 @@
+package com.android.settings.spa.app.specialaccess
+
+import android.content.Context
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.preference.Preference
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import com.android.settings.flags.Flags
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class VoiceActivationAppsPreferenceControllerTest {
+
+ @get:Rule
+ val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ doNothing().whenever(mock).startActivity(any())
+ }
+
+ private val matchedPreference = Preference(context).apply { key = preferenceKey }
+
+ private val misMatchedPreference = Preference(context).apply { key = testPreferenceKey }
+
+ private val controller = VoiceActivationAppsPreferenceController(context, preferenceKey)
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_VOICE_ACTIVATION_APPS_IN_SETTINGS)
+ fun getAvailabilityStatus_enableVoiceActivationApps_returnAvailable() {
+ assertThat(controller.isAvailable).isTrue()
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ENABLE_VOICE_ACTIVATION_APPS_IN_SETTINGS)
+ fun getAvailableStatus_disableVoiceActivationApps_returnConditionallyUnavailable() {
+ assertThat(controller.isAvailable).isFalse()
+ }
+
+ @Test
+ fun handlePreferenceTreeClick_keyMatched_returnTrue() {
+ assertThat(controller.handlePreferenceTreeClick(matchedPreference)).isTrue()
+ }
+
+ @Test
+ fun handlePreferenceTreeClick_keyMisMatched_returnFalse() {
+ assertThat(controller.handlePreferenceTreeClick(misMatchedPreference)).isFalse()
+ }
+
+ companion object {
+ private const val preferenceKey: String = "voice_activation_apps"
+ private const val testPreferenceKey: String = "test_key"
+ }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsTest.kt
new file mode 100644
index 0000000..7d636b3
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/VoiceActivationAppsTest.kt
@@ -0,0 +1,50 @@
+package com.android.settings.spa.app.specialaccess
+
+import android.Manifest
+import android.app.AppOpsManager
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settings.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class VoiceActivationAppsTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val listModel = VoiceActivationAppsListModel(context)
+
+ @Test
+ fun pageTitleResId() {
+ assertThat(listModel.pageTitleResId).isEqualTo(R.string.voice_activation_apps_title)
+ }
+
+ @Test
+ fun switchTitleResId() {
+ assertThat(listModel.switchTitleResId).isEqualTo(R.string.permit_voice_activation_apps)
+ }
+
+ @Test
+ fun footerResId() {
+ assertThat(listModel.footerResId)
+ .isEqualTo(R.string.allow_voice_activation_apps_description)
+ }
+
+ @Test
+ fun appOp() {
+ assertThat(listModel.appOp).isEqualTo(AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO)
+ }
+
+ @Test
+ fun permission() {
+ assertThat(listModel.permission).isEqualTo(
+ Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO)
+ }
+
+ @Test
+ fun setModeByUid() {
+ assertThat(listModel.setModeByUid).isTrue()
+ }
+}
\ No newline at end of file
diff --git a/tests/spa_unit/src/com/android/settings/spa/development/compat/PlatformCompatAppListModelTest.kt b/tests/spa_unit/src/com/android/settings/spa/development/compat/PlatformCompatAppListModelTest.kt
index 0cfdc7d..6aee4ce 100644
--- a/tests/spa_unit/src/com/android/settings/spa/development/compat/PlatformCompatAppListModelTest.kt
+++ b/tests/spa_unit/src/com/android/settings/spa/development/compat/PlatformCompatAppListModelTest.kt
@@ -20,7 +20,6 @@
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.PackageInfoFlags
-import androidx.compose.runtime.State
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -79,20 +78,20 @@
@Test
fun getSummary() = runTest {
- val summaryState = getSummaryState(APP)
+ val summary = getSummary(APP)
- assertThat(summaryState.value).isEqualTo(PACKAGE_NAME)
+ assertThat(summary).isEqualTo(PACKAGE_NAME)
}
- private fun getSummaryState(app: ApplicationInfo): State<String> {
- lateinit var summary: State<String>
+ private fun getSummary(app: ApplicationInfo): String {
+ lateinit var summary: () -> String
composeTestRule.setContent {
summary = listModel.getSummary(
option = 0,
record = PlatformCompatAppRecord(app),
)
}
- return summary
+ return summary()
}
private companion object {
diff --git a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
index 54299eb..9b098a7 100644
--- a/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
+++ b/tests/spa_unit/src/com/android/settings/testutils/FakeFeatureFactory.kt
@@ -41,6 +41,7 @@
import com.android.settings.overlay.FeatureFactory
import com.android.settings.overlay.SurveyFeatureProvider
import com.android.settings.panel.PanelFeatureProvider
+import com.android.settings.privatespace.PrivateSpaceLoginFeatureProvider
import com.android.settings.search.SearchFeatureProvider
import com.android.settings.security.SecurityFeatureProvider
import com.android.settings.security.SecuritySettingsFeatureProvider
@@ -143,4 +144,6 @@
get() = TODO("Not yet implemented")
override val fastPairFeatureProvider: FastPairFeatureProvider
get() = TODO("Not yet implemented")
+ override val privateSpaceLoginFeatureProvider: PrivateSpaceLoginFeatureProvider
+ get() = TODO("Not yet implemented")
}
diff --git a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
index b5062a0..bf2c84a 100644
--- a/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
+++ b/tests/unit/src/com/android/settings/testutils/FakeFeatureFactory.java
@@ -46,6 +46,7 @@
import com.android.settings.overlay.SupportFeatureProvider;
import com.android.settings.overlay.SurveyFeatureProvider;
import com.android.settings.panel.PanelFeatureProvider;
+import com.android.settings.privatespace.PrivateSpaceLoginFeatureProvider;
import com.android.settings.search.SearchFeatureProvider;
import com.android.settings.security.SecurityFeatureProvider;
import com.android.settings.security.SecuritySettingsFeatureProvider;
@@ -98,6 +99,7 @@
public StylusFeatureProvider mStylusFeatureProvider;
public OnboardingFeatureProvider mOnboardingFeatureProvider;
public FastPairFeatureProvider mFastPairFeatureProvider;
+ public PrivateSpaceLoginFeatureProvider mPrivateSpaceLoginFeatureProvider;
/**
* Call this in {@code @Before} method of the test class to use fake factory.
@@ -145,6 +147,7 @@
mStylusFeatureProvider = mock(StylusFeatureProvider.class);
mOnboardingFeatureProvider = mock(OnboardingFeatureProvider.class);
mFastPairFeatureProvider = mock(FastPairFeatureProvider.class);
+ mPrivateSpaceLoginFeatureProvider = mock(PrivateSpaceLoginFeatureProvider.class);
}
@Override
@@ -322,4 +325,9 @@
public FastPairFeatureProvider getFastPairFeatureProvider() {
return mFastPairFeatureProvider;
}
+
+ @Override
+ public PrivateSpaceLoginFeatureProvider getPrivateSpaceLoginFeatureProvider() {
+ return mPrivateSpaceLoginFeatureProvider;
+ }
}