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