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