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