diff --git a/res/values/strings.xml b/res/values/strings.xml
index 5902387bf58..366d0fc5bba 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -13367,7 +13367,7 @@
Show additional information
- Display time, date, weather, air quality, and Cast details on the screen saver
+ Display things like the time, weather, or other information on the screen saver
More settings
diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java
index 24bd1921c11..557863131ba 100644
--- a/src/com/android/settings/applications/manageapplications/ManageApplications.java
+++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java
@@ -133,7 +133,7 @@ import com.android.settings.notification.ConfigureNotificationSettings;
import com.android.settings.notification.NotificationBackend;
import com.android.settings.notification.app.AppNotificationSettings;
import com.android.settings.spa.SpaActivity;
-import com.android.settings.spa.app.InstallUnknownAppsListProvider;
+import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider;
import com.android.settings.widget.LoadingViewController;
import com.android.settings.wifi.AppStateChangeWifiStateBridge;
import com.android.settings.wifi.ChangeWifiStateDetails;
diff --git a/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java b/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java
index b58d9c35d28..e2d92c7ce50 100644
--- a/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/AdvancedConnectedDeviceDashboardFragment.java
@@ -69,8 +69,12 @@ public class AdvancedConnectedDeviceDashboardFragment extends DashboardFragment
public void onAttach(Context context) {
super.onAttach(context);
UwbPreferenceController uwbPreferenceController = use(UwbPreferenceController.class);
- if (uwbPreferenceController != null && getSettingsLifecycle() != null) {
- getSettingsLifecycle().addObserver(uwbPreferenceController);
+ // We only need the observer listen to the broadcast in the background for refreshing
+ // UI if the device supports UWB.
+ if (uwbPreferenceController != null && uwbPreferenceController.isUwbSupportedOnDevice()) {
+ if (getSettingsLifecycle() != null) {
+ getSettingsLifecycle().addObserver(uwbPreferenceController);
+ }
}
}
diff --git a/src/com/android/settings/datausage/DataSaverSummary.java b/src/com/android/settings/datausage/DataSaverSummary.java
index 744f692bcb0..31a62d4af44 100644
--- a/src/com/android/settings/datausage/DataSaverSummary.java
+++ b/src/com/android/settings/datausage/DataSaverSummary.java
@@ -55,17 +55,21 @@ public class DataSaverSummary extends SettingsPreferenceFragment
// Flag used to avoid infinite loop due if user switch it on/off too quicky.
private boolean mSwitching;
+ private Runnable mLoadAppRunnable = () -> {
+ mApplicationsState = ApplicationsState.getInstance(
+ (Application) getContext().getApplicationContext());
+ mDataUsageBridge = new AppStateDataUsageBridge(mApplicationsState, this, mDataSaverBackend);
+ mSession = mApplicationsState.newSession(this, getSettingsLifecycle());
+ mDataUsageBridge.resume(true /* forceLoadAllApps */);
+ };
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.data_saver);
mUnrestrictedAccess = findPreference(KEY_UNRESTRICTED_ACCESS);
- mApplicationsState = ApplicationsState.getInstance(
- (Application) getContext().getApplicationContext());
mDataSaverBackend = new DataSaverBackend(getContext());
- mDataUsageBridge = new AppStateDataUsageBridge(mApplicationsState, this, mDataSaverBackend);
- mSession = mApplicationsState.newSession(this, getSettingsLifecycle());
}
@Override
@@ -83,14 +87,20 @@ public class DataSaverSummary extends SettingsPreferenceFragment
mDataSaverBackend.refreshAllowlist();
mDataSaverBackend.refreshDenylist();
mDataSaverBackend.addListener(this);
- mDataUsageBridge.resume(true /* forceLoadAllApps */);
+ if (mDataUsageBridge != null) {
+ mDataUsageBridge.resume(true /* forceLoadAllApps */);
+ } else {
+ getView().post(mLoadAppRunnable);
+ }
}
@Override
public void onPause() {
super.onPause();
mDataSaverBackend.remListener(this);
- mDataUsageBridge.pause();
+ if (mDataUsageBridge != null) {
+ mDataUsageBridge.pause();
+ }
}
@Override
@@ -132,24 +142,7 @@ public class DataSaverSummary extends SettingsPreferenceFragment
@Override
public void onExtraInfoUpdated() {
- if (!isAdded()) {
- return;
- }
- int count = 0;
- final ArrayList allApps = mSession.getAllApps();
- final int N = allApps.size();
- for (int i = 0; i < N; i++) {
- final AppEntry entry = allApps.get(i);
- if (!ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(entry)) {
- continue;
- }
- if (entry.extraInfo != null && ((AppStateDataUsageBridge.DataUsageState)
- entry.extraInfo).isDataSaverAllowlisted) {
- count++;
- }
- }
- mUnrestrictedAccess.setSummary(getResources().getQuantityString(
- R.plurals.data_saver_unrestricted_summary, count, count));
+ updateUnrestrictedAccessSummary();
}
@Override
@@ -179,12 +172,12 @@ public class DataSaverSummary extends SettingsPreferenceFragment
@Override
public void onAllSizesComputed() {
-
+ updateUnrestrictedAccessSummary();
}
@Override
public void onLauncherInfoChanged() {
-
+ updateUnrestrictedAccessSummary();
}
@Override
@@ -192,6 +185,23 @@ public class DataSaverSummary extends SettingsPreferenceFragment
}
+ private void updateUnrestrictedAccessSummary() {
+ if (!isAdded() || isFinishingOrDestroyed() || mSession == null) return;
+
+ int count = 0;
+ for (AppEntry entry : mSession.getAllApps()) {
+ if (!ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(entry)) {
+ continue;
+ }
+ if (entry.extraInfo != null && ((AppStateDataUsageBridge.DataUsageState)
+ entry.extraInfo).isDataSaverAllowlisted) {
+ count++;
+ }
+ }
+ mUnrestrictedAccess.setSummary(getResources().getQuantityString(
+ R.plurals.data_saver_unrestricted_summary, count, count));
+ }
+
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.data_saver) {
diff --git a/src/com/android/settings/spa/SpaEnvironment.kt b/src/com/android/settings/spa/SpaEnvironment.kt
index 7da253af25c..489c8a8a926 100644
--- a/src/com/android/settings/spa/SpaEnvironment.kt
+++ b/src/com/android/settings/spa/SpaEnvironment.kt
@@ -16,7 +16,10 @@
package com.android.settings.spa
-import com.android.settings.spa.app.InstallUnknownAppsListProvider
+import com.android.settings.spa.app.AppsMainPageProvider
+import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider
+import com.android.settings.spa.app.specialaccess.PictureInPictureListProvider
+import com.android.settings.spa.app.specialaccess.SpecialAppAccessPageProvider
import com.android.settings.spa.home.HomePageProvider
import com.android.settingslib.spa.framework.common.SettingsEntryRepository
import com.android.settingslib.spa.framework.common.SettingsPage
@@ -29,11 +32,16 @@ object SpaEnvironment {
val settingsPageProviders: SettingsPageProviderRepository by
lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
val togglePermissionAppListTemplate = TogglePermissionAppListTemplate(
- allProviders = listOf(InstallUnknownAppsListProvider),
+ allProviders = listOf(
+ InstallUnknownAppsListProvider,
+ PictureInPictureListProvider,
+ ),
)
SettingsPageProviderRepository(
allPageProviders = listOf(
HomePageProvider,
+ AppsMainPageProvider,
+ SpecialAppAccessPageProvider,
NotificationMainPageProvider,
AppListNotificationsPageProvider,
) + togglePermissionAppListTemplate.createPageProviders(),
diff --git a/src/com/android/settings/spa/app/AppsMain.kt b/src/com/android/settings/spa/app/AppsMain.kt
new file mode 100644
index 00000000000..b3ee12fb387
--- /dev/null
+++ b/src/com/android/settings/spa/app/AppsMain.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.app
+
+import android.os.Bundle
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Apps
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.android.settings.R
+import com.android.settings.spa.app.specialaccess.SpecialAppAccessPageProvider
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+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
+import com.android.settingslib.spa.widget.ui.SettingsIcon
+
+object AppsMainPageProvider : SettingsPageProvider {
+ override val name = "AppsMain"
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ AppsMain()
+ }
+
+ @Composable
+ fun EntryItem() {
+ 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 onClick = navigator(name)
+ override val icon = @Composable {
+ SettingsIcon(imageVector = Icons.Outlined.Apps)
+ }
+ })
+ }
+
+ fun buildInjectEntry() =
+ SettingsEntryBuilder.createInject(SettingsPage.create(name)).setIsAllowSearch(false)
+
+ override fun buildEntry(arguments: Bundle?): List {
+ val owner = SettingsPage.create(name, parameter, arguments)
+ return listOf(
+ SpecialAppAccessPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ )
+ }
+}
+
+@Composable
+private fun AppsMain() {
+ RegularScaffold(title = stringResource(R.string.apps_dashboard_title)) {
+ SpecialAppAccessPageProvider.EntryItem()
+ }
+}
diff --git a/src/com/android/settings/spa/app/InstallUnknownApps.kt b/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt
similarity index 98%
rename from src/com/android/settings/spa/app/InstallUnknownApps.kt
rename to src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt
index a09fda2b098..77c0f685bd2 100644
--- a/src/com/android/settings/spa/app/InstallUnknownApps.kt
+++ b/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settings.spa.app
+package com.android.settings.spa.app.specialaccess
import android.Manifest
import android.app.AppGlobals
diff --git a/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt b/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt
new file mode 100644
index 00000000000..0c56e56d4d3
--- /dev/null
+++ b/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.app.specialaccess
+
+import android.app.AppOpsManager.OP_PICTURE_IN_PICTURE
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager.GET_ACTIVITIES
+import android.content.pm.PackageManager.PackageInfoFlags
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.livedata.observeAsState
+import com.android.settings.R
+import com.android.settingslib.spaprivileged.model.app.AppOpsController
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spaprivileged.model.app.userId
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListProvider
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+
+object PictureInPictureListProvider : TogglePermissionAppListProvider {
+ override val permissionType = "PictureInPicture"
+ override fun createModel(context: Context) = PictureInPictureListModel(context)
+}
+
+data class PictureInPictureRecord(
+ override val app: ApplicationInfo,
+ val isSupport: Boolean,
+ val appOpsController: AppOpsController,
+) : AppRecord
+
+class PictureInPictureListModel(private val context: Context)
+ : TogglePermissionAppListModel {
+ override val pageTitleResId = R.string.picture_in_picture_title
+ override val switchTitleResId = R.string.picture_in_picture_app_detail_switch
+ override val footerResId = R.string.picture_in_picture_app_detail_summary
+
+ private val packageManager = context.packageManager
+
+ override fun transform(userIdFlow: Flow, appListFlow: Flow>) =
+ userIdFlow.map(::getPictureInPicturePackages)
+ .combine(appListFlow) { pictureInPicturePackages, appList ->
+ appList.map { app ->
+ createPictureInPictureRecord(
+ app = app,
+ isSupport = app.packageName in pictureInPicturePackages,
+ )
+ }
+ }
+
+ override fun transformItem(app: ApplicationInfo): PictureInPictureRecord {
+ val packageInfo =
+ packageManager.getPackageInfoAsUser(app.packageName, GET_ACTIVITIES_FLAGS, app.userId)
+ return createPictureInPictureRecord(
+ app = app,
+ isSupport = packageInfo.supportsPictureInPicture(),
+ )
+ }
+
+ private fun createPictureInPictureRecord(app: ApplicationInfo, isSupport: Boolean) =
+ PictureInPictureRecord(
+ app = app,
+ isSupport = isSupport,
+ appOpsController = AppOpsController(
+ context = context,
+ app = app,
+ op = OP_PICTURE_IN_PICTURE,
+ ),
+ )
+
+ override fun filter(userIdFlow: Flow, recordListFlow: Flow>) =
+ recordListFlow.map { recordList ->
+ recordList.filter { it.isSupport }
+ }
+
+ @Composable
+ override fun isAllowed(record: PictureInPictureRecord) =
+ record.appOpsController.isAllowed.observeAsState()
+
+ override fun isChangeable(record: PictureInPictureRecord) = record.isSupport
+
+ override fun setAllowed(record: PictureInPictureRecord, newAllowed: Boolean) {
+ record.appOpsController.setAllowed(newAllowed)
+ }
+
+ private fun getPictureInPicturePackages(userId: Int): Set =
+ packageManager.getInstalledPackagesAsUser(GET_ACTIVITIES_FLAGS, userId)
+ .filter { it.supportsPictureInPicture() }
+ .map { it.packageName }
+ .toSet()
+
+ companion object {
+ private fun PackageInfo.supportsPictureInPicture() =
+ activities?.any(ActivityInfo::supportsPictureInPicture) ?: false
+
+ private val GET_ACTIVITIES_FLAGS = PackageInfoFlags.of(GET_ACTIVITIES.toLong())
+ }
+}
diff --git a/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt b/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt
new file mode 100644
index 00000000000..f7b1f8260ed
--- /dev/null
+++ b/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.spa.app.specialaccess
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.android.settings.R
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+
+object SpecialAppAccessPageProvider : SettingsPageProvider {
+ override val name = "SpecialAppAccess"
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ SpecialAppAccessPage()
+ }
+
+ @Composable
+ fun EntryItem() {
+ Preference(object : PreferenceModel {
+ override val title = stringResource(R.string.special_access)
+ override val onClick = navigator(name)
+ })
+ }
+
+ fun buildInjectEntry() =
+ SettingsEntryBuilder.createInject(SettingsPage.create(name)).setIsAllowSearch(false)
+
+ override fun buildEntry(arguments: Bundle?): List {
+ val owner = SettingsPage.create(name, parameter, arguments)
+ return listOf(
+ PictureInPictureListProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ InstallUnknownAppsListProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ )
+ }
+}
+
+@Composable
+private fun SpecialAppAccessPage() {
+ RegularScaffold(title = stringResource(R.string.special_access)) {
+ PictureInPictureListProvider.EntryItem()
+ InstallUnknownAppsListProvider.EntryItem()
+ }
+}
diff --git a/src/com/android/settings/spa/home/HomePage.kt b/src/com/android/settings/spa/home/HomePage.kt
index 3b8fb84310d..424d91ffa34 100644
--- a/src/com/android/settings/spa/home/HomePage.kt
+++ b/src/com/android/settings/spa/home/HomePage.kt
@@ -23,8 +23,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.android.settings.R
import com.android.settings.spa.SpaEnvironment
-import com.android.settings.spa.app.InstallUnknownAppsListProvider
+import com.android.settings.spa.app.AppsMainPageProvider
import com.android.settings.spa.notification.NotificationMainPageProvider
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.widget.scaffold.HomeScaffold
@@ -35,12 +37,19 @@ object HomePageProvider : SettingsPageProvider {
override fun Page(arguments: Bundle?) {
HomePage()
}
+
+ override fun buildEntry(arguments: Bundle?): List {
+ val owner = SettingsPage.create(name, parameter, arguments)
+ return listOf(
+ AppsMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ )
+ }
}
@Composable
private fun HomePage() {
HomeScaffold(title = stringResource(R.string.settings_label)) {
- InstallUnknownAppsListProvider.EntryItem()
+ AppsMainPageProvider.EntryItem()
NotificationMainPageProvider.EntryItem()
/**
diff --git a/src/com/android/settings/uwb/UwbPreferenceController.java b/src/com/android/settings/uwb/UwbPreferenceController.java
index ad040ed6043..fb0836d6d3c 100644
--- a/src/com/android/settings/uwb/UwbPreferenceController.java
+++ b/src/com/android/settings/uwb/UwbPreferenceController.java
@@ -75,8 +75,7 @@ public class UwbPreferenceController extends TogglePreferenceController implemen
};
}
- @VisibleForTesting
- boolean isUwbSupportedOnDevice() {
+ public boolean isUwbSupportedOnDevice() {
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB);
}
@@ -99,6 +98,9 @@ public class UwbPreferenceController extends TogglePreferenceController implemen
@Override
public boolean isChecked() {
+ if (!isUwbSupportedOnDevice()) {
+ return false;
+ }
int state = mUwbManager.getAdapterState();
return state == STATE_ENABLED_ACTIVE || state == STATE_ENABLED_INACTIVE;
}