1 /* 2 * Copyright (C) 2017 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 package com.android.wallpaper.model; 17 18 import android.app.Activity; 19 import android.app.WallpaperManager; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.PackageManager; 24 import android.content.pm.ResolveInfo; 25 import android.content.res.Resources; 26 import android.net.Uri; 27 import android.os.Parcel; 28 import android.service.wallpaper.WallpaperService; 29 import android.text.TextUtils; 30 import android.util.AttributeSet; 31 import android.util.Log; 32 33 import androidx.annotation.Nullable; 34 35 import com.android.wallpaper.R; 36 import com.android.wallpaper.asset.Asset; 37 import com.android.wallpaper.asset.LiveWallpaperThumbAsset; 38 import com.android.wallpaper.compat.BuildCompat; 39 import com.android.wallpaper.module.InjectorProvider; 40 import com.android.wallpaper.module.LiveWallpaperInfoFactory; 41 import com.android.wallpaper.util.ActivityUtils; 42 43 import org.xmlpull.v1.XmlPullParserException; 44 45 import java.io.IOException; 46 import java.text.Collator; 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.Collections; 50 import java.util.Comparator; 51 import java.util.Iterator; 52 import java.util.List; 53 import java.util.Set; 54 55 /** 56 * Represents a live wallpaper from the system. 57 */ 58 public class LiveWallpaperInfo extends WallpaperInfo { 59 public static final Creator<LiveWallpaperInfo> CREATOR = 60 new Creator<LiveWallpaperInfo>() { 61 @Override 62 public LiveWallpaperInfo createFromParcel(Parcel in) { 63 return new LiveWallpaperInfo(in); 64 } 65 66 @Override 67 public LiveWallpaperInfo[] newArray(int size) { 68 return new LiveWallpaperInfo[size]; 69 } 70 }; 71 72 public static final String TAG_NAME = "live-wallpaper"; 73 74 private static final String TAG = "LiveWallpaperInfo"; 75 public static final String ATTR_ID = "id"; 76 public static final String ATTR_PACKAGE = "package"; 77 public static final String ATTR_SERVICE = "service"; 78 79 /** 80 * Creates a new {@link LiveWallpaperInfo} from an XML {@link AttributeSet} 81 * @param context used to construct the {@link android.app.WallpaperInfo} associated with the 82 * new {@link LiveWallpaperInfo} 83 * @param categoryId Id of the category the new wallpaper will belong to 84 * @param attrs {@link AttributeSet} to parse 85 * @return a newly created {@link LiveWallpaperInfo} or {@code null} if one couldn't be created. 86 */ 87 @Nullable fromAttributeSet(Context context, String categoryId, AttributeSet attrs)88 public static LiveWallpaperInfo fromAttributeSet(Context context, String categoryId, 89 AttributeSet attrs) { 90 String wallpaperId = attrs.getAttributeValue(null, ATTR_ID); 91 if (TextUtils.isEmpty(wallpaperId)) { 92 Log.w(TAG, "Live wallpaper declaration without id in category " + categoryId); 93 return null; 94 } 95 String packageName = attrs.getAttributeValue(null, ATTR_PACKAGE); 96 String serviceName = attrs.getAttributeValue(null, ATTR_SERVICE); 97 return fromPackageAndServiceName(context, categoryId, wallpaperId, packageName, 98 serviceName); 99 } 100 101 /** 102 * Creates a new {@link LiveWallpaperInfo} from its individual components 103 * @return a newly created {@link LiveWallpaperInfo} or {@code null} if one couldn't be created. 104 */ 105 @Nullable fromPackageAndServiceName(Context context, String categoryId, String wallpaperId, String packageName, String serviceName)106 public static LiveWallpaperInfo fromPackageAndServiceName(Context context, String categoryId, 107 String wallpaperId, String packageName, String serviceName) { 108 if (TextUtils.isEmpty(serviceName)) { 109 Log.w(TAG, "Live wallpaper declaration without service: " + wallpaperId); 110 return null; 111 } 112 113 Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE); 114 if (TextUtils.isEmpty(packageName)) { 115 String [] parts = serviceName.split("/"); 116 if (parts != null && parts.length == 2) { 117 packageName = parts[0]; 118 serviceName = parts[1]; 119 } else { 120 Log.w(TAG, "Live wallpaper declaration with invalid service: " + wallpaperId); 121 return null; 122 } 123 } 124 intent.setClassName(packageName, serviceName); 125 List<ResolveInfo> resolveInfos = context.getPackageManager().queryIntentServices(intent, 126 PackageManager.GET_META_DATA); 127 if (resolveInfos.isEmpty()) { 128 Log.w(TAG, "Couldn't find live wallpaper for " + serviceName); 129 return null; 130 } 131 android.app.WallpaperInfo wallpaperInfo; 132 try { 133 wallpaperInfo = new android.app.WallpaperInfo(context, resolveInfos.get(0)); 134 } catch (XmlPullParserException | IOException e) { 135 Log.w(TAG, "Skipping wallpaper " + resolveInfos.get(0).serviceInfo, e); 136 return null; 137 } 138 139 return new LiveWallpaperInfo(wallpaperInfo, false, categoryId); 140 } 141 142 protected android.app.WallpaperInfo mInfo; 143 protected LiveWallpaperThumbAsset mThumbAsset; 144 private boolean mVisibleTitle; 145 @Nullable private final String mCollectionId; 146 147 /** 148 * Constructs a LiveWallpaperInfo wrapping the given system WallpaperInfo object, representing 149 * a particular live wallpaper. 150 * 151 * @param info 152 */ LiveWallpaperInfo(android.app.WallpaperInfo info)153 public LiveWallpaperInfo(android.app.WallpaperInfo info) { 154 this(info, true, null); 155 } 156 157 /** 158 * Constructs a LiveWallpaperInfo wrapping the given system WallpaperInfo object, representing 159 * a particular live wallpaper. 160 */ LiveWallpaperInfo(android.app.WallpaperInfo info, boolean visibleTitle, @Nullable String collectionId)161 public LiveWallpaperInfo(android.app.WallpaperInfo info, boolean visibleTitle, 162 @Nullable String collectionId) { 163 mInfo = info; 164 mVisibleTitle = visibleTitle; 165 mCollectionId = collectionId; 166 } 167 LiveWallpaperInfo(Parcel in)168 protected LiveWallpaperInfo(Parcel in) { 169 super(in); 170 mInfo = in.readParcelable(android.app.WallpaperInfo.class.getClassLoader()); 171 mVisibleTitle = in.readInt() == 1; 172 mCollectionId = in.readString(); 173 } 174 175 /** 176 * Returns all live wallpapers found on the device, excluding those residing in APKs described by 177 * the package names in excludedPackageNames. 178 */ getAll(Context context, @Nullable Set<String> excludedPackageNames)179 public static List<WallpaperInfo> getAll(Context context, 180 @Nullable Set<String> excludedPackageNames) { 181 List<ResolveInfo> resolveInfos = getAllOnDevice(context); 182 List<WallpaperInfo> wallpaperInfos = new ArrayList<>(); 183 LiveWallpaperInfoFactory factory = 184 InjectorProvider.getInjector().getLiveWallpaperInfoFactory(context); 185 for (int i = 0; i < resolveInfos.size(); i++) { 186 ResolveInfo resolveInfo = resolveInfos.get(i); 187 android.app.WallpaperInfo wallpaperInfo; 188 try { 189 wallpaperInfo = new android.app.WallpaperInfo(context, resolveInfo); 190 } catch (XmlPullParserException | IOException e) { 191 Log.w(TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e); 192 continue; 193 } 194 195 if (excludedPackageNames != null && excludedPackageNames.contains( 196 wallpaperInfo.getPackageName())) { 197 continue; 198 } 199 200 wallpaperInfos.add(factory.getLiveWallpaperInfo(wallpaperInfo)); 201 } 202 203 return wallpaperInfos; 204 } 205 206 /** 207 * Returns the live wallpapers having the given service names, found within the APK with the 208 * given package name. 209 */ getFromSpecifiedPackage( Context context, String packageName, @Nullable List<String> serviceNames, boolean shouldShowTitle)210 public static List<WallpaperInfo> getFromSpecifiedPackage( 211 Context context, String packageName, @Nullable List<String> serviceNames, 212 boolean shouldShowTitle) { 213 List<ResolveInfo> resolveInfos; 214 if (serviceNames != null) { 215 resolveInfos = getAllContainingServiceNames(context, serviceNames); 216 } else { 217 resolveInfos = getAllOnDevice(context); 218 } 219 List<WallpaperInfo> wallpaperInfos = new ArrayList<>(); 220 LiveWallpaperInfoFactory factory = 221 InjectorProvider.getInjector().getLiveWallpaperInfoFactory(context); 222 223 for (int i = 0; i < resolveInfos.size(); i++) { 224 ResolveInfo resolveInfo = resolveInfos.get(i); 225 if (resolveInfo == null) { 226 Log.e(TAG, "Found a null resolve info"); 227 continue; 228 } 229 230 android.app.WallpaperInfo wallpaperInfo; 231 try { 232 wallpaperInfo = new android.app.WallpaperInfo(context, resolveInfo); 233 } catch (XmlPullParserException e) { 234 Log.w(TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e); 235 continue; 236 } catch (IOException e) { 237 Log.w(TAG, "Skipping wallpaper " + resolveInfo.serviceInfo, e); 238 continue; 239 } 240 241 if (!packageName.equals(wallpaperInfo.getPackageName())) { 242 continue; 243 } 244 245 wallpaperInfos.add(factory.getLiveWallpaperInfo(wallpaperInfo, shouldShowTitle, null)); 246 } 247 248 return wallpaperInfos; 249 } 250 251 /** 252 * Returns ResolveInfo objects for all live wallpaper services with the specified fully qualified 253 * service names, keeping order intact. 254 */ getAllContainingServiceNames(Context context, List<String> serviceNames)255 private static List<ResolveInfo> getAllContainingServiceNames(Context context, 256 List<String> serviceNames) { 257 final PackageManager pm = context.getPackageManager(); 258 259 List<ResolveInfo> allResolveInfos = pm.queryIntentServices( 260 new Intent(WallpaperService.SERVICE_INTERFACE), 261 PackageManager.GET_META_DATA); 262 263 // Filter ALL live wallpapers for only those in the list of specified service names. 264 // Prefer this approach so we can make only one call to PackageManager (expensive!) rather than 265 // one call per live wallpaper. 266 ResolveInfo[] specifiedResolveInfos = new ResolveInfo[serviceNames.size()]; 267 for (ResolveInfo resolveInfo : allResolveInfos) { 268 int index = serviceNames.indexOf(resolveInfo.serviceInfo.name); 269 if (index != -1) { 270 specifiedResolveInfos[index] = resolveInfo; 271 } 272 } 273 274 return Arrays.asList(specifiedResolveInfos); 275 } 276 277 /** 278 * Returns ResolveInfo objects for all live wallpaper services installed on the device. System 279 * wallpapers are listed first, unsorted, with other installed wallpapers following sorted 280 * in alphabetical order. 281 */ getAllOnDevice(Context context)282 private static List<ResolveInfo> getAllOnDevice(Context context) { 283 final PackageManager pm = context.getPackageManager(); 284 final String packageName = context.getPackageName(); 285 286 List<ResolveInfo> resolveInfos = pm.queryIntentServices( 287 new Intent(WallpaperService.SERVICE_INTERFACE), 288 PackageManager.GET_META_DATA); 289 290 List<ResolveInfo> wallpaperInfos = new ArrayList<>(); 291 292 // Remove the "Rotating Image Wallpaper" live wallpaper, which is owned by this package, 293 // and separate system wallpapers to sort only non-system ones. 294 Iterator<ResolveInfo> iter = resolveInfos.iterator(); 295 while (iter.hasNext()) { 296 ResolveInfo resolveInfo = iter.next(); 297 if (packageName.equals(resolveInfo.serviceInfo.packageName)) { 298 iter.remove(); 299 } else if (isSystemApp(resolveInfo.serviceInfo.applicationInfo)) { 300 wallpaperInfos.add(resolveInfo); 301 iter.remove(); 302 } 303 } 304 305 if (resolveInfos.isEmpty()) { 306 return wallpaperInfos; 307 } 308 309 // Sort non-system wallpapers alphabetically and append them to system ones 310 Collections.sort(resolveInfos, new Comparator<ResolveInfo>() { 311 final Collator mCollator = Collator.getInstance(); 312 313 @Override 314 public int compare(ResolveInfo info1, ResolveInfo info2) { 315 return mCollator.compare(info1.loadLabel(pm), info2.loadLabel(pm)); 316 } 317 }); 318 wallpaperInfos.addAll(resolveInfos); 319 320 return wallpaperInfos; 321 } 322 323 /** 324 * @return whether the given app is a system app 325 */ isSystemApp(ApplicationInfo appInfo)326 public static boolean isSystemApp(ApplicationInfo appInfo) { 327 return (appInfo.flags & (ApplicationInfo.FLAG_SYSTEM 328 | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0; 329 } 330 331 @Override getTitle(Context context)332 public String getTitle(Context context) { 333 if (mVisibleTitle) { 334 CharSequence labelCharSeq = mInfo.loadLabel(context.getPackageManager()); 335 return labelCharSeq == null ? null : labelCharSeq.toString(); 336 } 337 return null; 338 } 339 340 @Override getAttributions(Context context)341 public List<String> getAttributions(Context context) { 342 List<String> attributions = new ArrayList<>(); 343 PackageManager packageManager = context.getPackageManager(); 344 CharSequence labelCharSeq = mInfo.loadLabel(packageManager); 345 attributions.add(labelCharSeq == null ? null : labelCharSeq.toString()); 346 347 try { 348 CharSequence authorCharSeq = mInfo.loadAuthor(packageManager); 349 if (authorCharSeq != null) { 350 String author = authorCharSeq.toString(); 351 attributions.add(author); 352 } 353 } catch (Resources.NotFoundException e) { 354 // No author specified, so no other attribution to add. 355 } 356 357 try { 358 CharSequence descCharSeq = mInfo.loadDescription(packageManager); 359 if (descCharSeq != null) { 360 String desc = descCharSeq.toString(); 361 attributions.add(desc); 362 } 363 } catch (Resources.NotFoundException e) { 364 // No description specified, so no other attribution to add. 365 } 366 367 return attributions; 368 } 369 370 @Override getActionUrl(Context context)371 public String getActionUrl(Context context) { 372 if (BuildCompat.isAtLeastNMR1()) { 373 try { 374 Uri wallpaperContextUri = mInfo.loadContextUri(context.getPackageManager()); 375 if (wallpaperContextUri != null) { 376 return wallpaperContextUri.toString(); 377 } 378 } catch (Resources.NotFoundException e) { 379 return null; 380 } 381 } 382 383 return null; 384 } 385 386 /** 387 * Get an optional description for the action button if provided by this LiveWallpaper. 388 */ 389 @Nullable getActionDescription(Context context)390 public CharSequence getActionDescription(Context context) { 391 try { 392 return mInfo.loadContextDescription(context.getPackageManager()); 393 } catch (Resources.NotFoundException e) { 394 return null; 395 } 396 } 397 398 @Override getAsset(Context context)399 public Asset getAsset(Context context) { 400 return null; 401 } 402 403 @Override getThumbAsset(Context context)404 public Asset getThumbAsset(Context context) { 405 if (mThumbAsset == null) { 406 mThumbAsset = new LiveWallpaperThumbAsset(context, mInfo); 407 } 408 return mThumbAsset; 409 } 410 411 @Override showPreview(Activity srcActivity, InlinePreviewIntentFactory factory, int requestCode)412 public void showPreview(Activity srcActivity, InlinePreviewIntentFactory factory, 413 int requestCode) { 414 //Only use internal live picker if available, otherwise, default to the Framework one 415 if (factory.shouldUseInternalLivePicker(srcActivity)) { 416 srcActivity.startActivityForResult(factory.newIntent(srcActivity, this), requestCode); 417 } else { 418 Intent preview = new Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER); 419 preview.putExtra(WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT, mInfo.getComponent()); 420 ActivityUtils.startActivityForResultSafely(srcActivity, preview, requestCode); 421 } 422 } 423 424 @Override writeToParcel(Parcel parcel, int i)425 public void writeToParcel(Parcel parcel, int i) { 426 super.writeToParcel(parcel, i); 427 parcel.writeParcelable(mInfo, 0 /* flags */); 428 parcel.writeInt(mVisibleTitle ? 1 : 0); 429 parcel.writeString(mCollectionId); 430 } 431 432 @Override getWallpaperComponent()433 public android.app.WallpaperInfo getWallpaperComponent() { 434 return mInfo; 435 } 436 437 @Override getCollectionId(Context context)438 public String getCollectionId(Context context) { 439 return TextUtils.isEmpty(mCollectionId) 440 ? context.getString(R.string.live_wallpaper_collection_id) 441 : mCollectionId; 442 } 443 444 @Override getWallpaperId()445 public String getWallpaperId() { 446 return mInfo.getServiceName(); 447 } 448 } 449