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