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