1 /*
2  * Copyright (C) 2015 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.settingslib.dream;
18 
19 import android.annotation.IntDef;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ResolveInfo;
26 import android.content.pm.ServiceInfo;
27 import android.content.res.Resources;
28 import android.graphics.drawable.Drawable;
29 import android.os.RemoteException;
30 import android.os.ServiceManager;
31 import android.os.UserHandle;
32 import android.provider.Settings;
33 import android.service.dreams.DreamService;
34 import android.service.dreams.IDreamManager;
35 import android.util.ArraySet;
36 import android.util.Log;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.internal.util.FrameworkStatsLog;
40 
41 import java.lang.annotation.Retention;
42 import java.lang.annotation.RetentionPolicy;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.Comparator;
46 import java.util.List;
47 import java.util.Set;
48 import java.util.stream.Collectors;
49 
50 public class DreamBackend {
51     private static final String TAG = "DreamBackend";
52     private static final boolean DEBUG = false;
53 
54     public static class DreamInfo {
55         public CharSequence caption;
56         public Drawable icon;
57         public boolean isActive;
58         public ComponentName componentName;
59         public ComponentName settingsComponentName;
60         public CharSequence description;
61         public Drawable previewImage;
62         public boolean supportsComplications = false;
63 
64         @Override
toString()65         public String toString() {
66             StringBuilder sb = new StringBuilder(DreamInfo.class.getSimpleName());
67             sb.append('[').append(caption);
68             if (isActive) {
69                 sb.append(",active");
70             }
71             sb.append(',').append(componentName);
72             if (settingsComponentName != null) {
73                 sb.append("settings=").append(settingsComponentName);
74             }
75             return sb.append(']').toString();
76         }
77     }
78 
79     @Retention(RetentionPolicy.SOURCE)
80     @IntDef({WHILE_CHARGING, WHILE_DOCKED, EITHER, NEVER})
81     public @interface WhenToDream {
82     }
83 
84     public static final int WHILE_CHARGING = 0;
85     public static final int WHILE_DOCKED = 1;
86     public static final int EITHER = 2;
87     public static final int NEVER = 3;
88 
89     /**
90      * The type of dream complications which can be provided by a
91      * {@link com.android.systemui.dreams.ComplicationProvider}.
92      */
93     @IntDef(prefix = {"COMPLICATION_TYPE_"}, value = {
94             COMPLICATION_TYPE_TIME,
95             COMPLICATION_TYPE_DATE,
96             COMPLICATION_TYPE_WEATHER,
97             COMPLICATION_TYPE_AIR_QUALITY,
98             COMPLICATION_TYPE_CAST_INFO,
99             COMPLICATION_TYPE_HOME_CONTROLS,
100             COMPLICATION_TYPE_SMARTSPACE,
101             COMPLICATION_TYPE_MEDIA_ENTRY
102     })
103     @Retention(RetentionPolicy.SOURCE)
104     public @interface ComplicationType {
105     }
106 
107     public static final int COMPLICATION_TYPE_TIME = 1;
108     public static final int COMPLICATION_TYPE_DATE = 2;
109     public static final int COMPLICATION_TYPE_WEATHER = 3;
110     public static final int COMPLICATION_TYPE_AIR_QUALITY = 4;
111     public static final int COMPLICATION_TYPE_CAST_INFO = 5;
112     public static final int COMPLICATION_TYPE_HOME_CONTROLS = 6;
113     public static final int COMPLICATION_TYPE_SMARTSPACE = 7;
114     public static final int COMPLICATION_TYPE_MEDIA_ENTRY = 8;
115 
116     private static final int SCREENSAVER_HOME_CONTROLS_ENABLED_DEFAULT = 1;
117     private static final int LOCKSCREEN_SHOW_CONTROLS_DEFAULT = 0;
118 
119     private static final int DS_TYPE_ENABLED = FrameworkStatsLog
120             .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_ENABLED;
121     private static final int DS_TYPE_WHEN_TO_DREAM = FrameworkStatsLog
122             .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_WHEN_TO_DREAM;
123     private static final int DS_TYPE_DREAM_COMPONENT = FrameworkStatsLog
124             .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_DREAM_COMPONENT;
125     private static final int DS_TYPE_SHOW_ADDITIONAL_INFO = FrameworkStatsLog
126             .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_SHOW_ADDITIONAL_INFO;
127     private static final int DS_TYPE_SHOW_HOME_CONTROLS = FrameworkStatsLog
128             .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_SHOW_HOME_CONTROLS;
129 
130     private static final int WHEN_TO_DREAM_UNSPECIFIED = FrameworkStatsLog
131             .DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_UNSPECIFIED;
132     private static final int WHEN_TO_DREAM_CHARGING = FrameworkStatsLog
133             .DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_WHILE_CHARGING_ONLY;
134     private static final int WHEN_TO_DREAM_DOCKED = FrameworkStatsLog
135             .DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_WHILE_DOCKED_ONLY;
136     private static final int WHEN_TO_DREAM_CHARGING_OR_DOCKED = FrameworkStatsLog
137             .DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_EITHER_CHARGING_OR_DOCKED;
138 
139     private final Context mContext;
140     private final IDreamManager mDreamManager;
141     private final DreamInfoComparator mComparator;
142     private final boolean mDreamsEnabledByDefault;
143     private final boolean mDreamsActivatedOnSleepByDefault;
144     private final boolean mDreamsActivatedOnDockByDefault;
145     private final Set<ComponentName> mDisabledDreams;
146     private final List<String> mLoggableDreamPrefixes;
147     private Set<Integer> mSupportedComplications;
148     private static DreamBackend sInstance;
149 
getInstance(Context context)150     public static DreamBackend getInstance(Context context) {
151         if (sInstance == null) {
152             sInstance = new DreamBackend(context);
153         }
154         return sInstance;
155     }
156 
DreamBackend(Context context)157     public DreamBackend(Context context) {
158         mContext = context.getApplicationContext();
159         final Resources resources = mContext.getResources();
160 
161         mDreamManager = IDreamManager.Stub.asInterface(
162                 ServiceManager.getService(DreamService.DREAM_SERVICE));
163         mComparator = new DreamInfoComparator(getDefaultDream());
164         mDreamsEnabledByDefault = resources.getBoolean(
165                 com.android.internal.R.bool.config_dreamsEnabledByDefault);
166         mDreamsActivatedOnSleepByDefault = resources.getBoolean(
167                 com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault);
168         mDreamsActivatedOnDockByDefault = resources.getBoolean(
169                 com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
170         mDisabledDreams = Arrays.stream(resources.getStringArray(
171                         com.android.internal.R.array.config_disabledDreamComponents))
172                 .map(ComponentName::unflattenFromString)
173                 .collect(Collectors.toSet());
174         mLoggableDreamPrefixes = Arrays.stream(resources.getStringArray(
175                 com.android.internal.R.array.config_loggable_dream_prefixes)).toList();
176 
177         mSupportedComplications = Arrays.stream(resources.getIntArray(
178                         com.android.internal.R.array.config_supportedDreamComplications))
179                 .boxed()
180                 .collect(Collectors.toSet());
181     }
182 
getDreamInfos()183     public List<DreamInfo> getDreamInfos() {
184         logd("getDreamInfos()");
185         ComponentName activeDream = getActiveDream();
186         PackageManager pm = mContext.getPackageManager();
187         Intent dreamIntent = new Intent(DreamService.SERVICE_INTERFACE);
188         List<ResolveInfo> resolveInfos = pm.queryIntentServices(dreamIntent,
189                 PackageManager.GET_META_DATA);
190         List<DreamInfo> dreamInfos = new ArrayList<>(resolveInfos.size());
191         for (ResolveInfo resolveInfo : resolveInfos) {
192             final ComponentName componentName = getDreamComponentName(resolveInfo);
193             if (componentName == null || mDisabledDreams.contains(componentName)) {
194                 continue;
195             }
196 
197             DreamInfo dreamInfo = new DreamInfo();
198             dreamInfo.caption = resolveInfo.loadLabel(pm);
199             dreamInfo.icon = resolveInfo.loadIcon(pm);
200             dreamInfo.description = getDescription(resolveInfo, pm);
201             dreamInfo.componentName = componentName;
202             dreamInfo.isActive = dreamInfo.componentName.equals(activeDream);
203 
204             final DreamService.DreamMetadata dreamMetadata = DreamService.getDreamMetadata(mContext,
205                     resolveInfo.serviceInfo);
206             if (dreamMetadata != null) {
207                 dreamInfo.settingsComponentName = dreamMetadata.settingsActivity;
208                 dreamInfo.previewImage = dreamMetadata.previewImage;
209                 dreamInfo.supportsComplications = dreamMetadata.showComplications;
210             }
211             dreamInfos.add(dreamInfo);
212         }
213         dreamInfos.sort(mComparator);
214         return dreamInfos;
215     }
216 
getDescription(ResolveInfo resolveInfo, PackageManager pm)217     private static CharSequence getDescription(ResolveInfo resolveInfo, PackageManager pm) {
218         String packageName = resolveInfo.resolvePackageName;
219         ApplicationInfo applicationInfo = null;
220         if (packageName == null) {
221             packageName = resolveInfo.serviceInfo.packageName;
222             applicationInfo = resolveInfo.serviceInfo.applicationInfo;
223         }
224         if (resolveInfo.serviceInfo.descriptionRes != 0) {
225             return pm.getText(packageName,
226                     resolveInfo.serviceInfo.descriptionRes,
227                     applicationInfo);
228         }
229         return null;
230     }
231 
getDefaultDream()232     public ComponentName getDefaultDream() {
233         if (mDreamManager == null) {
234             return null;
235         }
236         try {
237             return mDreamManager.getDefaultDreamComponentForUser(mContext.getUserId());
238         } catch (RemoteException e) {
239             Log.w(TAG, "Failed to get default dream", e);
240             return null;
241         }
242     }
243 
getActiveDreamName()244     public CharSequence getActiveDreamName() {
245         ComponentName cn = getActiveDream();
246         if (cn != null) {
247             PackageManager pm = mContext.getPackageManager();
248             try {
249                 ServiceInfo ri = pm.getServiceInfo(cn, 0);
250                 if (ri != null) {
251                     return ri.loadLabel(pm);
252                 }
253             } catch (PackageManager.NameNotFoundException exc) {
254                 return null; // uninstalled?
255             }
256         }
257         return null;
258     }
259 
260     /**
261      * Gets an icon from active dream.
262      */
getActiveIcon()263     public Drawable getActiveIcon() {
264         final ComponentName cn = getActiveDream();
265         if (cn != null) {
266             final PackageManager pm = mContext.getPackageManager();
267             try {
268                 final ServiceInfo ri = pm.getServiceInfo(cn, 0);
269                 if (ri != null) {
270                     return ri.loadIcon(pm);
271                 }
272             } catch (PackageManager.NameNotFoundException exc) {
273                 return null;
274             }
275         }
276         return null;
277     }
278 
279     @WhenToDream
getWhenToDreamSetting()280     public int getWhenToDreamSetting() {
281         return isActivatedOnDock() && isActivatedOnSleep() ? EITHER
282                 : isActivatedOnDock() ? WHILE_DOCKED
283                         : isActivatedOnSleep() ? WHILE_CHARGING
284                                 : NEVER;
285     }
286 
setWhenToDream(@henToDream int whenToDream)287     public void setWhenToDream(@WhenToDream int whenToDream) {
288         setEnabled(whenToDream != NEVER);
289 
290         switch (whenToDream) {
291             case WHILE_CHARGING:
292                 setActivatedOnDock(false);
293                 setActivatedOnSleep(true);
294                 break;
295 
296             case WHILE_DOCKED:
297                 setActivatedOnDock(true);
298                 setActivatedOnSleep(false);
299                 break;
300 
301             case EITHER:
302                 setActivatedOnDock(true);
303                 setActivatedOnSleep(true);
304                 break;
305 
306             case NEVER:
307             default:
308                 break;
309         }
310 
311         logDreamSettingChangeToStatsd(DS_TYPE_WHEN_TO_DREAM);
312     }
313 
314     /** Gets all complications which have been enabled by the user. */
getEnabledComplications()315     public Set<Integer> getEnabledComplications() {
316         final Set<Integer> enabledComplications =
317                 getComplicationsEnabled()
318                         ? new ArraySet<>(mSupportedComplications) : new ArraySet<>();
319 
320         if (!getHomeControlsEnabled()) {
321             enabledComplications.remove(COMPLICATION_TYPE_HOME_CONTROLS);
322         } else if (mSupportedComplications.contains(COMPLICATION_TYPE_HOME_CONTROLS)) {
323             // Add home control type to list of enabled complications, even if other complications
324             // have been disabled.
325             enabledComplications.add(COMPLICATION_TYPE_HOME_CONTROLS);
326         }
327         return enabledComplications;
328     }
329 
330     /** Sets complication enabled state. */
setComplicationsEnabled(boolean enabled)331     public void setComplicationsEnabled(boolean enabled) {
332         Settings.Secure.putInt(mContext.getContentResolver(),
333                 Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, enabled ? 1 : 0);
334         logDreamSettingChangeToStatsd(DS_TYPE_SHOW_ADDITIONAL_INFO);
335     }
336 
337     /** Sets whether home controls are enabled by the user on the dream */
setHomeControlsEnabled(boolean enabled)338     public void setHomeControlsEnabled(boolean enabled) {
339         Settings.Secure.putInt(mContext.getContentResolver(),
340                 Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, enabled ? 1 : 0);
341         logDreamSettingChangeToStatsd(DS_TYPE_SHOW_HOME_CONTROLS);
342     }
343 
344     /** Gets whether home controls button is enabled on the dream */
getHomeControlsEnabled()345     private boolean getHomeControlsEnabled() {
346         return Settings.Secure.getInt(
347                 mContext.getContentResolver(),
348                 Settings.Secure.LOCKSCREEN_SHOW_CONTROLS,
349                 LOCKSCREEN_SHOW_CONTROLS_DEFAULT) == 1
350                 && Settings.Secure.getInt(
351                         mContext.getContentResolver(),
352                         Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED,
353                         SCREENSAVER_HOME_CONTROLS_ENABLED_DEFAULT) == 1;
354     }
355 
356     /**
357      * Gets whether complications are enabled on this device
358      */
getComplicationsEnabled()359     public boolean getComplicationsEnabled() {
360         return Settings.Secure.getInt(
361                 mContext.getContentResolver(),
362                 Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, 1) == 1;
363     }
364 
365     /** Gets all dream complications which are supported on this device. **/
getSupportedComplications()366     public Set<Integer> getSupportedComplications() {
367         return mSupportedComplications;
368     }
369 
370     /**
371      * Sets the list of supported complications. Should only be used in tests.
372      */
373     @VisibleForTesting
setSupportedComplications(Set<Integer> complications)374     public void setSupportedComplications(Set<Integer> complications) {
375         mSupportedComplications = complications;
376     }
377 
isEnabled()378     public boolean isEnabled() {
379         return getBoolean(Settings.Secure.SCREENSAVER_ENABLED, mDreamsEnabledByDefault);
380     }
381 
setEnabled(boolean value)382     public void setEnabled(boolean value) {
383         logd("setEnabled(%s)", value);
384         setBoolean(Settings.Secure.SCREENSAVER_ENABLED, value);
385         logDreamSettingChangeToStatsd(DS_TYPE_ENABLED);
386     }
387 
isActivatedOnDock()388     public boolean isActivatedOnDock() {
389         return getBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
390                 mDreamsActivatedOnDockByDefault);
391     }
392 
setActivatedOnDock(boolean value)393     public void setActivatedOnDock(boolean value) {
394         logd("setActivatedOnDock(%s)", value);
395         setBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, value);
396     }
397 
isActivatedOnSleep()398     public boolean isActivatedOnSleep() {
399         return getBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
400                 mDreamsActivatedOnSleepByDefault);
401     }
402 
setActivatedOnSleep(boolean value)403     public void setActivatedOnSleep(boolean value) {
404         logd("setActivatedOnSleep(%s)", value);
405         setBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, value);
406     }
407 
getBoolean(String key, boolean def)408     private boolean getBoolean(String key, boolean def) {
409         return Settings.Secure.getInt(mContext.getContentResolver(), key, def ? 1 : 0) == 1;
410     }
411 
setBoolean(String key, boolean value)412     private void setBoolean(String key, boolean value) {
413         Settings.Secure.putInt(mContext.getContentResolver(), key, value ? 1 : 0);
414     }
415 
setActiveDream(ComponentName dream)416     public void setActiveDream(ComponentName dream) {
417         logd("setActiveDream(%s)", dream);
418         if (mDreamManager == null) {
419             return;
420         }
421         try {
422             ComponentName[] dreams = {dream};
423             mDreamManager.setDreamComponents(dream == null ? null : dreams);
424             logDreamSettingChangeToStatsd(DS_TYPE_DREAM_COMPONENT);
425         } catch (RemoteException e) {
426             Log.w(TAG, "Failed to set active dream to " + dream, e);
427         }
428     }
429 
getActiveDream()430     public ComponentName getActiveDream() {
431         if (mDreamManager == null) {
432             return null;
433         }
434         try {
435             ComponentName[] dreams = mDreamManager.getDreamComponents();
436             return dreams != null && dreams.length > 0 ? dreams[0] : null;
437         } catch (RemoteException e) {
438             Log.w(TAG, "Failed to get active dream", e);
439             return null;
440         }
441     }
442 
launchSettings(Context uiContext, DreamInfo dreamInfo)443     public void launchSettings(Context uiContext, DreamInfo dreamInfo) {
444         logd("launchSettings(%s)", dreamInfo);
445         if (dreamInfo == null || dreamInfo.settingsComponentName == null) {
446             return;
447         }
448         final Intent intent = new Intent()
449                 .setComponent(dreamInfo.settingsComponentName)
450                 .addFlags(
451                         Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
452         uiContext.startActivity(intent);
453     }
454 
455     /**
456      * Preview a dream, given the component name.
457      */
preview(ComponentName componentName)458     public void preview(ComponentName componentName) {
459         logd("preview(%s)", componentName);
460         if (mDreamManager == null || componentName == null) {
461             return;
462         }
463         try {
464             mDreamManager.testDream(mContext.getUserId(), componentName);
465         } catch (RemoteException e) {
466             Log.w(TAG, "Failed to preview " + componentName, e);
467         }
468     }
469 
startDreaming()470     public void startDreaming() {
471         logd("startDreaming()");
472         if (mDreamManager == null) {
473             return;
474         }
475         try {
476             mDreamManager.dream();
477         } catch (RemoteException e) {
478             Log.w(TAG, "Failed to dream", e);
479         }
480     }
481 
getDreamComponentName(ResolveInfo resolveInfo)482     private static ComponentName getDreamComponentName(ResolveInfo resolveInfo) {
483         if (resolveInfo == null || resolveInfo.serviceInfo == null) {
484             return null;
485         }
486         return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
487     }
488 
logd(String msg, Object... args)489     private static void logd(String msg, Object... args) {
490         if (DEBUG) {
491             Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args));
492         }
493     }
494 
logDreamSettingChangeToStatsd(int dreamSettingType)495     private void logDreamSettingChangeToStatsd(int dreamSettingType) {
496         FrameworkStatsLog.write(
497                 FrameworkStatsLog.DREAM_SETTING_CHANGED, /*atom_tag*/
498                 UserHandle.myUserId(), /*uid*/
499                 isEnabled(), /*enabled*/
500                 getActiveDreamComponentForStatsd(), /*dream_component*/
501                 getWhenToDreamForStatsd(), /*when_to_dream*/
502                 getComplicationsEnabled(), /*show_additional_info*/
503                 getHomeControlsEnabled(), /*show_home_controls*/
504                 dreamSettingType /*dream_setting_type*/
505         );
506     }
507 
508     /**
509      * Returns the user selected dream component in string format for stats logging. If the dream
510      * component is not loggable, returns "other".
511      */
getActiveDreamComponentForStatsd()512     private String getActiveDreamComponentForStatsd() {
513         final ComponentName activeDream = getActiveDream();
514         if (activeDream == null) {
515             return "";
516         }
517 
518         final String component = activeDream.flattenToShortString();
519         if (isLoggableDreamComponentForStatsd(component)) {
520             return component;
521         } else {
522             return "other";
523         }
524     }
525 
526     /**
527      * Whether the dream component is loggable. Only components from the predefined packages are
528      * allowed to be logged for privacy.
529      */
isLoggableDreamComponentForStatsd(String component)530     private boolean isLoggableDreamComponentForStatsd(String component) {
531         for (int i = 0; i < mLoggableDreamPrefixes.size(); i++) {
532             if (component.startsWith(mLoggableDreamPrefixes.get(i))) {
533                 return true;
534             }
535         }
536 
537         return false;
538     }
539 
540     /**
541      * Returns the enum of "when to dream" setting for statsd logging.
542      */
getWhenToDreamForStatsd()543     private int getWhenToDreamForStatsd() {
544         switch (getWhenToDreamSetting()) {
545             case WHILE_CHARGING:
546                 return WHEN_TO_DREAM_CHARGING;
547             case WHILE_DOCKED:
548                 return WHEN_TO_DREAM_DOCKED;
549             case EITHER:
550                 return WHEN_TO_DREAM_CHARGING_OR_DOCKED;
551             case NEVER:
552             default:
553                 return WHEN_TO_DREAM_UNSPECIFIED;
554         }
555     }
556 
557     private static class DreamInfoComparator implements Comparator<DreamInfo> {
558         private final ComponentName mDefaultDream;
559 
DreamInfoComparator(ComponentName defaultDream)560         public DreamInfoComparator(ComponentName defaultDream) {
561             mDefaultDream = defaultDream;
562         }
563 
564         @Override
compare(DreamInfo lhs, DreamInfo rhs)565         public int compare(DreamInfo lhs, DreamInfo rhs) {
566             return sortKey(lhs).compareTo(sortKey(rhs));
567         }
568 
sortKey(DreamInfo di)569         private String sortKey(DreamInfo di) {
570             StringBuilder sb = new StringBuilder();
571             sb.append(di.componentName.equals(mDefaultDream) ? '0' : '1');
572             sb.append(di.caption);
573             return sb.toString();
574         }
575     }
576 }
577