1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.permissioncontroller.safetycenter.ui
18 
19 import android.content.Context
20 import android.os.Build
21 import android.os.UserHandle
22 import android.os.UserManager
23 import android.safetycenter.SafetyCenterEntry
24 import android.safetycenter.SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR
25 import android.text.TextUtils
26 import android.util.Log
27 import android.widget.ImageView
28 import android.widget.TextView
29 import androidx.annotation.RequiresApi
30 import androidx.preference.Preference
31 import androidx.preference.PreferenceViewHolder
32 import com.android.modules.utils.build.SdkLevel
33 import com.android.permissioncontroller.R
34 import com.android.permissioncontroller.safetycenter.SafetyCenterConstants.PERSONAL_PROFILE_SUFFIX
35 import com.android.permissioncontroller.safetycenter.SafetyCenterConstants.PRIVATE_PROFILE_SUFFIX
36 import com.android.permissioncontroller.safetycenter.SafetyCenterConstants.WORK_PROFILE_SUFFIX
37 import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel
38 import com.android.permissioncontroller.safetycenter.ui.view.SafetyEntryCommonViewsManager.Companion.changeEnabledState
39 import com.android.safetycenter.internaldata.SafetyCenterEntryId
40 import com.android.safetycenter.internaldata.SafetyCenterIds
41 import com.android.settingslib.widget.TwoTargetPreference
42 
43 /**
44  * A preference that displays a visual representation of a {@link SafetyCenterEntry} on the Safety
45  * Center subpage.
46  */
47 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
48 class SafetySubpageEntryPreference(
49     context: Context,
50     private val launchTaskId: Int?,
51     private val entry: SafetyCenterEntry,
52     private val viewModel: SafetyCenterViewModel
53 ) : TwoTargetPreference(context), ComparablePreference {
54 
55     init {
56         setupIconActionButton()
57         setupClickListener()
58         setTitle(entry.title)
59         setSummary(entry.summary)
60         setSelectable(true)
61         setupPreferenceKey()
62     }
63 
setupIconActionButtonnull64     private fun setupIconActionButton() {
65         if (entry.iconAction != null) {
66             setIconSize(ICON_SIZE_MEDIUM)
67             setWidgetLayoutResource(
68                 if (entry.iconAction!!.type == ICON_ACTION_TYPE_GEAR) {
69                     R.layout.preference_entry_icon_action_gear_widget
70                 } else {
71                     R.layout.preference_entry_icon_action_info_widget
72                 }
73             )
74         }
75     }
76 
setupClickListenernull77     private fun setupClickListener() {
78         val pendingIntent = entry.pendingIntent
79         if (pendingIntent != null) {
80             setOnPreferenceClickListener {
81                 try {
82                     PendingIntentSender.send(pendingIntent, launchTaskId)
83                     viewModel.interactionLogger.recordForEntry(Action.ENTRY_CLICKED, entry)
84                     true
85                 } catch (ex: Exception) {
86                     Log.e(TAG, "Failed to execute pending intent for $entry", ex)
87                     false
88                 }
89             }
90             setEnabled(true)
91         } else {
92             Log.w(TAG, "Pending intent is null for $entry")
93             setEnabled(false)
94         }
95     }
96 
setupPreferenceKeynull97     private fun setupPreferenceKey() {
98         val entryId: SafetyCenterEntryId = SafetyCenterIds.entryIdFromString(entry.id)
99         val userContext = context.createContextAsUser(UserHandle.of(entryId.userId), /* flags= */ 0)
100         val userUserManager = userContext.getSystemService(UserManager::class.java) ?: return
101         if (userUserManager.isManagedProfile) {
102             setKey("${entryId.safetySourceId}_$WORK_PROFILE_SUFFIX")
103         } else if (isPrivateProfileSupported() && userUserManager.isPrivateProfile) {
104             setKey("${entryId.safetySourceId}_$PRIVATE_PROFILE_SUFFIX")
105         } else {
106             setKey("${entryId.safetySourceId}_$PERSONAL_PROFILE_SUFFIX")
107         }
108     }
109 
isPrivateProfileSupportednull110     private fun isPrivateProfileSupported(): Boolean {
111         return SdkLevel.isAtLeastV() &&
112             com.android.permission.flags.Flags.privateProfileSupported() &&
113             android.os.Flags.allowPrivateProfile()
114     }
115 
onBindViewHoldernull116     override fun onBindViewHolder(holder: PreferenceViewHolder) {
117         super.onBindViewHolder(holder)
118         val iconAction = entry.iconAction
119         if (iconAction == null) {
120             Log.w(TAG, "Icon action is null for $entry")
121         } else {
122             val iconActionButton = holder.findViewById(R.id.icon_action_button) as? ImageView?
123             iconActionButton?.setOnClickListener {
124                 try {
125                     PendingIntentSender.send(iconAction.pendingIntent, launchTaskId)
126                     viewModel.interactionLogger.recordForEntry(
127                         Action.ENTRY_ICON_ACTION_CLICKED,
128                         entry
129                     )
130                 } catch (ex: Exception) {
131                     Log.e(TAG, "Failed to execute icon action intent for $entry", ex)
132                 }
133             }
134         }
135 
136         val titleView = holder.findViewById(android.R.id.title) as? TextView?
137         val summaryView = holder.findViewById(android.R.id.summary) as? TextView?
138         changeEnabledState(context, entry.isEnabled, isEnabled(), titleView, summaryView)
139     }
140 
shouldHideSecondTargetnull141     override fun shouldHideSecondTarget(): Boolean = entry.iconAction == null
142 
143     override fun isSameItem(preference: Preference): Boolean =
144         preference is SafetySubpageEntryPreference &&
145             TextUtils.equals(entry.id, preference.entry.id)
146 
147     override fun hasSameContents(preference: Preference): Boolean =
148         preference is SafetySubpageEntryPreference && entry == preference.entry
149 
150     companion object {
151         private val TAG: String = SafetySubpageEntryPreference::class.java.simpleName
152     }
153 }
154