Settings: Introduce App Lock [3/4]

Squashed:

* AppLockCredentialActivity is a stripped down version of ConfirmDeviceCredentialActivity in Settings

Signed-off-by: jhonboy121 <alfredmathew05@gmail.com>
Signed-off-by: Adithya R <gh0strider.2k18.reborn@gmail.com>

Settings: applock: adapt for API changes

Signed-off-by: jhonboy121 <alfredmathew05@gmail.com>

Settings: use a new task stack for app lock fragments

* fixes fragments staying in recents on going home
* also made other preferences do binder calls asynchronously

Signed-off-by: jhonboy121 <alfredmathew05@gmail.com>
Signed-off-by: Adithya R <gh0strider.2k18.reborn@gmail.com>

Settings: applock: fix unlock prompt disappearing when trying to open apps

Signed-off-by: jhonboy121 <alfredmathew05@gmail.com>

Settings: applock: add hidden app settings

Signed-off-by: jhonboy121 <alfredmathew05@gmail.com>
Signed-off-by: Adithya R <gh0strider.2k18.reborn@gmail.com>

AppLockPackageConfigFragment: allow launching app from app icon

Signed-off-by: jhonboy121 <alfredmathew05@gmail.com>

FlamingoSettings: applock: early return in setChecked if backing field has the same value

Signed-off-by: jhonboy121 <alfredmathew05@gmail.com>

Signed-off-by: Adithya R <gh0strider.2k18.reborn@gmail.com>

[nift4: drop useless plurals.xml, make timeout values translatable]

* Dhina17 <dhinalogu@gmail.com>
        applock: Adapt for Android 14

Change-Id: I85d72ee72353417ead528483bbbe1ac1e6860063
Signed-off-by: Pranav Vashi <neobuddy89@gmail.com>
This commit is contained in:
jhonboy121
2022-03-24 21:42:31 +05:30
committed by Joey
parent eff5a9865a
commit edadced2f0
21 changed files with 1249 additions and 0 deletions

View File

