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