• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.tv.settings.library.settingslib;
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.PackageManager;
24 import android.content.pm.ResolveInfo;
25 import android.content.pm.ServiceInfo;
26 import android.content.res.Resources;
27 import android.content.res.TypedArray;
28 import android.content.res.XmlResourceParser;
29 import android.graphics.drawable.Drawable;
30 import android.os.RemoteException;
31 import android.os.ServiceManager;
32 import android.provider.Settings;
33 import android.service.dreams.DreamService;
34 import android.service.dreams.IDreamManager;
35 import android.util.AttributeSet;
36 import android.util.Log;
37 import android.util.Xml;
38 
39 import org.xmlpull.v1.XmlPullParser;
40 import org.xmlpull.v1.XmlPullParserException;
41 
42 import java.io.IOException;
43 import java.lang.annotation.Retention;
44 import java.lang.annotation.RetentionPolicy;
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.Comparator;
48 import java.util.List;
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 
61         @Override
toString()62         public String toString() {
63             StringBuilder sb = new StringBuilder(DreamBackend.DreamInfo.class.getSimpleName());
64             sb.append('[').append(caption);
65             if (isActive) {
66                 sb.append(",active");
67             }
68             sb.append(',').append(componentName);
69             if (settingsComponentName != null) {
70                 sb.append("settings=").append(settingsComponentName);
71             }
72             return sb.append(']').toString();
73         }
74     }
75 
76     @Retention(RetentionPolicy.SOURCE)
77     @IntDef({WHILE_CHARGING, WHILE_DOCKED, EITHER, NEVER})
78     public @interface WhenToDream {
79     }
80 
81     public static final int WHILE_CHARGING = 0;
82     public static final int WHILE_DOCKED = 1;
83     public static final int EITHER = 2;
84     public static final int NEVER = 3;
85 
86     private final Context mContext;
87     private final IDreamManager mDreamManager;
88     private final DreamBackend.DreamInfoComparator mComparator;
89     private final boolean mDreamsEnabledByDefault;
90     private final boolean mDreamsActivatedOnSleepByDefault;
91     private final boolean mDreamsActivatedOnDockByDefault;
92 
93     private static DreamBackend sInstance;
94 
getInstance(Context context)95     public static DreamBackend getInstance(Context context) {
96         if (sInstance == null) {
97             sInstance = new DreamBackend(context);
98         }
99         return sInstance;
100     }
101 
DreamBackend(Context context)102     public DreamBackend(Context context) {
103         mContext = context.getApplicationContext();
104         mDreamManager = IDreamManager.Stub.asInterface(
105                 ServiceManager.getService(DreamService.DREAM_SERVICE));
106         mComparator = new DreamBackend.DreamInfoComparator(getDefaultDream());
107         mDreamsEnabledByDefault = mContext.getResources()
108                 .getBoolean(mContext.getResources().getIdentifier("config_dreamsEnabledByDefault",
109                         "bool", "android"));
110         mDreamsActivatedOnSleepByDefault = mContext.getResources()
111                 .getBoolean(mContext.getResources().getIdentifier(
112                 "config_dreamsActivatedOnSleepByDefault", "bool", "android"));
113         mDreamsActivatedOnDockByDefault = mContext.getResources()
114                 .getBoolean(mContext.getResources().getIdentifier(
115                 "config_dreamsActivatedOnDockByDefault", "bool", "android"));
116     }
117 
getDreamInfos()118     public List<DreamBackend.DreamInfo> getDreamInfos() {
119         logd("getDreamInfos()");
120         ComponentName activeDream = getActiveDream();
121         PackageManager pm = mContext.getPackageManager();
122         Intent dreamIntent = new Intent(DreamService.SERVICE_INTERFACE);
123         List<ResolveInfo> resolveInfos = pm.queryIntentServices(dreamIntent,
124                 PackageManager.GET_META_DATA);
125         List<DreamBackend.DreamInfo> dreamInfos = new ArrayList<>(resolveInfos.size());
126         for (ResolveInfo resolveInfo : resolveInfos) {
127             if (resolveInfo.serviceInfo == null) {
128                 continue;
129             }
130             DreamBackend.DreamInfo
131                     dreamInfo = new DreamBackend.DreamInfo();
132             dreamInfo.caption = resolveInfo.loadLabel(pm);
133             dreamInfo.icon = resolveInfo.loadIcon(pm);
134             dreamInfo.componentName = getDreamComponentName(resolveInfo);
135             dreamInfo.isActive = dreamInfo.componentName.equals(activeDream);
136             dreamInfo.settingsComponentName = getSettingsComponentName(pm, resolveInfo);
137             dreamInfos.add(dreamInfo);
138         }
139         Collections.sort(dreamInfos, mComparator);
140         return dreamInfos;
141     }
142 
getDefaultDream()143     public ComponentName getDefaultDream() {
144         if (mDreamManager == null) {
145             return null;
146         }
147         try {
148             return mDreamManager.getDefaultDreamComponentForUser(mContext.getUserId());
149         } catch (RemoteException e) {
150             Log.w(TAG, "Failed to get default dream", e);
151             return null;
152         }
153     }
154 
getActiveDreamName()155     public CharSequence getActiveDreamName() {
156         ComponentName cn = getActiveDream();
157         if (cn != null) {
158             PackageManager pm = mContext.getPackageManager();
159             try {
160                 ServiceInfo ri = pm.getServiceInfo(cn, 0);
161                 if (ri != null) {
162                     return ri.loadLabel(pm);
163                 }
164             } catch (PackageManager.NameNotFoundException exc) {
165                 return null; // uninstalled?
166             }
167         }
168         return null;
169     }
170 
171     /**
172      * Gets an icon from active dream.
173      */
getActiveIcon()174     public Drawable getActiveIcon() {
175         final ComponentName cn = getActiveDream();
176         if (cn != null) {
177             final PackageManager pm = mContext.getPackageManager();
178             try {
179                 final ServiceInfo ri = pm.getServiceInfo(cn, 0);
180                 if (ri != null) {
181                     return ri.loadIcon(pm);
182                 }
183             } catch (PackageManager.NameNotFoundException exc) {
184                 return null;
185             }
186         }
187         return null;
188     }
189 
190     public @DreamBackend.WhenToDream
getWhenToDreamSetting()191     int getWhenToDreamSetting() {
192         if (!isEnabled()) {
193             return NEVER;
194         }
195         return isActivatedOnDock() && isActivatedOnSleep() ? EITHER
196                 : isActivatedOnDock() ? WHILE_DOCKED
197                         : isActivatedOnSleep() ? WHILE_CHARGING
198                                 : NEVER;
199     }
200 
setWhenToDream(@reamBackend.WhenToDream int whenToDream)201     public void setWhenToDream(@DreamBackend.WhenToDream int whenToDream) {
202         setEnabled(whenToDream != NEVER);
203 
204         switch (whenToDream) {
205             case WHILE_CHARGING:
206                 setActivatedOnDock(false);
207                 setActivatedOnSleep(true);
208                 break;
209 
210             case WHILE_DOCKED:
211                 setActivatedOnDock(true);
212                 setActivatedOnSleep(false);
213                 break;
214 
215             case EITHER:
216                 setActivatedOnDock(true);
217                 setActivatedOnSleep(true);
218                 break;
219 
220             case NEVER:
221             default:
222                 break;
223         }
224 
225     }
226 
isEnabled()227     public boolean isEnabled() {
228         return getBoolean(Settings.Secure.SCREENSAVER_ENABLED, mDreamsEnabledByDefault);
229     }
230 
setEnabled(boolean value)231     public void setEnabled(boolean value) {
232         logd("setEnabled(%s)", value);
233         setBoolean(Settings.Secure.SCREENSAVER_ENABLED, value);
234     }
235 
isActivatedOnDock()236     public boolean isActivatedOnDock() {
237         return getBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
238                 mDreamsActivatedOnDockByDefault);
239     }
240 
setActivatedOnDock(boolean value)241     public void setActivatedOnDock(boolean value) {
242         logd("setActivatedOnDock(%s)", value);
243         setBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, value);
244     }
245 
isActivatedOnSleep()246     public boolean isActivatedOnSleep() {
247         return getBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
248                 mDreamsActivatedOnSleepByDefault);
249     }
250 
setActivatedOnSleep(boolean value)251     public void setActivatedOnSleep(boolean value) {
252         logd("setActivatedOnSleep(%s)", value);
253         setBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, value);
254     }
255 
getBoolean(String key, boolean def)256     private boolean getBoolean(String key, boolean def) {
257         return Settings.Secure.getInt(mContext.getContentResolver(), key, def ? 1 : 0) == 1;
258     }
259 
setBoolean(String key, boolean value)260     private void setBoolean(String key, boolean value) {
261         Settings.Secure.putInt(mContext.getContentResolver(), key, value ? 1 : 0);
262     }
263 
setActiveDream(ComponentName dream)264     public void setActiveDream(ComponentName dream) {
265         logd("setActiveDream(%s)", dream);
266         if (mDreamManager == null) {
267             return;
268         }
269         try {
270             ComponentName[] dreams = {dream};
271             mDreamManager.setDreamComponents(dream == null ? null : dreams);
272         } catch (RemoteException e) {
273             Log.w(TAG, "Failed to set active dream to " + dream, e);
274         }
275     }
276 
getActiveDream()277     public ComponentName getActiveDream() {
278         if (mDreamManager == null) {
279             return null;
280         }
281         try {
282             ComponentName[] dreams = mDreamManager.getDreamComponents();
283             return dreams != null && dreams.length > 0 ? dreams[0] : null;
284         } catch (RemoteException e) {
285             Log.w(TAG, "Failed to get active dream", e);
286             return null;
287         }
288     }
289 
launchSettings(Context uiContext, DreamBackend.DreamInfo dreamInfo)290     public void launchSettings(Context uiContext, DreamBackend.DreamInfo dreamInfo) {
291         logd("launchSettings(%s)", dreamInfo);
292         if (dreamInfo == null || dreamInfo.settingsComponentName == null) {
293             return;
294         }
295         uiContext.startActivity(new Intent().setComponent(dreamInfo.settingsComponentName));
296     }
297 
preview(DreamBackend.DreamInfo dreamInfo)298     public void preview(DreamBackend.DreamInfo dreamInfo) {
299         logd("preview(%s)", dreamInfo);
300         if (mDreamManager == null || dreamInfo == null || dreamInfo.componentName == null) {
301             return;
302         }
303         try {
304             mDreamManager.testDream(mContext.getUserId(), dreamInfo.componentName);
305         } catch (RemoteException e) {
306             Log.w(TAG, "Failed to preview " + dreamInfo, e);
307         }
308     }
309 
startDreaming()310     public void startDreaming() {
311         logd("startDreaming()");
312         if (mDreamManager == null) {
313             return;
314         }
315         try {
316             mDreamManager.dream();
317         } catch (RemoteException e) {
318             Log.w(TAG, "Failed to dream", e);
319         }
320     }
321 
getDreamComponentName(ResolveInfo resolveInfo)322     private static ComponentName getDreamComponentName(ResolveInfo resolveInfo) {
323         if (resolveInfo == null || resolveInfo.serviceInfo == null) {
324             return null;
325         }
326         return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
327     }
328 
getSettingsComponentName(PackageManager pm, ResolveInfo resolveInfo)329     private static ComponentName getSettingsComponentName(PackageManager pm,
330             ResolveInfo resolveInfo) {
331         if (resolveInfo == null
332                 || resolveInfo.serviceInfo == null
333                 || resolveInfo.serviceInfo.metaData == null) {
334             return null;
335         }
336         String cn = null;
337         XmlResourceParser parser = null;
338         Exception caughtException = null;
339         try {
340             parser = resolveInfo.serviceInfo.loadXmlMetaData(pm, DreamService.DREAM_META_DATA);
341             if (parser == null) {
342                 Log.w(TAG, "No " + DreamService.DREAM_META_DATA + " meta-data");
343                 return null;
344             }
345             Resources res = pm.getResourcesForApplication(resolveInfo.serviceInfo.applicationInfo);
346             AttributeSet attrs = Xml.asAttributeSet(parser);
347             int type;
348             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
349                     && type != XmlPullParser.START_TAG) {
350             }
351             String nodeName = parser.getName();
352             if (!"dream".equals(nodeName)) {
353                 Log.w(TAG, "Meta-data does not start with dream tag");
354                 return null;
355             }
356             TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.Dream);
357             cn = sa.getString(
358                     res.getIdentifier("Dream_settingsActivity", "styleable", "android"));
359             sa.recycle();
360         } catch (PackageManager.NameNotFoundException | IOException | XmlPullParserException e) {
361             caughtException = e;
362         } finally {
363             if (parser != null) parser.close();
364         }
365         if (caughtException != null) {
366             Log.w(TAG, "Error parsing : " + resolveInfo.serviceInfo.packageName, caughtException);
367             return null;
368         }
369         if (cn != null && cn.indexOf('/') < 0) {
370             cn = resolveInfo.serviceInfo.packageName + "/" + cn;
371         }
372         return cn == null ? null : ComponentName.unflattenFromString(cn);
373     }
374 
logd(String msg, Object... args)375     private static void logd(String msg, Object... args) {
376         if (DEBUG) {
377             Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args));
378         }
379     }
380 
381     private static class DreamInfoComparator implements
382             Comparator<DreamBackend.DreamInfo> {
383         private final ComponentName mDefaultDream;
384 
DreamInfoComparator(ComponentName defaultDream)385         public DreamInfoComparator(ComponentName defaultDream) {
386             mDefaultDream = defaultDream;
387         }
388 
389         @Override
compare( DreamBackend.DreamInfo lhs, DreamBackend.DreamInfo rhs)390         public int compare(
391                 DreamBackend.DreamInfo lhs, DreamBackend.DreamInfo rhs) {
392             return sortKey(lhs).compareTo(sortKey(rhs));
393         }
394 
sortKey(DreamBackend.DreamInfo di)395         private String sortKey(DreamBackend.DreamInfo di) {
396             StringBuilder sb = new StringBuilder();
397             sb.append(di.componentName.equals(mDefaultDream) ? '0' : '1');
398             sb.append(di.caption);
399             return sb.toString();
400         }
401     }
402 }
403 
404