1 /* 2 * Copyright (C) 2019 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.picker; 17 18 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; 19 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; 20 import static android.view.View.MeasureSpec.EXACTLY; 21 import static android.view.View.MeasureSpec.makeMeasureSpec; 22 23 import static com.android.wallpaper.widget.BottomActionBar.BottomAction.APPLY; 24 import static com.android.wallpaper.widget.BottomActionBar.BottomAction.EDIT; 25 import static com.android.wallpaper.widget.BottomActionBar.BottomAction.INFORMATION; 26 27 import android.animation.Animator; 28 import android.animation.AnimatorListenerAdapter; 29 import android.app.Activity; 30 import android.app.WallpaperColors; 31 import android.content.Context; 32 import android.content.res.Resources; 33 import android.graphics.Bitmap; 34 import android.graphics.BitmapFactory; 35 import android.graphics.Color; 36 import android.graphics.ColorSpace; 37 import android.graphics.Point; 38 import android.graphics.PointF; 39 import android.graphics.Rect; 40 import android.os.Bundle; 41 import android.os.Handler; 42 import android.util.Log; 43 import android.view.LayoutInflater; 44 import android.view.Surface; 45 import android.view.SurfaceControlViewHost; 46 import android.view.SurfaceHolder; 47 import android.view.SurfaceView; 48 import android.view.View; 49 import android.view.ViewGroup; 50 import android.view.ViewGroup.LayoutParams; 51 import android.widget.ImageView; 52 53 import androidx.annotation.NonNull; 54 import androidx.annotation.Nullable; 55 import androidx.cardview.widget.CardView; 56 import androidx.constraintlayout.widget.ConstraintLayout; 57 import androidx.constraintlayout.widget.ConstraintSet; 58 import androidx.fragment.app.FragmentActivity; 59 60 import com.android.wallpaper.R; 61 import com.android.wallpaper.asset.Asset; 62 import com.android.wallpaper.asset.CurrentWallpaperAssetVN; 63 import com.android.wallpaper.model.SetWallpaperViewModel; 64 import com.android.wallpaper.module.BitmapCropper; 65 import com.android.wallpaper.module.Injector; 66 import com.android.wallpaper.module.InjectorProvider; 67 import com.android.wallpaper.module.WallpaperPersister.Destination; 68 import com.android.wallpaper.util.FullScreenAnimation; 69 import com.android.wallpaper.util.ResourceUtils; 70 import com.android.wallpaper.util.ScreenSizeCalculator; 71 import com.android.wallpaper.util.SizeCalculator; 72 import com.android.wallpaper.util.WallpaperCropUtils; 73 import com.android.wallpaper.widget.BottomActionBar; 74 import com.android.wallpaper.widget.BottomActionBar.AccessibilityCallback; 75 import com.android.wallpaper.widget.LockScreenPreviewer; 76 77 import com.bumptech.glide.Glide; 78 import com.bumptech.glide.MemoryCategory; 79 import com.davemorrissey.labs.subscaleview.ImageSource; 80 import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; 81 82 import java.io.ByteArrayOutputStream; 83 import java.util.Locale; 84 import java.util.concurrent.ExecutionException; 85 import java.util.concurrent.Executor; 86 import java.util.concurrent.Executors; 87 import java.util.concurrent.Future; 88 import java.util.concurrent.atomic.AtomicInteger; 89 90 /** 91 * Fragment which displays the UI for previewing an individual static wallpaper and its attribution 92 * information. 93 */ 94 public class ImagePreviewFragment extends PreviewFragment { 95 96 private static final String TAG = "ImagePreviewFragment"; 97 private static final float DEFAULT_WALLPAPER_MAX_ZOOM = 8f; 98 private static final Executor sExecutor = Executors.newCachedThreadPool(); 99 100 private final WallpaperSurfaceCallback mWallpaperSurfaceCallback = 101 new WallpaperSurfaceCallback(); 102 103 private final AtomicInteger mImageScaleChangeCounter = new AtomicInteger(0); 104 private final AtomicInteger mRecalculateColorCounter = new AtomicInteger(0); 105 private final Injector mInjector = InjectorProvider.getInjector(); 106 107 private SubsamplingScaleImageView mFullResImageView; 108 private Asset mWallpaperAsset; 109 /** 110 * Size of the screen considered for cropping the wallpaper (typically the same as 111 * {@link #mScreenSize} but it could be different on multi-display) 112 */ 113 private Point mWallpaperScreenSize; 114 /** 115 * The size of the current screen 116 */ 117 private Point mScreenSize; 118 private Point mRawWallpaperSize; // Native size of wallpaper image. 119 private ImageView mLowResImageView; 120 private TouchForwardingLayout mTouchForwardingLayout; 121 private ConstraintLayout mContainer; 122 private SurfaceView mWallpaperSurface; 123 private boolean mIsSurfaceCreated = false; 124 private WallpaperColors mWallpaperColors; 125 126 protected SurfaceView mWorkspaceSurface; 127 protected WorkspaceSurfaceHolderCallback mWorkspaceSurfaceCallback; 128 protected ViewGroup mLockPreviewContainer; 129 protected LockScreenPreviewer mLockScreenPreviewer; 130 private Future<Integer> mPlaceholderColorFuture; 131 132 @Override onCreate(Bundle savedInstanceState)133 public void onCreate(Bundle savedInstanceState) { 134 super.onCreate(savedInstanceState); 135 mWallpaperAsset = mWallpaper.getAsset(requireContext().getApplicationContext()); 136 mPlaceholderColorFuture = mWallpaper.computePlaceholderColor(requireContext()); 137 } 138 139 @Override getLayoutResId()140 protected int getLayoutResId() { 141 return R.layout.fragment_image_preview; 142 } 143 144 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)145 public View onCreateView(LayoutInflater inflater, ViewGroup container, 146 Bundle savedInstanceState) { 147 View view = super.onCreateView(inflater, container, savedInstanceState); 148 149 Activity activity = requireActivity(); 150 ScreenSizeCalculator screenSizeCalculator = ScreenSizeCalculator.getInstance(); 151 mScreenSize = screenSizeCalculator.getScreenSize( 152 activity.getWindowManager().getDefaultDisplay()); 153 // "Wallpaper screen" size will be the size of the largest screen available 154 mWallpaperScreenSize = screenSizeCalculator.getScreenSize( 155 mInjector.getDisplayUtils(activity).getWallpaperDisplay()); 156 157 mContainer = view.findViewById(R.id.container); 158 mTouchForwardingLayout = mContainer.findViewById(R.id.touch_forwarding_layout); 159 mTouchForwardingLayout.setForwardingEnabled(true); 160 161 // Update preview header color which covers toolbar and status bar area. 162 View previewHeader = view.findViewById(R.id.preview_header); 163 previewHeader.setBackgroundColor(activity.getColor(R.color.settingslib_colorSurfaceHeader)); 164 165 // Set aspect ratio on the preview card dynamically. 166 ConstraintSet set = new ConstraintSet(); 167 set.clone(mContainer); 168 String ratio = String.format(Locale.US, "%d:%d", mScreenSize.x, mScreenSize.y); 169 set.setDimensionRatio(mTouchForwardingLayout.getId(), ratio); 170 set.applyTo(mContainer); 171 172 mWorkspaceSurface = mContainer.findViewById(R.id.workspace_surface); 173 mWorkspaceSurfaceCallback = createWorkspaceSurfaceCallback(mWorkspaceSurface); 174 mWallpaperSurface = mContainer.findViewById(R.id.wallpaper_surface); 175 mLockPreviewContainer = mContainer.findViewById(R.id.lock_screen_preview_container); 176 int placeHolderColor = ResourceUtils.getColorAttr(getContext(), 177 android.R.attr.colorBackground); 178 mWorkspaceSurface.setResizeBackgroundColor(placeHolderColor); 179 mLockScreenPreviewer = new LockScreenPreviewer(getLifecycle(), getContext(), 180 mLockPreviewContainer); 181 mLockScreenPreviewer.setDateViewVisibility(!mFullScreenAnimation.isFullScreen()); 182 mFullScreenAnimation.setFullScreenStatusListener( 183 isFullScreen -> { 184 mLockScreenPreviewer.setDateViewVisibility(!isFullScreen); 185 if (!isFullScreen) { 186 mBottomActionBar.focusAccessibilityAction(EDIT); 187 } 188 }); 189 setUpTabs(view.findViewById(R.id.separated_tabs)); 190 191 view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 192 @Override 193 public void onLayoutChange(View thisView, int left, int top, int right, int bottom, 194 int oldLeft, int oldTop, int oldRight, int oldBottom) { 195 ((CardView) mWorkspaceSurface.getParent()).setRadius( 196 SizeCalculator.getPreviewCornerRadius(activity, 197 ((CardView) mWorkspaceSurface.getParent()).getMeasuredWidth())); 198 view.removeOnLayoutChangeListener(this); 199 } 200 }); 201 202 renderImageWallpaper(); 203 renderWorkspaceSurface(); 204 205 // Trim some memory from Glide to make room for the full-size image in this fragment. 206 Glide.get(activity).setMemoryCategory(MemoryCategory.LOW); 207 208 return view; 209 } 210 211 @Override onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)212 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 213 super.onViewCreated(view, savedInstanceState); 214 } 215 onWallpaperColorsChanged(@ullable WallpaperColors colors)216 protected void onWallpaperColorsChanged(@Nullable WallpaperColors colors) { 217 // Make it enabled since the buttons are disabled while wallpaper is moving. 218 mBottomActionBar.enableActionButtonsWithBottomSheet(true); 219 220 mWallpaperColors = colors; 221 mLockScreenPreviewer.setColor(colors); 222 223 mFullScreenAnimation.setFullScreenTextColor( 224 colors == null || (colors.getColorHints() 225 & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0 226 ? FullScreenAnimation.FullScreenTextColor.LIGHT 227 : FullScreenAnimation.FullScreenTextColor.DARK); 228 } 229 230 @Override isLoaded()231 protected boolean isLoaded() { 232 return mFullResImageView != null && mFullResImageView.hasImage(); 233 } 234 235 @Override onClickOk()236 public void onClickOk() { 237 FragmentActivity activity = getActivity(); 238 if (activity != null) { 239 activity.finish(); 240 } 241 } 242 243 @Override onDestroy()244 public void onDestroy() { 245 super.onDestroy(); 246 247 if (mFullResImageView != null) { 248 mFullResImageView.recycle(); 249 } 250 251 if (mLockScreenPreviewer != null) { 252 mLockScreenPreviewer.release(); 253 } 254 255 mWallpaperSurfaceCallback.cleanUp(); 256 mWorkspaceSurfaceCallback.cleanUp(); 257 } 258 259 @Override onBottomActionBarReady(BottomActionBar bottomActionBar)260 protected void onBottomActionBarReady(BottomActionBar bottomActionBar) { 261 super.onBottomActionBarReady(bottomActionBar); 262 mBottomActionBar.bindBottomSheetContentWithAction( 263 new WallpaperInfoContent(getContext()), INFORMATION); 264 mBottomActionBar.showActionsOnly(INFORMATION, EDIT, APPLY); 265 266 mBottomActionBar.setActionClickListener(APPLY, this::onSetWallpaperClicked); 267 268 View separatedTabsContainer = getView().findViewById(R.id.separated_tabs_container); 269 // Update target view's accessibility param since it will be blocked by the bottom sheet 270 // when expanded. 271 mBottomActionBar.setAccessibilityCallback(new AccessibilityCallback() { 272 @Override 273 public void onBottomSheetCollapsed() { 274 mContainer.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 275 separatedTabsContainer.setImportantForAccessibility( 276 IMPORTANT_FOR_ACCESSIBILITY_YES); 277 } 278 279 @Override 280 public void onBottomSheetExpanded() { 281 mContainer.setImportantForAccessibility( 282 IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); 283 separatedTabsContainer.setImportantForAccessibility( 284 IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); 285 286 } 287 }); 288 289 mBottomActionBar.show(); 290 // To avoid applying the wallpaper when the wallpaper's not parsed. 291 mBottomActionBar.disableActions(); 292 // If the wallpaper is parsed, enable the bottom action bar. 293 if (mRawWallpaperSize != null) { 294 mBottomActionBar.enableActions(); 295 } 296 } 297 298 /** 299 * Initializes MosaicView by initializing tiling, setting a fallback page bitmap, and 300 * initializing a zoom-scroll observer and click listener. 301 */ initFullResView()302 private synchronized void initFullResView() { 303 if (mRawWallpaperSize == null || mFullResImageView == null 304 || mFullResImageView.isImageLoaded()) { 305 return; 306 } 307 308 // Minimum scale will only be respected under this scale type. 309 mFullResImageView.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM); 310 // When we set a minimum scale bigger than the scale with which the full image is shown, 311 // disallow user to pan outside the view we show the wallpaper in. 312 mFullResImageView.setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE); 313 314 // Then set a fallback "page bitmap" to cover the whole MosaicView, which is an actual 315 // (lower res) version of the image to be displayed. 316 Point targetPageBitmapSize = new Point(mRawWallpaperSize); 317 mWallpaperAsset.decodeBitmap(targetPageBitmapSize.x, targetPageBitmapSize.y, 318 pageBitmap -> { 319 // Check that the activity is still around since the decoding task started. 320 if (getActivity() == null) { 321 return; 322 } 323 324 // The page bitmap may be null if there was a decoding error, so show an 325 // error dialog. 326 if (pageBitmap == null) { 327 showLoadWallpaperErrorDialog(); 328 return; 329 } 330 // Some of these may be null depending on if the Fragment is paused, stopped, 331 // or destroyed. 332 mWallpaperSurface.setBackgroundColor(Color.TRANSPARENT); 333 if (mFullResImageView != null) { 334 // Set page bitmap. 335 mFullResImageView.setImage(ImageSource.bitmap(pageBitmap)); 336 // Hide full image view then show it when wallpaper color is updated 337 mFullResImageView.setAlpha(0f); 338 339 setDefaultWallpaperZoomAndScroll( 340 mWallpaperAsset instanceof CurrentWallpaperAssetVN); 341 mFullResImageView.setOnStateChangedListener( 342 new SubsamplingScaleImageView.DefaultOnStateChangedListener() { 343 @Override 344 public void onCenterChanged(PointF newCenter, int origin) { 345 super.onCenterChanged(newCenter, origin); 346 // Disallow bottom sheet to popup when wallpaper is moving 347 // by user dragging. 348 mBottomActionBar.enableActionButtonsWithBottomSheet(false); 349 mImageScaleChangeCounter.incrementAndGet(); 350 mFullResImageView.postDelayed(() -> { 351 if (mImageScaleChangeCounter.decrementAndGet() == 0) { 352 recalculateColors(); 353 } 354 }, /* delayMillis= */ 100); 355 } 356 }); 357 mFullResImageView.post(this::recalculateColors); 358 } 359 }); 360 361 mFullResImageView.setOnTouchListener((v, ev) -> { 362 // Consume the touch event for collapsing bottom sheet while it is expanded or 363 // dragging (not collapsed). 364 if (mBottomActionBar != null && !mBottomActionBar.isBottomSheetCollapsed()) { 365 mBottomActionBar.collapseBottomSheetIfExpanded(); 366 return true; 367 } 368 return false; 369 }); 370 } 371 recalculateColors()372 private void recalculateColors() { 373 Context context = getContext(); 374 if (context == null) { 375 Log.e(TAG, "Got null context, skip recalculating colors"); 376 return; 377 } 378 379 BitmapCropper bitmapCropper = mInjector.getBitmapCropper(); 380 bitmapCropper.cropAndScaleBitmap(mWallpaperAsset, mFullResImageView.getScale(), 381 calculateCropRect(context), /* adjustForRtl= */ false, 382 new BitmapCropper.Callback() { 383 @Override 384 public void onBitmapCropped(Bitmap croppedBitmap) { 385 mRecalculateColorCounter.incrementAndGet(); 386 sExecutor.execute(() -> { 387 boolean shouldRecycle = false; 388 ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(); 389 Bitmap cropped = croppedBitmap; 390 if (cropped.compress(Bitmap.CompressFormat.PNG, 100, tmpOut)) { 391 byte[] outByteArray = tmpOut.toByteArray(); 392 BitmapFactory.Options options = new BitmapFactory.Options(); 393 options.inPreferredColorSpace = 394 ColorSpace.get(ColorSpace.Named.SRGB); 395 cropped = BitmapFactory.decodeByteArray(outByteArray, 0, 396 outByteArray.length); 397 } 398 if (cropped.getConfig() == Bitmap.Config.HARDWARE) { 399 cropped = cropped.copy(Bitmap.Config.ARGB_8888, false); 400 shouldRecycle = true; 401 } 402 WallpaperColors colors = WallpaperColors.fromBitmap(cropped); 403 if (shouldRecycle) { 404 cropped.recycle(); 405 } 406 if (mRecalculateColorCounter.decrementAndGet() == 0) { 407 Handler.getMain().post(() -> { 408 onWallpaperColorsChanged(colors); 409 if (mFullResImageView.getAlpha() == 0f) { 410 crossFadeInMosaicView(); 411 } 412 }); 413 } 414 }); 415 } 416 417 @Override 418 public void onError(@Nullable Throwable e) { 419 Log.w(TAG, "Recalculate colors, crop and scale bitmap failed.", e); 420 } 421 }); 422 } 423 424 /** 425 * Makes the MosaicView visible with an alpha fade-in animation while fading out the loading 426 * indicator. 427 */ crossFadeInMosaicView()428 private void crossFadeInMosaicView() { 429 if (getActivity() != null && isAdded()) { 430 long shortAnimationDuration = getResources().getInteger( 431 android.R.integer.config_shortAnimTime); 432 433 mFullResImageView.setAlpha(0f); 434 mFullResImageView.animate() 435 .alpha(1f) 436 .setInterpolator(ALPHA_OUT) 437 .setDuration(shortAnimationDuration) 438 .setListener(new AnimatorListenerAdapter() { 439 @Override 440 public void onAnimationEnd(Animator animation) { 441 // Clear the thumbnail bitmap reference to save memory since it's no 442 // longer visible. 443 if (mLowResImageView != null) { 444 mLowResImageView.setImageBitmap(null); 445 } 446 } 447 }); 448 } 449 } 450 451 /** 452 * Sets the default wallpaper zoom and scroll position based on a "crop surface" (with extra 453 * width to account for parallax) superimposed on the screen. Shows as much of the wallpaper as 454 * possible on the crop surface and align screen to crop surface such that the default preview 455 * matches what would be seen by the user in the left-most home screen. 456 * 457 * <p>This method is called once in the Fragment lifecycle after the wallpaper asset has loaded 458 * and rendered to the layout. 459 * 460 * @param offsetToStart {@code true} if we want to offset the visible rectangle to the start 461 * side of the raw wallpaper; {@code false} otherwise. 462 */ setDefaultWallpaperZoomAndScroll(boolean offsetToStart)463 private void setDefaultWallpaperZoomAndScroll(boolean offsetToStart) { 464 // Determine minimum zoom to fit maximum visible area of wallpaper on crop surface. 465 int cropWidth = mWallpaperSurface.getMeasuredWidth(); 466 int cropHeight = mWallpaperSurface.getMeasuredHeight(); 467 Point crop = new Point(cropWidth, cropHeight); 468 Rect visibleRawWallpaperRect = 469 WallpaperCropUtils.calculateVisibleRect(mRawWallpaperSize, crop); 470 if (offsetToStart) { 471 if (WallpaperCropUtils.isRtl(requireContext())) { 472 visibleRawWallpaperRect.offsetTo(mRawWallpaperSize.x 473 - visibleRawWallpaperRect.width(), visibleRawWallpaperRect.top); 474 } else { 475 visibleRawWallpaperRect.offsetTo(/* newLeft= */ 0, visibleRawWallpaperRect.top); 476 } 477 } 478 479 final PointF centerPosition = new PointF(visibleRawWallpaperRect.centerX(), 480 visibleRawWallpaperRect.centerY()); 481 482 Point visibleRawWallpaperSize = new Point(visibleRawWallpaperRect.width(), 483 visibleRawWallpaperRect.height()); 484 485 final float defaultWallpaperZoom = WallpaperCropUtils.calculateMinZoom( 486 visibleRawWallpaperSize, crop); 487 final float minWallpaperZoom = defaultWallpaperZoom; 488 489 490 // Set min wallpaper zoom and max zoom on MosaicView widget. 491 mFullResImageView.setMaxScale(Math.max(DEFAULT_WALLPAPER_MAX_ZOOM, defaultWallpaperZoom)); 492 mFullResImageView.setMinScale(minWallpaperZoom); 493 494 // Set center to composite positioning between scaled wallpaper and screen. 495 mFullResImageView.setScaleAndCenter(minWallpaperZoom, centerPosition); 496 } 497 calculateCropRect(Context context)498 private Rect calculateCropRect(Context context) { 499 float wallpaperZoom = mFullResImageView.getScale(); 500 Context appContext = context.getApplicationContext(); 501 502 Rect visibleFileRect = new Rect(); 503 mFullResImageView.visibleFileRect(visibleFileRect); 504 505 int cropWidth = mWallpaperSurface.getMeasuredWidth(); 506 int cropHeight = mWallpaperSurface.getMeasuredHeight(); 507 int maxCrop = Math.max(cropWidth, cropHeight); 508 int minCrop = Math.min(cropWidth, cropHeight); 509 Point hostViewSize = new Point(cropWidth, cropHeight); 510 511 Resources res = appContext.getResources(); 512 Point cropSurfaceSize = WallpaperCropUtils.calculateCropSurfaceSize(res, maxCrop, minCrop, 513 cropWidth, cropHeight); 514 return WallpaperCropUtils.calculateCropRect(appContext, hostViewSize, 515 cropSurfaceSize, mRawWallpaperSize, visibleFileRect, wallpaperZoom); 516 } 517 518 @Override setCurrentWallpaper(@estination int destination)519 protected void setCurrentWallpaper(@Destination int destination) { 520 mWallpaperSetter.setCurrentWallpaper(getActivity(), mWallpaper, mWallpaperAsset, 521 destination, mFullResImageView.getScale(), calculateCropRect(getContext()), 522 mWallpaperColors, SetWallpaperViewModel.getCallback(mViewModelProvider)); 523 } 524 renderWorkspaceSurface()525 private void renderWorkspaceSurface() { 526 mWorkspaceSurface.setZOrderMediaOverlay(true); 527 mWorkspaceSurface.getHolder().addCallback(mWorkspaceSurfaceCallback); 528 } 529 renderImageWallpaper()530 private void renderImageWallpaper() { 531 mWallpaperSurface.getHolder().addCallback(mWallpaperSurfaceCallback); 532 } 533 534 private class WallpaperSurfaceCallback implements SurfaceHolder.Callback { 535 private Surface mLastSurface; 536 private SurfaceControlViewHost mHost; 537 538 @Override surfaceCreated(SurfaceHolder holder)539 public void surfaceCreated(SurfaceHolder holder) { 540 if (mLastSurface != holder.getSurface()) { 541 mLastSurface = holder.getSurface(); 542 if (mFullResImageView != null) { 543 mFullResImageView.recycle(); 544 } 545 Context context = getContext(); 546 View wallpaperPreviewContainer = LayoutInflater.from(context).inflate( 547 R.layout.fullscreen_wallpaper_preview, null); 548 mFullResImageView = wallpaperPreviewContainer.findViewById(R.id.full_res_image); 549 mLowResImageView = wallpaperPreviewContainer.findViewById(R.id.low_res_image); 550 mWallpaperAsset.decodeRawDimensions(getActivity(), dimensions -> { 551 // Don't continue loading the wallpaper if the Fragment is detached. 552 if (getActivity() == null) { 553 return; 554 } 555 556 // Return early and show a dialog if dimensions are null (signaling a decoding 557 // error). 558 if (dimensions == null) { 559 showLoadWallpaperErrorDialog(); 560 return; 561 } 562 563 // To avoid applying the wallpaper when it's not parsed. Now it's parsed, enable 564 // the bottom action bar to allow applying the wallpaper. 565 if (mBottomActionBar != null) { 566 mBottomActionBar.enableActions(); 567 } 568 569 mRawWallpaperSize = dimensions; 570 initFullResView(); 571 }); 572 573 // Calculate the size of mWallpaperSurface based on system zoom's scale and 574 // on the larger screen size (if more than one) so that the wallpaper is 575 // rendered in a larger surface than what preview shows, simulating the behavior of 576 // the actual wallpaper surface and so we can crop it to a size that fits in all 577 // screens. 578 float scale = WallpaperCropUtils.getSystemWallpaperMaximumScale(context); 579 int origWidth = mWallpaperSurface.getWidth(); 580 int origHeight = mWallpaperSurface.getHeight(); 581 582 if (!mScreenSize.equals(mWallpaperScreenSize)) { 583 float previewToScreenScale = (float) origWidth / mScreenSize.x; 584 origWidth = (int) (mWallpaperScreenSize.x * previewToScreenScale); 585 } 586 int width = (int) (origWidth * scale); 587 int height = (int) (origHeight * scale); 588 int left = (origWidth - width) / 2; 589 int top = (origHeight - height) / 2; 590 591 if (WallpaperCropUtils.isRtl(context)) { 592 left *= -1; 593 } 594 595 LayoutParams params = mWallpaperSurface.getLayoutParams(); 596 params.width = width; 597 params.height = height; 598 mWallpaperSurface.setX(left); 599 mWallpaperSurface.setY(top); 600 mWallpaperSurface.setLayoutParams(params); 601 mWallpaperSurface.requestLayout(); 602 603 // Load a low-res placeholder image if there's a thumbnail available from the asset 604 // that can be shown to the user more quickly than the full-sized image. 605 Activity activity = requireActivity(); 606 // Change to background color if colorValue is Color.TRANSPARENT 607 int placeHolderColor = ResourceUtils.getColorAttr(activity, 608 android.R.attr.colorBackground); 609 if (mPlaceholderColorFuture.isDone()) { 610 try { 611 int colorValue = mWallpaper.computePlaceholderColor(context).get(); 612 if (colorValue != Color.TRANSPARENT) { 613 placeHolderColor = colorValue; 614 } 615 } catch (InterruptedException | ExecutionException e) { 616 // Do nothing 617 } 618 } 619 mWallpaperSurface.setResizeBackgroundColor(placeHolderColor); 620 mWallpaperSurface.setBackgroundColor(placeHolderColor); 621 mLowResImageView.setBackgroundColor(placeHolderColor); 622 623 mWallpaperAsset.loadLowResDrawable(activity, mLowResImageView, placeHolderColor, 624 mPreviewBitmapTransformation); 625 626 wallpaperPreviewContainer.measure( 627 makeMeasureSpec(width, EXACTLY), 628 makeMeasureSpec(height, EXACTLY)); 629 wallpaperPreviewContainer.layout(0, 0, width, height); 630 mTouchForwardingLayout.setTargetView(mFullResImageView); 631 632 cleanUp(); 633 mHost = new SurfaceControlViewHost(context, 634 context.getDisplay(), mWallpaperSurface.getHostToken()); 635 mHost.setView(wallpaperPreviewContainer, wallpaperPreviewContainer.getWidth(), 636 wallpaperPreviewContainer.getHeight()); 637 mWallpaperSurface.setChildSurfacePackage(mHost.getSurfacePackage()); 638 // After surface creating, update workspaceSurface. 639 mIsSurfaceCreated = true; 640 updateScreenPreview(mLastSelectedTabPositionOptional.orElse(0) == 0); 641 } 642 } 643 644 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)645 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } 646 647 @Override surfaceDestroyed(SurfaceHolder holder)648 public void surfaceDestroyed(SurfaceHolder holder) { } 649 cleanUp()650 public void cleanUp() { 651 if (mHost != null) { 652 mHost.release(); 653 mHost = null; 654 } 655 mIsSurfaceCreated = false; 656 } 657 } 658 659 @Override updateScreenPreview(boolean isHomeSelected)660 protected void updateScreenPreview(boolean isHomeSelected) { 661 // Use View.GONE for WorkspaceSurface's visibility before its surface is created. 662 mWorkspaceSurface.setVisibility(isHomeSelected && mIsSurfaceCreated ? View.VISIBLE : 663 View.GONE); 664 665 mLockPreviewContainer.setVisibility(isHomeSelected ? View.INVISIBLE : View.VISIBLE); 666 667 mFullScreenAnimation.setIsHomeSelected(isHomeSelected); 668 } 669 } 670