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