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