• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.provider.Settings;
32 import android.service.dreams.DreamService;
33 import android.service.dreams.IDreamManager;
34 import android.util.ArraySet;
35 import android.util.Log;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 
39 import java.lang.annotation.Retention;
40 import java.lang.annotation.RetentionPolicy;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Comparator;
44 import java.util.List;
45 import java.util.Set;
46 import java.util.stream.Collectors;
47 
48 public class DreamBackend {
49     private static final String TAG = "DreamBackend";
50     private static final boolean DEBUG = false;
51 
52     public static class DreamInfo {
53         public CharSequence caption;
54         public Drawable icon;
55         public boolean isActive;
56         public ComponentName componentName;
57         public ComponentName settingsComponentName;
58         public CharSequence description;
59         public Drawable previewImage;
60         public boolean supportsComplications = false;
61 
62         @Override
toString()63         public String toString() {
64             StringBuilder sb = new StringBuilder(DreamInfo.class.getSimpleName());
65             sb.append('[').append(caption);
66             if (isActive) {
67                 sb.append(",active");
68             }
69             sb.append(',').append(componentName);
70             if (settingsComponentName != null) {
71                 sb.append("settings=").append(settingsComponentName);
72             }
73             return sb.append(']').toString();
74         }
75     }
76 
77     @Retention(RetentionPolicy.SOURCE)
78     @IntDef({WHILE_CHARGING, WHILE_DOCKED, EITHER, NEVER})
79     public @interface WhenToDream {
80     }
81 
82     public static final int WHILE_CHARGING = 0;
83     public static final int WHILE_DOCKED = 1;
84     public static final int EITHER = 2;
85     public static final int NEVER = 3;
86 
87     /**
88      * The type of dream complications which can be provided by a
89      * {@link com.android.systemui.dreams.ComplicationProvider}.
90      */
91     @IntDef(prefix = {"COMPLICATION_TYPE_"}, value = {
92             COMPLICATION_TYPE_TIME,
93             COMPLICATION_TYPE_DATE,
94             COMPLICATION_TYPE_WEATHER,
95             COMPLICATION_TYPE_AIR_QUALITY,
96             COMPLICATION_TYPE_CAST_INFO,
97             COMPLICATION_TYPE_HOME_CONTROLS,
98             COMPLICATION_TYPE_SMARTSPACE,
99             COMPLICATION_TYPE_MEDIA_ENTRY
100     })
101     @Retention(RetentionPolicy.SOURCE)
102     public @interface ComplicationType {
103     }
104 
105     public static final int COMPLICATION_TYPE_TIME = 1;
106     public static final int COMPLICATION_TYPE_DATE = 2;
107     public static final int COMPLICATION_TYPE_WEATHER = 3;
108     public static final int COMPLICATION_TYPE_AIR_QUALITY = 4;
109     public static final int COMPLICATION_TYPE_CAST_INFO = 5;
110     public static final int COMPLICATION_TYPE_HOME_CONTROLS = 6;
111     public static final int COMPLICATION_TYPE_SMARTSPACE = 7;
112     public static final int COMPLICATION_TYPE_MEDIA_ENTRY = 8;
113 
114     private final Context mContext;
115     private final IDreamManager mDreamManager;
116     private final DreamInfoComparator mComparator;
117     private final boolean mDreamsEnabledByDefault;
118     private final boolean mDreamsActivatedOnSleepByDefault;
119     private final boolean mDreamsActivatedOnDockByDefault;
120     private final Set<ComponentName> mDisabledDreams;
121     private Set<Integer> mSupportedComplications;
122     private static DreamBackend sInstance;
123 
getInstance(Context context)124     public static DreamBackend getInstance(Context context) {
125         if (sInstance == null) {
126             sInstance = new DreamBackend(context);
127         }
128         return sInstance;
129     }
130 
DreamBackend(Context context)131     public DreamBackend(Context context) {
132         mContext = context.getApplicationContext();
133         final Resources resources = mContext.getResources();
134 
135         mDreamManager = IDreamManager.Stub.asInterface(
136                 ServiceManager.getService(DreamService.DREAM_SERVICE));
137         mComparator = new DreamInfoComparator(getDefaultDream());
138         mDreamsEnabledByDefault = resources.getBoolean(
139                 com.android.internal.R.bool.config_dreamsEnabledByDefault);
140         mDreamsActivatedOnSleepByDefault = resources.getBoolean(
141                 com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault);
142         mDreamsActivatedOnDockByDefault = resources.getBoolean(
143                 com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
144         mDisabledDreams = Arrays.stream(resources.getStringArray(
145                         com.android.internal.R.array.config_disabledDreamComponents))
146                 .map(ComponentName::unflattenFromString)
147                 .collect(Collectors.toSet());
148 
149         mSupportedComplications = Arrays.stream(resources.getIntArray(
150                         com.android.internal.R.array.config_supportedDreamComplications))
151                 .boxed()
152                 .collect(Collectors.toSet());
153     }
154 
getDreamInfos()155     public List<DreamInfo> getDreamInfos() {
156         logd("getDreamInfos()");
157         ComponentName activeDream = getActiveDream();
158         PackageManager pm = mContext.getPackageManager();
159         Intent dreamIntent = new Intent(DreamService.SERVICE_INTERFACE);
160         List<ResolveInfo> resolveInfos = pm.queryIntentServices(dreamIntent,
161                 PackageManager.GET_META_DATA);
162         List<DreamInfo> dreamInfos = new ArrayList<>(resolveInfos.size());
163         for (ResolveInfo resolveInfo : resolveInfos) {
164             final ComponentName componentName = getDreamComponentName(resolveInfo);
165             if (componentName == null || mDisabledDreams.contains(componentName)) {
166                 continue;
167             }
168 
169             DreamInfo dreamInfo = new DreamInfo();
170             dreamInfo.caption = resolveInfo.loadLabel(pm);
171             dreamInfo.icon = resolveInfo.loadIcon(pm);
172             dreamInfo.description = getDescription(resolveInfo, pm);
173             dreamInfo.componentName = componentName;
174             dreamInfo.isActive = dreamInfo.componentName.equals(activeDream);
175 
176             final DreamService.DreamMetadata dreamMetadata = DreamService.getDreamMetadata(mContext,
177                     resolveInfo.serviceInfo);
178             if (dreamMetadata != null) {
179                 dreamInfo.settingsComponentName = dreamMetadata.settingsActivity;
180                 dreamInfo.previewImage = dreamMetadata.previewImage;
181                 dreamInfo.supportsComplications = dreamMetadata.showComplications;
182             }
183             dreamInfos.add(dreamInfo);
184         }
185         dreamInfos.sort(mComparator);
186         return dreamInfos;
187     }
188 
getDescription(ResolveInfo resolveInfo, PackageManager pm)189     private static CharSequence getDescription(ResolveInfo resolveInfo, PackageManager pm) {
190         String packageName = resolveInfo.resolvePackageName;
191         ApplicationInfo applicationInfo = null;
192         if (packageName == null) {
193             packageName = resolveInfo.serviceInfo.packageName;
194             applicationInfo = resolveInfo.serviceInfo.applicationInfo;
195         }
196         if (resolveInfo.serviceInfo.descriptionRes != 0) {
197             return pm.getText(packageName,
198                     resolveInfo.serviceInfo.descriptionRes,
199                     applicationInfo);
200         }
201         return null;
202     }
203 
getDefaultDream()204     public ComponentName getDefaultDream() {
205         if (mDreamManager == null) {
206             return null;
207         }
208         try {
209             return mDreamManager.getDefaultDreamComponentForUser(mContext.getUserId());
210         } catch (RemoteException e) {
211             Log.w(TAG, "Failed to get default dream", e);
212             return null;
213         }
214     }
215 
getActiveDreamName()216     public CharSequence getActiveDreamName() {
217         ComponentName cn = getActiveDream();
218         if (cn != null) {
219             PackageManager pm = mContext.getPackageManager();
220             try {
221                 ServiceInfo ri = pm.getServiceInfo(cn, 0);
222                 if (ri != null) {
223                     return ri.loadLabel(pm);
224                 }
225             } catch (PackageManager.NameNotFoundException exc) {
226                 return null; // uninstalled?
227             }
228         }
229         return null;
230     }
231 
232     /**
233      * Gets an icon from active dream.
234      */
getActiveIcon()235     public Drawable getActiveIcon() {
236         final ComponentName cn = getActiveDream();
237         if (cn != null) {
238             final PackageManager pm = mContext.getPackageManager();
239             try {
240                 final ServiceInfo ri = pm.getServiceInfo(cn, 0);
241                 if (ri != null) {
242                     return ri.loadIcon(pm);
243                 }
244             } catch (PackageManager.NameNotFoundException exc) {
245                 return null;
246             }
247         }
248         return null;
249     }
250 
251     @WhenToDream
getWhenToDreamSetting()252     public int getWhenToDreamSetting() {
253         return isActivatedOnDock() && isActivatedOnSleep() ? EITHER
254                 : isActivatedOnDock() ? WHILE_DOCKED
255                         : isActivatedOnSleep() ? WHILE_CHARGING
256                                 : NEVER;
257     }
258 
setWhenToDream(@henToDream int whenToDream)259     public void setWhenToDream(@WhenToDream int whenToDream) {
260         setEnabled(whenToDream != NEVER);
261 
262         switch (whenToDream) {
263             case WHILE_CHARGING:
264                 setActivatedOnDock(false);
265                 setActivatedOnSleep(true);
266                 break;
267 
268             case WHILE_DOCKED:
269                 setActivatedOnDock(true);
270                 setActivatedOnSleep(false);
271                 break;
272 
273             case EITHER:
274                 setActivatedOnDock(true);
275                 setActivatedOnSleep(true);
276                 break;
277 
278             case NEVER:
279             default:
280                 break;
281         }
282     }
283 
284     /** Gets all complications which have been enabled by the user. */
getEnabledComplications()285     public Set<Integer> getEnabledComplications() {
286         final Set<Integer> enabledComplications =
287                 getComplicationsEnabled()
288                         ? new ArraySet<>(mSupportedComplications) : new ArraySet<>();
289 
290         if (!getHomeControlsEnabled()) {
291             enabledComplications.remove(COMPLICATION_TYPE_HOME_CONTROLS);
292         } else if (mSupportedComplications.contains(COMPLICATION_TYPE_HOME_CONTROLS)) {
293             // Add home control type to list of enabled complications, even if other complications
294             // have been disabled.
295             enabledComplications.add(COMPLICATION_TYPE_HOME_CONTROLS);
296         }
297         return enabledComplications;
298     }
299 
300     /** Sets complication enabled state. */
setComplicationsEnabled(boolean enabled)301     public void setComplicationsEnabled(boolean enabled) {
302         Settings.Secure.putInt(mContext.getContentResolver(),
303                 Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, enabled ? 1 : 0);
304     }
305 
306     /** Sets whether home controls are enabled by the user on the dream */
setHomeControlsEnabled(boolean enabled)307     public void setHomeControlsEnabled(boolean enabled) {
308         Settings.Secure.putInt(mContext.getContentResolver(),
309                 Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, enabled ? 1 : 0);
310     }
311 
312     /** Gets whether home controls button is enabled on the dream */
getHomeControlsEnabled()313     private boolean getHomeControlsEnabled() {
314         return Settings.Secure.getInt(mContext.getContentResolver(),
315                 Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, 1) == 1;
316     }
317 
318     /**
319      * Gets whether complications are enabled on this device
320      */
getComplicationsEnabled()321     public boolean getComplicationsEnabled() {
322         return Settings.Secure.getInt(
323                 mContext.getContentResolver(),
324                 Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, 1) == 1;
325     }
326 
327     /** Gets all dream complications which are supported on this device. **/
getSupportedComplications()328     public Set<Integer> getSupportedComplications() {
329         return mSupportedComplications;
330     }
331 
332     /**
333      * Sets the list of supported complications. Should only be used in tests.
334      */
335     @VisibleForTesting
setSupportedComplications(Set<Integer> complications)336     public void setSupportedComplications(Set<Integer> complications) {
337         mSupportedComplications = complications;
338     }
339 
isEnabled()340     public boolean isEnabled() {
341         return getBoolean(Settings.Secure.SCREENSAVER_ENABLED, mDreamsEnabledByDefault);
342     }
343 
setEnabled(boolean value)344     public void setEnabled(boolean value) {
345         logd("setEnabled(%s)", value);
346         setBoolean(Settings.Secure.SCREENSAVER_ENABLED, value);
347     }
348 
isActivatedOnDock()349     public boolean isActivatedOnDock() {
350         return getBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
351                 mDreamsActivatedOnDockByDefault);
352     }
353 
setActivatedOnDock(boolean value)354     public void setActivatedOnDock(boolean value) {
355         logd("setActivatedOnDock(%s)", value);
356         setBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, value);
357     }
358 
isActivatedOnSleep()359     public boolean isActivatedOnSleep() {
360         return getBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
361                 mDreamsActivatedOnSleepByDefault);
362     }
363 
setActivatedOnSleep(boolean value)364     public void setActivatedOnSleep(boolean value) {
365         logd("setActivatedOnSleep(%s)", value);
366         setBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, value);
367     }
368 
getBoolean(String key, boolean def)369     private boolean getBoolean(String key, boolean def) {
370         return Settings.Secure.getInt(mContext.getContentResolver(), key, def ? 1 : 0) == 1;
371     }
372 
setBoolean(String key, boolean value)373     private void setBoolean(String key, boolean value) {
374         Settings.Secure.putInt(mContext.getContentResolver(), key, value ? 1 : 0);
375     }
376 
setActiveDream(ComponentName dream)377     public void setActiveDream(ComponentName dream) {
378         logd("setActiveDream(%s)", dream);
379         if (mDreamManager == null) {
380             return;
381         }
382         try {
383             ComponentName[] dreams = {dream};
384             mDreamManager.setDreamComponents(dream == null ? null : dreams);
385         } catch (RemoteException e) {
386             Log.w(TAG, "Failed to set active dream to " + dream, e);
387         }
388     }
389 
getActiveDream()390     public ComponentName getActiveDream() {
391         if (mDreamManager == null) {
392             return null;
393         }
394         try {
395             ComponentName[] dreams = mDreamManager.getDreamComponents();
396             return dreams != null && dreams.length > 0 ? dreams[0] : null;
397         } catch (RemoteException e) {
398             Log.w(TAG, "Failed to get active dream", e);
399             return null;
400         }
401     }
402 
launchSettings(Context uiContext, DreamInfo dreamInfo)403     public void launchSettings(Context uiContext, DreamInfo dreamInfo) {
404         logd("launchSettings(%s)", dreamInfo);
405         if (dreamInfo == null || dreamInfo.settingsComponentName == null) {
406             return;
407         }
408         final Intent intent = new Intent()
409                 .setComponent(dreamInfo.settingsComponentName)
410                 .addFlags(
411                         Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
412         uiContext.startActivity(intent);
413     }
414 
415     /**
416      * Preview a dream, given the component name.
417      */
preview(ComponentName componentName)418     public void preview(ComponentName componentName) {
419         logd("preview(%s)", componentName);
420         if (mDreamManager == null || componentName == null) {
421             return;
422         }
423         try {
424             mDreamManager.testDream(mContext.getUserId(), componentName);
425         } catch (RemoteException e) {
426             Log.w(TAG, "Failed to preview " + componentName, e);
427         }
428     }
429 
startDreaming()430     public void startDreaming() {
431         logd("startDreaming()");
432         if (mDreamManager == null) {
433             return;
434         }
435         try {
436             mDreamManager.dream();
437         } catch (RemoteException e) {
438             Log.w(TAG, "Failed to dream", e);
439         }
440     }
441 
getDreamComponentName(ResolveInfo resolveInfo)442     private static ComponentName getDreamComponentName(ResolveInfo resolveInfo) {
443         if (resolveInfo == null || resolveInfo.serviceInfo == null) {
444             return null;
445         }
446         return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
447     }
448 
logd(String msg, Object... args)449     private static void logd(String msg, Object... args) {
450         if (DEBUG) {
451             Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args));
452         }
453     }
454 
455     private static class DreamInfoComparator implements Comparator<DreamInfo> {
456         private final ComponentName mDefaultDream;
457 
DreamInfoComparator(ComponentName defaultDream)458         public DreamInfoComparator(ComponentName defaultDream) {
459             mDefaultDream = defaultDream;
460         }
461 
462         @Override
compare(DreamInfo lhs, DreamInfo rhs)463         public int compare(DreamInfo lhs, DreamInfo rhs) {
464             return sortKey(lhs).compareTo(sortKey(rhs));
465         }
466 
sortKey(DreamInfo di)467         private String sortKey(DreamInfo di) {
468             StringBuilder sb = new StringBuilder();
469             sb.append(di.componentName.equals(mDefaultDream) ? '0' : '1');
470             sb.append(di.caption);
471             return sb.toString();
472         }
473     }
474 }
475