blob: e2f05300760258d3cb3bea08b61fda29756ff4a1 [file] [log] [blame]
Jon Miranda16ea1b12017-12-12 14:52:48 -08001/*
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 */
16package com.android.wallpaper.module;
17
Chuck Liaoe2d35f52020-09-22 21:20:47 +080018import static com.android.wallpaper.module.NetworkStatusNotifier.NETWORK_NOT_INITIALIZED;
19
Jon Miranda16ea1b12017-12-12 14:52:48 -080020import android.content.Context;
21import android.content.pm.PackageManager;
Santiago Etchebeherea2429992020-05-27 11:27:29 -070022import android.content.res.Resources;
23import android.content.res.XmlResourceParser;
Jon Miranda16ea1b12017-12-12 14:52:48 -080024import android.os.AsyncTask;
Santiago Etchebeherea2429992020-05-27 11:27:29 -070025import android.util.Log;
26import android.util.Xml;
27
28import androidx.annotation.XmlRes;
Jon Miranda16ea1b12017-12-12 14:52:48 -080029
30import com.android.wallpaper.R;
31import com.android.wallpaper.model.Category;
32import com.android.wallpaper.model.CategoryProvider;
33import com.android.wallpaper.model.CategoryReceiver;
34import com.android.wallpaper.model.DefaultWallpaperInfo;
Jon Miranda16ea1b12017-12-12 14:52:48 -080035import com.android.wallpaper.model.ImageCategory;
36import com.android.wallpaper.model.LegacyPartnerWallpaperInfo;
37import com.android.wallpaper.model.LiveWallpaperInfo;
38import com.android.wallpaper.model.PartnerWallpaperInfo;
Santiago Etchebeherea2429992020-05-27 11:27:29 -070039import com.android.wallpaper.model.SystemStaticWallpaperInfo;
Jon Miranda16ea1b12017-12-12 14:52:48 -080040import com.android.wallpaper.model.ThirdPartyAppCategory;
Santiago Etchebeherea2429992020-05-27 11:27:29 -070041import com.android.wallpaper.model.ThirdPartyLiveWallpaperCategory;
Jon Miranda16ea1b12017-12-12 14:52:48 -080042import com.android.wallpaper.model.WallpaperCategory;
43import com.android.wallpaper.model.WallpaperInfo;
Chuck Liaoe2d35f52020-09-22 21:20:47 +080044import com.android.wallpaper.module.NetworkStatusNotifier.NetworkStatus;
Jon Miranda16ea1b12017-12-12 14:52:48 -080045
Santiago Etchebeherea2429992020-05-27 11:27:29 -070046import org.xmlpull.v1.XmlPullParser;
47import org.xmlpull.v1.XmlPullParserException;
48
49import java.io.IOException;
Jon Miranda16ea1b12017-12-12 14:52:48 -080050import java.util.ArrayList;
51import java.util.Arrays;
Santiago Etchebeherea2429992020-05-27 11:27:29 -070052import java.util.Collections;
53import java.util.HashSet;
Jon Miranda16ea1b12017-12-12 14:52:48 -080054import java.util.List;
Chuck Liao144d9b12020-10-21 13:25:33 +080055import java.util.Locale;
Santiago Etchebeherea2429992020-05-27 11:27:29 -070056import java.util.Set;
57import java.util.stream.Collectors;
Jon Miranda16ea1b12017-12-12 14:52:48 -080058
59/**
60 * Default implementation of CategoryProvider.
61 */
62public class DefaultCategoryProvider implements CategoryProvider {
63
Santiago Etchebeherea2429992020-05-27 11:27:29 -070064 private static final String TAG = "DefaultCategoryProvider";
65
Jon Miranda16ea1b12017-12-12 14:52:48 -080066 /**
67 * Relative category priorities. Lower numbers correspond to higher priorities (i.e., should
68 * appear higher in the categories list).
69 */
Chuck Liaoc4462d02021-05-20 01:06:23 +080070 protected static final int PRIORITY_MY_PHOTOS = 1;
Santiago Etchebeherea2429992020-05-27 11:27:29 -070071 private static final int PRIORITY_SYSTEM = 100;
Jon Miranda16ea1b12017-12-12 14:52:48 -080072 private static final int PRIORITY_ON_DEVICE = 200;
73 private static final int PRIORITY_LIVE = 300;
74 private static final int PRIORITY_THIRD_PARTY = 400;
Santiago Etchebeherea2429992020-05-27 11:27:29 -070075 protected static List<Category> sSystemCategories;
Jon Miranda16ea1b12017-12-12 14:52:48 -080076
77 protected final Context mAppContext;
78 protected ArrayList<Category> mCategories;
79 protected boolean mFetchedCategories;
80
Chuck Liaoe2d35f52020-09-22 21:20:47 +080081 private NetworkStatusNotifier mNetworkStatusNotifier;
82 // The network status of the last fetch from the server.
83 @NetworkStatus
84 private int mNetworkStatus;
Chuck Liao144d9b12020-10-21 13:25:33 +080085 private Locale mLocale;
Chuck Liaoe2d35f52020-09-22 21:20:47 +080086
Jon Miranda16ea1b12017-12-12 14:52:48 -080087 public DefaultCategoryProvider(Context context) {
88 mAppContext = context.getApplicationContext();
89 mCategories = new ArrayList<>();
Chuck Liaoe2d35f52020-09-22 21:20:47 +080090 mNetworkStatusNotifier = InjectorProvider.getInjector().getNetworkStatusNotifier(context);
91 mNetworkStatus = NETWORK_NOT_INITIALIZED;
Jon Miranda16ea1b12017-12-12 14:52:48 -080092 }
93
94 @Override
95 public void fetchCategories(CategoryReceiver receiver, boolean forceRefresh) {
96 if (!forceRefresh && mFetchedCategories) {
97 for (Category category : mCategories) {
98 receiver.onCategoryReceived(category);
99 }
100 receiver.doneFetchingCategories();
101 return;
102 } else if (forceRefresh) {
103 mCategories.clear();
104 mFetchedCategories = false;
105 }
106
Chuck Liaoe2d35f52020-09-22 21:20:47 +0800107 mNetworkStatus = mNetworkStatusNotifier.getNetworkStatus();
Chuck Liao144d9b12020-10-21 13:25:33 +0800108 mLocale = getLocale();
Santiago Etchebeherec2ed7782019-06-25 21:47:54 -0700109 doFetch(receiver, forceRefresh);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800110 }
111
112 @Override
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700113 public int getSize() {
114 return mFetchedCategories ? mCategories.size() : 0;
115 }
116
117 @Override
118 public Category getCategory(int index) {
119 if (!mFetchedCategories) {
120 throw new IllegalStateException("Categories are not available");
121 }
122 return mCategories.get(index);
123 }
124
125 @Override
Jon Miranda16ea1b12017-12-12 14:52:48 -0800126 public Category getCategory(String collectionId) {
127 Category category;
128 for (int i = 0; i < mCategories.size(); i++) {
129 category = mCategories.get(i);
130 if (category.getCollectionId().equals(collectionId)) {
131 return category;
132 }
133 }
134 return null;
135 }
136
Chuck Liaof6b4b192020-08-07 02:31:32 +0800137 @Override
138 public boolean isCategoriesFetched() {
139 return mFetchedCategories;
140 }
141
Chuck Liaoe2d35f52020-09-22 21:20:47 +0800142 @Override
Chuck Liao56fe7362021-06-11 23:43:11 +0800143 public boolean resetIfNeeded() {
Chuck Liao144d9b12020-10-21 13:25:33 +0800144 if (mNetworkStatus != mNetworkStatusNotifier.getNetworkStatus()
145 || mLocale != getLocale()) {
Chuck Liaoe2d35f52020-09-22 21:20:47 +0800146 mCategories.clear();
147 mFetchedCategories = false;
Chuck Liao56fe7362021-06-11 23:43:11 +0800148 return true;
Chuck Liaoe2d35f52020-09-22 21:20:47 +0800149 }
Chuck Liao56fe7362021-06-11 23:43:11 +0800150 return false;
Chuck Liaoe2d35f52020-09-22 21:20:47 +0800151 }
152
Chuck Liaoddf2b522021-04-15 00:36:25 +0800153 @Override
Chuck Liaoc4462d02021-05-20 01:06:23 +0800154 public boolean isFeaturedCollectionAvailable() {
Chuck Liaoddf2b522021-04-15 00:36:25 +0800155 return false;
156 }
157
Santiago Etchebeherec2ed7782019-06-25 21:47:54 -0700158 protected void doFetch(final CategoryReceiver receiver, boolean forceRefresh) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800159 CategoryReceiver delegatingReceiver = new CategoryReceiver() {
160 @Override
161 public void onCategoryReceived(Category category) {
162 receiver.onCategoryReceived(category);
163 mCategories.add(category);
164 }
165
166 @Override
167 public void doneFetchingCategories() {
168 receiver.doneFetchingCategories();
169 mFetchedCategories = true;
170 }
171 };
172
Santiago Etchebehere0a6e3842019-06-10 11:02:56 -0700173 new FetchCategoriesTask(delegatingReceiver, mAppContext).execute();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800174 }
175
Chuck Liao144d9b12020-10-21 13:25:33 +0800176 private Locale getLocale() {
177 return mAppContext.getResources().getConfiguration().getLocales().get(0);
178 }
179
Jon Miranda16ea1b12017-12-12 14:52:48 -0800180 /**
181 * AsyncTask subclass used for fetching all the categories and pushing them one at a time to
182 * the receiver.
183 */
Santiago Etchebehere0a6e3842019-06-10 11:02:56 -0700184 protected static class FetchCategoriesTask extends AsyncTask<Void, Category, Void> {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800185 private CategoryReceiver mReceiver;
Santiago Etchebehere30fd9f52021-04-09 15:49:18 -0700186 private PartnerProvider mPartnerProvider;
Santiago Etchebehere0a6e3842019-06-10 11:02:56 -0700187 protected final Context mAppContext;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800188
Santiago Etchebehere0a6e3842019-06-10 11:02:56 -0700189 public FetchCategoriesTask(CategoryReceiver receiver, Context context) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800190 mReceiver = receiver;
Santiago Etchebehere0a6e3842019-06-10 11:02:56 -0700191 mAppContext = context.getApplicationContext();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800192 }
193
194 @Override
195 protected Void doInBackground(Void... voids) {
Santiago Etchebehere30fd9f52021-04-09 15:49:18 -0700196 mPartnerProvider = InjectorProvider.getInjector().getPartnerProvider(
197 mAppContext);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800198
199 // "My photos" wallpapers
Chuck Liaod4119012022-01-07 23:34:56 +0800200 publishProgress(getMyPhotosCategory());
Jon Miranda16ea1b12017-12-12 14:52:48 -0800201
Santiago Etchebeherea2429992020-05-27 11:27:29 -0700202 publishDeviceCategories();
203
204 // Legacy On-device wallpapers. Only show if on mobile.
205 publishProgress(getOnDeviceCategory());
Jon Miranda16ea1b12017-12-12 14:52:48 -0800206
207 // Live wallpapers -- if the device supports them.
208 if (mAppContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LIVE_WALLPAPER)) {
209 List<WallpaperInfo> liveWallpapers = LiveWallpaperInfo.getAll(
210 mAppContext, getExcludedLiveWallpaperPackageNames());
211 if (liveWallpapers.size() > 0) {
212 publishProgress(
Santiago Etchebeherea2429992020-05-27 11:27:29 -0700213 new ThirdPartyLiveWallpaperCategory(
Jon Miranda16ea1b12017-12-12 14:52:48 -0800214 mAppContext.getString(R.string.live_wallpapers_category_title),
215 mAppContext.getString(R.string.live_wallpaper_collection_id),
216 liveWallpapers,
Santiago Etchebehere1ee76a22018-05-15 15:02:24 -0700217 PRIORITY_LIVE,
218 getExcludedLiveWallpaperPackageNames()));
Jon Miranda16ea1b12017-12-12 14:52:48 -0800219 }
220 }
221
Chuck Liaod4119012022-01-07 23:34:56 +0800222 // Third party apps.
223 List<ThirdPartyAppCategory> thirdPartyApps = ThirdPartyAppCategory.getAll(
224 mAppContext, PRIORITY_THIRD_PARTY, getExcludedThirdPartyPackageNames());
225 for (ThirdPartyAppCategory thirdPartyApp : thirdPartyApps) {
226 publishProgress(thirdPartyApp);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800227 }
228
229 return null;
230 }
231
232 /**
233 * Publishes the device categories.
234 */
Santiago Etchebehereed3f3d02020-06-08 14:37:25 -0700235 private void publishDeviceCategories() {
Santiago Etchebeherea2429992020-05-27 11:27:29 -0700236 if (sSystemCategories != null) {
237 for (int i = 0; i < sSystemCategories.size(); i++) {
238 publishProgress(sSystemCategories.get(i));
239 }
240 return;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800241 }
Santiago Etchebeherea2429992020-05-27 11:27:29 -0700242 sSystemCategories = getSystemCategories();
Jon Miranda16ea1b12017-12-12 14:52:48 -0800243 }
244
Santiago Etchebeherea2429992020-05-27 11:27:29 -0700245 public Set<String> getExcludedLiveWallpaperPackageNames() {
246 Set<String> excluded = new HashSet<>();
247 if (sSystemCategories != null) {
248 excluded.addAll(sSystemCategories.stream()
249 .filter(c -> c instanceof WallpaperCategory)
250 .flatMap(c -> ((WallpaperCategory) c).getUnmodifiableWallpapers().stream()
251 .filter(wallpaperInfo -> wallpaperInfo instanceof LiveWallpaperInfo)
252 .map(wallpaperInfo ->
253 ((LiveWallpaperInfo) wallpaperInfo).getWallpaperComponent()
254 .getPackageName()))
255 .collect(Collectors.toSet()));
256 }
257 return excluded;
Jon Miranda16ea1b12017-12-12 14:52:48 -0800258 }
259
260 protected List<String> getExcludedThirdPartyPackageNames() {
261 return Arrays.asList(
262 "com.android.launcher", // Legacy launcher
263 "com.android.wallpaper.livepicker"); // Live wallpaper picker
264 }
265
266 /**
267 * Return a list of WallpaperInfos specific to this app. Overriding this method will
268 * allow derivative projects to add custom wallpaper tiles to the
269 * "On-device wallpapers" category.
270 */
271 protected List<WallpaperInfo> getPrivateDeviceWallpapers() {
272 return null;
273 }
274
Santiago Etchebeherea2429992020-05-27 11:27:29 -0700275 protected List<Category> getSystemCategories() {
276 Resources partnerRes = mPartnerProvider.getResources();
277 String packageName = mPartnerProvider.getPackageName();
278 List<Category> categories = new ArrayList<>();
279 if (partnerRes == null || packageName == null) {
280 return categories;
281 }
282
283 @XmlRes int wallpapersResId = partnerRes.getIdentifier(PartnerProvider.WALLPAPER_RES_ID,
284 "xml", packageName);
285 // Certain partner configurations don't have wallpapers provided, so need to check;
286 // return early if they are missing.
287 if (wallpapersResId == 0) {
288 return categories;
289 }
290
291 try (XmlResourceParser parser = partnerRes.getXml(wallpapersResId)) {
292 final int depth = parser.getDepth();
293 int type;
294 int priorityTracker = 0;
295 while (((type = parser.next()) != XmlPullParser.END_TAG
296 || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
297 if ((type == XmlPullParser.START_TAG)
298 && WallpaperCategory.TAG_NAME.equals(parser.getName())) {
299
300 WallpaperCategory.Builder categoryBuilder =
301 new WallpaperCategory.Builder(mPartnerProvider.getResources(),
302 Xml.asAttributeSet(parser));
303 categoryBuilder.setPriorityIfEmpty(PRIORITY_SYSTEM + priorityTracker++);
304 final int categoryDepth = parser.getDepth();
Santiago Etchebeherea2429992020-05-27 11:27:29 -0700305 while (((type = parser.next()) != XmlPullParser.END_TAG
306 || parser.getDepth() > categoryDepth)
307 && type != XmlPullParser.END_DOCUMENT) {
308 if (type == XmlPullParser.START_TAG) {
Santiago Etchebeherea2429992020-05-27 11:27:29 -0700309 WallpaperInfo wallpaper = null;
310 if (SystemStaticWallpaperInfo.TAG_NAME.equals(parser.getName())) {
311 wallpaper = SystemStaticWallpaperInfo
312 .fromAttributeSet(mPartnerProvider.getPackageName(),
313 categoryBuilder.getId(),
314 Xml.asAttributeSet(parser));
315
316 } else if (LiveWallpaperInfo.TAG_NAME.equals(parser.getName())) {
317 wallpaper = LiveWallpaperInfo.fromAttributeSet(mAppContext,
318 categoryBuilder.getId(), Xml.asAttributeSet(parser));
319 }
320 if (wallpaper != null) {
321 categoryBuilder.addWallpaper(wallpaper);
322 }
323 }
324 }
325 WallpaperCategory category = categoryBuilder.build();
Santiago Etchebehere39c31342021-05-03 15:35:06 -0700326 if (!category.getUnmodifiableWallpapers().isEmpty()) {
327 categories.add(category);
328 publishProgress(category);
329 }
Santiago Etchebeherea2429992020-05-27 11:27:29 -0700330 }
331 }
332 } catch (IOException | XmlPullParserException e) {
333 Log.w(TAG, "Couldn't read system wallpapers definition", e);
334 return Collections.emptyList();
335 }
336 return categories;
337 }
338
Jon Miranda16ea1b12017-12-12 14:52:48 -0800339 /**
340 * Returns a category which incorporates both GEL and bundled wallpapers.
341 */
Santiago Etchebeherea2429992020-05-27 11:27:29 -0700342 protected Category getOnDeviceCategory() {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800343 List<WallpaperInfo> onDeviceWallpapers = new ArrayList<>();
344
Santiago Etchebeherecba2a2a2020-05-15 12:03:22 -0700345 if (!mPartnerProvider.shouldHideDefaultWallpaper()) {
Jon Miranda16ea1b12017-12-12 14:52:48 -0800346 DefaultWallpaperInfo defaultWallpaperInfo = new DefaultWallpaperInfo();
347 onDeviceWallpapers.add(defaultWallpaperInfo);
348 }
349
350 List<WallpaperInfo> partnerWallpaperInfos = PartnerWallpaperInfo.getAll(mAppContext);
351 onDeviceWallpapers.addAll(partnerWallpaperInfos);
352
353 List<WallpaperInfo> legacyPartnerWallpaperInfos = LegacyPartnerWallpaperInfo.getAll(
354 mAppContext);
355 onDeviceWallpapers.addAll(legacyPartnerWallpaperInfos);
356
357 List<WallpaperInfo> privateWallpapers = getPrivateDeviceWallpapers();
358 if (privateWallpapers != null) {
359 onDeviceWallpapers.addAll(privateWallpapers);
360 }
361
Santiago Etchebeherea2429992020-05-27 11:27:29 -0700362 return onDeviceWallpapers.isEmpty() ? null : new WallpaperCategory(
Jon Miranda16ea1b12017-12-12 14:52:48 -0800363 mAppContext.getString(R.string.on_device_wallpapers_category_title),
364 mAppContext.getString(R.string.on_device_wallpaper_collection_id),
365 onDeviceWallpapers,
366 PRIORITY_ON_DEVICE);
367 }
368
Jon Miranda16ea1b12017-12-12 14:52:48 -0800369 /**
370 * Returns an appropriate "my photos" custom photo category for the given device form factor.
371 */
Chuck Liaod4119012022-01-07 23:34:56 +0800372 private Category getMyPhotosCategory() {
373 return new ImageCategory(
Jon Miranda16ea1b12017-12-12 14:52:48 -0800374 mAppContext.getString(R.string.my_photos_category_title),
375 mAppContext.getString(R.string.image_wallpaper_collection_id),
376 PRIORITY_MY_PHOTOS,
zonghuayang21aa2762021-12-08 18:06:21 +0800377 R.drawable.wallpaperpicker_emptystate /* overlayIconResId */);
Jon Miranda16ea1b12017-12-12 14:52:48 -0800378 }
379
380 @Override
381 protected void onProgressUpdate(Category... values) {
382 super.onProgressUpdate(values);
383
384 for (int i = 0; i < values.length; i++) {
385 Category category = values[i];
Santiago Etchebeherea2429992020-05-27 11:27:29 -0700386 if (category != null) {
387 mReceiver.onCategoryReceived(category);
388 }
Jon Miranda16ea1b12017-12-12 14:52:48 -0800389 }
390 }
391
392 @Override
393 protected void onPostExecute(Void unused) {
394 mReceiver.doneFetchingCategories();
395 }
396 }
397}