1 /* <lambda>null2 * Copyright (C) 2023 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 */ 17 18 package com.android.wallpaper.picker.customization.data.content 19 20 import android.app.Flags.liveWallpaperContentHandling 21 import android.app.WallpaperColors 22 import android.app.WallpaperManager 23 import android.app.WallpaperManager.FLAG_LOCK 24 import android.app.WallpaperManager.FLAG_SYSTEM 25 import android.app.WallpaperManager.SetWallpaperFlags 26 import android.app.wallpaper.WallpaperDescription 27 import android.content.ComponentName 28 import android.content.ContentResolver 29 import android.content.ContentValues 30 import android.content.Context 31 import android.database.ContentObserver 32 import android.graphics.Bitmap 33 import android.graphics.BitmapFactory 34 import android.graphics.Color 35 import android.graphics.Point 36 import android.graphics.Rect 37 import android.net.Uri 38 import android.util.Log 39 import androidx.exifinterface.media.ExifInterface 40 import com.android.app.tracing.TraceUtils.traceAsync 41 import com.android.wallpaper.asset.Asset 42 import com.android.wallpaper.asset.BitmapUtils 43 import com.android.wallpaper.asset.CurrentWallpaperAsset 44 import com.android.wallpaper.asset.StreamableAsset 45 import com.android.wallpaper.model.LiveWallpaperPrefMetadata 46 import com.android.wallpaper.model.Screen 47 import com.android.wallpaper.model.StaticWallpaperPrefMetadata 48 import com.android.wallpaper.model.WallpaperInfo 49 import com.android.wallpaper.model.WallpaperModelsPair 50 import com.android.wallpaper.module.InjectorProvider 51 import com.android.wallpaper.module.WallpaperPreferences 52 import com.android.wallpaper.module.logging.UserEventLogger 53 import com.android.wallpaper.module.logging.UserEventLogger.SetWallpaperEntryPoint 54 import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination 55 import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination.BOTH 56 import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination.Companion.toDestinationInt 57 import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination.Companion.toSetWallpaperFlags 58 import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination.HOME 59 import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination.LOCK 60 import com.android.wallpaper.picker.customization.shared.model.WallpaperModel as RecentWallpaperModel 61 import com.android.wallpaper.picker.data.WallpaperModel.LiveWallpaperModel 62 import com.android.wallpaper.picker.data.WallpaperModel.StaticWallpaperModel 63 import com.android.wallpaper.picker.di.modules.BackgroundDispatcher 64 import com.android.wallpaper.picker.preview.shared.model.FullPreviewCropModel 65 import com.android.wallpaper.util.WallpaperCropUtils 66 import com.android.wallpaper.util.converter.WallpaperModelFactory 67 import dagger.hilt.android.qualifiers.ApplicationContext 68 import java.io.IOException 69 import java.io.InputStream 70 import javax.inject.Inject 71 import javax.inject.Singleton 72 import kotlinx.coroutines.CancellableContinuation 73 import kotlinx.coroutines.CoroutineScope 74 import kotlinx.coroutines.ExperimentalCoroutinesApi 75 import kotlinx.coroutines.flow.MutableStateFlow 76 import kotlinx.coroutines.flow.asStateFlow 77 import kotlinx.coroutines.flow.filterNotNull 78 import kotlinx.coroutines.flow.take 79 import kotlinx.coroutines.launch 80 import kotlinx.coroutines.suspendCancellableCoroutine 81 82 @OptIn(ExperimentalCoroutinesApi::class) 83 @Singleton 84 class WallpaperClientImpl 85 @Inject 86 constructor( 87 @ApplicationContext private val context: Context, 88 private val wallpaperManager: WallpaperManager, 89 private val wallpaperPreferences: WallpaperPreferences, 90 private val wallpaperModelFactory: WallpaperModelFactory, 91 private val logger: UserEventLogger, 92 @BackgroundDispatcher val backgroundScope: CoroutineScope, 93 ) : WallpaperClient { 94 95 private var recentsContentProviderAvailable: Boolean? = null 96 private val recentHomeWallpapers = MutableStateFlow<List<RecentWallpaperModel>?>(null) 97 private val recentLockWallpapers = MutableStateFlow<List<RecentWallpaperModel>?>(null) 98 99 init { 100 backgroundScope.launch { 101 recentHomeWallpapers.value = queryRecentWallpapers(destination = HOME) 102 recentLockWallpapers.value = queryRecentWallpapers(destination = LOCK) 103 } 104 105 if (areRecentsAvailable()) { 106 context.contentResolver.registerContentObserver( 107 LIST_RECENTS_URI, 108 /* notifyForDescendants= */ true, 109 object : ContentObserver(null) { 110 override fun onChange(selfChange: Boolean) { 111 backgroundScope.launch { 112 recentHomeWallpapers.value = queryRecentWallpapers(destination = HOME) 113 recentLockWallpapers.value = queryRecentWallpapers(destination = LOCK) 114 } 115 } 116 }, 117 ) 118 } 119 } 120 121 override fun recentWallpapers(destination: WallpaperDestination, limit: Int) = 122 when (destination) { 123 HOME -> recentHomeWallpapers.asStateFlow().filterNotNull().take(limit) 124 LOCK -> recentLockWallpapers.asStateFlow().filterNotNull().take(limit) 125 BOTH -> 126 throw IllegalStateException( 127 "Destination $destination should not be used for getting recent wallpapers." 128 ) 129 } 130 131 override suspend fun setStaticWallpaper( 132 @SetWallpaperEntryPoint setWallpaperEntryPoint: Int, 133 destination: WallpaperDestination, 134 wallpaperModel: StaticWallpaperModel, 135 bitmap: Bitmap, 136 wallpaperSize: Point, 137 asset: Asset, 138 fullPreviewCropModels: Map<Point, FullPreviewCropModel>?, 139 ) { 140 if (destination == HOME || destination == BOTH) { 141 // Disable rotation wallpaper when setting to home screen. Daily rotation rotates 142 // both home and lock screen wallpaper when lock screen is not set; otherwise daily 143 // rotation only rotates home screen while lock screen wallpaper stays as what it's 144 // set to. 145 stopWallpaperRotation() 146 } 147 148 traceAsync(TAG, "setStaticWallpaper") { 149 val cropHintsWithParallax = 150 fullPreviewCropModels?.let { cropModels -> 151 cropModels.mapValues { it.value.adjustCropForParallax(wallpaperSize) } 152 } ?: emptyMap() 153 val managerId = 154 wallpaperManager.setStaticWallpaperToSystem( 155 asset.getStreamOrFromBitmap(bitmap), 156 bitmap, 157 cropHintsWithParallax, 158 destination, 159 asset, 160 ) 161 162 wallpaperPreferences.setStaticWallpaperMetadata( 163 metadata = wallpaperModel.getMetadata(bitmap, managerId), 164 destination = destination, 165 ) 166 167 logger.logWallpaperApplied( 168 collectionId = wallpaperModel.commonWallpaperData.id.collectionId, 169 wallpaperId = wallpaperModel.commonWallpaperData.id.wallpaperId, 170 effects = null, 171 setWallpaperEntryPoint = setWallpaperEntryPoint, 172 destination = 173 UserEventLogger.toWallpaperDestinationForLogging(destination.toDestinationInt()), 174 ) 175 176 // Save the static wallpaper to recent wallpapers 177 // TODO(b/309138446): check if we can update recent with all cropHints from WM later 178 wallpaperPreferences.addStaticWallpaperToRecentWallpapers( 179 destination, 180 wallpaperModel, 181 bitmap, 182 cropHintsWithParallax, 183 ) 184 } 185 } 186 187 private fun stopWallpaperRotation() { 188 wallpaperPreferences.setWallpaperPresentationMode( 189 WallpaperPreferences.PRESENTATION_MODE_STATIC 190 ) 191 wallpaperPreferences.clearDailyRotations() 192 } 193 194 /** 195 * Use [WallpaperManager] to set a static wallpaper to the system. 196 * 197 * @return Wallpaper manager ID 198 */ 199 private fun WallpaperManager.setStaticWallpaperToSystem( 200 inputStream: InputStream?, 201 bitmap: Bitmap, 202 cropHints: Map<Point, Rect>, 203 destination: WallpaperDestination, 204 asset: Asset, 205 ): Int { 206 // The InputStream of current wallpaper points to system wallpaper file which will be 207 // overwritten during set wallpaper and reads 0 bytes, use Bitmap instead. 208 return if (inputStream != null && asset !is CurrentWallpaperAsset) { 209 setStreamWithCrops( 210 inputStream, 211 cropHints, 212 /* allowBackup= */ true, 213 destination.toSetWallpaperFlags(), 214 ) 215 } else { 216 setBitmapWithCrops( 217 bitmap, 218 cropHints, 219 /* allowBackup= */ true, 220 destination.toSetWallpaperFlags(), 221 ) 222 } 223 } 224 225 private fun StaticWallpaperModel.getMetadata( 226 bitmap: Bitmap, 227 managerId: Int, 228 ): StaticWallpaperPrefMetadata { 229 val bitmapHash = BitmapUtils.generateHashCode(bitmap) 230 return StaticWallpaperPrefMetadata( 231 commonWallpaperData.attributions, 232 commonWallpaperData.exploreActionUrl, 233 commonWallpaperData.id.collectionId, 234 bitmapHash, 235 managerId, 236 commonWallpaperData.id.uniqueId, 237 ) 238 } 239 240 /** 241 * Save wallpaper metadata in the preference for two purposes: 242 * 1. Quickly reconstruct the currently-selected wallpaper when opening the app 243 * 2. Snapshot logging 244 */ 245 private fun WallpaperPreferences.setStaticWallpaperMetadata( 246 metadata: StaticWallpaperPrefMetadata, 247 destination: WallpaperDestination, 248 ) { 249 when (destination) { 250 HOME -> { 251 clearHomeWallpaperMetadata() 252 setHomeStaticImageWallpaperMetadata(metadata) 253 } 254 LOCK -> { 255 clearLockWallpaperMetadata() 256 setLockStaticImageWallpaperMetadata(metadata) 257 } 258 BOTH -> { 259 clearHomeWallpaperMetadata() 260 setHomeStaticImageWallpaperMetadata(metadata) 261 clearLockWallpaperMetadata() 262 setLockStaticImageWallpaperMetadata(metadata) 263 } 264 } 265 } 266 267 override suspend fun setLiveWallpaper( 268 setWallpaperEntryPoint: Int, 269 destination: WallpaperDestination, 270 wallpaperModel: LiveWallpaperModel, 271 ) { 272 if (destination == HOME || destination == BOTH) { 273 // Disable rotation wallpaper when setting to home screen. Daily rotation rotates 274 // both home and lock screen wallpaper when lock screen is not set; otherwise daily 275 // rotation only rotates home screen while lock screen wallpaper stays as what it's 276 // set to. 277 stopWallpaperRotation() 278 } 279 280 traceAsync(TAG, "setLiveWallpaper") { 281 val managerId = wallpaperManager.setLiveWallpaperToSystem(wallpaperModel, destination) 282 283 wallpaperPreferences.setLiveWallpaperMetadata( 284 metadata = wallpaperModel.getMetadata(managerId), 285 destination = destination, 286 ) 287 288 logger.logWallpaperApplied( 289 collectionId = wallpaperModel.commonWallpaperData.id.collectionId, 290 wallpaperId = wallpaperModel.commonWallpaperData.id.wallpaperId, 291 effects = wallpaperModel.liveWallpaperData.effectNames, 292 setWallpaperEntryPoint = setWallpaperEntryPoint, 293 destination = 294 UserEventLogger.toWallpaperDestinationForLogging(destination.toDestinationInt()), 295 ) 296 297 wallpaperPreferences.addLiveWallpaperToRecentWallpapers(destination, wallpaperModel) 298 } 299 } 300 301 private fun tryAndroidBSetComponent( 302 wallpaperModel: LiveWallpaperModel, 303 destination: WallpaperDestination, 304 ): Boolean { 305 try { 306 val method = 307 wallpaperManager.javaClass.getMethod( 308 "setWallpaperComponentWithDescription", 309 WallpaperDescription::class.java, 310 Int::class.javaPrimitiveType, 311 ) 312 method.invoke( 313 wallpaperManager, 314 wallpaperModel.liveWallpaperData.description, 315 destination.toSetWallpaperFlags(), 316 ) 317 return true 318 } catch (e: NoSuchMethodException) { 319 return false 320 } 321 } 322 323 private fun tryAndroidUSetComponent( 324 wallpaperModel: LiveWallpaperModel, 325 destination: WallpaperDestination, 326 ): Boolean { 327 try { 328 val method = 329 wallpaperManager.javaClass.getMethod( 330 "setWallpaperComponentWithFlags", 331 ComponentName::class.java, 332 Int::class.javaPrimitiveType, 333 ) 334 method.invoke( 335 wallpaperManager, 336 wallpaperModel.commonWallpaperData.id.componentName, 337 destination.toSetWallpaperFlags(), 338 ) 339 if (liveWallpaperContentHandling()) { 340 Log.w( 341 TAG, 342 "live wallpaper content handling enabled, but Android U setWallpaperComponentWithFlags called", 343 ) 344 } 345 return true 346 } catch (e: NoSuchMethodException) { 347 return false 348 } 349 } 350 351 /** 352 * Use [WallpaperManager] to set a live wallpaper to the system. 353 * 354 * @return Wallpaper manager ID 355 */ 356 private fun WallpaperManager.setLiveWallpaperToSystem( 357 wallpaperModel: LiveWallpaperModel, 358 destination: WallpaperDestination, 359 ): Int { 360 if (tryAndroidBSetComponent(wallpaperModel, destination)) { 361 // intentional no-op 362 } else if (tryAndroidUSetComponent(wallpaperModel, destination)) { 363 // intentional no-op 364 } else { 365 setWallpaperComponent(wallpaperModel.commonWallpaperData.id.componentName) 366 } 367 368 // Be careful that WallpaperManager.getWallpaperId can only accept either 369 // WallpaperManager.FLAG_SYSTEM or WallpaperManager.FLAG_LOCK. 370 // If destination is BOTH, either flag should return the same wallpaper manager ID. 371 return getWallpaperId( 372 if (destination == BOTH || destination == HOME) FLAG_SYSTEM else FLAG_LOCK 373 ) 374 } 375 376 private fun LiveWallpaperModel.getMetadata(managerId: Int): LiveWallpaperPrefMetadata { 377 return LiveWallpaperPrefMetadata( 378 commonWallpaperData.attributions, 379 liveWallpaperData.systemWallpaperInfo.serviceName, 380 liveWallpaperData.effectNames, 381 commonWallpaperData.id.collectionId, 382 managerId, 383 ) 384 } 385 386 /** 387 * Save wallpaper metadata in the preference for two purposes: 388 * 1. Quickly reconstruct the currently-selected wallpaper when opening the app 389 * 2. Snapshot logging 390 */ 391 private fun WallpaperPreferences.setLiveWallpaperMetadata( 392 metadata: LiveWallpaperPrefMetadata, 393 destination: WallpaperDestination, 394 ) { 395 when (destination) { 396 HOME -> { 397 clearHomeWallpaperMetadata() 398 setHomeLiveWallpaperMetadata(metadata) 399 } 400 LOCK -> { 401 clearLockWallpaperMetadata() 402 setLockLiveWallpaperMetadata(metadata) 403 } 404 BOTH -> { 405 clearHomeWallpaperMetadata() 406 setHomeLiveWallpaperMetadata(metadata) 407 clearLockWallpaperMetadata() 408 setLockLiveWallpaperMetadata(metadata) 409 } 410 } 411 } 412 413 override suspend fun setRecentWallpaper( 414 @SetWallpaperEntryPoint setWallpaperEntryPoint: Int, 415 destination: WallpaperDestination, 416 wallpaperId: String, 417 onDone: () -> Unit, 418 ) { 419 val updateValues = ContentValues() 420 updateValues.put(KEY_ID, wallpaperId) 421 updateValues.put(KEY_SCREEN, destination.asString()) 422 updateValues.put(KEY_SET_WALLPAPER_ENTRY_POINT, setWallpaperEntryPoint) 423 traceAsync(TAG, "setRecentWallpaper") { 424 val updatedRowCount = 425 context.contentResolver.update(SET_WALLPAPER_URI, updateValues, null) 426 if (updatedRowCount == 0) { 427 Log.e(TAG, "Error setting wallpaper: $wallpaperId") 428 } 429 onDone.invoke() 430 } 431 } 432 433 private suspend fun queryRecentWallpapers( 434 destination: WallpaperDestination 435 ): List<RecentWallpaperModel> = 436 if (!areRecentsAvailable()) { 437 listOf(getCurrentWallpaperFromFactory(destination)) 438 } else { 439 queryAllRecentWallpapers(destination) 440 } 441 442 private fun queryAllRecentWallpapers( 443 destination: WallpaperDestination 444 ): List<RecentWallpaperModel> { 445 context.contentResolver 446 .query( 447 LIST_RECENTS_URI.buildUpon().appendPath(destination.asString()).build(), 448 arrayOf(KEY_ID, KEY_PLACEHOLDER_COLOR, KEY_LAST_UPDATED), 449 null, 450 null, 451 ) 452 .use { cursor -> 453 if (cursor == null || cursor.count == 0) { 454 return emptyList() 455 } 456 457 return buildList { 458 val idColumnIndex = cursor.getColumnIndex(KEY_ID) 459 val placeholderColorColumnIndex = cursor.getColumnIndex(KEY_PLACEHOLDER_COLOR) 460 val lastUpdatedColumnIndex = cursor.getColumnIndex(KEY_LAST_UPDATED) 461 val titleColumnIndex = cursor.getColumnIndex(TITLE) 462 while (cursor.moveToNext()) { 463 val wallpaperId = cursor.getString(idColumnIndex) 464 val placeholderColor = cursor.getInt(placeholderColorColumnIndex) 465 val lastUpdated = cursor.getLong(lastUpdatedColumnIndex) 466 val title = 467 if (titleColumnIndex > -1) cursor.getString(titleColumnIndex) else null 468 469 add( 470 RecentWallpaperModel( 471 wallpaperId = wallpaperId, 472 placeholderColor = placeholderColor, 473 lastUpdated = lastUpdated, 474 title = title, 475 ) 476 ) 477 } 478 } 479 } 480 } 481 482 private suspend fun getCurrentWallpaperFromFactory( 483 destination: WallpaperDestination 484 ): RecentWallpaperModel { 485 val currentWallpapers = getCurrentWallpapers() 486 val wallpaper: WallpaperInfo = 487 if (destination == LOCK) { 488 currentWallpapers.second ?: currentWallpapers.first 489 } else { 490 currentWallpapers.first 491 } 492 val colors = wallpaperManager.getWallpaperColors(destination.toSetWallpaperFlags()) 493 494 return RecentWallpaperModel( 495 wallpaperId = wallpaper.wallpaperId, 496 placeholderColor = colors?.primaryColor?.toArgb() ?: Color.TRANSPARENT, 497 title = wallpaper.getTitle(context), 498 ) 499 } 500 501 private suspend fun getCurrentWallpapers(): Pair<WallpaperInfo, WallpaperInfo?> = 502 suspendCancellableCoroutine { continuation -> 503 InjectorProvider.getInjector() 504 .getCurrentWallpaperInfoFactory(context) 505 .createCurrentWallpaperInfos(context, /* forceRefresh= */ false) { 506 homeWallpaper, 507 lockWallpaper, 508 _ -> 509 continuation.resume(Pair(homeWallpaper, lockWallpaper), null) 510 } 511 } 512 513 override suspend fun getCurrentWallpaperModels(): WallpaperModelsPair { 514 val currentWallpapers = getCurrentWallpapers() 515 val homeWallpaper = currentWallpapers.first 516 val lockWallpaper = currentWallpapers.second 517 return WallpaperModelsPair( 518 wallpaperModelFactory.getWallpaperModel(context, homeWallpaper), 519 lockWallpaper?.let { wallpaperModelFactory.getWallpaperModel(context, it) }, 520 ) 521 } 522 523 override suspend fun loadThumbnail( 524 wallpaperId: String, 525 destination: WallpaperDestination, 526 ): Bitmap? { 527 if (areRecentsAvailable()) { 528 try { 529 // We're already using this in a suspend function, so we're okay. 530 @Suppress("BlockingMethodInNonBlockingContext") 531 context.contentResolver 532 .openFile( 533 GET_THUMBNAIL_BASE_URI.buildUpon() 534 .appendPath(wallpaperId) 535 .appendQueryParameter(KEY_DESTINATION, destination.asString()) 536 .build(), 537 "r", 538 null, 539 ) 540 .use { file -> 541 if (file == null) { 542 Log.e(TAG, "Error getting wallpaper preview: $wallpaperId") 543 } else { 544 return BitmapFactory.decodeFileDescriptor(file.fileDescriptor) 545 } 546 } 547 } catch (e: IOException) { 548 Log.e( 549 TAG, 550 "Error getting wallpaper preview: $wallpaperId, destination: ${destination.asString()}", 551 e, 552 ) 553 } 554 } else { 555 val currentWallpapers = getCurrentWallpapers() 556 val wallpaper = 557 if (currentWallpapers.first.wallpaperId == wallpaperId) { 558 currentWallpapers.first 559 } else if (currentWallpapers.second?.wallpaperId == wallpaperId) { 560 currentWallpapers.second 561 } else null 562 return wallpaper?.getThumbAsset(context)?.getLowResBitmap(context) 563 } 564 565 return null 566 } 567 568 override fun areRecentsAvailable(): Boolean { 569 if (recentsContentProviderAvailable == null) { 570 recentsContentProviderAvailable = 571 try { 572 context.packageManager.resolveContentProvider(AUTHORITY, 0) != null 573 } catch (e: Exception) { 574 Log.w( 575 TAG, 576 "Exception trying to resolve recents content provider, skipping it", 577 e, 578 ) 579 false 580 } 581 } 582 return recentsContentProviderAvailable == true 583 } 584 585 override fun getCurrentCropHints( 586 displaySizes: List<Point>, 587 @SetWallpaperFlags which: Int, 588 ): Map<Point, Rect>? { 589 val flags = InjectorProvider.getInjector().getFlags() 590 if (!flags.isMultiCropEnabled()) { 591 return null 592 } 593 val cropHints: List<Rect>? = 594 wallpaperManager.getBitmapCrops(displaySizes, which, /* originalBitmap= */ true) 595 596 return cropHints?.indices?.associate { displaySizes[it] to cropHints[it] } 597 } 598 599 override suspend fun getWallpaperColors( 600 bitmap: Bitmap, 601 cropHints: Map<Point, Rect>?, 602 ): WallpaperColors? { 603 return wallpaperManager.getWallpaperColors(bitmap, cropHints) 604 } 605 606 override fun getWallpaperColors(screen: Screen): WallpaperColors? { 607 return wallpaperManager.getWallpaperColors( 608 if (screen == Screen.LOCK_SCREEN) { 609 FLAG_LOCK 610 } else { 611 FLAG_SYSTEM 612 } 613 ) 614 } 615 616 fun WallpaperDestination.asString(): String { 617 return when (this) { 618 BOTH -> SCREEN_ALL 619 HOME -> SCREEN_HOME 620 LOCK -> SCREEN_LOCK 621 } 622 } 623 624 /** 625 * Adjusts cropHints for parallax effect. 626 * 627 * [WallpaperCropUtils.calculateCropRect] calculates based on the scaled size, the scale depends 628 * on the view size hosting the preview and the wallpaper zoom of the preview on that view, 629 * whereas the rest of multi-crop is based on full wallpaper size. So scaled back at the end. 630 * 631 * If [CropSizeModel] is null, returns the original cropHint without parallax. 632 * 633 * @param wallpaperSize full wallpaper image size. 634 */ 635 private fun FullPreviewCropModel.adjustCropForParallax(wallpaperSize: Point): Rect { 636 return cropSizeModel?.let { 637 WallpaperCropUtils.calculateCropRect( 638 context, 639 it.hostViewSize, 640 it.cropViewSize, 641 wallpaperSize, 642 cropHint, 643 it.wallpaperZoom, 644 /* cropExtraWidth= */ true, 645 ) 646 .apply { 647 scale(1f / it.wallpaperZoom) 648 if (right > wallpaperSize.x) right = wallpaperSize.x 649 if (bottom > wallpaperSize.y) bottom = wallpaperSize.y 650 } 651 } ?: cropHint 652 } 653 654 private suspend fun Asset.getStreamOrFromBitmap(bitmap: Bitmap): InputStream? = 655 suspendCancellableCoroutine { k: CancellableContinuation<InputStream?> -> 656 if (this is StreamableAsset) { 657 if (exifOrientation != ExifInterface.ORIENTATION_NORMAL) { 658 k.resumeWith(Result.success(BitmapUtils.bitmapToInputStream(bitmap))) 659 } else { 660 fetchInputStream { k.resumeWith(Result.success(it)) } 661 } 662 } else { 663 k.resumeWith(Result.success(null)) 664 } 665 } 666 667 companion object { 668 private const val TAG = "WallpaperClientImpl" 669 private const val AUTHORITY = "com.google.android.apps.wallpaper.recents" 670 671 /** Path for making a content provider request to set the wallpaper. */ 672 private const val PATH_SET_WALLPAPER = "set_recent_wallpaper" 673 /** Path for making a content provider request to query for the recent wallpapers. */ 674 private const val PATH_LIST_RECENTS = "list_recent" 675 /** Path for making a content provider request to query for the thumbnail of a wallpaper. */ 676 private const val PATH_GET_THUMBNAIL = "thumb" 677 678 private val BASE_URI = 679 Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build() 680 /** [Uri] for making a content provider request to set the wallpaper. */ 681 private val SET_WALLPAPER_URI = BASE_URI.buildUpon().appendPath(PATH_SET_WALLPAPER).build() 682 /** [Uri] for making a content provider request to query for the recent wallpapers. */ 683 private val LIST_RECENTS_URI = BASE_URI.buildUpon().appendPath(PATH_LIST_RECENTS).build() 684 /** 685 * [Uri] for making a content provider request to query for the thumbnail of a wallpaper. 686 */ 687 private val GET_THUMBNAIL_BASE_URI = 688 BASE_URI.buildUpon().appendPath(PATH_GET_THUMBNAIL).build() 689 690 /** Key for a parameter used to pass the wallpaper ID to/from the content provider. */ 691 private const val KEY_ID = "id" 692 /** Key for a parameter used to pass the screen to/from the content provider. */ 693 private const val KEY_SCREEN = "screen" 694 /** Key for a parameter used to pass the wallpaper destination to/from content provider. */ 695 private const val KEY_DESTINATION = "destination" 696 /** Key for a parameter used to pass the screen to/from the content provider. */ 697 private const val KEY_SET_WALLPAPER_ENTRY_POINT = "set_wallpaper_entry_point" 698 private const val KEY_LAST_UPDATED = "last_updated" 699 private const val SCREEN_ALL = "all_screens" 700 private const val SCREEN_HOME = "home_screen" 701 private const val SCREEN_LOCK = "lock_screen" 702 703 private const val TITLE = "title" 704 /** 705 * Key for a parameter used to get the placeholder color for a wallpaper from the content 706 * provider. 707 */ 708 private const val KEY_PLACEHOLDER_COLOR = "placeholder_color" 709 } 710 } 711