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.safetycenter; 18 19 import static com.android.safetycenter.UserProfileGroup.PROFILE_TYPE_MANAGED; 20 import static com.android.safetycenter.UserProfileGroup.PROFILE_TYPE_PRIMARY; 21 import static com.android.safetycenter.UserProfileGroup.PROFILE_TYPE_PRIVATE; 22 23 import static java.util.Collections.emptyList; 24 import static java.util.Collections.unmodifiableList; 25 import static java.util.Objects.requireNonNull; 26 27 import android.content.res.Resources; 28 import android.safetycenter.config.SafetyCenterConfig; 29 import android.safetycenter.config.SafetySource; 30 import android.safetycenter.config.SafetySourcesGroup; 31 import android.util.ArrayMap; 32 import android.util.Log; 33 34 import androidx.annotation.Nullable; 35 36 import com.android.safetycenter.UserProfileGroup.ProfileType; 37 import com.android.safetycenter.config.ParseException; 38 import com.android.safetycenter.config.SafetyCenterConfigParser; 39 import com.android.safetycenter.resources.SafetyCenterResourcesApk; 40 41 import java.io.InputStream; 42 import java.io.PrintWriter; 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.Objects; 46 47 import javax.annotation.concurrent.NotThreadSafe; 48 49 /** 50 * A class that reads the {@link SafetyCenterConfig} and allows overriding it for tests. 51 * 52 * <p>This class isn't thread safe. Thread safety must be handled by the caller. 53 * 54 * @hide 55 */ 56 @NotThreadSafe 57 public final class SafetyCenterConfigReader { 58 59 private static final String TAG = "SafetyCenterConfigReade"; 60 61 private final SafetyCenterResourcesApk mSafetyCenterResourcesApk; 62 63 @Nullable private SafetyCenterConfigInternal mConfigInternalFromXml; 64 65 @Nullable private SafetyCenterConfigInternal mConfigInternalOverrideForTests; 66 67 /** Creates a {@link SafetyCenterConfigReader} from a {@link SafetyCenterResourcesApk}. */ SafetyCenterConfigReader(SafetyCenterResourcesApk safetyCenterResourcesApk)68 SafetyCenterConfigReader(SafetyCenterResourcesApk safetyCenterResourcesApk) { 69 mSafetyCenterResourcesApk = safetyCenterResourcesApk; 70 } 71 72 /** 73 * Loads the {@link SafetyCenterConfig} from the XML file defined in {@code 74 * safety_center_config.xml}; and returns whether this was successful. 75 * 76 * <p>This method must be called prior to any other call to this class. This call must also be 77 * successful; interacting with this class requires checking that the boolean value returned by 78 * this method was {@code true}. 79 */ loadConfig()80 boolean loadConfig() { 81 SafetyCenterConfig safetyCenterConfig = loadSafetyCenterConfig(); 82 if (safetyCenterConfig == null) { 83 return false; 84 } 85 mConfigInternalFromXml = SafetyCenterConfigInternal.from(safetyCenterConfig); 86 return true; 87 } 88 89 /** 90 * Sets an override {@link SafetyCenterConfig} for tests. 91 * 92 * <p>When set, information provided by this class will be based on the overridden {@link 93 * SafetyCenterConfig}. 94 */ setConfigOverrideForTests(SafetyCenterConfig safetyCenterConfig)95 void setConfigOverrideForTests(SafetyCenterConfig safetyCenterConfig) { 96 mConfigInternalOverrideForTests = SafetyCenterConfigInternal.from(safetyCenterConfig); 97 } 98 99 /** 100 * Clears the {@link SafetyCenterConfig} override set by {@link 101 * #setConfigOverrideForTests(SafetyCenterConfig)}, if any. 102 */ clearConfigOverrideForTests()103 void clearConfigOverrideForTests() { 104 mConfigInternalOverrideForTests = null; 105 } 106 107 /** Returns the currently active {@link SafetyCenterConfig}. */ getSafetyCenterConfig()108 SafetyCenterConfig getSafetyCenterConfig() { 109 return getCurrentConfigInternal().getSafetyCenterConfig(); 110 } 111 112 /** Returns the groups of {@link SafetySource}, in the order expected by the UI. */ getSafetySourcesGroups()113 public List<SafetySourcesGroup> getSafetySourcesGroups() { 114 return getCurrentConfigInternal().getSafetyCenterConfig().getSafetySourcesGroups(); 115 } 116 117 /** 118 * Returns the groups of {@link SafetySource}, filtering out any sources where {@link 119 * SafetySources#isLoggable(SafetySource)} is {@code false} (and any resulting empty groups). 120 */ getLoggableSafetySourcesGroups()121 public List<SafetySourcesGroup> getLoggableSafetySourcesGroups() { 122 return getCurrentConfigInternal().getLoggableSourcesGroups(); 123 } 124 125 /** 126 * Returns the {@link ExternalSafetySource} associated with the {@code safetySourceId}, if any. 127 * 128 * <p>The returned {@link SafetySource} can either be associated with the XML or overridden 129 * {@link SafetyCenterConfig}; {@link #isExternalSafetySourceActive(String, String)} can be used 130 * to check if it is associated with the current {@link SafetyCenterConfig}. This is to continue 131 * allowing sources from the XML config to interact with SafetCenter during tests (but their 132 * calls will be no-oped). 133 * 134 * <p>The {@code callingPackageName} can help break the tie when the source is available in both 135 * the overridden config and the "real" config. Otherwise, the test config is preferred. This is 136 * to support overriding "real" sources in tests while ensuring package checks continue to pass 137 * for "real" sources that interact with our APIs. 138 */ 139 @Nullable getExternalSafetySource( String safetySourceId, String callingPackageName)140 public ExternalSafetySource getExternalSafetySource( 141 String safetySourceId, String callingPackageName) { 142 SafetyCenterConfigInternal testConfig = mConfigInternalOverrideForTests; 143 SafetyCenterConfigInternal xmlConfig = requireNonNull(mConfigInternalFromXml); 144 if (testConfig == null) { 145 // No override, access source directly. 146 return xmlConfig.getExternalSafetySources().get(safetySourceId); 147 } 148 149 ExternalSafetySource externalSafetySourceInTestConfig = 150 testConfig.getExternalSafetySources().get(safetySourceId); 151 ExternalSafetySource externalSafetySourceInRealConfig = 152 xmlConfig.getExternalSafetySources().get(safetySourceId); 153 154 if (externalSafetySourceInTestConfig != null 155 && Objects.equals( 156 externalSafetySourceInTestConfig.getSafetySource().getPackageName(), 157 callingPackageName)) { 158 return externalSafetySourceInTestConfig; 159 } 160 161 if (externalSafetySourceInRealConfig != null 162 && Objects.equals( 163 externalSafetySourceInRealConfig.getSafetySource().getPackageName(), 164 callingPackageName)) { 165 return externalSafetySourceInRealConfig; 166 } 167 168 if (externalSafetySourceInTestConfig != null) { 169 return externalSafetySourceInTestConfig; 170 } 171 172 return externalSafetySourceInRealConfig; 173 } 174 175 /** 176 * Returns whether the {@code safetySourceId} is associated with an {@link ExternalSafetySource} 177 * that is currently active. 178 * 179 * <p>The source may either be "active" or "inactive". An active source is a source that is 180 * currently expected to interact with our API and may affect Safety Center status. An inactive 181 * source is expected to interact with Safety Center, but is currently being silenced / no-ops 182 * while an override for tests is in place. 183 * 184 * <p>The {@code callingPackageName} can be used to differentiate a real source being 185 * overridden. It could be that a test is overriding a real source and as such the real source 186 * should not be able to provide data while its override is in place. 187 */ isExternalSafetySourceActive( String safetySourceId, @Nullable String callingPackageName)188 public boolean isExternalSafetySourceActive( 189 String safetySourceId, @Nullable String callingPackageName) { 190 ExternalSafetySource externalSafetySourceInCurrentConfig = 191 getCurrentConfigInternal().getExternalSafetySources().get(safetySourceId); 192 if (externalSafetySourceInCurrentConfig == null) { 193 return false; 194 } 195 if (callingPackageName == null) { 196 return true; 197 } 198 return Objects.equals( 199 externalSafetySourceInCurrentConfig.getSafetySource().getPackageName(), 200 callingPackageName); 201 } 202 203 /** 204 * Returns whether the {@code safetySourceId} is associated with an {@link ExternalSafetySource} 205 * that is in the real config XML file (i.e. not being overridden). 206 */ isExternalSafetySourceFromRealConfig(String safetySourceId)207 public boolean isExternalSafetySourceFromRealConfig(String safetySourceId) { 208 return requireNonNull(mConfigInternalFromXml) 209 .getExternalSafetySources() 210 .containsKey(safetySourceId); 211 } 212 213 /** 214 * Returns the {@link Broadcast} defined in the {@link SafetyCenterConfig}, with all the sources 215 * that they should handle and the profile on which they should be dispatched. 216 */ getBroadcasts()217 List<Broadcast> getBroadcasts() { 218 return getCurrentConfigInternal().getBroadcasts(); 219 } 220 getCurrentConfigInternal()221 private SafetyCenterConfigInternal getCurrentConfigInternal() { 222 // We require the XML config must be loaded successfully for SafetyCenterManager APIs to 223 // function, regardless of whether the config is subsequently overridden. 224 requireNonNull(mConfigInternalFromXml); 225 226 if (mConfigInternalOverrideForTests == null) { 227 return mConfigInternalFromXml; 228 } 229 230 return mConfigInternalOverrideForTests; 231 } 232 233 @Nullable loadSafetyCenterConfig()234 private SafetyCenterConfig loadSafetyCenterConfig() { 235 InputStream in = mSafetyCenterResourcesApk.getSafetyCenterConfig(); 236 if (in == null) { 237 Log.e(TAG, "Cannot access Safety Center config file"); 238 return null; 239 } 240 241 Resources resources = mSafetyCenterResourcesApk.getResources(); 242 try { 243 SafetyCenterConfig safetyCenterConfig = 244 SafetyCenterConfigParser.parseXmlResource(in, resources); 245 Log.d(TAG, "SafetyCenterConfig loaded successfully"); 246 return safetyCenterConfig; 247 } catch (ParseException e) { 248 Log.e(TAG, "Cannot parse SafetyCenterConfig", e); 249 return null; 250 } 251 } 252 253 /** Dumps state for debugging purposes. */ dump(PrintWriter fout)254 void dump(PrintWriter fout) { 255 fout.println("XML CONFIG"); 256 fout.println("\t" + mConfigInternalFromXml); 257 fout.println(); 258 fout.println("OVERRIDE CONFIG"); 259 fout.println("\t" + mConfigInternalOverrideForTests); 260 fout.println(); 261 } 262 263 /** A wrapper class around the parsed XML config. */ 264 private static final class SafetyCenterConfigInternal { 265 266 private final SafetyCenterConfig mConfig; 267 private final ArrayMap<String, ExternalSafetySource> mExternalSafetySources; 268 private final List<SafetySourcesGroup> mLoggableSourcesGroups; 269 private final List<Broadcast> mBroadcasts; 270 SafetyCenterConfigInternal( SafetyCenterConfig safetyCenterConfig, ArrayMap<String, ExternalSafetySource> externalSafetySources, List<SafetySourcesGroup> loggableSourcesGroups, List<Broadcast> broadcasts)271 private SafetyCenterConfigInternal( 272 SafetyCenterConfig safetyCenterConfig, 273 ArrayMap<String, ExternalSafetySource> externalSafetySources, 274 List<SafetySourcesGroup> loggableSourcesGroups, 275 List<Broadcast> broadcasts) { 276 mConfig = safetyCenterConfig; 277 mExternalSafetySources = externalSafetySources; 278 mLoggableSourcesGroups = loggableSourcesGroups; 279 mBroadcasts = broadcasts; 280 } 281 getSafetyCenterConfig()282 private SafetyCenterConfig getSafetyCenterConfig() { 283 return mConfig; 284 } 285 getExternalSafetySources()286 private ArrayMap<String, ExternalSafetySource> getExternalSafetySources() { 287 return mExternalSafetySources; 288 } 289 getLoggableSourcesGroups()290 private List<SafetySourcesGroup> getLoggableSourcesGroups() { 291 return mLoggableSourcesGroups; 292 } 293 getBroadcasts()294 private List<Broadcast> getBroadcasts() { 295 return mBroadcasts; 296 } 297 298 @Override equals(Object o)299 public boolean equals(Object o) { 300 if (this == o) return true; 301 if (!(o instanceof SafetyCenterConfigInternal)) return false; 302 SafetyCenterConfigInternal configInternal = (SafetyCenterConfigInternal) o; 303 return mConfig.equals(configInternal.mConfig); 304 } 305 306 @Override hashCode()307 public int hashCode() { 308 return Objects.hash(mConfig); 309 } 310 311 @Override toString()312 public String toString() { 313 return "SafetyCenterConfigInternal{" 314 + "mConfig=" 315 + mConfig 316 + ", mExternalSafetySources=" 317 + mExternalSafetySources 318 + ", mLoggableSourcesGroups=" 319 + mLoggableSourcesGroups 320 + ", mBroadcasts=" 321 + mBroadcasts 322 + '}'; 323 } 324 from(SafetyCenterConfig safetyCenterConfig)325 private static SafetyCenterConfigInternal from(SafetyCenterConfig safetyCenterConfig) { 326 return new SafetyCenterConfigInternal( 327 safetyCenterConfig, 328 extractExternalSafetySources(safetyCenterConfig), 329 extractLoggableSafetySourcesGroups(safetyCenterConfig), 330 unmodifiableList(extractBroadcasts(safetyCenterConfig))); 331 } 332 extractExternalSafetySources( SafetyCenterConfig safetyCenterConfig)333 private static ArrayMap<String, ExternalSafetySource> extractExternalSafetySources( 334 SafetyCenterConfig safetyCenterConfig) { 335 ArrayMap<String, ExternalSafetySource> externalSafetySources = new ArrayMap<>(); 336 List<SafetySourcesGroup> safetySourcesGroups = 337 safetyCenterConfig.getSafetySourcesGroups(); 338 for (int i = 0; i < safetySourcesGroups.size(); i++) { 339 SafetySourcesGroup safetySourcesGroup = safetySourcesGroups.get(i); 340 341 List<SafetySource> safetySources = safetySourcesGroup.getSafetySources(); 342 for (int j = 0; j < safetySources.size(); j++) { 343 SafetySource safetySource = safetySources.get(j); 344 345 if (!SafetySources.isExternal(safetySource)) { 346 continue; 347 } 348 349 boolean hasEntryInStatelessGroup = 350 safetySource.getType() == SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC 351 && safetySourcesGroup.getType() 352 == SafetySourcesGroup 353 .SAFETY_SOURCES_GROUP_TYPE_STATELESS; 354 355 externalSafetySources.put( 356 safetySource.getId(), 357 new ExternalSafetySource(safetySource, hasEntryInStatelessGroup)); 358 } 359 } 360 361 return externalSafetySources; 362 } 363 extractLoggableSafetySourcesGroups( SafetyCenterConfig safetyCenterConfig)364 private static List<SafetySourcesGroup> extractLoggableSafetySourcesGroups( 365 SafetyCenterConfig safetyCenterConfig) { 366 List<SafetySourcesGroup> originalGroups = safetyCenterConfig.getSafetySourcesGroups(); 367 List<SafetySourcesGroup> filteredGroups = new ArrayList<>(originalGroups.size()); 368 369 for (int i = 0; i < originalGroups.size(); i++) { 370 SafetySourcesGroup originalGroup = originalGroups.get(i); 371 372 SafetySourcesGroup.Builder filteredGroupBuilder = 373 SafetySourcesGroups.copyToBuilderWithoutSources(originalGroup); 374 List<SafetySource> originalSources = originalGroup.getSafetySources(); 375 for (int j = 0; j < originalSources.size(); j++) { 376 SafetySource source = originalSources.get(j); 377 378 if (SafetySources.isLoggable(source)) { 379 filteredGroupBuilder.addSafetySource(source); 380 } 381 } 382 383 SafetySourcesGroup filteredGroup = filteredGroupBuilder.build(); 384 if (!filteredGroup.getSafetySources().isEmpty()) { 385 filteredGroups.add(filteredGroup); 386 } 387 } 388 389 return filteredGroups; 390 } 391 extractBroadcasts(SafetyCenterConfig safetyCenterConfig)392 private static List<Broadcast> extractBroadcasts(SafetyCenterConfig safetyCenterConfig) { 393 ArrayMap<String, Broadcast> packageNameToBroadcast = new ArrayMap<>(); 394 List<Broadcast> broadcasts = new ArrayList<>(); 395 List<SafetySourcesGroup> safetySourcesGroups = 396 safetyCenterConfig.getSafetySourcesGroups(); 397 for (int i = 0; i < safetySourcesGroups.size(); i++) { 398 SafetySourcesGroup safetySourcesGroup = safetySourcesGroups.get(i); 399 400 List<SafetySource> safetySources = safetySourcesGroup.getSafetySources(); 401 for (int j = 0; j < safetySources.size(); j++) { 402 SafetySource safetySource = safetySources.get(j); 403 404 if (!SafetySources.isExternal(safetySource)) { 405 continue; 406 } 407 408 Broadcast broadcast = packageNameToBroadcast.get(safetySource.getPackageName()); 409 if (broadcast == null) { 410 broadcast = new Broadcast(safetySource.getPackageName()); 411 packageNameToBroadcast.put(safetySource.getPackageName(), broadcast); 412 broadcasts.add(broadcast); 413 } 414 broadcast.mSourceIdsForProfileParent.add(safetySource.getId()); 415 if (safetySource.isRefreshOnPageOpenAllowed()) { 416 broadcast.mSourceIdsForProfileParentOnPageOpen.add(safetySource.getId()); 417 } 418 boolean needsManagedProfilesBroadcast = 419 SafetySources.supportsProfileType(safetySource, PROFILE_TYPE_MANAGED); 420 if (needsManagedProfilesBroadcast) { 421 broadcast.mSourceIdsForManagedProfiles.add(safetySource.getId()); 422 if (safetySource.isRefreshOnPageOpenAllowed()) { 423 broadcast.mSourceIdsForManagedProfilesOnPageOpen.add( 424 safetySource.getId()); 425 } 426 } 427 428 // TODO(b/317378205): think about generalising these fields in Broadcast so that 429 // we are not duplicating the code - it can be a source of confusion and errors 430 // in future. 431 boolean needsPrivateProfileBroadcast = 432 SafetySources.supportsProfileType(safetySource, PROFILE_TYPE_PRIVATE); 433 if (needsPrivateProfileBroadcast) { 434 broadcast.mSourceIdsForPrivateProfile.add(safetySource.getId()); 435 if (safetySource.isRefreshOnPageOpenAllowed()) { 436 broadcast.mSourceIdsForPrivateProfileOnPageOpen.add( 437 safetySource.getId()); 438 } 439 } 440 } 441 } 442 443 return broadcasts; 444 } 445 } 446 447 /** 448 * A wrapper class around a {@link SafetySource} that is providing data externally. 449 * 450 * @hide 451 */ 452 public static final class ExternalSafetySource { 453 private final SafetySource mSafetySource; 454 private final boolean mHasEntryInStatelessGroup; 455 ExternalSafetySource(SafetySource safetySource, boolean hasEntryInStatelessGroup)456 private ExternalSafetySource(SafetySource safetySource, boolean hasEntryInStatelessGroup) { 457 mSafetySource = safetySource; 458 mHasEntryInStatelessGroup = hasEntryInStatelessGroup; 459 } 460 461 /** Returns the external {@link SafetySource}. */ getSafetySource()462 public SafetySource getSafetySource() { 463 return mSafetySource; 464 } 465 466 /** 467 * Returns whether the external {@link SafetySource} has an entry in a stateless {@link 468 * SafetySourcesGroup}. 469 */ hasEntryInStatelessGroup()470 public boolean hasEntryInStatelessGroup() { 471 return mHasEntryInStatelessGroup; 472 } 473 474 @Override equals(Object o)475 public boolean equals(Object o) { 476 if (this == o) return true; 477 if (!(o instanceof ExternalSafetySource)) return false; 478 ExternalSafetySource that = (ExternalSafetySource) o; 479 return mHasEntryInStatelessGroup == that.mHasEntryInStatelessGroup 480 && mSafetySource.equals(that.mSafetySource); 481 } 482 483 @Override hashCode()484 public int hashCode() { 485 return Objects.hash(mSafetySource, mHasEntryInStatelessGroup); 486 } 487 488 @Override toString()489 public String toString() { 490 return "ExternalSafetySource{" 491 + "mSafetySource=" 492 + mSafetySource 493 + ", mHasEntryInStatelessGroup=" 494 + mHasEntryInStatelessGroup 495 + '}'; 496 } 497 } 498 499 /** A class that represents a broadcast to be sent to safety sources. */ 500 static final class Broadcast { 501 502 private final String mPackageName; 503 504 private final List<String> mSourceIdsForProfileParent = new ArrayList<>(); 505 private final List<String> mSourceIdsForProfileParentOnPageOpen = new ArrayList<>(); 506 private final List<String> mSourceIdsForManagedProfiles = new ArrayList<>(); 507 private final List<String> mSourceIdsForManagedProfilesOnPageOpen = new ArrayList<>(); 508 private final List<String> mSourceIdsForPrivateProfile = new ArrayList<>(); 509 private final List<String> mSourceIdsForPrivateProfileOnPageOpen = new ArrayList<>(); 510 Broadcast(String packageName)511 private Broadcast(String packageName) { 512 mPackageName = packageName; 513 } 514 515 /** Returns the package name to dispatch the broadcast to. */ getPackageName()516 String getPackageName() { 517 return mPackageName; 518 } 519 520 /** 521 * Returns the safety source ids associated with this broadcast in the given profile type. 522 * 523 * <p>If this list is empty, there are no sources to dispatch to in the given profile type. 524 */ getSourceIdsForProfileType(@rofileType int profileType)525 List<String> getSourceIdsForProfileType(@ProfileType int profileType) { 526 switch (profileType) { 527 case PROFILE_TYPE_PRIMARY: 528 return unmodifiableList(mSourceIdsForProfileParent); 529 case PROFILE_TYPE_MANAGED: 530 return unmodifiableList(mSourceIdsForManagedProfiles); 531 case PROFILE_TYPE_PRIVATE: 532 return unmodifiableList(mSourceIdsForPrivateProfile); 533 default: 534 Log.w(TAG, "source ids asked for unexpected profile " + profileType); 535 return emptyList(); 536 } 537 } 538 539 /** 540 * Returns the safety source ids associated with this broadcast in the given profile type 541 * that have refreshOnPageOpenAllowed set to true in the XML config. 542 * 543 * <p>If this list is empty, there are no sources to dispatch to in the given profile type. 544 */ getSourceIdsOnPageOpenForProfileType(@rofileType int profileType)545 List<String> getSourceIdsOnPageOpenForProfileType(@ProfileType int profileType) { 546 switch (profileType) { 547 case PROFILE_TYPE_PRIMARY: 548 return unmodifiableList(mSourceIdsForProfileParentOnPageOpen); 549 case PROFILE_TYPE_MANAGED: 550 return unmodifiableList(mSourceIdsForManagedProfilesOnPageOpen); 551 case PROFILE_TYPE_PRIVATE: 552 return unmodifiableList(mSourceIdsForPrivateProfileOnPageOpen); 553 default: 554 Log.w(TAG, "source ids asked for unexpected profile " + profileType); 555 return emptyList(); 556 } 557 } 558 559 @Override equals(Object o)560 public boolean equals(Object o) { 561 if (this == o) return true; 562 if (!(o instanceof Broadcast)) return false; 563 Broadcast that = (Broadcast) o; 564 return mPackageName.equals(that.mPackageName) 565 && mSourceIdsForProfileParent.equals(that.mSourceIdsForProfileParent) 566 && mSourceIdsForProfileParentOnPageOpen.equals( 567 that.mSourceIdsForProfileParentOnPageOpen) 568 && mSourceIdsForManagedProfiles.equals(that.mSourceIdsForManagedProfiles) 569 && mSourceIdsForManagedProfilesOnPageOpen.equals( 570 that.mSourceIdsForManagedProfilesOnPageOpen) 571 && mSourceIdsForPrivateProfile.equals(that.mSourceIdsForPrivateProfile) 572 && mSourceIdsForPrivateProfileOnPageOpen.equals( 573 that.mSourceIdsForPrivateProfileOnPageOpen); 574 } 575 576 @Override hashCode()577 public int hashCode() { 578 return Objects.hash( 579 mPackageName, 580 mSourceIdsForProfileParent, 581 mSourceIdsForProfileParentOnPageOpen, 582 mSourceIdsForManagedProfiles, 583 mSourceIdsForManagedProfilesOnPageOpen, 584 mSourceIdsForPrivateProfile, 585 mSourceIdsForPrivateProfileOnPageOpen); 586 } 587 588 @Override toString()589 public String toString() { 590 return "Broadcast{" 591 + "mPackageName='" 592 + mPackageName 593 + "', mSourceIdsForProfileParent=" 594 + mSourceIdsForProfileParent 595 + ", mSourceIdsForProfileParentOnPageOpen=" 596 + mSourceIdsForProfileParentOnPageOpen 597 + ", mSourceIdsForManagedProfiles=" 598 + mSourceIdsForManagedProfiles 599 + ", mSourceIdsForManagedProfilesOnPageOpen=" 600 + mSourceIdsForManagedProfilesOnPageOpen 601 + ", mSourceIdsForPrivateProfile=" 602 + mSourceIdsForPrivateProfile 603 + ", mSourceIdsForPrivateProfileOnPageOpen=" 604 + mSourceIdsForPrivateProfileOnPageOpen 605 + '}'; 606 } 607 } 608 } 609