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