diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java index 07388fd7422..8baac63c5dd 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplications.java +++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java @@ -283,7 +283,7 @@ public class ManageApplications extends InstrumentedFragment final String className = getClassName(activity.getIntent(), getArguments()); if (className.equals(ManageExternalSourcesActivity.class.getName())) { SpaActivity.startSpaActivity( - context, InstallUnknownAppsListProvider.INSTANCE.getRoute()); + context, InstallUnknownAppsListProvider.INSTANCE.getAppListRoute()); activity.finish(); } } diff --git a/src/com/android/settings/spa/SpaEnvironment.kt b/src/com/android/settings/spa/SpaEnvironment.kt index ad9999ae9c9..4d2e3e22d91 100644 --- a/src/com/android/settings/spa/SpaEnvironment.kt +++ b/src/com/android/settings/spa/SpaEnvironment.kt @@ -17,6 +17,8 @@ package com.android.settings.spa import com.android.settings.spa.app.AppsMainPageProvider +import com.android.settings.spa.app.appsettings.AppSettingsProvider +import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider import com.android.settings.spa.app.specialaccess.AllFilesAccessAppListProvider import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider import com.android.settings.spa.app.specialaccess.InstallUnknownAppsListProvider @@ -42,14 +44,16 @@ object SpaEnvironment { DisplayOverOtherAppsAppListProvider, MediaManagementAppsAppListProvider, ModifySystemSettingsAppListProvider, - InstallUnknownAppsListProvider, PictureInPictureListProvider, + InstallUnknownAppsListProvider, + AlarmsAndRemindersAppListProvider, ), ) SettingsPageProviderRepository( allPageProviders = listOf( HomePageProvider, AppsMainPageProvider, + AppSettingsProvider, SpecialAppAccessPageProvider, NotificationMainPageProvider, AppListNotificationsPageProvider, diff --git a/src/com/android/settings/spa/app/appsettings/AppSettings.kt b/src/com/android/settings/spa/app/appsettings/AppSettings.kt new file mode 100644 index 00000000000..d0a230f1ddd --- /dev/null +++ b/src/com/android/settings/spa/app/appsettings/AppSettings.kt @@ -0,0 +1,85 @@ +/* + * 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.appsettings + +import android.content.pm.ApplicationInfo +import android.content.pm.PackageInfo +import android.os.Bundle +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.res.stringResource +import androidx.navigation.NavType +import androidx.navigation.navArgument +import com.android.settings.R +import com.android.settings.spa.app.specialaccess.AlarmsAndRemindersAppListProvider +import com.android.settings.spa.app.specialaccess.DisplayOverOtherAppsAppListProvider +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.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.widget.scaffold.RegularScaffold +import com.android.settingslib.spa.widget.ui.Category +import com.android.settingslib.spaprivileged.model.app.PackageManagers +import com.android.settingslib.spaprivileged.model.app.toRoute +import com.android.settingslib.spaprivileged.template.app.AppInfoProvider + +private const val PACKAGE_NAME = "packageName" +private const val USER_ID = "userId" + +object AppSettingsProvider : SettingsPageProvider { + override val name = "AppSettings" + + override val parameter = listOf( + navArgument(PACKAGE_NAME) { type = NavType.StringType }, + navArgument(USER_ID) { type = NavType.IntType }, + ) + + @Composable + override fun Page(arguments: Bundle?) { + val packageName = arguments!!.getString(PACKAGE_NAME)!! + val userId = arguments.getInt(USER_ID) + remember { PackageManagers.getPackageInfoAsUser(packageName, userId) }?.let { + AppSettings(it) + } + } + + @Composable + fun navigator(app: ApplicationInfo) = navigator(route = "$name/${app.toRoute()}") +} + +@Composable +private fun AppSettings(packageInfo: PackageInfo) { + RegularScaffold(title = stringResource(R.string.application_info_label)) { + val appInfoProvider = remember { AppInfoProvider(packageInfo) } + + appInfoProvider.AppInfo() + + Category(title = stringResource(R.string.advanced_apps)) { + val app = packageInfo.applicationInfo + DisplayOverOtherAppsAppListProvider.InfoPageEntryItem(app) + ModifySystemSettingsAppListProvider.InfoPageEntryItem(app) + PictureInPictureListProvider.InfoPageEntryItem(app) + InstallUnknownAppsListProvider.InfoPageEntryItem(app) + // TODO: interact_across_profiles + AlarmsAndRemindersAppListProvider.InfoPageEntryItem(app) + } + + // TODO: app_installer + appInfoProvider.FooterAppVersion() + } +} diff --git a/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersAppList.kt b/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersAppList.kt new file mode 100644 index 00000000000..2439c20a19f --- /dev/null +++ b/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersAppList.kt @@ -0,0 +1,99 @@ +/* + * 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.Manifest +import android.app.AlarmManager +import android.app.compat.CompatChanges +import android.content.Context +import android.content.pm.ApplicationInfo +import androidx.compose.runtime.Composable +import androidx.compose.runtime.livedata.observeAsState +import com.android.settings.R +import com.android.settingslib.spaprivileged.model.app.AppRecord +import com.android.settingslib.spaprivileged.model.app.PackageManagers +import com.android.settingslib.spaprivileged.model.app.PackageManagers.hasRequestPermission +import com.android.settingslib.spaprivileged.model.app.userHandle +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 AlarmsAndRemindersAppListProvider : TogglePermissionAppListProvider { + override val permissionType = "AlarmsAndReminders" + override fun createModel(context: Context) = AlarmsAndRemindersAppListModel(context) +} + +data class AlarmsAndRemindersAppRecord( + override val app: ApplicationInfo, + val isChangeable: Boolean, + var controller: AlarmsAndRemindersController, +) : AppRecord + +class AlarmsAndRemindersAppListModel( + private val context: Context, +) : TogglePermissionAppListModel { + override val pageTitleResId = R.string.alarms_and_reminders_title + override val switchTitleResId = R.string.alarms_and_reminders_switch_title + override val footerResId = R.string.alarms_and_reminders_footer_title + + override fun transform(userIdFlow: Flow, appListFlow: Flow>) = + userIdFlow.map { userId -> + PackageManagers.getAppOpPermissionPackages(userId, PERMISSION) + }.combine(appListFlow) { packageNames, appList -> + appList.map { app -> + createRecord(app = app, hasRequestPermission = app.packageName in packageNames) + } + } + + override fun transformItem(app: ApplicationInfo) = + createRecord(app = app, hasRequestPermission = app.hasRequestPermission(PERMISSION)) + + override fun filter( + userIdFlow: Flow, + recordListFlow: Flow>, + ) = recordListFlow.map { recordList -> + recordList.filter { it.isChangeable } + } + + @Composable + override fun isAllowed(record: AlarmsAndRemindersAppRecord) = + record.controller.isAllowed.observeAsState() + + override fun isChangeable(record: AlarmsAndRemindersAppRecord) = record.isChangeable + + override fun setAllowed(record: AlarmsAndRemindersAppRecord, newAllowed: Boolean) { + record.controller.setAllowed(newAllowed) + } + + private fun createRecord(app: ApplicationInfo, hasRequestPermission: Boolean) = + AlarmsAndRemindersAppRecord( + app = app, + isChangeable = hasRequestPermission && app.isChangeEnabled(), + controller = AlarmsAndRemindersController(context, app), + ) + + companion object { + private const val PERMISSION: String = Manifest.permission.SCHEDULE_EXACT_ALARM + + private fun ApplicationInfo.isChangeEnabled(): Boolean = + CompatChanges.isChangeEnabled( + AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, packageName, userHandle, + ) + } +} diff --git a/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersController.kt b/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersController.kt new file mode 100644 index 00000000000..c83e20bf8b1 --- /dev/null +++ b/src/com/android/settings/spa/app/specialaccess/AlarmsAndRemindersController.kt @@ -0,0 +1,53 @@ +/* + * 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.AlarmManager +import android.app.AppOpsManager +import android.app.AppOpsManager.MODE_ALLOWED +import android.app.AppOpsManager.MODE_ERRORED +import android.content.Context +import android.content.pm.ApplicationInfo +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.android.settingslib.spaprivileged.model.app.userId + +class AlarmsAndRemindersController( + context: Context, + private val app: ApplicationInfo, +) { + private val alarmManager = context.getSystemService(AlarmManager::class.java)!! + private val appOpsManager = context.getSystemService(AppOpsManager::class.java)!! + + val isAllowed: LiveData + get() = _allowed + + fun setAllowed(allowed: Boolean) { + val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED + appOpsManager.setUidMode(AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, app.uid, mode) + _allowed.postValue(allowed) + } + + private val _allowed = object : MutableLiveData() { + override fun onActive() { + postValue(alarmManager.hasScheduleExactAlarm(app.packageName, app.userId)) + } + + override fun onInactive() { + } + } +} diff --git a/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt b/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt index 7ab1db77750..11abdfc5818 100644 --- a/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt +++ b/src/com/android/settings/spa/app/specialaccess/SpecialAppAccess.kt @@ -34,7 +34,11 @@ object SpecialAppAccessPageProvider : SettingsPageProvider { @Composable override fun Page(arguments: Bundle?) { - SpecialAppAccessPage() + RegularScaffold(title = stringResource(R.string.special_access)) { + for (entry in buildEntry(arguments)) { + entry.UiLayout() + } + } } @Composable @@ -51,26 +55,12 @@ object SpecialAppAccessPageProvider : SettingsPageProvider { override fun buildEntry(arguments: Bundle?): List { val owner = SettingsPage.create(name, parameter, arguments) return listOf( - AllFilesAccessAppListProvider.buildInjectEntry().setLink(fromPage = owner).build(), - DisplayOverOtherAppsAppListProvider.buildInjectEntry() - .setLink(fromPage = owner).build(), - MediaManagementAppsAppListProvider.buildInjectEntry().setLink(fromPage = owner).build(), - ModifySystemSettingsAppListProvider.buildInjectEntry() - .setLink(fromPage = owner).build(), - PictureInPictureListProvider.buildInjectEntry().setLink(fromPage = owner).build(), - InstallUnknownAppsListProvider.buildInjectEntry().setLink(fromPage = owner).build(), - ) - } -} - -@Composable -private fun SpecialAppAccessPage() { - RegularScaffold(title = stringResource(R.string.special_access)) { - AllFilesAccessAppListProvider.EntryItem() - DisplayOverOtherAppsAppListProvider.EntryItem() - MediaManagementAppsAppListProvider.EntryItem() - ModifySystemSettingsAppListProvider.EntryItem() - PictureInPictureListProvider.EntryItem() - InstallUnknownAppsListProvider.EntryItem() + AllFilesAccessAppListProvider, + DisplayOverOtherAppsAppListProvider, + MediaManagementAppsAppListProvider, + ModifySystemSettingsAppListProvider, + PictureInPictureListProvider, + InstallUnknownAppsListProvider, + ).map { it.buildAppListInjectEntry().setLink(fromPage = owner).build() } } }