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