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.WallpaperColors;
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.graphics.Bitmap;
23 import android.graphics.Color;
24 import android.graphics.Point;
25 import android.graphics.Rect;
26 import android.graphics.drawable.Drawable;
27 import android.net.Uri;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 
31 import androidx.annotation.DrawableRes;
32 import androidx.annotation.IntDef;
33 import androidx.annotation.Nullable;
34 
35 import com.android.wallpaper.asset.Asset;
36 import com.android.wallpaper.config.BaseFlags;
37 import com.android.wallpaper.module.InjectorProvider;
38 
39 import java.util.HashMap;
40 import java.util.Iterator;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.PriorityQueue;
44 import java.util.concurrent.CompletableFuture;
45 import java.util.concurrent.ExecutorService;
46 import java.util.concurrent.Executors;
47 import java.util.concurrent.Future;
48 
49 /**
50  * Interface for wallpaper info model.
51  */
52 public abstract class WallpaperInfo implements Parcelable {
53 
54     private static final ExecutorService sExecutor = Executors.newCachedThreadPool();
55     private ColorInfo mColorInfo = new ColorInfo();
56 
57     private PriorityQueue<String> mEffectNames = new PriorityQueue<>();
58 
59     protected final HashMap<String, String> mCropHints = new HashMap<>();
60 
WallpaperInfo()61     public WallpaperInfo() {
62     }
63 
WallpaperInfo(Parcel in)64     protected WallpaperInfo(Parcel in) {
65         mColorInfo = new ColorInfo(in.readParcelable(WallpaperColors.class.getClassLoader()),
66                 in.readInt());
67     }
68 
69     @Override
writeToParcel(Parcel parcel, int flags)70     public void writeToParcel(Parcel parcel, int flags) {
71         parcel.writeParcelable(mColorInfo.getWallpaperColors(), flags);
72         parcel.writeInt(mColorInfo.getPlaceholderColor());
73     }
74 
75     public static final int BACKUP_NOT_ALLOWED = 0;
76     public static final int BACKUP_ALLOWED = 1;
77 
78     /**
79      * @param context
80      * @return The title for this wallpaper, if applicable (as in a wallpaper "app" or live
81      * wallpaper), or null if not applicable.
82      */
getTitle(Context context)83     public String getTitle(Context context) {
84         return null;
85     }
86 
87     /**
88      * Returns the content description for this wallpaper, or null if none exists.
89      */
getContentDescription(Context context)90     public String getContentDescription(Context context) {
91         return null;
92     }
93 
94     /**
95      * @return The available attributions for this wallpaper, as a list of strings. These represent
96      * the author / website or any other attribution required to be displayed for this wallpaper
97      * regarding authorship, ownership, etc.
98      */
getAttributions(Context context)99     public abstract List<String> getAttributions(Context context);
100 
101     /**
102      * Returns the base (remote) image URL for this wallpaper, or null if none exists.
103      */
getBaseImageUrl()104     public String getBaseImageUrl() {
105         return null;
106     }
107 
108     /**
109      * Returns the action or "explore" URL for the wallpaper, or null if none exists.
110      */
getActionUrl(Context unused)111     public String getActionUrl(Context unused) {
112         return null;
113     }
114 
115     /** Returns the URI corresponding to the wallpaper, or null if none exists. */
getUri()116     public Uri getUri() {
117         return null;
118     }
119 
120     /**
121      * Returns the icon to use to represent the action link corresponding to
122      * {@link #getActionUrl(Context)}
123      */
124 
125     /**
126      * @param context
127      * @return An overlay icon to be used instead of a thumbnail, if appropriate, or null if not
128      * applicable.
129      */
getOverlayIcon(Context context)130     public Drawable getOverlayIcon(Context context) {
131         return null;
132     }
133 
134     ;
135 
136     @Override
describeContents()137     public int describeContents() {
138         return 0;
139     }
140 
141     /**
142      * @param context The client application's context.
143      * @return The {@link Asset} representing the wallpaper image.
144      */
getAsset(Context context)145     public abstract Asset getAsset(Context context);
146 
147     /**
148      * @param context The client application's context.
149      * @return The {@link Asset} representing the wallpaper's thumbnail.
150      */
getThumbAsset(Context context)151     public abstract Asset getThumbAsset(Context context);
152 
153     /**
154      * @param context The client application's context.
155      * @return An {@link Asset} that is appropriately sized to be directly set to the desktop. By
156      * default, this just the full wallpaper image asset (#getAsset) but subclasses may provide an
157      * Asset sized exactly for the device's primary display (i.e., cropped prior to providing a
158      * bitmap or input stream).
159      */
getDesktopAsset(Context context)160     public Asset getDesktopAsset(Context context) {
161         return getAsset(context);
162     }
163 
164     /**
165      * @return the {@link android.app.WallpaperInfo} associated with this wallpaper, which is
166      * generally present for live wallpapers, or null if there is none.
167      */
getWallpaperComponent()168     public android.app.WallpaperInfo getWallpaperComponent() {
169         return null;
170     }
171 
172     /**
173      * Returns the ID of the collection this image is associated with, if any.
174      */
getCollectionId(Context context)175     public abstract String getCollectionId(Context context);
176 
177     /**
178      * Returns the ID of this wallpaper or null if there is no ID.
179      */
getWallpaperId()180     public String getWallpaperId() {
181         return null;
182     }
183 
184     /**
185      * Returns the distinct ID of the stored wallpaper or null if there is no ID.
186      */
getStoredWallpaperId(Context context)187     public String getStoredWallpaperId(Context context) {
188         if (getWallpaperId() == null) {
189             return null;
190         }
191         return getCollectionId(context) + "-" + getWallpaperId();
192     }
193 
194     /**
195      * Returns whether backup is allowed for this wallpaper.
196      */
197     @BackupPermission
getBackupPermission()198     public int getBackupPermission() {
199         return BACKUP_ALLOWED;
200     }
201 
202     /**
203      * Shows the appropriate preview activity for this WallpaperInfo.
204      *
205      * @param srcActivity
206      * @param factory     A factory for showing the inline preview activity for within this app.
207      *                    Only used for certain WallpaperInfo implementations that require an inline preview
208      *                    (as opposed to some external preview activity).
209      * @param requestCode Request code to pass in when starting the inline preview activity.
210      */
showPreview(Activity srcActivity, InlinePreviewIntentFactory factory, int requestCode, boolean isAssetIdPresent)211     public abstract void showPreview(Activity srcActivity, InlinePreviewIntentFactory factory,
212                                      int requestCode, boolean isAssetIdPresent);
213 
214     /**
215      * Returns a Future to obtain a wallpaper color and a placeholder color calculated in a
216      * background thread for this wallpaper's thumbnail.
217      * If it's already available, the Future will return the color immediately.
218      * This is intended to be a "best effort" attempt and might not obtain a color if no low res
219      * thumbnail is available.
220      */
computeColorInfo(Context context)221     public Future<ColorInfo> computeColorInfo(Context context) {
222         if (mColorInfo.getWallpaperColors() != null
223                 && mColorInfo.getPlaceholderColor() != Color.TRANSPARENT) {
224             return CompletableFuture.completedFuture(mColorInfo);
225         }
226         final Context appContext = context.getApplicationContext();
227         return sExecutor.submit(() -> {
228             synchronized (WallpaperInfo.this) {
229                 if (mColorInfo.getWallpaperColors() != null
230                         && mColorInfo.getPlaceholderColor() != Color.TRANSPARENT) {
231                     return mColorInfo;
232                 }
233 
234                 Asset thumbAsset = getThumbAsset(appContext);
235                 Bitmap lowResBitmap = thumbAsset.getLowResBitmap(appContext);
236                 if (lowResBitmap == null) {
237                     return new ColorInfo(
238                             new WallpaperColors(Color.valueOf(Color.TRANSPARENT), null, null),
239                             Color.TRANSPARENT);
240                 }
241                 mColorInfo = new ColorInfo(WallpaperColors.fromBitmap(lowResBitmap));
242                 return mColorInfo;
243             }
244         });
245     }
246 
247     /**
248      * Remove the effect name from this wallpaper, only use it for logging.
249      */
250     public void removeEffectName(String effect) {
251         mEffectNames.remove(effect);
252     }
253 
254     /**
255      * Add the effect name apply with this wallpaper, only use it for logging.
256      */
257     public void addEffectName(String effect) {
258         mEffectNames.add(effect);
259     }
260 
261     /**
262      * Returns the effects apply with this wallpaper.
263      */
264     public String getEffectNames() {
265         if (mEffectNames.isEmpty()) {
266             return null;
267         }
268         String effectNames = "";
269         Iterator value = mEffectNames.iterator();
270         while (value.hasNext()) {
271             if (!effectNames.isEmpty()) {
272                 effectNames += ",";
273             }
274             effectNames += value.next();
275         }
276 
277         return effectNames;
278     }
279 
280     /**
281      * Whether backup is allowed for this type of wallpaper.
282      */
283     @IntDef({
284             BACKUP_NOT_ALLOWED,
285             BACKUP_ALLOWED
286     })
287     public @interface BackupPermission {
288     }
289 
290     /**
291      * Returns a group name under which this Wallpaper should be grouped when displayed in
292      * a gallery, or an empty String if no grouping is required.
293      */
294     public String getGroupName(Context context) {
295         return "";
296     }
297 
298     /**
299      * Returns the resource id of a drawable to use as a badge when displaying this wallpaper
300      * in a gallery, or {@link Resources#ID_NULL} if no badge is required.
301      */
302     @DrawableRes
303     public int getBadgeDrawableRes() {
304         return Resources.ID_NULL;
305     }
306 
307     /** Sets the crop {@link Rect} of each displaySize for this wallpaper. */
308     public void setWallpaperCropHints(Map<Point, Rect> cropHints) {
309         if (cropHints == null) {
310             return;
311         }
312 
313         cropHints.forEach((displaySize, rect) -> {
314             if (rect != null) {
315                 mCropHints.put(displaySize.flattenToString(),
316                         rect.flattenToString());
317             }
318         });
319     }
320 
321     /** Returns the crop {@link Rect} of each displaySize for this wallpaper. */
322     public @Nullable Map<Point, Rect> getWallpaperCropHints() {
323         BaseFlags flags = InjectorProvider.getInjector().getFlags();
324         if (!flags.isMultiCropEnabled()) {
325             return null;
326         }
327 
328         Map<Point, Rect> cropHints = new HashMap<>();
329         mCropHints.forEach(
330                 (displaySize, rect) -> cropHints.put(
331                         Point.unflattenFromString(displaySize),
332                         Rect.unflattenFromString(rect)));
333         return cropHints;
334     }
335 
336     /**
337      * Inner class to keep wallpaper colors and placeholder color.
338      */
339     public static class ColorInfo {
340         private WallpaperColors mWallpaperColors;
341         private Integer mPlaceholderColor = Color.TRANSPARENT;
342 
343         public ColorInfo() {
344         }
345 
346         public ColorInfo(WallpaperColors wallpaperColors) {
347             mWallpaperColors = wallpaperColors;
348             if (mWallpaperColors != null) {
349                 mPlaceholderColor = mWallpaperColors.getPrimaryColor().toArgb();
350             }
351         }
352 
353         public ColorInfo(WallpaperColors wallpaperColors, Integer placeholderColor) {
354             mWallpaperColors = wallpaperColors;
355             mPlaceholderColor = placeholderColor;
356         }
357 
358         public WallpaperColors getWallpaperColors() {
359             return mWallpaperColors;
360         }
361 
362         public Integer getPlaceholderColor() {
363             return mPlaceholderColor;
364         }
365     }
366 
367     public ColorInfo getColorInfo() {
368         return mColorInfo;
369     }
370 }
371