@@ -128,6 +128,12 @@ android_library {
// Lineage dependencies
"org.lineageos.platform.internal",
"LineagePreferenceLib",
"androidx.fragment_fragment",
"androidx.fragment_fragment-ktx",
"androidx.preference_preference-ktx",
"kotlin-stdlib",
"kotlinx_coroutines_android",
"kotlinx_coroutines",
],
plugins: [

View File

@@ -158,6 +158,9 @@
<uses-permission android:name="android.permission.MANAGE_COMPANION_DEVICES" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- App lock -->
<uses-permission android:name="android.permission.MANAGE_APP_LOCK" />
<application
android:name=".SettingsApplication"
android:label="@string/settings_label"
@@ -5764,6 +5767,27 @@
</intent-filter>
</activity>
<!-- App lock -->
<activity android:name=".security.applock.AppLockCredentialActivity"
android:exported="false"
android:permission="android.permission.MANAGE_APP_LOCK"
android:excludeFromRecents="true"
android:stateNotNeeded="true"
android:taskAffinity="com.android.settings.applock"
android:launchMode="singleInstance"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.app.action.UNLOCK_APP" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".security.applock.AppLockSubSettings"
android:exported="false"
android:excludeFromRecents="true"
android:taskAffinity="com.android.settings.applock"
android:launchMode="singleTask" />
<!-- This is the longest AndroidManifest.xml ever. -->
</application>
</manifest>

View File

@@ -97,4 +97,25 @@
<item>com.android.systemui</item>
<item>com.android.shell</item>
</string-array>
<!-- App lock timeout -->
<string-array name="app_lock_timeout_entries">
<item>@string/custom_timeout_summary_5secs</item>
<item>@string/custom_timeout_summary_10secs</item>
<item>@string/custom_timeout_summary_30secs</item>
<item>@string/custom_timeout_summary_1min</item>
<item>@string/custom_timeout_summary_5mins</item>
<item>@string/custom_timeout_summary_10mins</item>
<item>@string/custom_timeout_summary_30mins</item>
</string-array>
<string-array name="app_lock_timeout_values" translatable="false">
<item>5000</item>
<item>10000</item>
<item>30000</item>
<item>60000</item>
<item>300000</item>
<item>600000</item>
<item>1800000</item>
</string-array>
</resources>

View File

@@ -208,4 +208,26 @@
<!-- App search preference -->
<string name="search">Search</string>
<string name="search_apps">Search apps</string>
<!-- App lock -->
<string name="app_lock_title">App lock</string>
<plurals name="app_lock_summary">
<item quantity="one"><xliff:g example="1" id="Number of applications">%1$d</xliff:g> application is protected</item>
<item quantity="other"><xliff:g example="10" id="Number of applications">%1$d</xliff:g> applications are protected</item>
</plurals>
<string name="app_lock_authentication_dialog_title">Unlock</string>
<string name="enable_debugging">Enable debugging</string>
<string name="disable_debugging">Disable debugging</string>
<string name="app_lock_packages_title">Protected apps</string>
<string name="app_lock_packages_summary">Select the apps to protect with biometrics or device credentials</string>
<string name="app_lock_timeout_title">Auto lock timeout</string>
<string name="app_lock_timeout_summary">Duration of time after which an unlocked app in background should be locked</string>
<string name="app_lock_notifications_title">Redact notifications</string>
<string name="app_lock_notifications_summary">Notification content will be hidden and collapsed for selected apps when they are locked. Heads up notifications will be automatically disabled.</string>
<string name="app_lock_notifications_disabled_summary">Protect an application first</string>
<string name="app_lock_biometrics_allowed_title">Enable biometrics for unlocking</string>
<string name="app_lock_footer_text">Bubbles will be automatically dismissed after timeout</string>
<string name="enable_protection">Enable protection</string>
<string name="hide_from_launcher_title">Hide from launcher</string>
<string name="hide_from_launcher_summary">Prevent this application from showing up in any launcher. Requires a launcher restart for changes to take effect.</string>
</resources>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2022 FlamingoOS 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.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto">
<com.android.settingslib.widget.LayoutPreference
android:key="header_view"
android:layout="@layout/settings_entity_header"
android:selectable="false" />
<com.android.settingslib.widget.MainSwitchPreference
android:key="main_switch"
android:title="@string/enable_protection" />
<SwitchPreferenceCompat
android:key="redact_notifications"
android:title="@string/app_lock_notifications_title"
android:summary="@string/app_lock_notifications_summary"
android:dependency="main_switch" />
<SwitchPreferenceCompat
android:key="hide_from_launcher"
android:title="@string/hide_from_launcher_title"
android:summary="@string/hide_from_launcher_summary"
android:dependency="main_switch" />
<com.android.settingslib.widget.FooterPreference
android:title="@string/app_lock_footer_text"
android:selectable="false"
android:dependency="main_switch"
settings:searchable="false" />
</PreferenceScreen>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2022 FlamingoOS 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.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/app_lock_packages_title">
</PreferenceScreen>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2022 FlamingoOS 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.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/app_lock_title">
<Preference
android:key="app_lock_packages"
android:title="@string/app_lock_packages_title"
android:summary="@string/app_lock_packages_summary"
android:fragment="com.android.settings.security.applock.AppLockPackageListFragment" />
<ListPreference
android:key="app_lock_timeout"
android:title="@string/app_lock_timeout_title"
android:summary="@string/app_lock_timeout_summary"
android:entries="@array/app_lock_timeout_entries"
android:entryValues="@array/app_lock_timeout_values"
android:defaultValue="0"
android:persistent="false"
settings:controller="com.android.settings.security.applock.AppLockTimeoutPreferenceController" />
<SwitchPreferenceCompat
android:key="app_lock_biometrics_allowed"
android:title="@string/app_lock_biometrics_allowed_title"
android:persistent="false" />
</PreferenceScreen>

View File

@@ -60,6 +60,11 @@
android:title="@string/security_settings_biometric_preference_title"
android:summary="@string/summary_placeholder"
settings:keywords="@string/keywords_biometric_settings" />
<!-- App lock -->
<com.android.settingslib.RestrictedPreference
android:key="app_lock"
android:title="@string/app_lock_title" />
</PreferenceCategory>
<Preference

View File

@@ -32,6 +32,7 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.safetycenter.SafetyCenterManagerWrapper;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.security.applock.AppLockSettingsPreferenceController;
import com.android.settings.security.trustagent.TrustAgentListPreferenceController;
import com.android.settings.widget.PreferenceCategoryController;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -55,6 +56,8 @@ public class SecuritySettings extends DashboardFragment {
@VisibleForTesting
static final String KEY_FACE_SETTINGS = "face_settings";
private static final String APP_LOCK_PREF_KEY = "app_lock";
@Override
public int getMetricsCategory() {
return SettingsEnums.SECURITY;
@@ -116,6 +119,8 @@ public class SecuritySettings extends DashboardFragment {
securityPreferenceControllers.add(new ChangeScreenLockPreferenceController(context, host));
controllers.add(new PreferenceCategoryController(context, SECURITY_CATEGORY)
.setChildren(securityPreferenceControllers));
controllers.add(new AppLockSettingsPreferenceController(
context, APP_LOCK_PREF_KEY, host, lifecycle));
controllers.addAll(securityPreferenceControllers);
return controllers;

View File

@@ -0,0 +1,76 @@
/*
* Copyright (C) 2022 FlamingoOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.security.applock
import android.app.AppLockManager
import android.content.Context
import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
private const val KEY = "app_lock_biometrics_allowed"
class AppLockBiometricPreferenceController(
context: Context,
private val coroutineScope: CoroutineScope
) : AppLockTogglePreferenceController(context, KEY) {
private val appLockManager = context.getSystemService(AppLockManager::class.java)
private val biometricManager = context.getSystemService(BiometricManager::class.java)
private var preference: Preference? = null
private var isBiometricsAllowed = false
init {
coroutineScope.launch {
isBiometricsAllowed = withContext(Dispatchers.Default) {
appLockManager.isBiometricsAllowed()
}
preference?.let {
updateState(it)
}
}
}
override fun getAvailabilityStatus(): Int {
val result = biometricManager.canAuthenticate(BIOMETRIC_STRONG)
return if (result == BiometricManager.BIOMETRIC_SUCCESS) AVAILABLE else CONDITIONALLY_UNAVAILABLE
}
override fun isChecked() = isBiometricsAllowed
override fun setChecked(checked: Boolean): Boolean {
if (isBiometricsAllowed == checked) return false
isBiometricsAllowed = checked
coroutineScope.launch(Dispatchers.Default) {
appLockManager.setBiometricsAllowed(isBiometricsAllowed)
}
return true
}
override fun displayPreference(screen: PreferenceScreen) {
super.displayPreference(screen)
preference = screen.findPreference(preferenceKey)
}
}

View File

@@ -0,0 +1,205 @@
/*
* Copyright (C) 2014 The Android Open Source Project
* Copyright (C) 2022 FlamingoOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.security.applock
import android.app.Activity
import android.app.AppLockManager
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.hardware.biometrics.BiometricConstants
import android.hardware.biometrics.BiometricManager.Authenticators
import android.hardware.biometrics.BiometricPrompt
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback
import android.hardware.biometrics.PromptInfo
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.UserHandle.USER_NULL
import android.os.UserManager
import android.util.Log
import android.view.WindowManager
import androidx.fragment.app.commit
import androidx.fragment.app.FragmentActivity
import com.android.internal.widget.LockPatternUtils
import com.android.settings.R
import com.android.settings.password.BiometricFragment
import com.android.settings.password.ConfirmDeviceCredentialUtils
class AppLockCredentialActivity : FragmentActivity() {
private val handler = Handler(Looper.getMainLooper())
private lateinit var lockPatternUtils: LockPatternUtils
private lateinit var userManager: UserManager
private lateinit var appLockManager: AppLockManager
private var packageName: String? = null
private var label: String? = null
private var userId: Int = USER_NULL
private var biometricFragment: BiometricFragment? = null
private var goingToBackground = false
private var waitingForBiometricCallback = false
private val authenticationCallback = object : AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
if (!goingToBackground) {
waitingForBiometricCallback = false
if (errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED
|| errorCode == BiometricPrompt.BIOMETRIC_ERROR_CANCELED) {
finish()
}
} else if (waitingForBiometricCallback) { // goingToBackground is true
waitingForBiometricCallback = false
finish()
}
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
waitingForBiometricCallback = false
appLockManager.unlockPackage(packageName)
ConfirmDeviceCredentialUtils.checkForPendingIntent(this@AppLockCredentialActivity)
setResult(Activity.RESULT_OK)
finish()
}
override fun onAuthenticationFailed() {
waitingForBiometricCallback = false
}
override fun onSystemEvent(event: Int) {
if (event == BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL) {
finish()
}
}
}
override protected fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.apply {
addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = Color.TRANSPARENT
}
appLockManager = getSystemService(AppLockManager::class.java)
userManager = UserManager.get(this)
lockPatternUtils = LockPatternUtils(this)
packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME)
if (packageName == null) {
Log.e(TAG, "Failed to get package name, aborting unlock")
finish()
return
}
label = intent.getStringExtra(AppLockManager.EXTRA_PACKAGE_LABEL)
userId = intent.getIntExtra(Intent.EXTRA_USER_ID, USER_NULL)
if (userId == USER_NULL) {
Log.e(TAG, "Invalid user id, aborting")
finish()
return
}
val biometricsAllowed = intent.getBooleanExtra(
AppLockManager.EXTRA_ALLOW_BIOMETRICS,
AppLockManager.DEFAULT_BIOMETRICS_ALLOWED
)
var allowedAuthenticators = Authenticators.DEVICE_CREDENTIAL
if (biometricsAllowed) {
allowedAuthenticators = allowedAuthenticators or Authenticators.BIOMETRIC_STRONG
}
val promptInfo = PromptInfo().apply {
title = getString(com.android.internal.R.string.unlock_application, label)
isDisallowBiometricsIfPolicyExists = true
authenticators = allowedAuthenticators
isAllowBackgroundAuthentication = true
}
if (isBiometricAllowed()) {
// Don't need to check if biometrics / pin/pattern/pass are enrolled. It will go to
// onAuthenticationError and do the right thing automatically.
showBiometricPrompt(promptInfo)
waitingForBiometricCallback = true
} else {
finish()
}
}
override protected fun onStart() {
super.onStart()
// Translucent activity that is "visible", so it doesn't complain about finish()
// not being called before onResume().
setVisible(true)
}
override fun onPause() {
super.onPause()
if (!isChangingConfigurations()) {
goingToBackground = true
if (!waitingForBiometricCallback) {
finish()
}
} else {
goingToBackground = false
}
}
// User could be locked while Effective user is unlocked even though the effective owns the
// credential. Otherwise, biometric can't unlock fbe/keystore through
// verifyTiedProfileChallenge. In such case, we also wanna show the user message that
// biometric is disabled due to device restart.
private fun isStrongAuthRequired() =
!lockPatternUtils.isBiometricAllowedForUser(userId) ||
!userManager.isUserUnlocked(userId)
private fun isBiometricAllowed() =
!isStrongAuthRequired() && !lockPatternUtils.hasPendingEscrowToken(userId)
private fun showBiometricPrompt(promptInfo: PromptInfo) {
biometricFragment = supportFragmentManager.findFragmentByTag(TAG_BIOMETRIC_FRAGMENT)
as? BiometricFragment
var newFragment = false
if (biometricFragment == null) {
biometricFragment = BiometricFragment.newInstance(promptInfo)
newFragment = true
}
biometricFragment?.also {
it.setCallbacks({
handler.post(it)
}, authenticationCallback)
it.setUser(userId)
}
if (newFragment) {
biometricFragment?.let {
supportFragmentManager.commit {
add(it, TAG_BIOMETRIC_FRAGMENT)
}
}
}
}
companion object {
private const val TAG = "AppLockCredentialActivity"
private const val TAG_BIOMETRIC_FRAGMENT = "fragment"
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2022 FlamingoOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.security.applock
import android.app.AppLockManager
import android.content.Context
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
private const val KEY = "hide_from_launcher"
class AppLockHideAppPC(
context: Context,
private val packageName: String,
private val coroutineScope: CoroutineScope
) : AppLockTogglePreferenceController(context, KEY) {
private val appLockManager = context.getSystemService(AppLockManager::class.java)
private var hideFromLauncher = AppLockManager.DEFAULT_HIDE_IN_LAUNCHER
private var preference: Preference? = null
init {
coroutineScope.launch {
hideFromLauncher = withContext(Dispatchers.Default) {
appLockManager.hiddenPackages.any { it == packageName }
}
preference?.let {
updateState(it)
}
}
}
override fun getAvailabilityStatus() = AVAILABLE
override fun isChecked() = hideFromLauncher
override fun setChecked(checked: Boolean): Boolean {
if (hideFromLauncher == checked) return false
hideFromLauncher = checked
coroutineScope.launch(Dispatchers.Default) {
appLockManager.setPackageHidden(packageName, hideFromLauncher)
}
return true
}
override fun displayPreference(screen: PreferenceScreen) {
super.displayPreference(screen)
preference = screen.findPreference(preferenceKey)
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2022 FlamingoOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.security.applock
import android.app.AppLockManager
import android.content.Context
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
private const val KEY = "redact_notifications"
class AppLockNotificationRedactionPC(
context: Context,
private val packageName: String,
private val coroutineScope: CoroutineScope
) : AppLockTogglePreferenceController(context, KEY) {
private val appLockManager = context.getSystemService(AppLockManager::class.java)
private var shouldRedactNotification = AppLockManager.DEFAULT_REDACT_NOTIFICATION
private var preference: Preference? = null
init {
coroutineScope.launch {
shouldRedactNotification = withContext(Dispatchers.Default) {
appLockManager.packageData.find {
it.packageName == packageName
}?.shouldRedactNotification == true
}
preference?.let {
updateState(it)
}
}
}
override fun getAvailabilityStatus() = AVAILABLE
override fun isChecked() = shouldRedactNotification
override fun setChecked(checked: Boolean): Boolean {
if (shouldRedactNotification == checked) return false
shouldRedactNotification = checked
coroutineScope.launch(Dispatchers.Default) {
appLockManager.setShouldRedactNotification(packageName, checked)
}
return true
}
override fun displayPreference(screen: PreferenceScreen) {
super.displayPreference(screen)
preference = screen.findPreference(preferenceKey)
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright (C) 2022 FlamingoOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.security.applock
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import com.android.internal.logging.nano.MetricsProto
import com.android.settings.R
import com.android.settings.dashboard.DashboardFragment
import com.android.settings.widget.EntityHeaderController
import com.android.settingslib.applications.ApplicationsState.AppEntry
import com.android.settingslib.core.AbstractPreferenceController
import com.android.settingslib.widget.LayoutPreference
private val TAG = AppLockPackageConfigFragment::class.simpleName
private const val KEY_HEADER = "header_view"
class AppLockPackageConfigFragment : DashboardFragment() {
private lateinit var packageInfo: PackageInfo
override fun onAttach(context: Context) {
packageInfo = arguments?.getParcelable(PACKAGE_INFO, PackageInfo::class.java)!!
super.onAttach(context)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
super.onCreatePreferences(savedInstanceState, rootKey)
val appEntry = AppEntry(requireContext(), packageInfo.applicationInfo, 0)
val header = preferenceScreen.findPreference<LayoutPreference>(KEY_HEADER)
EntityHeaderController.newInstance(
requireActivity(),
this,
header?.findViewById(R.id.entity_header)
).setRecyclerView(listView, settingsLifecycle)
.setPackageName(packageInfo.packageName)
.setButtonActions(
EntityHeaderController.ActionType.ACTION_NONE,
EntityHeaderController.ActionType.ACTION_NONE
)
.bindHeaderButtons()
.setLabel(appEntry)
.setIcon(appEntry)
.done(requireActivity(), false /* rebindActions */)
}
override protected fun createPreferenceControllers(
context: Context
) : List<AbstractPreferenceController> = listOf(
AppLockPackageProtectionPC(context, packageInfo.packageName, lifecycleScope),
AppLockNotificationRedactionPC(context, packageInfo.packageName, lifecycleScope),
AppLockHideAppPC(context, packageInfo.packageName, lifecycleScope)
)
override fun getMetricsCategory(): Int = MetricsProto.MetricsEvent.EVOLVER
override protected fun getPreferenceScreenResId() = R.xml.app_lock_package_config_settings
override protected fun getLogTag() = TAG
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright (C) 2022 FlamingoOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.security.applock
import android.app.AppLockManager
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.PackageInfoFlags
import android.os.Bundle
import android.view.View
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.forEach
import com.android.internal.logging.nano.MetricsProto
import com.android.settings.R
import com.android.settings.core.SubSettingLauncher
import com.android.settings.dashboard.DashboardFragment
import com.android.settingslib.PrimarySwitchPreference
import com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_SMALL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
private val TAG = AppLockPackageListFragment::class.simpleName
internal const val PACKAGE_INFO = "package_info"
class AppLockPackageListFragment : DashboardFragment() {
private lateinit var appLockManager: AppLockManager
private lateinit var pm: PackageManager
private lateinit var whiteListedPackages: Array<String>
override fun onAttach(context: Context) {
super.onAttach(context)
appLockManager = context.getSystemService(AppLockManager::class.java)
pm = context.packageManager
whiteListedPackages = resources.getStringArray(
com.android.internal.R.array.config_appLockAllowedSystemApps)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
super.onCreatePreferences(savedInstanceState, rootKey)
lifecycleScope.launch {
val selectedPackages = getSelectedPackages()
val preferences = withContext(Dispatchers.Default) {
pm.getInstalledPackages(
PackageInfoFlags.of(PackageManager.MATCH_ALL.toLong())
).filter {
!it.applicationInfo.isSystemApp() || whiteListedPackages.contains(it.packageName)
}.sortedWith { first, second ->
getLabel(first).compareTo(getLabel(second))
}
}.map { packageInfo ->
createPreference(packageInfo, selectedPackages.contains(packageInfo.packageName))
}
preferenceScreen?.let {
preferences.forEach { pref ->
it.addPreference(pref)
}
}
}
}
override fun onResume() {
super.onResume()
lifecycleScope.launch {
val selectedPackages = getSelectedPackages()
preferenceScreen?.forEach {
if (it is PrimarySwitchPreference) {
it.isChecked = selectedPackages.contains(it.key)
}
}
}
}
private suspend fun getSelectedPackages(): Set<String> {
return withContext(Dispatchers.IO) {
appLockManager.packageData.map { it.packageName }.toSet()
}
}
private fun getLabel(packageInfo: PackageInfo) =
packageInfo.applicationInfo.loadLabel(pm).toString()
private fun createPreference(packageInfo: PackageInfo, isProtected: Boolean): Preference {
val label = getLabel(packageInfo)
return PrimarySwitchPreference(requireContext()).apply {
key = packageInfo.packageName
title = label
icon = packageInfo.applicationInfo.loadIcon(pm)
setIconSize(ICON_SIZE_SMALL)
isChecked = isProtected
setOnPreferenceChangeListener { _, newValue ->
lifecycleScope.launch(Dispatchers.IO) {
if (newValue as Boolean) {
appLockManager.addPackage(packageInfo.packageName)
} else {
appLockManager.removePackage(packageInfo.packageName)
}
}
return@setOnPreferenceChangeListener true
}
setOnPreferenceClickListener {
SubSettingLauncher(requireContext())
.setDestination(AppLockPackageConfigFragment::class.qualifiedName)
.setSourceMetricsCategory(metricsCategory)
.setTitleText(label)
.setArguments(
Bundle(1).apply {
putParcelable(PACKAGE_INFO, packageInfo)
}
)
.launch()
true
}
}
}
override fun getMetricsCategory(): Int = MetricsProto.MetricsEvent.EVOLVER
override protected fun getPreferenceScreenResId() = R.xml.app_lock_package_list_settings
override protected fun getLogTag() = TAG
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2022 FlamingoOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.security.applock
import android.app.AppLockManager
import android.content.Context
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
private const val KEY = "main_switch"
class AppLockPackageProtectionPC(
context: Context,
private val packageName: String,
private val coroutineScope: CoroutineScope
) : AppLockTogglePreferenceController(context, KEY) {
private val appLockManager = context.getSystemService(AppLockManager::class.java)
private var isProtected = false
private var preference: Preference? = null
init {
coroutineScope.launch {
isProtected = withContext(Dispatchers.Default) {
appLockManager.packageData.any {
it.packageName == packageName
}
}
preference?.let {
updateState(it)
}
}
}
override fun getAvailabilityStatus() = AVAILABLE
override fun isChecked() = isProtected
override fun setChecked(checked: Boolean): Boolean {
if (isProtected == checked) return false
isProtected = checked
coroutineScope.launch(Dispatchers.Default) {
if (isProtected) {
appLockManager.addPackage(packageName)
} else {
appLockManager.removePackage(packageName)
}
}
return true
}
override fun displayPreference(screen: PreferenceScreen) {
super.displayPreference(screen)
preference = screen.findPreference(preferenceKey)
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2022 FlamingoOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.security.applock
import android.content.Context
import android.os.SystemProperties
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import com.android.internal.logging.nano.MetricsProto
import androidx.lifecycle.lifecycleScope
import com.android.settings.R
import com.android.settings.dashboard.DashboardFragment
import com.android.settings.search.BaseSearchIndexProvider
import com.android.settingslib.core.AbstractPreferenceController
import com.android.settingslib.search.SearchIndexable
@SearchIndexable
class AppLockSettingsFragment : DashboardFragment(),
MenuItem.OnMenuItemClickListener {
private var debugEnabled = SystemProperties.get(DEBUG_PROPERTY, null) == LEVEL_DEBUG
private var handledClick = false
override protected fun getPreferenceScreenResId() = R.xml.app_lock_settings
override fun getMetricsCategory() = MetricsProto.MetricsEvent.EVOLVER
override protected fun getLogTag() = TAG
override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) {
super.onCreateOptionsMenu(menu, menuInflater)
menu.add(
0 /* groupId */,
MENU_ITEM_DEBUG_ID,
0 /* order */,
getDebugMenuItemTitle(),
).setOnMenuItemClickListener(this)
}
private fun getDebugMenuItemTitle(): Int =
if (debugEnabled) R.string.disable_debugging else R.string.enable_debugging
override fun onMenuItemClick(item: MenuItem): Boolean {
if (item.itemId == MENU_ITEM_DEBUG_ID) {
debugEnabled = !debugEnabled
SystemProperties.set(DEBUG_PROPERTY, if (debugEnabled) LEVEL_DEBUG else null)
item.setTitle(getDebugMenuItemTitle())
return true
}
return false
}
override protected fun createPreferenceControllers(
context: Context
) : List<AbstractPreferenceController> = listOf(
AppLockBiometricPreferenceController(context, lifecycleScope)
)
companion object {
private const val TAG = "AppLockSettingsFragment"
private const val DEBUG_PROPERTY = "log.tag.AppLockManagerService"
private const val LEVEL_DEBUG = "DEBUG"
private const val MENU_ITEM_DEBUG_ID = 101
@JvmField
val SEARCH_INDEX_DATA_PROVIDER = BaseSearchIndexProvider(R.xml.app_lock_settings)
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright (C) 2022 FlamingoOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.security.applock
import android.app.Activity
import android.app.AppLockManager
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.os.UserHandle
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.lifecycle.Lifecycle.Event
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import com.android.internal.widget.LockPatternUtils
import com.android.settings.R
import com.android.settings.Utils.SETTINGS_PACKAGE_NAME
import com.android.settings.core.SubSettingLauncher
import com.android.settings.password.ConfirmDeviceCredentialActivity
import com.android.settings.security.SecuritySettings
import com.android.settingslib.core.lifecycle.Lifecycle
import com.android.settingslib.transition.SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE
import com.android.settings.core.BasePreferenceController
import com.android.settings.SettingsActivity
import com.android.settings.core.SettingsBaseActivity
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider
class AppLockSettingsPreferenceController(
context: Context,
preferenceKey: String,
private val host: SecuritySettings?,
lifecycle: Lifecycle?,
) : BasePreferenceController(context, preferenceKey),
LifecycleEventObserver {
private val lockPatternUtils = LockPatternUtils(context)
private val appLockManager = context.getSystemService(AppLockManager::class.java)
private var preference: Preference? = null
private val securityPromptLauncher: ActivityResultLauncher<Intent>?
init {
lifecycle?.addObserver(this)
securityPromptLauncher = host?.registerForActivityResult(
StartActivityForResult()
) {
if (it?.resultCode == Activity.RESULT_OK) {
val intent = SubSettingLauncher(mContext)
.setDestination(AppLockSettingsFragment::class.qualifiedName)
.setSourceMetricsCategory(host.metricsCategory)
.setTransitionType(TRANSITION_SLIDE)
.toIntent()
intent.setClass(mContext, AppLockSubSettings::class.java)
mContext.startActivity(intent)
}
}
}
override fun getAvailabilityStatus() =
if (lockPatternUtils.isSecure(UserHandle.myUserId()))
AVAILABLE
else
DISABLED_DEPENDENT_SETTING
override fun onStateChanged(owner: LifecycleOwner, event: Event) {
if (event == Event.ON_START) {
preference?.let {
updateState(it)
}
}
}
override fun displayPreference(screen: PreferenceScreen) {
super.displayPreference(screen)
preference = screen.findPreference(preferenceKey)
}
override fun updateState(preference: Preference) {
preference.apply {
if (getAvailabilityStatus() == AVAILABLE) {
setEnabled(true)
summary = getSummaryForListSize(appLockManager.packageData.size)
} else {
setEnabled(false)
summary = mContext.getString(R.string.disabled_because_no_backup_security)
}
}
}
private fun getSummaryForListSize(size: Int): CharSequence? =
if (size == 0) {
null
} else {
mContext.resources.getQuantityString(R.plurals.app_lock_summary, size, size)
}
override fun handlePreferenceTreeClick(preference: Preference): Boolean {
if (preference.key == preferenceKey && securityPromptLauncher != null) {
val title = mContext.getString(R.string.app_lock_authentication_dialog_title)
val intent = Intent().apply {
setClassName(SETTINGS_PACKAGE_NAME,
ConfirmDeviceCredentialActivity::class.qualifiedName)
putExtra(KeyguardManager.EXTRA_TITLE, title)
}
securityPromptLauncher.launch(intent)
return true
}
return super.handlePreferenceTreeClick(preference)
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2022 FlamingoOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.security.applock
import com.android.settings.SettingsActivity
class AppLockSubSettings : SettingsActivity() {
override protected fun isValidFragment(fragmentName: String): Boolean {
return AppLockSettingsFragment::class.qualifiedName == fragmentName
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2022 FlamingoOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.security.applock
import android.app.AppLockManager
import android.content.Context
import androidx.preference.ListPreference
import androidx.preference.Preference
import com.android.settings.core.BasePreferenceController
class AppLockTimeoutPreferenceController(
context: Context,
key: String,
) : BasePreferenceController(context, key),
Preference.OnPreferenceChangeListener {
private val appLockManager = context.getSystemService(AppLockManager::class.java)
override fun getAvailabilityStatus() = AVAILABLE
override fun updateState(preference: Preference) {
(preference as ListPreference).value = appLockManager.timeout.takeIf {
it != -1L
}?.toString()
}
override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean {
appLockManager.timeout = (newValue as String).toLong()
return true
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2022 FlamingoOS Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.security.applock;
import android.content.Context
import android.widget.Switch
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import com.android.settings.R
import com.android.settings.core.TogglePreferenceController
import com.android.settingslib.widget.MainSwitchPreference
import com.android.settingslib.widget.OnMainSwitchChangeListener
abstract class AppLockTogglePreferenceController(
context: Context,
key: String,
) : TogglePreferenceController(context, key),
OnMainSwitchChangeListener {
override fun displayPreference(screen: PreferenceScreen) {
super.displayPreference(screen)
val preference = screen.findPreference<Preference>(preferenceKey) ?: return
if (preference is MainSwitchPreference) {
preference.addOnSwitchChangeListener(this)
}
}
override fun onSwitchChanged(switchView: Switch, isChecked: Boolean) {
setChecked(isChecked)
}
override fun getSliceHighlightMenuRes() = R.string.menu_key_security
}