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