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 static android.os.Build.VERSION_CODES.TIRAMISU; 20 21 import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID; 22 import static com.android.permissioncontroller.safetycenter.SafetyCenterConstants.QUICK_SETTINGS_SAFETY_CENTER_FRAGMENT; 23 24 import static java.util.Collections.emptyList; 25 import static java.util.Objects.requireNonNull; 26 27 import android.content.Context; 28 import android.content.Intent; 29 import android.graphics.Color; 30 import android.graphics.drawable.ColorDrawable; 31 import android.graphics.drawable.Drawable; 32 import android.os.Bundle; 33 import android.safetycenter.SafetyCenterData; 34 import android.safetycenter.SafetyCenterEntry; 35 import android.safetycenter.SafetyCenterEntryGroup; 36 import android.safetycenter.SafetyCenterEntryOrGroup; 37 import android.safetycenter.SafetyCenterIssue; 38 import android.safetycenter.SafetyCenterStaticEntry; 39 import android.safetycenter.SafetyCenterStaticEntryGroup; 40 import android.util.Log; 41 42 import androidx.annotation.Nullable; 43 import androidx.annotation.RequiresApi; 44 import androidx.preference.Preference; 45 import androidx.preference.PreferenceCategory; 46 import androidx.preference.PreferenceGroup; 47 48 import com.android.permissioncontroller.R; 49 import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData; 50 import com.android.permissioncontroller.safetycenter.ui.model.StatusUiData; 51 import com.android.safetycenter.internaldata.SafetyCenterBundles; 52 import com.android.safetycenter.resources.SafetyCenterResourcesContext; 53 54 import kotlin.Unit; 55 56 import java.util.List; 57 import java.util.Map; 58 59 /** Dashboard fragment for the Safety Center. */ 60 @RequiresApi(TIRAMISU) 61 public final class SafetyCenterDashboardFragment extends SafetyCenterFragment { 62 63 private static final String TAG = SafetyCenterDashboardFragment.class.getSimpleName(); 64 65 private static final String SAFETY_STATUS_KEY = "safety_status"; 66 private static final String ISSUES_GROUP_KEY = "issues_group"; 67 private static final String ENTRIES_GROUP_KEY = "entries_group"; 68 private static final String STATIC_ENTRIES_GROUP_KEY = "static_entries_group"; 69 private static final String SPACER_KEY = "spacer"; 70 71 private SafetyStatusPreference mSafetyStatusPreference; 72 private final CollapsableGroupCardHelper mCollapsableGroupCardHelper = 73 new CollapsableGroupCardHelper(); 74 private PreferenceGroup mIssuesGroup; 75 private PreferenceGroup mEntriesGroup; 76 private PreferenceGroup mStaticEntriesGroup; 77 private boolean mIsQuickSettingsFragment; 78 SafetyCenterDashboardFragment()79 public SafetyCenterDashboardFragment() {} 80 81 /** 82 * Create instance of SafetyCenterDashboardFragment with the arguments set 83 * 84 * @param isQuickSettingsFragment Denoting if it is the quick settings fragment 85 * @return SafetyCenterDashboardFragment with the arguments set 86 */ newInstance( long sessionId, boolean isQuickSettingsFragment)87 public static SafetyCenterDashboardFragment newInstance( 88 long sessionId, boolean isQuickSettingsFragment) { 89 Bundle args = new Bundle(); 90 args.putLong(EXTRA_SESSION_ID, sessionId); 91 args.putBoolean(QUICK_SETTINGS_SAFETY_CENTER_FRAGMENT, isQuickSettingsFragment); 92 93 SafetyCenterDashboardFragment frag = new SafetyCenterDashboardFragment(); 94 frag.setArguments(args); 95 return frag; 96 } 97 98 @Override onCreatePreferences(Bundle savedInstanceState, String rootKey)99 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 100 super.onCreatePreferences(savedInstanceState, rootKey); 101 setPreferencesFromResource(R.xml.safety_center_dashboard, rootKey); 102 103 if (getArguments() != null) { 104 mIsQuickSettingsFragment = 105 getArguments().getBoolean(QUICK_SETTINGS_SAFETY_CENTER_FRAGMENT, false); 106 } 107 mCollapsableGroupCardHelper.restoreState(savedInstanceState); 108 109 mSafetyStatusPreference = 110 requireNonNull(getPreferenceScreen().findPreference(SAFETY_STATUS_KEY)); 111 mSafetyStatusPreference.setViewModel(getSafetyCenterViewModel()); 112 113 mIssuesGroup = getPreferenceScreen().findPreference(ISSUES_GROUP_KEY); 114 mEntriesGroup = getPreferenceScreen().findPreference(ENTRIES_GROUP_KEY); 115 mStaticEntriesGroup = getPreferenceScreen().findPreference(STATIC_ENTRIES_GROUP_KEY); 116 if (mIsQuickSettingsFragment) { 117 getPreferenceScreen().removePreference(mEntriesGroup); 118 mEntriesGroup = null; 119 getPreferenceScreen().removePreference(mStaticEntriesGroup); 120 mStaticEntriesGroup = null; 121 Preference spacerPreference = getPreferenceScreen().findPreference(SPACER_KEY); 122 getPreferenceScreen().removePreference(spacerPreference); 123 } 124 getSafetyCenterViewModel().getStatusUiLiveData().observe(this, this::updateStatus); 125 126 prerenderCurrentSafetyCenterData(); 127 } 128 129 // Set the default divider line between preferences to be transparent 130 @Override setDivider(Drawable divider)131 public void setDivider(Drawable divider) { 132 super.setDivider(new ColorDrawable(Color.TRANSPARENT)); 133 } 134 135 @Override setDividerHeight(int height)136 public void setDividerHeight(int height) { 137 super.setDividerHeight(0); 138 } 139 140 @Override onResume()141 public void onResume() { 142 super.onResume(); 143 getSafetyCenterViewModel().pageOpen(); 144 } 145 146 @Override configureInteractionLogger()147 public void configureInteractionLogger() { 148 InteractionLogger logger = getSafetyCenterViewModel().getInteractionLogger(); 149 logger.setSessionId(getSafetyCenterSessionId()); 150 logger.setViewType(mIsQuickSettingsFragment ? ViewType.QUICK_SETTINGS : ViewType.FULL); 151 152 Intent intent = requireActivity().getIntent(); 153 logger.setNavigationSource(NavigationSource.fromIntent(intent)); 154 logger.setNavigationSensor(Sensor.fromIntent(intent)); 155 } 156 157 @Override onSaveInstanceState(Bundle outState)158 public void onSaveInstanceState(Bundle outState) { 159 super.onSaveInstanceState(outState); 160 mCollapsableGroupCardHelper.saveState(outState); 161 } 162 updateStatus(StatusUiData statusUiData)163 private void updateStatus(StatusUiData statusUiData) { 164 if (mIsQuickSettingsFragment) { 165 SafetyCenterResourcesContext safetyCenterResourcesContext = 166 new SafetyCenterResourcesContext(requireContext()); 167 boolean hasPendingActions = 168 safetyCenterResourcesContext 169 .getStringByName("overall_severity_level_ok_review_summary") 170 .equals(statusUiData.getOriginalSummary().toString()); 171 172 statusUiData = statusUiData.copyForPendingActions(hasPendingActions); 173 } 174 175 mSafetyStatusPreference.setData(statusUiData); 176 } 177 178 @Override renderSafetyCenterData(@ullable SafetyCenterUiData uiData)179 public void renderSafetyCenterData(@Nullable SafetyCenterUiData uiData) { 180 if (uiData == null) return; 181 SafetyCenterData data = uiData.getSafetyCenterData(); 182 183 Log.i(TAG, String.format("renderSafetyCenterData called with: %s", data)); 184 185 Context context = getContext(); 186 if (context == null) { 187 return; 188 } 189 190 // TODO(b/208212820): Only update entries that have changed since last 191 // update, rather than deleting and re-adding all. 192 updateIssues(context, data.getIssues(), uiData.getResolvedIssues()); 193 194 if (!mIsQuickSettingsFragment) { 195 updateSafetyEntries(context, data.getEntriesOrGroups()); 196 updateStaticSafetyEntries(context, data); 197 } 198 } 199 updateIssues( Context context, List<SafetyCenterIssue> issues, Map<String, String> resolvedIssues)200 private void updateIssues( 201 Context context, List<SafetyCenterIssue> issues, Map<String, String> resolvedIssues) { 202 mIssuesGroup.removeAll(); 203 getCollapsableIssuesCardHelper() 204 .addIssues( 205 context, 206 getSafetyCenterViewModel(), 207 getChildFragmentManager(), 208 mIssuesGroup, 209 issues, 210 emptyList(), 211 resolvedIssues, 212 getActivity().getTaskId()); 213 } 214 215 // TODO(b/208212820): Add groups and move to separate controller updateSafetyEntries( Context context, List<SafetyCenterEntryOrGroup> entriesOrGroups)216 private void updateSafetyEntries( 217 Context context, List<SafetyCenterEntryOrGroup> entriesOrGroups) { 218 mEntriesGroup.removeAll(); 219 220 for (int i = 0, size = entriesOrGroups.size(); i < size; i++) { 221 SafetyCenterEntryOrGroup entryOrGroup = entriesOrGroups.get(i); 222 SafetyCenterEntry entry = entryOrGroup.getEntry(); 223 SafetyCenterEntryGroup group = entryOrGroup.getEntryGroup(); 224 225 boolean isFirstElement = i == 0; 226 boolean isLastElement = i == size - 1; 227 228 if (SafetyCenterUiFlags.getShowSubpages() && group != null) { 229 mEntriesGroup.addPreference( 230 new SafetyHomepageEntryPreference( 231 context, group, getSafetyCenterSessionId())); 232 } else if (entry != null) { 233 addTopLevelEntry(context, entry, isFirstElement, isLastElement); 234 } else if (group != null) { 235 addGroupEntries(context, group, isFirstElement, isLastElement); 236 } 237 } 238 } 239 addTopLevelEntry( Context context, SafetyCenterEntry entry, boolean isFirstElement, boolean isLastElement)240 private void addTopLevelEntry( 241 Context context, 242 SafetyCenterEntry entry, 243 boolean isFirstElement, 244 boolean isLastElement) { 245 mEntriesGroup.addPreference( 246 new SafetyEntryPreference( 247 context, 248 PendingIntentSender.getTaskIdForEntry( 249 entry.getId(), getSameTaskSourceIds(), requireActivity()), 250 entry, 251 PositionInCardList.calculate(isFirstElement, isLastElement), 252 getSafetyCenterViewModel())); 253 } 254 addGroupEntries( Context context, SafetyCenterEntryGroup group, boolean isFirstCard, boolean isLastCard)255 private void addGroupEntries( 256 Context context, 257 SafetyCenterEntryGroup group, 258 boolean isFirstCard, 259 boolean isLastCard) { 260 mEntriesGroup.addPreference( 261 new SafetyGroupPreference( 262 context, 263 group, 264 mCollapsableGroupCardHelper::isGroupExpanded, 265 isFirstCard, 266 isLastCard, 267 (entryId) -> 268 PendingIntentSender.getTaskIdForEntry( 269 entryId, getSameTaskSourceIds(), requireActivity()), 270 getSafetyCenterViewModel(), 271 (groupId) -> { 272 mCollapsableGroupCardHelper.onGroupExpanded(groupId); 273 return Unit.INSTANCE; 274 }, 275 (groupId) -> { 276 mCollapsableGroupCardHelper.onGroupCollapsed(groupId); 277 return Unit.INSTANCE; 278 })); 279 } 280 updateStaticSafetyEntries(Context context, SafetyCenterData data)281 private void updateStaticSafetyEntries(Context context, SafetyCenterData data) { 282 mStaticEntriesGroup.removeAll(); 283 284 for (SafetyCenterStaticEntryGroup group : data.getStaticEntryGroups()) { 285 PreferenceCategory category = new ComparablePreferenceCategory(context); 286 category.setTitle(group.getTitle()); 287 mStaticEntriesGroup.addPreference(category); 288 289 for (SafetyCenterStaticEntry entry : group.getStaticEntries()) { 290 category.addPreference( 291 new StaticSafetyEntryPreference( 292 context, 293 requireActivity().getTaskId(), 294 entry, 295 SafetyCenterBundles.getStaticEntryId(data, entry), 296 getSafetyCenterViewModel())); 297 } 298 } 299 } 300 } 301