1 /*
2  * Copyright (C) 2023 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 
19 import static com.android.wallpaper.model.WallpaperInfoContract.WALLPAPER_EFFECTS_CLEAR_URI;
20 import static com.android.wallpaper.model.WallpaperInfoContract.WALLPAPER_EFFECTS_CURRENT_ID;
21 import static com.android.wallpaper.model.WallpaperInfoContract.WALLPAPER_EFFECTS_SECTION_SUBTITLE;
22 import static com.android.wallpaper.model.WallpaperInfoContract.WALLPAPER_EFFECTS_SECTION_TITLE;
23 
24 import android.annotation.Nullable;
25 import android.content.ContentProviderClient;
26 import android.content.Context;
27 import android.database.Cursor;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.text.TextUtils;
31 import android.util.Log;
32 
33 import com.android.wallpaper.asset.CreativeWallpaperThumbAsset;
34 
35 import java.util.ArrayList;
36 import java.util.List;
37 
38 /** The {@link WallpaperCategory} implements category for user created wallpapers. */
39 public class CreativeCategory extends WallpaperCategory {
40 
41     private static final String TAG = "CreativeCategory";
42     public static final String KEY_WALLPAPER_CREATIVE_CATEGORY =
43             "android.service.wallpaper.category";
44 
45     public static final String KEY_WALLPAPER_CREATIVE_WALLPAPERS =
46             "android.service.wallpaper.categorizedwallpapers";
47     public static final String KEY_WALLPAPER_CREATIVE_WALLPAPER_EFFECTS =
48             "android.service.wallpaper.effects";
49 
50     public final android.app.WallpaperInfo mWallpaperInfo;
51 
52     public static final String KEY_WALLPAPER_SAVE_CREATIVE_CATEGORY_WALLPAPER =
53             "android.service.wallpaper.savewallpaper";
54     public static final String KEY_WALLPAPER_SAVE_CREATIVE_WALLPAPER_CURRENT =
55             "android.service.wallpaper.currentwallpapers";
56 
57     /** Return true for CreativeCategories since we support user generated wallpapers here. */
58     @Override
supportsUserCreatedWallpapers()59     public boolean supportsUserCreatedWallpapers() {
60         return true;
61     }
62 
63     /**
64      * This function aims at saving a creative category wallpaper by calling the relevant URI
65      * of the creative category wallpaper provider.
66      *
67      * @param context Context of calling activity
68      * @param wallpaper instance of the WallpaperInfo that we want to save
69      * @param saveWallpaperUri the URI to be used for saving the wallpaper
70      * @param destination depicts the destination of the wallpaper being saved
71      */
72     @Nullable
saveCreativeCategoryWallpaper(Context context, LiveWallpaperInfo wallpaper, Uri saveWallpaperUri, int destination)73     public static CreativeWallpaperInfo saveCreativeCategoryWallpaper(Context context,
74             LiveWallpaperInfo wallpaper, Uri saveWallpaperUri, int destination) {
75 
76         // Adding destination field which depicts whether it is home-screen or lock-screen.
77         Uri updatedSaveWallpaperUri = saveWallpaperUri.buildUpon()
78                 .appendQueryParameter("destination", String.valueOf(destination))
79                 .build();
80         try (ContentProviderClient client =
81                      context.getContentResolver().acquireContentProviderClient(
82                              updatedSaveWallpaperUri.getAuthority())) {
83             if (client == null) {
84                 Log.w(TAG, "Couldn't resolve content provider for " + updatedSaveWallpaperUri);
85                 return null;
86             }
87             try (Cursor cursor = client.query(updatedSaveWallpaperUri, /* projection= */ null,
88                     /* selection= */ null, /* selectionArgs= */ null, /* sortOrder= */ null)) {
89                 if (cursor == null || !cursor.moveToFirst()) {
90                     return null;
91                 }
92                 return CreativeWallpaperInfo.buildFromCursor(wallpaper.getWallpaperComponent(),
93                         cursor);
94             } catch (Throwable e) {
95                 Log.e(TAG, "Couldn't read creative category.", e);
96             }
97         }
98         return null;
99     }
100 
CreativeCategory(Context context, String title, String collectionId, Uri thumbUri, List<WallpaperInfo> wallpaperInfos, int priority, android.app.WallpaperInfo wallpaperInfo)101     public CreativeCategory(Context context, String title, String collectionId, Uri thumbUri,
102             List<WallpaperInfo> wallpaperInfos, int priority,
103             android.app.WallpaperInfo wallpaperInfo) {
104         super(title,
105                 collectionId,
106                 wallpaperInfos.isEmpty() ? null : new CreativeWallpaperThumbAsset(context,
107                         wallpaperInfos.get(0).getWallpaperComponent(), thumbUri),
108                 wallpaperInfos,
109                 priority);
110         mWallpaperInfo = wallpaperInfo;
111     }
112 
113     @Override
supportsWallpaperSetUpdates()114     public boolean supportsWallpaperSetUpdates() {
115         return true;
116     }
117 
118     @Override
fetchWallpapers(Context context, WallpaperReceiver receiver, boolean forceReload)119     public void fetchWallpapers(Context context, WallpaperReceiver receiver, boolean forceReload) {
120         if (!forceReload) {
121             super.fetchWallpapers(context, receiver, forceReload);
122             return;
123         }
124         List<WallpaperInfo> wallpapers = readCreativeWallpapers(
125                 context, getCollectionId(), mWallpaperInfo);
126         synchronized (this) {
127             getMutableWallpapers().clear();
128             getMutableWallpapers().addAll(wallpapers);
129         }
130         if (receiver != null) {
131             receiver.onWallpapersReceived(wallpapers);
132         }
133     }
134 
135     /**
136      * Returns a list of [CreativeWallpaperInfo] objects by creating them using the relevant
137      * info. obtained from creative-category APK on device.
138      *
139      * @param context context of the hosting activity
140      * @param collectionId ID of the collection to which these wallpapers belong to
141      * @param wallpaperInfo contains relevant metadata information about creative-category wallpaper
142      * @return list of CreativeWallpaperInfo objects
143      */
readCreativeWallpapers(Context context, String collectionId, android.app.WallpaperInfo wallpaperInfo)144     public static List<WallpaperInfo> readCreativeWallpapers(Context context,
145             String collectionId, android.app.WallpaperInfo wallpaperInfo) {
146         List<WallpaperInfo> wallpapers = new ArrayList<>();
147         Bundle metaData = wallpaperInfo.getServiceInfo().metaData;
148         Uri wallpapersUri = Uri.parse((String) metaData.get(KEY_WALLPAPER_CREATIVE_WALLPAPERS));
149         try (ContentProviderClient client =
150                      context.getContentResolver().acquireContentProviderClient(
151                              wallpapersUri.getAuthority())) {
152             try (Cursor cursor = client.query(wallpapersUri, /* projection= */ null,
153                     /* selection= */ null, /* selectionArgs= */ null, /* sortOrder= */ null)) {
154                 if (cursor == null || !cursor.moveToFirst()) {
155                     return wallpapers;
156                 }
157                 do {
158                     String categoryId = cursor.getString(
159                             cursor.getColumnIndex(WallpaperInfoContract.CATEGORY_ID));
160                     if (!TextUtils.equals(categoryId, collectionId)) {
161                         continue;
162                     }
163                     CreativeWallpaperInfo creativeWallpaperInfo =
164                             CreativeWallpaperInfo.buildFromCursor(wallpaperInfo, cursor);
165                     // If the meta data for wallpaper actions exists, only then can we query the
166                     // action fields and action table.
167                     if (metaData.get(KEY_WALLPAPER_CREATIVE_WALLPAPER_EFFECTS) != null) {
168                         String effectsBottomSheetTitle = cursor.getString(
169                                 cursor.getColumnIndex(WALLPAPER_EFFECTS_SECTION_TITLE));
170                         String effectsBottomSheetSubtitle = cursor.getString(
171                                 cursor.getColumnIndex(WALLPAPER_EFFECTS_SECTION_SUBTITLE));
172                         String currentEffectId = cursor.getString(
173                                 cursor.getColumnIndex(WALLPAPER_EFFECTS_CURRENT_ID));
174                         Uri clearActionUri = Uri.parse(cursor.getString(
175                                 cursor.getColumnIndex(WALLPAPER_EFFECTS_CLEAR_URI)));
176 
177                         creativeWallpaperInfo.setEffectsBottomSheetTitle(
178                                 effectsBottomSheetTitle);
179                         creativeWallpaperInfo.setEffectsBottomSheetSubtitle(
180                                 effectsBottomSheetSubtitle);
181                         creativeWallpaperInfo.setClearActionsUri(clearActionUri);
182                         creativeWallpaperInfo.setCurrentlyAppliedEffectId(currentEffectId);
183 
184                         Uri effectsUri = Uri.parse((String) metaData.get(
185                                 KEY_WALLPAPER_CREATIVE_WALLPAPER_EFFECTS));
186                         creativeWallpaperInfo.setEffectsUri(effectsUri);
187                     }
188                     wallpapers.add(creativeWallpaperInfo);
189                 } while (cursor.moveToNext());
190             }
191         } catch (Throwable e) {
192             Log.e(TAG, "Exception reading creative wallpapers", e);
193         }
194         return wallpapers;
195     }
196 
197     @Override
supportsThirdParty()198     public boolean supportsThirdParty() {
199         return true;
200     }
201 
202     @Override
isSingleWallpaperCategory()203     public boolean isSingleWallpaperCategory() {
204         // if the category is empty then we go directly to the creation screen
205         // if there is at least one wallpaper then we show the collection of wallpapers
206         // NOTE: don't count the "add walpapper button" wallpaper. This is why <= 1 is being used
207         // as opposed to <= 0
208         return getMutableWallpapers() == null || getMutableWallpapers().size() <= 1;
209     }
210 }
211