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