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
17package com.android.settingslib.dream;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.pm.PackageManager;
23import android.content.pm.ResolveInfo;
24import android.content.pm.ServiceInfo;
25import android.content.res.Resources;
26import android.content.res.TypedArray;
27import android.content.res.XmlResourceParser;
28import android.graphics.drawable.Drawable;
29import android.os.RemoteException;
30import android.os.ServiceManager;
31import android.provider.Settings;
32import android.service.dreams.DreamService;
33import android.service.dreams.IDreamManager;
34import android.util.AttributeSet;
35import android.util.Log;
36import android.util.Xml;
37
38import org.xmlpull.v1.XmlPullParser;
39import org.xmlpull.v1.XmlPullParserException;
40
41import java.io.IOException;
42import java.util.ArrayList;
43import java.util.Collections;
44import java.util.Comparator;
45import java.util.List;
46
47public class DreamBackend {
48    private static final String TAG = "DreamBackend";
49    private static final boolean DEBUG = false;
50
51    public static class DreamInfo {
52        public CharSequence caption;
53        public Drawable icon;
54        public boolean isActive;
55        public ComponentName componentName;
56        public ComponentName settingsComponentName;
57
58        @Override
59        public String toString() {
60            StringBuilder sb = new StringBuilder(DreamInfo.class.getSimpleName());
61            sb.append('[').append(caption);
62            if (isActive)
63                sb.append(",active");
64            sb.append(',').append(componentName);
65            if (settingsComponentName != null)
66                sb.append("settings=").append(settingsComponentName);
67            return sb.append(']').toString();
68        }
69    }
70
71    private final Context mContext;
72    private final IDreamManager mDreamManager;
73    private final DreamInfoComparator mComparator;
74    private final boolean mDreamsEnabledByDefault;
75    private final boolean mDreamsActivatedOnSleepByDefault;
76    private final boolean mDreamsActivatedOnDockByDefault;
77
78    public DreamBackend(Context context) {
79        mContext = context;
80        mDreamManager = IDreamManager.Stub.asInterface(
81                ServiceManager.getService(DreamService.DREAM_SERVICE));
82        mComparator = new DreamInfoComparator(getDefaultDream());
83        mDreamsEnabledByDefault = context.getResources()
84                .getBoolean(com.android.internal.R.bool.config_dreamsEnabledByDefault);
85        mDreamsActivatedOnSleepByDefault = context.getResources()
86                .getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault);
87        mDreamsActivatedOnDockByDefault = context.getResources()
88                .getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
89    }
90
91    public List<DreamInfo> getDreamInfos() {
92        logd("getDreamInfos()");
93        ComponentName activeDream = getActiveDream();
94        PackageManager pm = mContext.getPackageManager();
95        Intent dreamIntent = new Intent(DreamService.SERVICE_INTERFACE);
96        List<ResolveInfo> resolveInfos = pm.queryIntentServices(dreamIntent,
97                PackageManager.GET_META_DATA);
98        List<DreamInfo> dreamInfos = new ArrayList<>(resolveInfos.size());
99        for (ResolveInfo resolveInfo : resolveInfos) {
100            if (resolveInfo.serviceInfo == null)
101                continue;
102            DreamInfo dreamInfo = new DreamInfo();
103            dreamInfo.caption = resolveInfo.loadLabel(pm);
104            dreamInfo.icon = resolveInfo.loadIcon(pm);
105            dreamInfo.componentName = getDreamComponentName(resolveInfo);
106            dreamInfo.isActive = dreamInfo.componentName.equals(activeDream);
107            dreamInfo.settingsComponentName = getSettingsComponentName(pm, resolveInfo);
108            dreamInfos.add(dreamInfo);
109        }
110        Collections.sort(dreamInfos, mComparator);
111        return dreamInfos;
112    }
113
114    public ComponentName getDefaultDream() {
115        if (mDreamManager == null)
116            return null;
117        try {
118            return mDreamManager.getDefaultDreamComponent();
119        } catch (RemoteException e) {
120            Log.w(TAG, "Failed to get default dream", e);
121            return null;
122        }
123    }
124
125    public CharSequence getActiveDreamName() {
126        ComponentName cn = getActiveDream();
127        if (cn != null) {
128            PackageManager pm = mContext.getPackageManager();
129            try {
130                ServiceInfo ri = pm.getServiceInfo(cn, 0);
131                if (ri != null) {
132                    return ri.loadLabel(pm);
133                }
134            } catch (PackageManager.NameNotFoundException exc) {
135                return null; // uninstalled?
136            }
137        }
138        return null;
139    }
140
141    public boolean isEnabled() {
142        return getBoolean(Settings.Secure.SCREENSAVER_ENABLED, mDreamsEnabledByDefault);
143    }
144
145    public void setEnabled(boolean value) {
146        logd("setEnabled(%s)", value);
147        setBoolean(Settings.Secure.SCREENSAVER_ENABLED, value);
148    }
149
150    public boolean isActivatedOnDock() {
151        return getBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
152                mDreamsActivatedOnDockByDefault);
153    }
154
155    public void setActivatedOnDock(boolean value) {
156        logd("setActivatedOnDock(%s)", value);
157        setBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, value);
158    }
159
160    public boolean isActivatedOnSleep() {
161        return getBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
162                mDreamsActivatedOnSleepByDefault);
163    }
164
165    public void setActivatedOnSleep(boolean value) {
166        logd("setActivatedOnSleep(%s)", value);
167        setBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, value);
168    }
169
170    private boolean getBoolean(String key, boolean def) {
171        return Settings.Secure.getInt(mContext.getContentResolver(), key, def ? 1 : 0) == 1;
172    }
173
174    private void setBoolean(String key, boolean value) {
175        Settings.Secure.putInt(mContext.getContentResolver(), key, value ? 1 : 0);
176    }
177
178    public void setActiveDream(ComponentName dream) {
179        logd("setActiveDream(%s)", dream);
180        if (mDreamManager == null)
181            return;
182        try {
183            ComponentName[] dreams = { dream };
184            mDreamManager.setDreamComponents(dream == null ? null : dreams);
185        } catch (RemoteException e) {
186            Log.w(TAG, "Failed to set active dream to " + dream, e);
187        }
188    }
189
190    public ComponentName getActiveDream() {
191        if (mDreamManager == null)
192            return null;
193        try {
194            ComponentName[] dreams = mDreamManager.getDreamComponents();
195            return dreams != null && dreams.length > 0 ? dreams[0] : null;
196        } catch (RemoteException e) {
197            Log.w(TAG, "Failed to get active dream", e);
198            return null;
199        }
200    }
201
202    public void launchSettings(DreamInfo dreamInfo) {
203        logd("launchSettings(%s)", dreamInfo);
204        if (dreamInfo == null || dreamInfo.settingsComponentName == null)
205            return;
206        mContext.startActivity(new Intent().setComponent(dreamInfo.settingsComponentName));
207    }
208
209    public void preview(DreamInfo dreamInfo) {
210        logd("preview(%s)", dreamInfo);
211        if (mDreamManager == null || dreamInfo == null || dreamInfo.componentName == null)
212            return;
213        try {
214            mDreamManager.testDream(dreamInfo.componentName);
215        } catch (RemoteException e) {
216            Log.w(TAG, "Failed to preview " + dreamInfo, e);
217        }
218    }
219
220    public void startDreaming() {
221        logd("startDreaming()");
222        if (mDreamManager == null)
223            return;
224        try {
225            mDreamManager.dream();
226        } catch (RemoteException e) {
227            Log.w(TAG, "Failed to dream", e);
228        }
229    }
230
231    private static ComponentName getDreamComponentName(ResolveInfo resolveInfo) {
232        if (resolveInfo == null || resolveInfo.serviceInfo == null)
233            return null;
234        return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
235    }
236
237    private static ComponentName getSettingsComponentName(PackageManager pm, ResolveInfo resolveInfo) {
238        if (resolveInfo == null
239                || resolveInfo.serviceInfo == null
240                || resolveInfo.serviceInfo.metaData == null)
241            return null;
242        String cn = null;
243        XmlResourceParser parser = null;
244        Exception caughtException = null;
245        try {
246            parser = resolveInfo.serviceInfo.loadXmlMetaData(pm, DreamService.DREAM_META_DATA);
247            if (parser == null) {
248                Log.w(TAG, "No " + DreamService.DREAM_META_DATA + " meta-data");
249                return null;
250            }
251            Resources res = pm.getResourcesForApplication(resolveInfo.serviceInfo.applicationInfo);
252            AttributeSet attrs = Xml.asAttributeSet(parser);
253            int type;
254            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
255                    && type != XmlPullParser.START_TAG) {
256            }
257            String nodeName = parser.getName();
258            if (!"dream".equals(nodeName)) {
259                Log.w(TAG, "Meta-data does not start with dream tag");
260                return null;
261            }
262            TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.Dream);
263            cn = sa.getString(com.android.internal.R.styleable.Dream_settingsActivity);
264            sa.recycle();
265        } catch (PackageManager.NameNotFoundException|IOException|XmlPullParserException e) {
266            caughtException = e;
267        } finally {
268            if (parser != null) parser.close();
269        }
270        if (caughtException != null) {
271            Log.w(TAG, "Error parsing : " + resolveInfo.serviceInfo.packageName, caughtException);
272            return null;
273        }
274        if (cn != null && cn.indexOf('/') < 0) {
275            cn = resolveInfo.serviceInfo.packageName + "/" + cn;
276        }
277        return cn == null ? null : ComponentName.unflattenFromString(cn);
278    }
279
280    private static void logd(String msg, Object... args) {
281        if (DEBUG)
282            Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args));
283    }
284
285    private static class DreamInfoComparator implements Comparator<DreamInfo> {
286        private final ComponentName mDefaultDream;
287
288        public DreamInfoComparator(ComponentName defaultDream) {
289            mDefaultDream = defaultDream;
290        }
291
292        @Override
293        public int compare(DreamInfo lhs, DreamInfo rhs) {
294            return sortKey(lhs).compareTo(sortKey(rhs));
295        }
296
297        private String sortKey(DreamInfo di) {
298            StringBuilder sb = new StringBuilder();
299            sb.append(di.componentName.equals(mDefaultDream) ? '0' : '1');
300            sb.append(di.caption);
301            return sb.toString();
302        }
303    }
304}
305