Add SpaLib for Settings

SPA main activity can be launched by:
adb shell am start com.android.settings/.spa.SpaActivity

Bug: 235727273
Test: Manual launch SPA main activity
Change-Id: I7b196b0169f91732a6b37ff53a3f79b54267d93f
diff --git a/Android.bp b/Android.bp
index 5bf5514..4af8d09 100644
--- a/Android.bp
+++ b/Android.bp
@@ -51,6 +51,7 @@
     defaults: [
         "SettingsLibDefaults",
         "SettingsLib-search-defaults",
+        "SpaPrivilegedLib-defaults",
     ],
 
     srcs: ["src/**/*.java", "src/**/*.kt"],
@@ -63,6 +64,7 @@
         "androidx.core_core",
         "androidx.appcompat_appcompat",
         "androidx.cardview_cardview",
+        "androidx.compose.runtime_runtime-livedata",
         "androidx.preference_preference",
         "androidx.recyclerview_recyclerview",
         "androidx.window_window",
@@ -103,7 +105,10 @@
 
 android_app {
     name: "Settings",
-    defaults: ["platform_app_defaults"],
+    defaults: [
+        "platform_app_defaults",
+        "SpaPrivilegedLib-defaults",
+    ],
     platform_apis: true,
     certificate: "platform",
     system_ext_specific: true,
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index cf93f9b..c05d310 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -4544,6 +4544,11 @@
             </intent-filter>
         </activity>
 
+        <activity
+            android:name="com.android.settings.spa.SpaActivity"
+            android:exported="false">
+        </activity>
+
         <!-- This is the longest AndroidManifest.xml ever. -->
     </application>
 </manifest>
diff --git a/src/com/android/settings/spa/SpaActivity.kt b/src/com/android/settings/spa/SpaActivity.kt
new file mode 100644
index 0000000..7e79350
--- /dev/null
+++ b/src/com/android/settings/spa/SpaActivity.kt
@@ -0,0 +1,21 @@
+/*
+ * 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
+
+import com.android.settingslib.spa.framework.BrowseActivity
+
+class SpaActivity : BrowseActivity(settingsPageProviders)
diff --git a/src/com/android/settings/spa/SpaEnvironment.kt b/src/com/android/settings/spa/SpaEnvironment.kt
new file mode 100644
index 0000000..fad7ef2
--- /dev/null
+++ b/src/com/android/settings/spa/SpaEnvironment.kt
@@ -0,0 +1,33 @@
+/*
+ * 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
+
+import com.android.settings.spa.app.InstallUnknownAppsListProvider
+import com.android.settings.spa.home.HomePageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListTemplate
+
+private val togglePermissionAppListTemplate = TogglePermissionAppListTemplate(
+    allProviders = listOf(InstallUnknownAppsListProvider),
+)
+
+val settingsPageProviders = SettingsPageProviderRepository(
+    allPagesList = listOf(
+        HomePageProvider,
+    ) + togglePermissionAppListTemplate.createPageProviders(),
+    rootPages = listOf(HomePageProvider.name),
+)
diff --git a/src/com/android/settings/spa/app/InstallUnknownApps.kt b/src/com/android/settings/spa/app/InstallUnknownApps.kt
new file mode 100644
index 0000000..a09fda2
--- /dev/null
+++ b/src/com/android/settings/spa/app/InstallUnknownApps.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.Manifest
+import android.app.AppGlobals
+import android.app.AppOpsManager.MODE_DEFAULT
+import android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES
+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.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 InstallUnknownAppsListProvider : TogglePermissionAppListProvider {
+    override val permissionType = "InstallUnknownApps"
+    override fun createModel(context: Context) = InstallUnknownAppsListModel(context)
+}
+
+data class InstallUnknownAppsRecord(
+    override val app: ApplicationInfo,
+    val appOpsController: AppOpsController,
+) : AppRecord
+
+class InstallUnknownAppsListModel(private val context: Context) :
+    TogglePermissionAppListModel<InstallUnknownAppsRecord> {
+    override val pageTitleResId = R.string.install_other_apps
+    override val switchTitleResId = R.string.external_source_switch_title
+    override val footerResId = R.string.install_all_warning
+
+    override fun transformItem(app: ApplicationInfo) = InstallUnknownAppsRecord(
+        app = app,
+        appOpsController = AppOpsController(
+            context = context,
+            app = app,
+            op = OP_REQUEST_INSTALL_PACKAGES,
+        ),
+    )
+
+    override fun filter(
+        userIdFlow: Flow<Int>, recordListFlow: Flow<List<InstallUnknownAppsRecord>>,
+    ) = userIdFlow.map(::getPotentialPackageNames)
+        .combine(recordListFlow) { potentialPackageNames, recordList ->
+            recordList.filter { record ->
+                isChangeable(record, potentialPackageNames)
+            }
+        }
+
+    @Composable
+    override fun isAllowed(record: InstallUnknownAppsRecord) =
+        record.appOpsController.isAllowed.observeAsState()
+
+    override fun isChangeable(record: InstallUnknownAppsRecord) =
+        isChangeable(record, getPotentialPackageNames(record.app.userId))
+
+    override fun setAllowed(record: InstallUnknownAppsRecord, newAllowed: Boolean) {
+        record.appOpsController.setAllowed(newAllowed)
+    }
+
+    companion object {
+        private fun isChangeable(
+            record: InstallUnknownAppsRecord,
+            potentialPackageNames: Set<String>,
+        ) = record.appOpsController.getMode() != MODE_DEFAULT ||
+                record.app.packageName in potentialPackageNames
+
+        private fun getPotentialPackageNames(userId: Int): Set<String> =
+            AppGlobals.getPackageManager().getAppOpPermissionPackages(
+                Manifest.permission.REQUEST_INSTALL_PACKAGES, userId
+            ).toSet()
+    }
+}
diff --git a/src/com/android/settings/spa/home/HomePage.kt b/src/com/android/settings/spa/home/HomePage.kt
new file mode 100644
index 0000000..6cd6fe6
--- /dev/null
+++ b/src/com/android/settings/spa/home/HomePage.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.home
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.android.settings.R
+import com.android.settings.spa.app.InstallUnknownAppsListProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.widget.scaffold.HomeScaffold
+
+object HomePageProvider : SettingsPageProvider {
+    override val name = "Home"
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        HomePage()
+    }
+}
+
+@Composable
+private fun HomePage() {
+    HomeScaffold(title = stringResource(R.string.settings_label)) {
+        InstallUnknownAppsListProvider.EntryItem()
+    }
+}