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.module;
17 
18 import static android.app.WallpaperManager.FLAG_LOCK;
19 import static android.app.WallpaperManager.FLAG_SYSTEM;
20 
21 import android.annotation.Nullable;
22 import android.annotation.SuppressLint;
23 import android.app.WallpaperManager;
24 import android.content.ContentProviderClient;
25 import android.content.Context;
26 import android.database.Cursor;
27 import android.graphics.Bitmap;
28 import android.graphics.BitmapFactory;
29 import android.graphics.Point;
30 import android.graphics.Rect;
31 import android.graphics.drawable.BitmapDrawable;
32 import android.net.Uri;
33 import android.os.AsyncTask;
34 import android.os.Bundle;
35 import android.os.ParcelFileDescriptor;
36 import android.os.RemoteException;
37 import android.util.Log;
38 
39 import com.android.wallpaper.R;
40 import com.android.wallpaper.asset.BitmapUtils;
41 import com.android.wallpaper.model.CreativeCategory;
42 import com.android.wallpaper.model.LiveWallpaperMetadata;
43 import com.android.wallpaper.model.WallpaperInfoContract;
44 import com.android.wallpaper.model.WallpaperMetadata;
45 import com.android.wallpaper.picker.customization.data.content.WallpaperClient;
46 import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination;
47 import com.android.wallpaper.util.DisplayUtils;
48 
49 import java.io.FileInputStream;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.concurrent.Executor;
57 import java.util.concurrent.Executors;
58 
59 /**
60  * Default implementation of {@link WallpaperRefresher} which refreshes wallpaper metadata
61  * asynchronously.
62  */
63 @SuppressLint("ServiceCast")
64 public class DefaultWallpaperRefresher implements WallpaperRefresher {
65 
66     private static final String TAG = "DefaultWPRefresher";
67 
68     private final Context mAppContext;
69     private final WallpaperPreferences mWallpaperPreferences;
70     private final WallpaperManager mWallpaperManager;
71     private final WallpaperStatusChecker mWallpaperStatusChecker;
72 
73     private final DisplayUtils mDisplayUtils;
74 
75     private final WallpaperClient mWallpaperClient;
76 
77     private final Executor mExecutor = Executors.newCachedThreadPool();
78 
79 
80     /**
81      * @param context The application's context.
82      */
DefaultWallpaperRefresher(Context context)83     public DefaultWallpaperRefresher(Context context) {
84         mAppContext = context.getApplicationContext();
85 
86         Injector injector = InjectorProvider.getInjector();
87         mWallpaperPreferences = injector.getPreferences(mAppContext);
88         mWallpaperStatusChecker = injector.getWallpaperStatusChecker(context);
89         mDisplayUtils = injector.getDisplayUtils(mAppContext);
90         mWallpaperClient = injector.getWallpaperClient(mAppContext);
91 
92         // Retrieve WallpaperManager using Context#getSystemService instead of
93         // WallpaperManager#getInstance so it can be mocked out in test.
94         mWallpaperManager = (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
95     }
96 
97     @Override
refresh(RefreshListener listener)98     public void refresh(RefreshListener listener) {
99         GetWallpaperMetadataAsyncTask task = new GetWallpaperMetadataAsyncTask(listener);
100         task.executeOnExecutor(mExecutor);
101     }
102 
103     /**
104      * Retrieves the current wallpaper's thumbnail and metadata off the UI thread.
105      */
106     private class GetWallpaperMetadataAsyncTask extends
107             AsyncTask<Void, Void, List<WallpaperMetadata>> {
108 
109         private final RefreshListener mListener;
110         private final WallpaperManager mWallpaperManager;
111 
112         private long mCurrentHomeWallpaperHashCode;
113         private long mCurrentLockWallpaperHashCode;
114         private String mSystemWallpaperServiceName;
115 
116         @SuppressLint("ServiceCast")
GetWallpaperMetadataAsyncTask(RefreshListener listener)117         public GetWallpaperMetadataAsyncTask(RefreshListener listener) {
118             mListener = listener;
119             mWallpaperManager = WallpaperManager.getInstance(mAppContext);
120         }
121 
122         @Override
doInBackground(Void... unused)123         protected List<WallpaperMetadata> doInBackground(Void... unused) {
124             List<WallpaperMetadata> wallpaperMetadatas = new ArrayList<>();
125 
126             boolean isHomeScreenStatic = mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM) == null;
127             if (!isHomeScreenMetadataCurrent() || (isHomeScreenStatic
128                     && isHomeScreenAttributionsEmpty())) {
129                 mWallpaperPreferences.clearHomeWallpaperMetadata();
130                 setFallbackHomeScreenWallpaperMetadata();
131             }
132 
133             boolean isLockScreenWallpaperCurrentlySet =
134                     mWallpaperStatusChecker.isLockWallpaperSet();
135 
136             if (mWallpaperManager.getWallpaperInfo() == null) {
137                 wallpaperMetadatas.add(new WallpaperMetadata(
138                         mWallpaperPreferences.getHomeWallpaperAttributions(),
139                         mWallpaperPreferences.getHomeWallpaperActionUrl(),
140                         mWallpaperPreferences.getHomeWallpaperCollectionId(),
141                         /* wallpaperComponent= */ null,
142                         getCurrentWallpaperCropHints(FLAG_SYSTEM)));
143             } else {
144                 android.app.WallpaperInfo info = mWallpaperManager.getWallpaperInfo();
145                 Uri previewUri = getCreativePreviewUri(mAppContext, info,
146                         WallpaperDestination.HOME);
147                 wallpaperMetadatas.add(new LiveWallpaperMetadata(info, previewUri));
148             }
149 
150             // Return only home metadata if pre-N device or lock screen wallpaper is not explicitly
151             // set.
152             if (!isLockScreenWallpaperCurrentlySet) {
153                 return wallpaperMetadatas;
154             }
155 
156             boolean isLockScreenStatic = mWallpaperManager.getWallpaperInfo(FLAG_LOCK) == null;
157             if (!isLockScreenMetadataCurrent() || (isLockScreenStatic
158                     && isLockScreenAttributionsEmpty())) {
159                 mWallpaperPreferences.clearLockWallpaperMetadata();
160                 setFallbackLockScreenWallpaperMetadata();
161             }
162 
163             if (isLockScreenStatic) {
164                 wallpaperMetadatas.add(new WallpaperMetadata(
165                         mWallpaperPreferences.getLockWallpaperAttributions(),
166                         mWallpaperPreferences.getLockWallpaperActionUrl(),
167                         mWallpaperPreferences.getLockWallpaperCollectionId(),
168                         /* wallpaperComponent= */ null,
169                         getCurrentWallpaperCropHints(FLAG_LOCK)));
170             } else {
171                 android.app.WallpaperInfo info = mWallpaperManager.getWallpaperInfo(FLAG_LOCK);
172                 Uri previewUri = getCreativePreviewUri(mAppContext, info,
173                         WallpaperDestination.LOCK);
174                 wallpaperMetadatas.add(new LiveWallpaperMetadata(info, previewUri));
175             }
176 
177             return wallpaperMetadatas;
178         }
179 
180         @Override
onPostExecute(List<WallpaperMetadata> metadatas)181         protected void onPostExecute(List<WallpaperMetadata> metadatas) {
182             if (metadatas.size() > 2) {
183                 Log.e(TAG,
184                         "Got more than 2 WallpaperMetadata objects - only home and (optionally) "
185                                 + "lock are permitted.");
186                 return;
187             }
188 
189             mListener.onRefreshed(metadatas.get(0), metadatas.size() > 1 ? metadatas.get(1) : null,
190                     mWallpaperPreferences.getWallpaperPresentationMode());
191         }
192 
193         /**
194          * Sets fallback wallpaper attributions to WallpaperPreferences when the saved metadata did
195          * not match the system wallpaper. For live wallpapers, loads the label (title) but for
196          * image wallpapers loads a generic title string.
197          */
setFallbackHomeScreenWallpaperMetadata()198         private void setFallbackHomeScreenWallpaperMetadata() {
199             android.app.WallpaperInfo wallpaperComponent = mWallpaperManager.getWallpaperInfo();
200             if (wallpaperComponent == null) { // Image wallpaper
201                 mWallpaperPreferences.setHomeWallpaperAttributions(
202                         Arrays.asList(mAppContext.getResources()
203                                 .getString(R.string.fallback_wallpaper_title)));
204 
205                 mWallpaperPreferences.setHomeWallpaperManagerId(
206                         mWallpaperManager.getWallpaperId(FLAG_SYSTEM));
207             } else { // Live wallpaper
208                 mWallpaperPreferences.setHomeWallpaperAttributions(Arrays.asList(
209                         wallpaperComponent.loadLabel(mAppContext.getPackageManager()).toString()));
210                 mWallpaperPreferences.setHomeWallpaperServiceName(mSystemWallpaperServiceName);
211             }
212 
213             // Disable rotation wallpaper when setting fallback home screen wallpaper
214             // Daily rotation wallpaper only rotates the home screen wallpaper
215             mWallpaperPreferences.setWallpaperPresentationMode(
216                     WallpaperPreferences.PRESENTATION_MODE_STATIC);
217             mWallpaperPreferences.clearDailyRotations();
218         }
219 
220         /**
221          * Sets fallback lock screen wallpaper attributions to WallpaperPreferences. This should be
222          * called when the saved lock screen wallpaper metadata does not match the currently set
223          * lock screen wallpaper.
224          */
setFallbackLockScreenWallpaperMetadata()225         private void setFallbackLockScreenWallpaperMetadata() {
226             mWallpaperPreferences.setLockWallpaperAttributions(
227                     Arrays.asList(mAppContext.getResources()
228                             .getString(R.string.fallback_wallpaper_title)));
229             mWallpaperPreferences.setLockWallpaperManagerId(mWallpaperManager.getWallpaperId(
230                     FLAG_LOCK));
231         }
232 
233         /**
234          * Returns whether the home screen metadata saved in WallpaperPreferences corresponds to the
235          * current system wallpaper.
236          */
isHomeScreenMetadataCurrent()237         private boolean isHomeScreenMetadataCurrent() {
238             return (mWallpaperManager.getWallpaperInfo() == null)
239                     ? isHomeScreenImageWallpaperCurrent()
240                     : isHomeScreenLiveWallpaperCurrent();
241         }
242 
243         /**
244          * Returns whether the home screen attributions saved in WallpaperPreferences is empty.
245          */
isHomeScreenAttributionsEmpty()246         private boolean isHomeScreenAttributionsEmpty() {
247             List<String> homeScreenAttributions =
248                     mWallpaperPreferences.getHomeWallpaperAttributions();
249             return homeScreenAttributions.get(0) == null
250                     && homeScreenAttributions.get(1) == null
251                     && homeScreenAttributions.get(2) == null;
252         }
253 
getCurrentHomeWallpaperHashCode()254         private long getCurrentHomeWallpaperHashCode() {
255             if (mCurrentHomeWallpaperHashCode == 0) {
256                 BitmapDrawable wallpaperDrawable = (BitmapDrawable) mWallpaperManager.getDrawable();
257                 // wallpaperDrawable should always be non-null, unless if there's a error in
258                 // WallpaperManager's state, in which case we'll consider the hashcode as unset.
259                 Bitmap wallpaperBitmap = wallpaperDrawable != null ? wallpaperDrawable.getBitmap()
260                         : null;
261                 mCurrentHomeWallpaperHashCode =
262                         wallpaperBitmap != null ? BitmapUtils.generateHashCode(wallpaperBitmap) : 0;
263 
264                 // Manually request that WallpaperManager loses its reference to the current
265                 // wallpaper bitmap, which can occupy a large memory allocation for the lifetime of
266                 // the app.
267                 mWallpaperManager.forgetLoadedWallpaper();
268             }
269             return mCurrentHomeWallpaperHashCode;
270         }
271 
getCurrentLockWallpaperHashCode()272         private long getCurrentLockWallpaperHashCode() {
273             if (mCurrentLockWallpaperHashCode == 0
274                     && mWallpaperStatusChecker.isLockWallpaperSet()) {
275                 Bitmap wallpaperBitmap = getLockWallpaperBitmap();
276                 // If isLockWallpaperSet() returned true, wallpaperBitmap should always be
277                 // non-null, unless if there's a error in WallpaperManager, in which case we'll
278                 // consider the hashcode as unset.
279                 mCurrentLockWallpaperHashCode =
280                         wallpaperBitmap != null ? BitmapUtils.generateHashCode(wallpaperBitmap) : 0;
281             }
282             return mCurrentLockWallpaperHashCode;
283         }
284 
285         /**
286          * Returns the lock screen wallpaper currently set on the device as a Bitmap, or null if no
287          * lock screen wallpaper is set.
288          */
getLockWallpaperBitmap()289         private Bitmap getLockWallpaperBitmap() {
290             Bitmap lockBitmap = null;
291 
292             ParcelFileDescriptor pfd = mWallpaperManager.getWallpaperFile(FLAG_LOCK);
293             // getWallpaperFile returns null if the lock screen isn't explicitly set, so need this
294             // check.
295             if (pfd != null) {
296                 InputStream fileStream = null;
297                 try {
298                     fileStream = new FileInputStream(pfd.getFileDescriptor());
299                     lockBitmap = BitmapFactory.decodeStream(fileStream);
300                     pfd.close();
301                     return lockBitmap;
302                 } catch (IOException e) {
303                     Log.e(TAG, "IO exception when closing the file descriptor.");
304                 } finally {
305                     if (fileStream != null) {
306                         try {
307                             fileStream.close();
308                         } catch (IOException e) {
309                             Log.e(TAG,
310                                     "IO exception when closing input stream for lock screen WP.");
311                         }
312                     }
313                 }
314             }
315 
316             return lockBitmap;
317         }
318 
319         /**
320          * Returns whether the image wallpaper set to the system matches the metadata in
321          * WallpaperPreferences.
322          */
isHomeScreenImageWallpaperCurrent()323         private boolean isHomeScreenImageWallpaperCurrent() {
324             return mWallpaperPreferences.getHomeWallpaperManagerId()
325                     == mWallpaperManager.getWallpaperId(FLAG_SYSTEM);
326         }
327 
328         /**
329          * Returns whether the live wallpaper set to the system's home screen matches the metadata
330          * in WallpaperPreferences.
331          */
isHomeScreenLiveWallpaperCurrent()332         private boolean isHomeScreenLiveWallpaperCurrent() {
333             mSystemWallpaperServiceName = mWallpaperManager.getWallpaperInfo().getServiceName();
334             String homeWallpaperServiceName = mWallpaperPreferences.getHomeWallpaperServiceName();
335             return mSystemWallpaperServiceName.equals(homeWallpaperServiceName);
336         }
337 
338         /**
339          * Returns whether the lock screen metadata saved in WallpaperPreferences corresponds to the
340          * current lock screen wallpaper.
341          */
isLockScreenMetadataCurrent()342         private boolean isLockScreenMetadataCurrent() {
343             return (mWallpaperManager.getWallpaperInfo(FLAG_LOCK) == null)
344                     ? isLockScreenImageWallpaperCurrent()
345                     : isLockScreenLiveWallpaperCurrent();
346         }
347 
348         /**
349          * Returns whether the image wallpaper set for the lock screen matches the metadata in
350          * WallpaperPreferences.
351          */
isLockScreenImageWallpaperCurrent()352         private boolean isLockScreenImageWallpaperCurrent() {
353             // Check for lock wallpaper image same-ness only when there is no stored lock wallpaper
354             // hash code. Otherwise if there is a lock wallpaper hash code stored in
355             // {@link WallpaperPreferences}, then check hash codes.
356             long savedLockWallpaperHash = mWallpaperPreferences.getLockWallpaperHashCode();
357 
358             if (savedLockWallpaperHash == 0) {
359                 return mWallpaperPreferences.getLockWallpaperManagerId()
360                         == mWallpaperManager.getWallpaperId(FLAG_LOCK);
361             } else {
362                 return savedLockWallpaperHash == getCurrentLockWallpaperHashCode();
363             }
364         }
365 
366         /**
367          * Returns whether the live wallpaper for the home screen matches the metadata in
368          * WallpaperPreferences.
369          */
isLockScreenLiveWallpaperCurrent()370         private boolean isLockScreenLiveWallpaperCurrent() {
371             String currentServiceName = mWallpaperManager.getWallpaperInfo(FLAG_LOCK)
372                     .getServiceName();
373             String storedServiceName = mWallpaperPreferences.getLockWallpaperServiceName();
374             return currentServiceName.equals(storedServiceName);
375         }
376 
377 
378         /**
379          * Returns whether the lock screen attributions saved in WallpaperPreferences are empty.
380          */
isLockScreenAttributionsEmpty()381         private boolean isLockScreenAttributionsEmpty() {
382             List<String> attributions = mWallpaperPreferences.getLockWallpaperAttributions();
383             return attributions.get(0) == null
384                     && attributions.get(1) == null
385                     && attributions.get(2) == null;
386         }
387 
getCurrentWallpaperCropHints( @allpaperManager.SetWallpaperFlags int which)388         private Map<Point, Rect> getCurrentWallpaperCropHints(
389                 @WallpaperManager.SetWallpaperFlags int which) {
390             List<Point> displaySizes = mDisplayUtils
391                     .getInternalDisplaySizes(/* allDimensions= */ true);
392             return mWallpaperClient.getCurrentCropHints(displaySizes, which);
393         }
394     }
395 
396     // Queries a live wallpaper for its preview Uri, and returns it if it exists.
getCreativePreviewUri(Context context, android.app.WallpaperInfo info, WallpaperDestination destination)397     private static @Nullable Uri getCreativePreviewUri(Context context,
398             android.app.WallpaperInfo info,
399             WallpaperDestination destination) {
400         Bundle metaData = info.getServiceInfo().metaData;
401         String uri = metaData.getString(
402                 CreativeCategory.KEY_WALLPAPER_SAVE_CREATIVE_WALLPAPER_CURRENT);
403         if (uri == null) {
404             return null;
405         }
406         Uri currentAssetsUri = Uri.parse(uri);
407         try (ContentProviderClient client = context.getContentResolver()
408                 .acquireContentProviderClient(currentAssetsUri.getAuthority())) {
409             if (client == null) {
410                 return null;
411             }
412             try (Cursor cursor = client.query(currentAssetsUri, null, null, null, null)) {
413                 if (cursor == null || !cursor.moveToFirst()) {
414                     return null;
415                 }
416                 do {
417                     String dest = cursor.getString(
418                             cursor.getColumnIndex(WallpaperInfoContract.CURRENT_DESTINATION));
419                     Uri previewUri = Uri.parse(cursor.getString(
420                             cursor.getColumnIndex(
421                                     WallpaperInfoContract.CURRENT_CONFIG_PREVIEW_URI)));
422                     if ((dest.equals("home") && destination == WallpaperDestination.HOME)
423                             || (dest.equals("lock") && destination == WallpaperDestination.LOCK)) {
424                         return previewUri;
425                     }
426                 } while (cursor.moveToNext());
427             } catch (RemoteException e) {
428                 Log.w(TAG, "Error retrieving current creative asset id: ", e);
429             }
430         }
431         return null;
432     }
433 }
434