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