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