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 android.annotation.SuppressLint;
19 import android.app.WallpaperManager;
20 import android.content.Context;
21 import android.graphics.Bitmap;
22 import android.graphics.BitmapFactory;
23 import android.graphics.drawable.BitmapDrawable;
24 import android.os.AsyncTask;
25 import android.os.ParcelFileDescriptor;
26 import android.util.Log;
27 
28 import com.android.wallpaper.R;
29 import com.android.wallpaper.asset.BitmapUtils;
30 import com.android.wallpaper.compat.BuildCompat;
31 import com.android.wallpaper.compat.WallpaperManagerCompat;
32 import com.android.wallpaper.model.WallpaperMetadata;
33 
34 import java.io.FileInputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.List;
40 
41 /**
42  * Default implementation of {@link WallpaperRefresher} which refreshes wallpaper metadata
43  * asynchronously.
44  */
45 @SuppressLint("ServiceCast")
46 public class DefaultWallpaperRefresher implements WallpaperRefresher {
47     private static final String TAG = "DefaultWPRefresher";
48 
49     private final Context mAppContext;
50     private final WallpaperPreferences mWallpaperPreferences;
51     private final WallpaperManager mWallpaperManager;
52     private final WallpaperStatusChecker mWallpaperStatusChecker;
53 
54     /**
55      * @param context The application's context.
56      */
DefaultWallpaperRefresher(Context context)57     public DefaultWallpaperRefresher(Context context) {
58         mAppContext = context.getApplicationContext();
59 
60         Injector injector = InjectorProvider.getInjector();
61         mWallpaperPreferences = injector.getPreferences(mAppContext);
62         mWallpaperStatusChecker = injector.getWallpaperStatusChecker();
63 
64         // Retrieve WallpaperManager using Context#getSystemService instead of
65         // WallpaperManager#getInstance so it can be mocked out in test.
66         mWallpaperManager = (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
67     }
68 
69     @Override
refresh(RefreshListener listener)70     public void refresh(RefreshListener listener) {
71         GetWallpaperMetadataAsyncTask task = new GetWallpaperMetadataAsyncTask(listener);
72         task.execute();
73     }
74 
75     /**
76      * Retrieves the current wallpaper's thumbnail and metadata off the UI thread.
77      */
78     private class GetWallpaperMetadataAsyncTask extends
79             AsyncTask<Void, Void, List<WallpaperMetadata>> {
80         private final RefreshListener mListener;
81         private final WallpaperManagerCompat mWallpaperManagerCompat;
82 
83         private long mCurrentHomeWallpaperHashCode;
84         private long mCurrentLockWallpaperHashCode;
85         private String mSystemWallpaperPackageName;
86 
87         @SuppressLint("ServiceCast")
GetWallpaperMetadataAsyncTask(RefreshListener listener)88         public GetWallpaperMetadataAsyncTask(RefreshListener listener) {
89             mListener = listener;
90             mWallpaperManagerCompat =
91                     InjectorProvider.getInjector().getWallpaperManagerCompat(mAppContext);
92         }
93 
94         @Override
doInBackground(Void... unused)95         protected List<WallpaperMetadata> doInBackground(Void... unused) {
96             List<WallpaperMetadata> wallpaperMetadatas = new ArrayList<>();
97 
98             if (!isHomeScreenMetadataCurrent() || isHomeScreenAttributionsEmpty()) {
99                 mWallpaperPreferences.clearHomeWallpaperMetadata();
100                 setFallbackHomeScreenWallpaperMetadata();
101             }
102 
103             boolean isLockScreenWallpaperCurrentlySet = mWallpaperStatusChecker.isLockWallpaperSet(
104                     mAppContext);
105 
106             if (!BuildCompat.isAtLeastN() || !isLockScreenWallpaperCurrentlySet) {
107                 // Return only home metadata if pre-N device or lock screen wallpaper is not explicitly set.
108                 wallpaperMetadatas.add(new WallpaperMetadata(
109                         mWallpaperPreferences.getHomeWallpaperAttributions(),
110                         mWallpaperPreferences.getHomeWallpaperActionUrl(),
111                         mWallpaperPreferences.getHomeWallpaperActionLabelRes(),
112                         mWallpaperPreferences.getHomeWallpaperActionIconRes(),
113                         mWallpaperPreferences.getHomeWallpaperCollectionId(),
114                         mWallpaperPreferences.getHomeWallpaperBackingFileName(),
115                         mWallpaperManager.getWallpaperInfo()));
116                 return wallpaperMetadatas;
117             }
118 
119             if (!isLockScreenMetadataCurrent() || isLockScreenAttributionsEmpty()) {
120                 mWallpaperPreferences.clearLockWallpaperMetadata();
121                 setFallbackLockScreenWallpaperMetadata();
122             }
123 
124             wallpaperMetadatas.add(new WallpaperMetadata(
125                     mWallpaperPreferences.getHomeWallpaperAttributions(),
126                     mWallpaperPreferences.getHomeWallpaperActionUrl(),
127                     mWallpaperPreferences.getHomeWallpaperActionLabelRes(),
128                     mWallpaperPreferences.getHomeWallpaperActionIconRes(),
129                     mWallpaperPreferences.getHomeWallpaperCollectionId(),
130                     mWallpaperPreferences.getHomeWallpaperBackingFileName(),
131                     mWallpaperManager.getWallpaperInfo()));
132 
133             wallpaperMetadatas.add(new WallpaperMetadata(
134                     mWallpaperPreferences.getLockWallpaperAttributions(),
135                     mWallpaperPreferences.getLockWallpaperActionUrl(),
136                     mWallpaperPreferences.getLockWallpaperActionLabelRes(),
137                     mWallpaperPreferences.getLockWallpaperActionIconRes(),
138                     mWallpaperPreferences.getLockWallpaperCollectionId(),
139                     mWallpaperPreferences.getLockWallpaperBackingFileName(),
140                     null /* wallpaperComponent */));
141 
142             return wallpaperMetadatas;
143         }
144 
145         @Override
onPostExecute(List<WallpaperMetadata> metadatas)146         protected void onPostExecute(List<WallpaperMetadata> metadatas) {
147             if (metadatas.size() > 2) {
148                 Log.e(TAG, "Got more than 2 WallpaperMetadata objects - only home and (optionally) lock "
149                         + "are permitted.");
150                 return;
151             }
152 
153             mListener.onRefreshed(metadatas.get(0), metadatas.size() > 1 ? metadatas.get(1) : null,
154                     mWallpaperPreferences.getWallpaperPresentationMode());
155         }
156 
157         /**
158          * Sets fallback wallpaper attributions to WallpaperPreferences when the saved metadata did not
159          * match the system wallpaper. For live wallpapers, loads the label (title) but for image
160          * wallpapers loads a generic title string.
161          */
setFallbackHomeScreenWallpaperMetadata()162         private void setFallbackHomeScreenWallpaperMetadata() {
163             android.app.WallpaperInfo wallpaperComponent = mWallpaperManager.getWallpaperInfo();
164             if (wallpaperComponent == null) { // Image wallpaper
165                 mWallpaperPreferences.setHomeWallpaperAttributions(
166                         Arrays.asList(mAppContext.getResources().getString(R.string.fallback_wallpaper_title)));
167 
168                 // Set wallpaper ID if at least N or set a hash code if an earlier version of Android.
169                 if (BuildCompat.isAtLeastN()) {
170                     mWallpaperPreferences.setHomeWallpaperManagerId(mWallpaperManagerCompat.getWallpaperId(
171                             WallpaperManagerCompat.FLAG_SYSTEM));
172                 } else {
173                     mWallpaperPreferences.setHomeWallpaperHashCode(getCurrentHomeWallpaperHashCode());
174                 }
175             } else { // Live wallpaper
176                 mWallpaperPreferences.setHomeWallpaperAttributions(Arrays.asList(
177                         wallpaperComponent.loadLabel(mAppContext.getPackageManager()).toString()));
178                 mWallpaperPreferences.setHomeWallpaperPackageName(mSystemWallpaperPackageName);
179             }
180             mWallpaperPreferences.setWallpaperPresentationMode(
181                     WallpaperPreferences.PRESENTATION_MODE_STATIC);
182         }
183 
184         /**
185          * Sets fallback lock screen wallpaper attributions to WallpaperPreferences. This should be
186          * called when the saved lock screen wallpaper metadata does not match the currently set lock
187          * screen wallpaper.
188          */
setFallbackLockScreenWallpaperMetadata()189         private void setFallbackLockScreenWallpaperMetadata() {
190             mWallpaperPreferences.setLockWallpaperAttributions(
191                     Arrays.asList(mAppContext.getResources().getString(R.string.fallback_wallpaper_title)));
192             mWallpaperPreferences.setLockWallpaperId(mWallpaperManagerCompat.getWallpaperId(
193                     WallpaperManagerCompat.FLAG_LOCK));
194         }
195 
196         /**
197          * Returns whether the home screen metadata saved in WallpaperPreferences corresponds to the
198          * current system wallpaper.
199          */
isHomeScreenMetadataCurrent()200         private boolean isHomeScreenMetadataCurrent() {
201             return (mWallpaperManager.getWallpaperInfo() == null)
202                     ? isHomeScreenImageWallpaperCurrent()
203                     : isHomeScreenLiveWallpaperCurrent();
204         }
205 
206         /**
207          * Returns whether the home screen attributions saved in WallpaperPreferences is empty.
208          */
isHomeScreenAttributionsEmpty()209         private boolean isHomeScreenAttributionsEmpty() {
210             List<String> homeScreenAttributions = mWallpaperPreferences.getHomeWallpaperAttributions();
211             return homeScreenAttributions.get(0) == null
212                     && homeScreenAttributions.get(1) == null
213                     && homeScreenAttributions.get(2) == null;
214         }
215 
getCurrentHomeWallpaperHashCode()216         private long getCurrentHomeWallpaperHashCode() {
217             if (mCurrentHomeWallpaperHashCode == 0) {
218                     BitmapDrawable wallpaperDrawable = (BitmapDrawable) mWallpaperManagerCompat.getDrawable();
219                     Bitmap wallpaperBitmap = wallpaperDrawable.getBitmap();
220                     mCurrentHomeWallpaperHashCode = BitmapUtils.generateHashCode(wallpaperBitmap);
221 
222                     // Manually request that WallpaperManager loses its reference to the current wallpaper
223                     // bitmap, which can occupy a large memory allocation for the lifetime of the app.
224                     mWallpaperManager.forgetLoadedWallpaper();
225             }
226             return mCurrentHomeWallpaperHashCode;
227         }
228 
getCurrentLockWallpaperHashCode()229         private long getCurrentLockWallpaperHashCode() {
230             if (mCurrentLockWallpaperHashCode == 0
231                     && mWallpaperStatusChecker.isLockWallpaperSet(mAppContext)) {
232                 Bitmap wallpaperBitmap = getLockWallpaperBitmap();
233                 mCurrentLockWallpaperHashCode = BitmapUtils.generateHashCode(wallpaperBitmap);
234             }
235             return mCurrentLockWallpaperHashCode;
236         }
237 
238         /**
239          * Returns the lock screen wallpaper currently set on the device as a Bitmap, or null if no
240          * lock screen wallpaper is set.
241          */
getLockWallpaperBitmap()242         private Bitmap getLockWallpaperBitmap() {
243             Bitmap lockBitmap = null;
244 
245             ParcelFileDescriptor pfd = mWallpaperManagerCompat.getWallpaperFile(
246                     WallpaperManagerCompat.FLAG_LOCK);
247             // getWallpaperFile returns null if the lock screen isn't explicitly set, so need this
248             // check.
249             if (pfd != null) {
250                 InputStream fileStream = null;
251                 try {
252                     fileStream = new FileInputStream(pfd.getFileDescriptor());
253                     lockBitmap = BitmapFactory.decodeStream(fileStream);
254                     pfd.close();
255                     return lockBitmap;
256                 } catch (IOException e) {
257                     Log.e(TAG, "IO exception when closing the file descriptor.");
258                 } finally {
259                     if (fileStream != null) {
260                         try {
261                             fileStream.close();
262                         } catch (IOException e) {
263                             Log.e(TAG, "IO exception when closing input stream for lock screen WP.");
264                         }
265                     }
266                 }
267             }
268 
269             return lockBitmap;
270         }
271 
272         /**
273          * Returns whether the image wallpaper set to the system matches the metadata in
274          * WallpaperPreferences.
275          */
isHomeScreenImageWallpaperCurrent()276         private boolean isHomeScreenImageWallpaperCurrent() {
277             long savedBitmapHash = mWallpaperPreferences.getHomeWallpaperHashCode();
278 
279             // Use WallpaperManager IDs to check same-ness of image wallpaper on N+ versions of Android
280             // only when there is no saved bitmap hash code (which could be leftover from a previous build
281             // of the app that did not use wallpaper IDs).
282             if (BuildCompat.isAtLeastN() && savedBitmapHash == 0) {
283                 return mWallpaperPreferences.getHomeWallpaperManagerId()
284                         == mWallpaperManagerCompat.getWallpaperId(WallpaperManagerCompat.FLAG_SYSTEM);
285             }
286 
287             return savedBitmapHash == getCurrentHomeWallpaperHashCode();
288         }
289 
290         /**
291          * Returns whether the live wallpaper set to the system's home screen matches the metadata in
292          * WallpaperPreferences.
293          */
isHomeScreenLiveWallpaperCurrent()294         private boolean isHomeScreenLiveWallpaperCurrent() {
295             mSystemWallpaperPackageName = mWallpaperManager.getWallpaperInfo().getPackageName();
296             String homeWallpaperPackageName = mWallpaperPreferences.getHomeWallpaperPackageName();
297             return mSystemWallpaperPackageName.equals(homeWallpaperPackageName);
298         }
299 
300         /**
301          * Returns whether the lock screen metadata saved in WallpaperPreferences corresponds to the
302          * current lock screen wallpaper.
303          */
isLockScreenMetadataCurrent()304         private boolean isLockScreenMetadataCurrent() {
305             // Check for lock wallpaper image same-ness only when there is no stored lock wallpaper hash
306             // code. Otherwise if there is a lock wallpaper hash code stored in
307             // {@link WallpaperPreferences}, then check hash codes.
308             long savedLockWallpaperHash = mWallpaperPreferences.getLockWallpaperHashCode();
309 
310             return (savedLockWallpaperHash == 0)
311                     ? mWallpaperPreferences.getLockWallpaperId()
312                     == mWallpaperManagerCompat.getWallpaperId(WallpaperManagerCompat.FLAG_LOCK)
313                     : savedLockWallpaperHash == getCurrentLockWallpaperHashCode();
314         }
315 
316         /**
317          * Returns whether the lock screen attributions saved in WallpaperPreferences are empty.
318          */
isLockScreenAttributionsEmpty()319         private boolean isLockScreenAttributionsEmpty() {
320             List<String> attributions = mWallpaperPreferences.getLockWallpaperAttributions();
321             return attributions.get(0) == null
322                     && attributions.get(1) == null
323                     && attributions.get(2) == null;
324         }
325     }
326 }
327