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.os.UserHandle; 32 import android.provider.Settings; 33 import android.service.dreams.DreamService; 34 import android.service.dreams.IDreamManager; 35 import android.util.ArraySet; 36 import android.util.Log; 37 38 import com.android.internal.annotations.VisibleForTesting; 39 import com.android.internal.util.FrameworkStatsLog; 40 41 import java.lang.annotation.Retention; 42 import java.lang.annotation.RetentionPolicy; 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.Comparator; 46 import java.util.List; 47 import java.util.Set; 48 import java.util.stream.Collectors; 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 public CharSequence description; 61 public Drawable previewImage; 62 public boolean supportsComplications = false; 63 64 @Override toString()65 public String toString() { 66 StringBuilder sb = new StringBuilder(DreamInfo.class.getSimpleName()); 67 sb.append('[').append(caption); 68 if (isActive) { 69 sb.append(",active"); 70 } 71 sb.append(',').append(componentName); 72 if (settingsComponentName != null) { 73 sb.append("settings=").append(settingsComponentName); 74 } 75 return sb.append(']').toString(); 76 } 77 } 78 79 @Retention(RetentionPolicy.SOURCE) 80 @IntDef({WHILE_CHARGING, WHILE_DOCKED, EITHER, NEVER}) 81 public @interface WhenToDream { 82 } 83 84 public static final int WHILE_CHARGING = 0; 85 public static final int WHILE_DOCKED = 1; 86 public static final int EITHER = 2; 87 public static final int NEVER = 3; 88 89 /** 90 * The type of dream complications which can be provided by a 91 * {@link com.android.systemui.dreams.ComplicationProvider}. 92 */ 93 @IntDef(prefix = {"COMPLICATION_TYPE_"}, value = { 94 COMPLICATION_TYPE_TIME, 95 COMPLICATION_TYPE_DATE, 96 COMPLICATION_TYPE_WEATHER, 97 COMPLICATION_TYPE_AIR_QUALITY, 98 COMPLICATION_TYPE_CAST_INFO, 99 COMPLICATION_TYPE_HOME_CONTROLS, 100 COMPLICATION_TYPE_SMARTSPACE, 101 COMPLICATION_TYPE_MEDIA_ENTRY 102 }) 103 @Retention(RetentionPolicy.SOURCE) 104 public @interface ComplicationType { 105 } 106 107 public static final int COMPLICATION_TYPE_TIME = 1; 108 public static final int COMPLICATION_TYPE_DATE = 2; 109 public static final int COMPLICATION_TYPE_WEATHER = 3; 110 public static final int COMPLICATION_TYPE_AIR_QUALITY = 4; 111 public static final int COMPLICATION_TYPE_CAST_INFO = 5; 112 public static final int COMPLICATION_TYPE_HOME_CONTROLS = 6; 113 public static final int COMPLICATION_TYPE_SMARTSPACE = 7; 114 public static final int COMPLICATION_TYPE_MEDIA_ENTRY = 8; 115 116 private static final int SCREENSAVER_HOME_CONTROLS_ENABLED_DEFAULT = 1; 117 private static final int LOCKSCREEN_SHOW_CONTROLS_DEFAULT = 0; 118 119 private static final int DS_TYPE_ENABLED = FrameworkStatsLog 120 .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_ENABLED; 121 private static final int DS_TYPE_WHEN_TO_DREAM = FrameworkStatsLog 122 .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_WHEN_TO_DREAM; 123 private static final int DS_TYPE_DREAM_COMPONENT = FrameworkStatsLog 124 .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_DREAM_COMPONENT; 125 private static final int DS_TYPE_SHOW_ADDITIONAL_INFO = FrameworkStatsLog 126 .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_SHOW_ADDITIONAL_INFO; 127 private static final int DS_TYPE_SHOW_HOME_CONTROLS = FrameworkStatsLog 128 .DREAM_SETTING_CHANGED__DREAM_SETTING_TYPE__DREAM_SETTING_TYPE_SHOW_HOME_CONTROLS; 129 130 private static final int WHEN_TO_DREAM_UNSPECIFIED = FrameworkStatsLog 131 .DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_UNSPECIFIED; 132 private static final int WHEN_TO_DREAM_CHARGING = FrameworkStatsLog 133 .DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_WHILE_CHARGING_ONLY; 134 private static final int WHEN_TO_DREAM_DOCKED = FrameworkStatsLog 135 .DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_WHILE_DOCKED_ONLY; 136 private static final int WHEN_TO_DREAM_CHARGING_OR_DOCKED = FrameworkStatsLog 137 .DREAM_SETTING_CHANGED__WHEN_TO_DREAM__WHEN_TO_DREAM_EITHER_CHARGING_OR_DOCKED; 138 139 private final Context mContext; 140 private final IDreamManager mDreamManager; 141 private final DreamInfoComparator mComparator; 142 private final boolean mDreamsEnabledByDefault; 143 private final boolean mDreamsActivatedOnSleepByDefault; 144 private final boolean mDreamsActivatedOnDockByDefault; 145 private final Set<ComponentName> mDisabledDreams; 146 private final List<String> mLoggableDreamPrefixes; 147 private Set<Integer> mSupportedComplications; 148 private static DreamBackend sInstance; 149 getInstance(Context context)150 public static DreamBackend getInstance(Context context) { 151 if (sInstance == null) { 152 sInstance = new DreamBackend(context); 153 } 154 return sInstance; 155 } 156 DreamBackend(Context context)157 public DreamBackend(Context context) { 158 mContext = context.getApplicationContext(); 159 final Resources resources = mContext.getResources(); 160 161 mDreamManager = IDreamManager.Stub.asInterface( 162 ServiceManager.getService(DreamService.DREAM_SERVICE)); 163 mComparator = new DreamInfoComparator(getDefaultDream()); 164 mDreamsEnabledByDefault = resources.getBoolean( 165 com.android.internal.R.bool.config_dreamsEnabledByDefault); 166 mDreamsActivatedOnSleepByDefault = resources.getBoolean( 167 com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault); 168 mDreamsActivatedOnDockByDefault = resources.getBoolean( 169 com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault); 170 mDisabledDreams = Arrays.stream(resources.getStringArray( 171 com.android.internal.R.array.config_disabledDreamComponents)) 172 .map(ComponentName::unflattenFromString) 173 .collect(Collectors.toSet()); 174 mLoggableDreamPrefixes = Arrays.stream(resources.getStringArray( 175 com.android.internal.R.array.config_loggable_dream_prefixes)).toList(); 176 177 mSupportedComplications = Arrays.stream(resources.getIntArray( 178 com.android.internal.R.array.config_supportedDreamComplications)) 179 .boxed() 180 .collect(Collectors.toSet()); 181 } 182 getDreamInfos()183 public List<DreamInfo> getDreamInfos() { 184 logd("getDreamInfos()"); 185 ComponentName activeDream = getActiveDream(); 186 PackageManager pm = mContext.getPackageManager(); 187 Intent dreamIntent = new Intent(DreamService.SERVICE_INTERFACE); 188 List<ResolveInfo> resolveInfos = pm.queryIntentServices(dreamIntent, 189 PackageManager.GET_META_DATA); 190 List<DreamInfo> dreamInfos = new ArrayList<>(resolveInfos.size()); 191 for (ResolveInfo resolveInfo : resolveInfos) { 192 final ComponentName componentName = getDreamComponentName(resolveInfo); 193 if (componentName == null || mDisabledDreams.contains(componentName)) { 194 continue; 195 } 196 197 DreamInfo dreamInfo = new DreamInfo(); 198 dreamInfo.caption = resolveInfo.loadLabel(pm); 199 dreamInfo.icon = resolveInfo.loadIcon(pm); 200 dreamInfo.description = getDescription(resolveInfo, pm); 201 dreamInfo.componentName = componentName; 202 dreamInfo.isActive = dreamInfo.componentName.equals(activeDream); 203 204 final DreamService.DreamMetadata dreamMetadata = DreamService.getDreamMetadata(mContext, 205 resolveInfo.serviceInfo); 206 if (dreamMetadata != null) { 207 dreamInfo.settingsComponentName = dreamMetadata.settingsActivity; 208 dreamInfo.previewImage = dreamMetadata.previewImage; 209 dreamInfo.supportsComplications = dreamMetadata.showComplications; 210 } 211 dreamInfos.add(dreamInfo); 212 } 213 dreamInfos.sort(mComparator); 214 return dreamInfos; 215 } 216 getDescription(ResolveInfo resolveInfo, PackageManager pm)217 private static CharSequence getDescription(ResolveInfo resolveInfo, PackageManager pm) { 218 String packageName = resolveInfo.resolvePackageName; 219 ApplicationInfo applicationInfo = null; 220 if (packageName == null) { 221 packageName = resolveInfo.serviceInfo.packageName; 222 applicationInfo = resolveInfo.serviceInfo.applicationInfo; 223 } 224 if (resolveInfo.serviceInfo.descriptionRes != 0) { 225 return pm.getText(packageName, 226 resolveInfo.serviceInfo.descriptionRes, 227 applicationInfo); 228 } 229 return null; 230 } 231 getDefaultDream()232 public ComponentName getDefaultDream() { 233 if (mDreamManager == null) { 234 return null; 235 } 236 try { 237 return mDreamManager.getDefaultDreamComponentForUser(mContext.getUserId()); 238 } catch (RemoteException e) { 239 Log.w(TAG, "Failed to get default dream", e); 240 return null; 241 } 242 } 243 getActiveDreamName()244 public CharSequence getActiveDreamName() { 245 ComponentName cn = getActiveDream(); 246 if (cn != null) { 247 PackageManager pm = mContext.getPackageManager(); 248 try { 249 ServiceInfo ri = pm.getServiceInfo(cn, 0); 250 if (ri != null) { 251 return ri.loadLabel(pm); 252 } 253 } catch (PackageManager.NameNotFoundException exc) { 254 return null; // uninstalled? 255 } 256 } 257 return null; 258 } 259 260 /** 261 * Gets an icon from active dream. 262 */ getActiveIcon()263 public Drawable getActiveIcon() { 264 final ComponentName cn = getActiveDream(); 265 if (cn != null) { 266 final PackageManager pm = mContext.getPackageManager(); 267 try { 268 final ServiceInfo ri = pm.getServiceInfo(cn, 0); 269 if (ri != null) { 270 return ri.loadIcon(pm); 271 } 272 } catch (PackageManager.NameNotFoundException exc) { 273 return null; 274 } 275 } 276 return null; 277 } 278 279 @WhenToDream getWhenToDreamSetting()280 public int getWhenToDreamSetting() { 281 return isActivatedOnDock() && isActivatedOnSleep() ? EITHER 282 : isActivatedOnDock() ? WHILE_DOCKED 283 : isActivatedOnSleep() ? WHILE_CHARGING 284 : NEVER; 285 } 286 setWhenToDream(@henToDream int whenToDream)287 public void setWhenToDream(@WhenToDream int whenToDream) { 288 setEnabled(whenToDream != NEVER); 289 290 switch (whenToDream) { 291 case WHILE_CHARGING: 292 setActivatedOnDock(false); 293 setActivatedOnSleep(true); 294 break; 295 296 case WHILE_DOCKED: 297 setActivatedOnDock(true); 298 setActivatedOnSleep(false); 299 break; 300 301 case EITHER: 302 setActivatedOnDock(true); 303 setActivatedOnSleep(true); 304 break; 305 306 case NEVER: 307 default: 308 break; 309 } 310 311 logDreamSettingChangeToStatsd(DS_TYPE_WHEN_TO_DREAM); 312 } 313 314 /** Gets all complications which have been enabled by the user. */ getEnabledComplications()315 public Set<Integer> getEnabledComplications() { 316 final Set<Integer> enabledComplications = 317 getComplicationsEnabled() 318 ? new ArraySet<>(mSupportedComplications) : new ArraySet<>(); 319 320 if (!getHomeControlsEnabled()) { 321 enabledComplications.remove(COMPLICATION_TYPE_HOME_CONTROLS); 322 } else if (mSupportedComplications.contains(COMPLICATION_TYPE_HOME_CONTROLS)) { 323 // Add home control type to list of enabled complications, even if other complications 324 // have been disabled. 325 enabledComplications.add(COMPLICATION_TYPE_HOME_CONTROLS); 326 } 327 return enabledComplications; 328 } 329 330 /** Sets complication enabled state. */ setComplicationsEnabled(boolean enabled)331 public void setComplicationsEnabled(boolean enabled) { 332 Settings.Secure.putInt(mContext.getContentResolver(), 333 Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, enabled ? 1 : 0); 334 logDreamSettingChangeToStatsd(DS_TYPE_SHOW_ADDITIONAL_INFO); 335 } 336 337 /** Sets whether home controls are enabled by the user on the dream */ setHomeControlsEnabled(boolean enabled)338 public void setHomeControlsEnabled(boolean enabled) { 339 Settings.Secure.putInt(mContext.getContentResolver(), 340 Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, enabled ? 1 : 0); 341 logDreamSettingChangeToStatsd(DS_TYPE_SHOW_HOME_CONTROLS); 342 } 343 344 /** Gets whether home controls button is enabled on the dream */ getHomeControlsEnabled()345 private boolean getHomeControlsEnabled() { 346 return Settings.Secure.getInt( 347 mContext.getContentResolver(), 348 Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 349 LOCKSCREEN_SHOW_CONTROLS_DEFAULT) == 1 350 && Settings.Secure.getInt( 351 mContext.getContentResolver(), 352 Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, 353 SCREENSAVER_HOME_CONTROLS_ENABLED_DEFAULT) == 1; 354 } 355 356 /** 357 * Gets whether complications are enabled on this device 358 */ getComplicationsEnabled()359 public boolean getComplicationsEnabled() { 360 return Settings.Secure.getInt( 361 mContext.getContentResolver(), 362 Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, 1) == 1; 363 } 364 365 /** Gets all dream complications which are supported on this device. **/ getSupportedComplications()366 public Set<Integer> getSupportedComplications() { 367 return mSupportedComplications; 368 } 369 370 /** 371 * Sets the list of supported complications. Should only be used in tests. 372 */ 373 @VisibleForTesting setSupportedComplications(Set<Integer> complications)374 public void setSupportedComplications(Set<Integer> complications) { 375 mSupportedComplications = complications; 376 } 377 isEnabled()378 public boolean isEnabled() { 379 return getBoolean(Settings.Secure.SCREENSAVER_ENABLED, mDreamsEnabledByDefault); 380 } 381 setEnabled(boolean value)382 public void setEnabled(boolean value) { 383 logd("setEnabled(%s)", value); 384 setBoolean(Settings.Secure.SCREENSAVER_ENABLED, value); 385 logDreamSettingChangeToStatsd(DS_TYPE_ENABLED); 386 } 387 isActivatedOnDock()388 public boolean isActivatedOnDock() { 389 return getBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, 390 mDreamsActivatedOnDockByDefault); 391 } 392 setActivatedOnDock(boolean value)393 public void setActivatedOnDock(boolean value) { 394 logd("setActivatedOnDock(%s)", value); 395 setBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, value); 396 } 397 isActivatedOnSleep()398 public boolean isActivatedOnSleep() { 399 return getBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 400 mDreamsActivatedOnSleepByDefault); 401 } 402 setActivatedOnSleep(boolean value)403 public void setActivatedOnSleep(boolean value) { 404 logd("setActivatedOnSleep(%s)", value); 405 setBoolean(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, value); 406 } 407 getBoolean(String key, boolean def)408 private boolean getBoolean(String key, boolean def) { 409 return Settings.Secure.getInt(mContext.getContentResolver(), key, def ? 1 : 0) == 1; 410 } 411 setBoolean(String key, boolean value)412 private void setBoolean(String key, boolean value) { 413 Settings.Secure.putInt(mContext.getContentResolver(), key, value ? 1 : 0); 414 } 415 setActiveDream(ComponentName dream)416 public void setActiveDream(ComponentName dream) { 417 logd("setActiveDream(%s)", dream); 418 if (mDreamManager == null) { 419 return; 420 } 421 try { 422 ComponentName[] dreams = {dream}; 423 mDreamManager.setDreamComponents(dream == null ? null : dreams); 424 logDreamSettingChangeToStatsd(DS_TYPE_DREAM_COMPONENT); 425 } catch (RemoteException e) { 426 Log.w(TAG, "Failed to set active dream to " + dream, e); 427 } 428 } 429 getActiveDream()430 public ComponentName getActiveDream() { 431 if (mDreamManager == null) { 432 return null; 433 } 434 try { 435 ComponentName[] dreams = mDreamManager.getDreamComponents(); 436 return dreams != null && dreams.length > 0 ? dreams[0] : null; 437 } catch (RemoteException e) { 438 Log.w(TAG, "Failed to get active dream", e); 439 return null; 440 } 441 } 442 launchSettings(Context uiContext, DreamInfo dreamInfo)443 public void launchSettings(Context uiContext, DreamInfo dreamInfo) { 444 logd("launchSettings(%s)", dreamInfo); 445 if (dreamInfo == null || dreamInfo.settingsComponentName == null) { 446 return; 447 } 448 final Intent intent = new Intent() 449 .setComponent(dreamInfo.settingsComponentName) 450 .addFlags( 451 Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 452 uiContext.startActivity(intent); 453 } 454 455 /** 456 * Preview a dream, given the component name. 457 */ preview(ComponentName componentName)458 public void preview(ComponentName componentName) { 459 logd("preview(%s)", componentName); 460 if (mDreamManager == null || componentName == null) { 461 return; 462 } 463 try { 464 mDreamManager.testDream(mContext.getUserId(), componentName); 465 } catch (RemoteException e) { 466 Log.w(TAG, "Failed to preview " + componentName, e); 467 } 468 } 469 startDreaming()470 public void startDreaming() { 471 logd("startDreaming()"); 472 if (mDreamManager == null) { 473 return; 474 } 475 try { 476 mDreamManager.dream(); 477 } catch (RemoteException e) { 478 Log.w(TAG, "Failed to dream", e); 479 } 480 } 481 getDreamComponentName(ResolveInfo resolveInfo)482 private static ComponentName getDreamComponentName(ResolveInfo resolveInfo) { 483 if (resolveInfo == null || resolveInfo.serviceInfo == null) { 484 return null; 485 } 486 return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name); 487 } 488 logd(String msg, Object... args)489 private static void logd(String msg, Object... args) { 490 if (DEBUG) { 491 Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args)); 492 } 493 } 494 logDreamSettingChangeToStatsd(int dreamSettingType)495 private void logDreamSettingChangeToStatsd(int dreamSettingType) { 496 FrameworkStatsLog.write( 497 FrameworkStatsLog.DREAM_SETTING_CHANGED, /*atom_tag*/ 498 UserHandle.myUserId(), /*uid*/ 499 isEnabled(), /*enabled*/ 500 getActiveDreamComponentForStatsd(), /*dream_component*/ 501 getWhenToDreamForStatsd(), /*when_to_dream*/ 502 getComplicationsEnabled(), /*show_additional_info*/ 503 getHomeControlsEnabled(), /*show_home_controls*/ 504 dreamSettingType /*dream_setting_type*/ 505 ); 506 } 507 508 /** 509 * Returns the user selected dream component in string format for stats logging. If the dream 510 * component is not loggable, returns "other". 511 */ getActiveDreamComponentForStatsd()512 private String getActiveDreamComponentForStatsd() { 513 final ComponentName activeDream = getActiveDream(); 514 if (activeDream == null) { 515 return ""; 516 } 517 518 final String component = activeDream.flattenToShortString(); 519 if (isLoggableDreamComponentForStatsd(component)) { 520 return component; 521 } else { 522 return "other"; 523 } 524 } 525 526 /** 527 * Whether the dream component is loggable. Only components from the predefined packages are 528 * allowed to be logged for privacy. 529 */ isLoggableDreamComponentForStatsd(String component)530 private boolean isLoggableDreamComponentForStatsd(String component) { 531 for (int i = 0; i < mLoggableDreamPrefixes.size(); i++) { 532 if (component.startsWith(mLoggableDreamPrefixes.get(i))) { 533 return true; 534 } 535 } 536 537 return false; 538 } 539 540 /** 541 * Returns the enum of "when to dream" setting for statsd logging. 542 */ getWhenToDreamForStatsd()543 private int getWhenToDreamForStatsd() { 544 switch (getWhenToDreamSetting()) { 545 case WHILE_CHARGING: 546 return WHEN_TO_DREAM_CHARGING; 547 case WHILE_DOCKED: 548 return WHEN_TO_DREAM_DOCKED; 549 case EITHER: 550 return WHEN_TO_DREAM_CHARGING_OR_DOCKED; 551 case NEVER: 552 default: 553 return WHEN_TO_DREAM_UNSPECIFIED; 554 } 555 } 556 557 private static class DreamInfoComparator implements Comparator<DreamInfo> { 558 private final ComponentName mDefaultDream; 559 DreamInfoComparator(ComponentName defaultDream)560 public DreamInfoComparator(ComponentName defaultDream) { 561 mDefaultDream = defaultDream; 562 } 563 564 @Override compare(DreamInfo lhs, DreamInfo rhs)565 public int compare(DreamInfo lhs, DreamInfo rhs) { 566 return sortKey(lhs).compareTo(sortKey(rhs)); 567 } 568 sortKey(DreamInfo di)569 private String sortKey(DreamInfo di) { 570 StringBuilder sb = new StringBuilder(); 571 sb.append(di.componentName.equals(mDefaultDream) ? '0' : '1'); 572 sb.append(di.caption); 573 return sb.toString(); 574 } 575 } 576 } 577