blob: 48380e5d1041acb45cf6c714f2ddbd1491587f11 [file]
/*
* Copyright (C) 2024 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.security
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.content.pm.PackageManager
import android.security.advancedprotection.AdvancedProtectionManager
import android.security.advancedprotection.AdvancedProtectionManager.EXTRA_SUPPORT_DIALOG_FEATURE
import android.security.advancedprotection.AdvancedProtectionManager.EXTRA_SUPPORT_DIALOG_TYPE
import android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_CELLULAR_2G
import android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_INSECURE_WIFI_AUTOJOIN
import android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES
import android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_ENABLE_MTE
import android.security.advancedprotection.AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION
import android.security.advancedprotection.AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_DISABLED_SETTING
import android.security.advancedprotection.AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_UNKNOWN
import android.util.Log
import android.view.WindowManager
import androidx.annotation.VisibleForTesting
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import com.android.settings.R
import com.android.settingslib.spa.SpaDialogWindowTypeActivity
import com.android.settingslib.spa.widget.dialog.AlertDialogButton
import com.android.settingslib.spa.widget.dialog.SettingsAlertDialogContent
import com.android.settingslib.wifi.WifiUtils.Companion.DIALOG_WINDOW_TYPE
class ActionDisabledByAdvancedProtectionDialog : SpaDialogWindowTypeActivity() {
@Composable
override fun Content() {
val featureId = getIntentFeatureId()
val supportButton = getSupportButtonIfExists()
val configuration = androidx.compose.ui.platform.LocalConfiguration.current
if (featureId == FEATURE_ID_DISALLOW_INSECURE_WIFI_AUTOJOIN) {
// Remove shield icon if very large font in landscape mode
val dialogIcon: @Composable (() -> Unit)? =
if (shouldShowIcon(configuration)) {
{
Icon(
painter = painterResource(R.drawable.ic_settings_safety_center),
contentDescription = null,
)
}
} else null
// Custom dialog for Wi-Fi Autojoin
SettingsAlertDialogContent(
confirmButton =
AlertDialogButton(getString(R.string.wifi_autojoin_disabled_positive_button)) {
finish()
logDialogShown(learnMoreClicked = false)
},
dismissButton =
AlertDialogButton(getString(R.string.wifi_autojoin_disabled_negative_button)) {
setResult(RESULT_OK)
finish()
logDialogShown(learnMoreClicked = false)
},
title = getString(R.string.wifi_autojoin_disabled_title),
icon = dialogIcon,
text = { WifiAutojoinDisabledText(supportButton?.onClick) },
)
} else {
// Default dialog for all other features
SettingsAlertDialogContent(
confirmButton =
AlertDialogButton(getString(R.string.okay)) {
finish()
logDialogShown(learnMoreClicked = false)
},
dismissButton =
supportButton?.let { button ->
AlertDialogButton(button.text) {
button.onClick()
finish()
}
},
title = getString(R.string.disabled_by_advanced_protection_title),
icon = {
Icon(
painter = painterResource(R.drawable.ic_settings_safety_center),
contentDescription = null,
)
},
text = { Text(getDialogMessage()) },
)
}
}
/**
* Creates a composable body text. If a valid onClickAction is provided, it appends a clickable
* "Learn more" link that executes that action.
*/
@Composable
private fun WifiAutojoinDisabledText(onClickAction: (() -> Unit)?) {
val annotatedText = buildAnnotatedString {
append(getString(R.string.wifi_autojoin_disabled_body))
if (onClickAction != null) {
append(" ")
pushLink(
LinkAnnotation.Clickable(
tag = "LEARN_MORE",
linkInteractionListener = { _ -> onClickAction() },
)
)
withStyle(
style =
SpanStyle(
color = MaterialTheme.colorScheme.primary,
textDecoration = TextDecoration.Underline,
)
) {
append(getString(R.string.learn_more))
}
pop()
}
}
Text(
text = annotatedText,
style =
MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant
),
)
}
private fun getDialogMessage(): String {
val featureId = getIntentFeatureId()
val type = getIntentDialogueType()
val messageId =
when (type) {
SUPPORT_DIALOG_TYPE_DISABLED_SETTING -> {
if (featureIdsWithSettingOn.contains(featureId)) {
R.string.disabled_by_advanced_protection_setting_is_on_message
} else if (featureIdsWithSettingOff.contains(featureId)) {
R.string.disabled_by_advanced_protection_setting_is_off_message
} else {
defaultMessageId
}
}
SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION -> {
R.string.disabled_by_advanced_protection_action_message
}
else -> defaultMessageId
}
return getString(messageId)
}
@VisibleForTesting
fun getSupportButtonIfExists(): AlertDialogButton? {
try {
val helpIntentUri =
getString(
com.android.internal.R.string
.config_help_url_action_disabled_by_advanced_protection
)
val helpIntent = Intent.parseUri(helpIntentUri, Intent.URI_INTENT_SCHEME)
if (helpIntent == null) return null
helpIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val helpActivityInfo =
packageManager.resolveActivity(helpIntent, /* flags */ 0)?.activityInfo
if (helpActivityInfo == null) return null
return AlertDialogButton(
getString(R.string.disabled_by_advanced_protection_help_button_title)
) {
startActivity(helpIntent)
logDialogShown(learnMoreClicked = true)
}
} catch (e: Exception) {
Log.w(TAG, "Tried to set up help button, but this exception was thrown: ${e.message}")
}
return null
}
private fun logDialogShown(learnMoreClicked: Boolean) {
// We should always have this permission, but just in case we don't, we should not log.
if (
checkSelfPermission(android.Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE) !=
PackageManager.PERMISSION_GRANTED
) {
return
}
this.getSystemService(AdvancedProtectionManager::class.java)
.logDialogShown(getIntentFeatureId(), getIntentDialogueType(), learnMoreClicked)
}
override fun getDialogWindowType(): Int? =
if (intent.hasExtra(DIALOG_WINDOW_TYPE)) {
intent.getIntExtra(DIALOG_WINDOW_TYPE, WindowManager.LayoutParams.TYPE_APPLICATION)
} else null
private fun getIntentFeatureId(): Int {
return intent.getIntExtra(EXTRA_SUPPORT_DIALOG_FEATURE, -1)
}
private fun getIntentDialogueType(): Int {
return intent.getIntExtra(EXTRA_SUPPORT_DIALOG_TYPE, SUPPORT_DIALOG_TYPE_UNKNOWN)
}
/** Determines if the icon should be shown based on screen real estate. */
@VisibleForTesting
fun shouldShowIcon(configuration: android.content.res.Configuration): Boolean {
val isLandscape =
configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE
if (!isLandscape) return true
val isFontTooLarge = configuration.fontScale > A11Y_FONT_SCALE_THRESHOLD
val isScreenTooShort = configuration.screenHeightDp < MIN_HEIGHT_FOR_ICON_DP
return !(isFontTooLarge || isScreenTooShort)
}
private companion object {
const val TAG = "AdvancedProtectionDlg"
const val A11Y_FONT_SCALE_THRESHOLD = 1.1f
const val MIN_HEIGHT_FOR_ICON_DP = 400
val defaultMessageId = R.string.disabled_by_advanced_protection_action_message
val featureIdsWithSettingOn = setOf(FEATURE_ID_DISALLOW_CELLULAR_2G, FEATURE_ID_ENABLE_MTE)
val featureIdsWithSettingOff = setOf(FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES)
}
}