1 /* 2 * Copyright (C) 2022 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 static com.android.wallpaper.model.CreativeCategory.KEY_WALLPAPER_SAVE_CREATIVE_CATEGORY_WALLPAPER; 19 20 import android.annotation.Nullable; 21 import android.app.WallpaperInfo; 22 import android.content.ClipData; 23 import android.content.ContentProviderClient; 24 import android.content.ContentValues; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.PackageManager; 28 import android.database.Cursor; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.Parcel; 32 import android.text.TextUtils; 33 import android.util.Log; 34 35 import androidx.activity.result.contract.ActivityResultContract; 36 import androidx.annotation.NonNull; 37 38 import com.android.wallpaper.asset.Asset; 39 import com.android.wallpaper.asset.CreativeWallpaperThumbAsset; 40 41 import java.util.ArrayList; 42 import java.util.List; 43 44 /** 45 * Represents a creative live wallpaper component. 46 */ 47 public class CreativeWallpaperInfo extends LiveWallpaperInfo { 48 49 public static final Creator<CreativeWallpaperInfo> CREATOR = 50 new Creator<CreativeWallpaperInfo>() { 51 @Override 52 public CreativeWallpaperInfo createFromParcel(Parcel in) { 53 return new CreativeWallpaperInfo(in); 54 } 55 56 @Override 57 public CreativeWallpaperInfo[] newArray(int size) { 58 return new CreativeWallpaperInfo[size]; 59 } 60 }; 61 62 private Uri mConfigPreviewUri; 63 private Uri mCleanPreviewUri; 64 private Uri mDeleteUri; 65 private Uri mThumbnailUri; 66 private Uri mShareUri; 67 private String mTitle; 68 private String mAuthor; 69 private String mDescription; 70 private String mContentDescription; 71 private boolean mIsCurrent; 72 private String mGroupName; 73 74 private static final String TAG = "CreativeWallpaperInfo"; 75 76 private ArrayList<WallpaperAction> mEffectsToggles = new ArrayList<>(); 77 private String mEffectsBottomSheetTitle; 78 private String mEffectsBottomSheetSubtitle; 79 private Uri mClearActionsUri; 80 private Uri mEffectsUri = null; 81 private String mCurrentlyAppliedEffectId = null; 82 CreativeWallpaperInfo(WallpaperInfo info, String title, @Nullable String author, @Nullable String description, String contentDescription, Uri configPreviewUri, Uri cleanPreviewUri, Uri deleteUri, Uri thumbnailUri, Uri shareUri, String groupName, boolean isCurrent)83 public CreativeWallpaperInfo(WallpaperInfo info, String title, @Nullable String author, 84 @Nullable String description, String contentDescription, Uri configPreviewUri, 85 Uri cleanPreviewUri, Uri deleteUri, Uri thumbnailUri, Uri shareUri, String groupName, 86 boolean isCurrent) { 87 this(info, /* visibleTitle= */ false, /* collectionId= */ null); 88 mTitle = title; 89 mAuthor = author; 90 mDescription = description; 91 mContentDescription = contentDescription; 92 mConfigPreviewUri = configPreviewUri; 93 mCleanPreviewUri = cleanPreviewUri; 94 mDeleteUri = deleteUri; 95 mThumbnailUri = thumbnailUri; 96 mShareUri = shareUri; 97 mIsCurrent = isCurrent; 98 mGroupName = groupName; 99 } 100 CreativeWallpaperInfo(WallpaperInfo info, boolean isCurrent)101 public CreativeWallpaperInfo(WallpaperInfo info, boolean isCurrent) { 102 this(info, false, null); 103 mIsCurrent = isCurrent; 104 } 105 CreativeWallpaperInfo(WallpaperInfo info, boolean visibleTitle, @Nullable String collectionId)106 public CreativeWallpaperInfo(WallpaperInfo info, boolean visibleTitle, 107 @Nullable String collectionId) { 108 super(info, visibleTitle, collectionId); 109 } 110 CreativeWallpaperInfo(Parcel in)111 protected CreativeWallpaperInfo(Parcel in) { 112 super(in); 113 mTitle = in.readString(); 114 mAuthor = in.readString(); 115 mDescription = in.readString(); 116 mContentDescription = in.readString(); 117 mConfigPreviewUri = in.readParcelable(Uri.class.getClassLoader(), Uri.class); 118 mCleanPreviewUri = in.readParcelable(Uri.class.getClassLoader(), Uri.class); 119 mDeleteUri = in.readParcelable(Uri.class.getClassLoader(), Uri.class); 120 mThumbnailUri = in.readParcelable(Uri.class.getClassLoader(), Uri.class); 121 mShareUri = in.readParcelable(Uri.class.getClassLoader(), Uri.class); 122 mIsCurrent = in.readBoolean(); 123 mGroupName = in.readString(); 124 mCurrentlyAppliedEffectId = in.readString(); 125 mEffectsUri = in.readParcelable(Uri.class.getClassLoader(), Uri.class); 126 mClearActionsUri = in.readParcelable(Uri.class.getClassLoader(), Uri.class); 127 mEffectsToggles = in.readArrayList(WallpaperAction.class.getClassLoader(), 128 WallpaperAction.class); 129 mEffectsBottomSheetTitle = in.readString(); 130 mEffectsBottomSheetSubtitle = in.readString(); 131 } 132 133 @Override writeToParcel(Parcel parcel, int flags)134 public void writeToParcel(Parcel parcel, int flags) { 135 super.writeToParcel(parcel, flags); 136 parcel.writeString(mTitle); 137 parcel.writeString(mAuthor); 138 parcel.writeString(mDescription); 139 parcel.writeString(mContentDescription); 140 parcel.writeParcelable(mConfigPreviewUri, flags); 141 parcel.writeParcelable(mCleanPreviewUri, flags); 142 parcel.writeParcelable(mDeleteUri, flags); 143 parcel.writeParcelable(mThumbnailUri, flags); 144 parcel.writeParcelable(mShareUri, flags); 145 parcel.writeBoolean(mIsCurrent); 146 parcel.writeString(mGroupName); 147 parcel.writeString(mCurrentlyAppliedEffectId); 148 parcel.writeParcelable(mEffectsUri, flags); 149 parcel.writeParcelable(mClearActionsUri, flags); 150 parcel.writeList(mEffectsToggles); 151 parcel.writeString(mEffectsBottomSheetTitle); 152 parcel.writeString(mEffectsBottomSheetSubtitle); 153 } 154 155 /** 156 * Creates a new {@link ActivityResultContract} used to request the settings Activity overlay 157 * for this wallpaper. 158 * 159 * @param intent settings intent 160 */ getContract(Intent intent)161 public static ActivityResultContract<Void, Integer> getContract(Intent intent) { 162 return new ActivityResultContract<Void, Integer>() { 163 @NonNull 164 @Override 165 public Intent createIntent(@NonNull Context context, Void unused) { 166 return intent; 167 } 168 169 @Override 170 public Integer parseResult(int i, @Nullable Intent intent) { 171 return i; 172 } 173 }; 174 } 175 176 /** 177 * Loads the current wallpaper's effects. 178 * 179 * @param context context of the current android component 180 * @return an array list of WallpaperAction data objects 181 * for the currently previewing wallpaper 182 */ 183 @Nullable 184 public ArrayList<WallpaperAction> getWallpaperEffects(Context context) { 185 if (mEffectsUri == null) { 186 return null; 187 } 188 mEffectsToggles.clear(); 189 // TODO (269350033): Move content provider query off the main thread. 190 try (ContentProviderClient effectsClient = 191 context.getContentResolver().acquireContentProviderClient( 192 mEffectsUri.getAuthority())) { 193 try (Cursor effectsCursor = effectsClient.query(mEffectsUri, /* projection= */ null, 194 /* selection= */ null, /* selectionArgs= */ null, /* sortOrder= */ null)) { 195 if (effectsCursor == null) { 196 return null; 197 } 198 while (effectsCursor.moveToNext()) { 199 Uri effectsToggleUri = Uri.parse( 200 effectsCursor.getString(effectsCursor.getColumnIndex( 201 WallpaperInfoContract.WALLPAPER_EFFECTS_TOGGLE_URI))); 202 String effectsButtonLabel = effectsCursor.getString( 203 effectsCursor.getColumnIndex( 204 WallpaperInfoContract.WALLPAPER_EFFECTS_BUTTON_LABEL)); 205 String effectsId = effectsCursor.getString( 206 effectsCursor.getColumnIndex( 207 WallpaperInfoContract.WALLPAPER_EFFECTS_TOGGLE_ID)); 208 mEffectsToggles.add(new WallpaperAction(effectsButtonLabel, 209 effectsToggleUri, effectsId, /* toggled= */ false)); 210 } 211 return mEffectsToggles; 212 } 213 } catch (Exception e) { 214 Log.e(TAG, "Read wallpaper effects with exception.", e); 215 } 216 return null; 217 } 218 219 @Override 220 public String getTitle(Context context) { 221 if (mVisibleTitle) { 222 return mTitle; 223 } 224 return null; 225 } 226 227 @Override 228 public List<String> getAttributions(Context context) { 229 PackageManager packageManager = context.getPackageManager(); 230 CharSequence labelCharSeq = mInfo.loadLabel(packageManager); 231 List<String> attributions = new ArrayList<>(); 232 attributions.add(labelCharSeq == null ? null : labelCharSeq.toString()); 233 attributions.add(mAuthor == null ? "" : mAuthor); 234 attributions.add(mDescription == null ? "" : mDescription); 235 236 return attributions; 237 } 238 239 @Override 240 public String getContentDescription(Context context) { 241 return mContentDescription; 242 } 243 244 @Override 245 public Asset getThumbAsset(Context context) { 246 if (mThumbAsset == null) { 247 mThumbAsset = new CreativeWallpaperThumbAsset(context, mInfo, mThumbnailUri); 248 } 249 return mThumbAsset; 250 } 251 252 @Override 253 public String getGroupName(Context context) { 254 return mGroupName; 255 } 256 257 /** 258 * Calls the config URI to initialize the preview for this wallpaper. 259 */ 260 public void initializeWallpaperPreview(Context context) { 261 if (mConfigPreviewUri != null) { 262 context.getContentResolver().update(mConfigPreviewUri, new ContentValues(), null); 263 } 264 } 265 266 /** 267 * Calls the clean URI to de-initialize the preview for this wallpaper. 268 */ 269 public void cleanUpWallpaperPreview(Context context) { 270 if (mCleanPreviewUri != null) { 271 context.getContentResolver().update(mCleanPreviewUri, new ContentValues(), null); 272 } 273 } 274 275 /** 276 * Returns true if this wallpaper can be deleted. 277 */ 278 public boolean canBeDeleted() { 279 return mDeleteUri != null && !TextUtils.isEmpty(mDeleteUri.toString()); 280 } 281 282 @Override 283 public boolean isApplied(@Nullable WallpaperInfo currentHomeWallpaper, 284 @Nullable WallpaperInfo currentLockWallpaper) { 285 return super.isApplied(currentHomeWallpaper, currentLockWallpaper) && mIsCurrent; 286 } 287 288 /** 289 * Requests the content provider to delete this wallpaper. 290 */ 291 public void requestDelete(Context context) { 292 context.getContentResolver().delete(mDeleteUri, null, null); 293 } 294 295 public void setEffectsToggles(ArrayList<WallpaperAction> effectsToggles) { 296 mEffectsToggles = effectsToggles; 297 } 298 299 public ArrayList<WallpaperAction> getEffectsToggles() { 300 return mEffectsToggles; 301 } 302 303 public String getEffectsBottomSheetTitle() { 304 return mEffectsBottomSheetTitle; 305 } 306 307 public void setEffectsBottomSheetTitle(String effectsBottomSheetTitle) { 308 mEffectsBottomSheetTitle = effectsBottomSheetTitle; 309 } 310 311 /** 312 * Returns the URI that can be used to save a creative category wallpaper. 313 * @return the save wallpaper URI 314 */ 315 public Uri getSaveWallpaperUriForCreativeWallpaper() { 316 Bundle metaData = this.getWallpaperComponent().getServiceInfo().metaData; 317 if (metaData == null || !metaData.containsKey( 318 KEY_WALLPAPER_SAVE_CREATIVE_CATEGORY_WALLPAPER)) { 319 return null; 320 } 321 String keyForCreativeCategoryWallpaper = (String) metaData.get( 322 KEY_WALLPAPER_SAVE_CREATIVE_CATEGORY_WALLPAPER); 323 return Uri.parse(keyForCreativeCategoryWallpaper); 324 } 325 326 public String getEffectsBottomSheetSubtitle() { 327 return mEffectsBottomSheetSubtitle; 328 } 329 330 public void setEffectsBottomSheetSubtitle(String effectsBottomSheetSubtitle) { 331 mEffectsBottomSheetSubtitle = effectsBottomSheetSubtitle; 332 } 333 334 public Uri getClearActionsUri() { 335 return mClearActionsUri; 336 } 337 338 public void setClearActionsUri(Uri clearActionsUri) { 339 mClearActionsUri = clearActionsUri; 340 } 341 342 public Uri getEffectsUri() { 343 return mEffectsUri; 344 } 345 346 public void setEffectsUri(Uri effectsUri) { 347 mEffectsUri = effectsUri; 348 } 349 350 public String getCurrentlyAppliedEffectId() { 351 return mCurrentlyAppliedEffectId; 352 } 353 354 public void setCurrentlyAppliedEffectId(String currentlyAppliedEffectId) { 355 mCurrentlyAppliedEffectId = currentlyAppliedEffectId; 356 } 357 /** 358 * Returns true if this wallpaper can be shared. 359 */ 360 public boolean canBeShared() { 361 return mShareUri != null && !TextUtils.isEmpty(mShareUri.toString()); 362 } 363 364 /** 365 * Gets the share wallpaper image intent. 366 */ 367 public Intent getShareIntent() { 368 Intent shareIntent = new Intent(Intent.ACTION_SEND); 369 shareIntent.putExtra(Intent.EXTRA_STREAM, mShareUri); 370 shareIntent.setType("image/*"); 371 shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 372 shareIntent.setClipData(ClipData.newRawUri(null, mShareUri)); 373 return Intent.createChooser(shareIntent, null); 374 } 375 376 /** 377 * This method returns whether wallpaper effects are supported for this wallpaper. 378 * @return boolean 379 */ 380 public boolean doesSupportWallpaperEffects() { 381 return (mClearActionsUri != null && !TextUtils.isEmpty(mEffectsBottomSheetTitle)); 382 } 383 384 /** 385 * Triggers the content provider call to clear all effects upon the current. 386 * wallpaper. 387 */ 388 public void clearEffects(Context context) { 389 if (doesSupportWallpaperEffects() && mClearActionsUri != null) { 390 context.getContentResolver().update(mClearActionsUri, new ContentValues(), null); 391 } 392 } 393 394 /** 395 * Triggers the content provider call to apply the selected effect upon the current 396 * wallpaper. 397 */ 398 public void applyEffect(Context context, Uri applyEffectUri) { 399 if (doesSupportWallpaperEffects()) { 400 context.getContentResolver().update(applyEffectUri, new ContentValues(), null); 401 } 402 } 403 404 /** 405 * Creates an object of CreativeWallpaperInfo from the given cursor object. 406 * 407 * @param wallpaperInfo contains relevant metadata information about creative-category wallpaper 408 * @param cursor contains relevant info to create an object of CreativeWallpaperInfo 409 * @return an object of type CreativeWallpaperInfo 410 */ 411 @NonNull 412 public static CreativeWallpaperInfo buildFromCursor(WallpaperInfo wallpaperInfo, 413 Cursor cursor) { 414 String wallpaperTitle = cursor.getString( 415 cursor.getColumnIndex(WallpaperInfoContract.WALLPAPER_TITLE)); 416 String wallpaperAuthor = null; 417 if (cursor.getColumnIndex(WallpaperInfoContract.WALLPAPER_AUTHOR) >= 0) { 418 wallpaperAuthor = cursor.getString( 419 cursor.getColumnIndex(WallpaperInfoContract.WALLPAPER_AUTHOR)); 420 } 421 String wallpaperDescription = null; 422 if (cursor.getColumnIndex(WallpaperInfoContract.WALLPAPER_DESCRIPTION) >= 0) { 423 wallpaperDescription = cursor.getString( 424 cursor.getColumnIndex(WallpaperInfoContract.WALLPAPER_DESCRIPTION)); 425 } 426 String wallpaperContentDescription = null; 427 int wallpaperContentDescriptionIndex = cursor.getColumnIndex( 428 WallpaperInfoContract.WALLPAPER_CONTENT_DESCRIPTION); 429 if (wallpaperContentDescriptionIndex >= 0) { 430 wallpaperContentDescription = cursor.getString( 431 wallpaperContentDescriptionIndex); 432 } 433 Uri thumbnailUri = Uri.parse(cursor.getString(cursor.getColumnIndex( 434 WallpaperInfoContract.WALLPAPER_THUMBNAIL))); 435 Uri configPreviewUri = Uri.parse(cursor.getString(cursor.getColumnIndex( 436 WallpaperInfoContract.WALLPAPER_CONFIG_PREVIEW_URI))); 437 Uri cleanPreviewUri = Uri.parse(cursor.getString(cursor.getColumnIndex( 438 WallpaperInfoContract.WALLPAPER_CLEAN_PREVIEW_URI))); 439 Uri deleteUri = Uri.parse(cursor.getString( 440 cursor.getColumnIndex(WallpaperInfoContract.WALLPAPER_DELETE_URI))); 441 Uri shareUri = Uri.parse(cursor.getString(cursor.getColumnIndex( 442 WallpaperInfoContract.WALLPAPER_SHARE_URI))); 443 String groupName = cursor.getString( 444 cursor.getColumnIndex(WallpaperInfoContract.WALLPAPER_GROUP_NAME)); 445 int isCurrentApplied = cursor.getInt( 446 cursor.getColumnIndex(WallpaperInfoContract.WALLPAPER_IS_APPLIED)); 447 448 return new CreativeWallpaperInfo(wallpaperInfo, wallpaperTitle, wallpaperAuthor, 449 wallpaperDescription, wallpaperContentDescription, configPreviewUri, 450 cleanPreviewUri, deleteUri, thumbnailUri, shareUri, groupName, /* isCurrent= */ 451 (isCurrentApplied == 1)); 452 } 453 454 /** 455 * Saves a wallpaper of type of CreativeWallpaperInfo for a particular destination. 456 * @param context context of the calling activity 457 * @param destination depicts the destination of the wallpaper being saved 458 * @return CreativeWallpaperInfo object that has been saved 459 */ 460 @Override 461 public CreativeWallpaperInfo saveWallpaper(Context context, int destination) { 462 if (context == null) { 463 Log.w(TAG, "Context is null!!"); 464 return null; 465 } 466 467 Uri saveWallpaperUri = getSaveWallpaperUriForCreativeWallpaper(); 468 if (saveWallpaperUri == null) { 469 Log.w(TAG, "Missing save wallpaper uri in " + this.getWallpaperComponent() 470 .getServiceName()); 471 return null; 472 } 473 return CreativeCategory.saveCreativeCategoryWallpaper( 474 context, this, saveWallpaperUri, destination); 475 } 476 477 public void setConfigPreviewUri(Uri configPreviewUri) { 478 mConfigPreviewUri = configPreviewUri; 479 } 480 481 public Uri getConfigPreviewUri() { 482 return mConfigPreviewUri; 483 } 484 485 public Uri getCleanPreviewUri() { 486 return mCleanPreviewUri; 487 } 488 489 public Uri getDeleteUri() { 490 return mDeleteUri; 491 } 492 493 public Uri getThumbnailUri() { 494 return mThumbnailUri; 495 } 496 497 public Uri getShareUri() { 498 return mShareUri; 499 } 500 501 public String getTitle() { 502 return mTitle; 503 } 504 505 public String getAuthor() { 506 return mAuthor; 507 } 508 509 public String getDescription() { 510 return mDescription; 511 } 512 513 public String getContentDescription() { 514 return mContentDescription; 515 } 516 517 public boolean isCurrent() { 518 return mIsCurrent; 519 } 520 521 public String getGroupName() { 522 return mGroupName; 523 } 524 } 525