1 /* 2 * Copyright (C) 2022 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.VISIBLE; 19 20 import static com.android.wallpaper.util.LaunchSourceUtils.LAUNCH_SOURCE_LAUNCHER; 21 import static com.android.wallpaper.util.LaunchSourceUtils.LAUNCH_SOURCE_SETTINGS_HOMEPAGE; 22 import static com.android.wallpaper.util.LaunchSourceUtils.WALLPAPER_LAUNCH_SOURCE; 23 import static com.android.wallpaper.widget.FloatingSheet.INFORMATION; 24 25 import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED; 26 import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN; 27 28 import android.animation.Animator; 29 import android.animation.AnimatorListenerAdapter; 30 import android.annotation.SuppressLint; 31 import android.app.Activity; 32 import android.app.AlertDialog; 33 import android.app.WallpaperColors; 34 import android.content.Context; 35 import android.content.Intent; 36 import android.content.res.Resources.NotFoundException; 37 import android.graphics.drawable.Drawable; 38 import android.os.Bundle; 39 import android.util.Log; 40 import android.view.LayoutInflater; 41 import android.view.SurfaceView; 42 import android.view.View; 43 import android.view.ViewGroup; 44 import android.view.Window; 45 import android.view.animation.Interpolator; 46 import android.view.animation.PathInterpolator; 47 import android.widget.CompoundButton; 48 import android.widget.FrameLayout; 49 import android.widget.ProgressBar; 50 import android.widget.Toast; 51 import android.widget.Toolbar; 52 53 import androidx.annotation.CallSuper; 54 import androidx.annotation.IntDef; 55 import androidx.annotation.Nullable; 56 import androidx.appcompat.content.res.AppCompatResources; 57 import androidx.core.content.res.ResourcesCompat; 58 import androidx.core.view.AccessibilityDelegateCompat; 59 import androidx.core.view.ViewCompat; 60 import androidx.core.view.WindowCompat; 61 import androidx.core.view.WindowInsetsControllerCompat; 62 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; 63 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; 64 import androidx.fragment.app.Fragment; 65 import androidx.fragment.app.FragmentActivity; 66 import androidx.lifecycle.LifecycleOwnerKt; 67 import androidx.lifecycle.ViewModelProvider; 68 69 import com.android.wallpaper.R; 70 import com.android.wallpaper.model.LiveWallpaperInfo; 71 import com.android.wallpaper.model.SetWallpaperViewModel; 72 import com.android.wallpaper.model.WallpaperInfo; 73 import com.android.wallpaper.module.Injector; 74 import com.android.wallpaper.module.InjectorProvider; 75 import com.android.wallpaper.module.WallpaperPersister.Destination; 76 import com.android.wallpaper.module.WallpaperSetter; 77 import com.android.wallpaper.module.logging.UserEventLogger; 78 import com.android.wallpaper.util.PreviewUtils; 79 import com.android.wallpaper.util.ResourceUtils; 80 import com.android.wallpaper.widget.DuoTabs; 81 import com.android.wallpaper.widget.FloatingSheet; 82 import com.android.wallpaper.widget.WallpaperControlButtonGroup; 83 import com.android.wallpaper.widget.floatingsheetcontent.WallpaperInfoContent; 84 85 import com.google.android.material.bottomsheet.BottomSheetBehavior; 86 import com.google.android.material.transition.MaterialSharedAxis; 87 88 import java.util.List; 89 90 import kotlinx.coroutines.BuildersKt; 91 import kotlinx.coroutines.CoroutineStart; 92 import kotlinx.coroutines.Dispatchers; 93 94 /** 95 * Base Fragment to display the UI for previewing an individual wallpaper. 96 */ 97 public abstract class PreviewFragment extends Fragment implements WallpaperColorThemePreview { 98 99 public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f); 100 101 /** 102 * User can view wallpaper and attributions in full screen, but "Set wallpaper" button is 103 * hidden. 104 */ 105 public static final int MODE_VIEW_ONLY = 0; 106 107 /** 108 * User can view wallpaper and attributions in full screen and click "Set wallpaper" to set the 109 * wallpaper with pan and crop position to the device. 110 */ 111 public static final int MODE_CROP_AND_SET_WALLPAPER = 1; 112 113 /** 114 * Possible preview modes for the fragment. 115 */ 116 @IntDef({ 117 MODE_VIEW_ONLY, 118 MODE_CROP_AND_SET_WALLPAPER}) 119 public @interface PreviewMode { 120 } 121 122 public static final String ARG_IS_NEW_TASK = "is_new_task"; 123 public static final String ARG_IS_ASSET_ID_PRESENT = "is_asset_id_present"; 124 public static final String ARG_WALLPAPER = "wallpaper"; 125 public static final String ARG_VIEW_AS_HOME = "view_as_home"; 126 127 private static final String TAG = "PreviewFragment"; 128 129 protected WallpaperInfo mWallpaper; 130 protected WallpaperSetter mWallpaperSetter; 131 protected ViewModelProvider mViewModelProvider; 132 protected WallpaperColors mWallpaperColors; 133 protected UserEventLogger mUserEventLogger; 134 private SetWallpaperViewModel mSetWallpaperViewModel; 135 136 // UI 137 private SurfaceView mWorkspaceSurface; 138 private WorkspaceSurfaceHolderCallback mWorkspaceSurfaceCallback; 139 private SurfaceView mLockSurface; 140 private WorkspaceSurfaceHolderCallback mLockSurfaceCallback; 141 private View mHideFloatingSheetTouchLayout; 142 private DuoTabs mOverlayTabs; 143 private @DuoTabs.Tab int mInitSelectedTab; 144 private View mExitFullPreviewButton; 145 protected View mSetWallpaperButton; 146 protected FrameLayout mSetWallpaperButtonContainer; 147 protected View mPreviewScrim; 148 protected Toolbar mToolbar; 149 protected WallpaperControlButtonGroup mWallpaperControlButtonGroup; 150 protected FloatingSheet mFloatingSheet; 151 protected TouchForwardingLayout mTouchForwardingLayout; 152 153 protected ProgressBar mProgressBar; 154 155 protected boolean mIsViewAsHome; 156 157 /** 158 * We create an instance of WallpaperInfo from CurrentWallpaperInfo when a user taps on 159 * the preview of a wallpapers in the wallpaper picker main screen. However, there are 160 * other instances as well in which an instance of the specific WallpaperInfo is created. This 161 * variable is used in order to identify whether the instance created has an assetId or not. 162 * This is needed for restricting the destination where a wallpaper can be set after editing 163 * it. 164 */ 165 protected boolean mIsAssetIdPresent; 166 167 /** 168 * True if the activity of this fragment is launched with {@link Intent#FLAG_ACTIVITY_NEW_TASK}. 169 */ 170 private boolean mIsNewTask; 171 172 // The system "short" animation time duration, in milliseconds. This 173 // duration is ideal for subtle animations or animations that occur 174 // very frequently. 175 private int mShortAnimTimeMillis; 176 177 private final BottomSheetBehavior.BottomSheetCallback mStandardFloatingSheetCallback = 178 new BottomSheetBehavior.BottomSheetCallback() { 179 @Override 180 public void onStateChanged(@androidx.annotation.NonNull View bottomSheet, 181 int newState) { 182 if (newState == STATE_EXPANDED) { 183 mHideFloatingSheetTouchLayout.setVisibility(View.VISIBLE); 184 mTouchForwardingLayout.setVisibility(View.GONE); 185 } 186 if (newState == STATE_HIDDEN) { 187 mWallpaperControlButtonGroup.deselectAllFloatingSheetControlButtons(); 188 mHideFloatingSheetTouchLayout.setVisibility(View.GONE); 189 mTouchForwardingLayout.setVisibility(VISIBLE); 190 mTouchForwardingLayout.requestFocus(); 191 } 192 } 193 194 @Override 195 public void onSlide(@androidx.annotation.NonNull View bottomSheet, 196 float slideOffset) { 197 } 198 }; 199 200 protected final BottomSheetBehavior.BottomSheetCallback 201 mShowOverlayOnHideFloatingSheetCallback = 202 new BottomSheetBehavior.BottomSheetCallback() { 203 @Override 204 public void onStateChanged(@androidx.annotation.NonNull View bottomSheet, 205 int newState) { 206 if (newState == STATE_HIDDEN) { 207 hideScreenPreviewOverlay(/* hide= */false); 208 } 209 } 210 211 @Override 212 public void onSlide(@androidx.annotation.NonNull View bottomSheet, 213 float slideOffset) { 214 } 215 }; 216 217 /** 218 * Sets current wallpaper to the device based on current zoom and scroll state. 219 * 220 * @param destination The wallpaper destination i.e. home vs. lockscreen vs. both. 221 */ setWallpaper(@estination int destination)222 protected abstract void setWallpaper(@Destination int destination); 223 224 @Override onCreate(Bundle savedInstanceState)225 public void onCreate(Bundle savedInstanceState) { 226 super.onCreate(savedInstanceState); 227 Bundle args = requireArguments(); 228 mWallpaper = args.getParcelable(ARG_WALLPAPER); 229 mIsViewAsHome = args.getBoolean(ARG_VIEW_AS_HOME); 230 mIsAssetIdPresent = args.getBoolean(ARG_IS_ASSET_ID_PRESENT); 231 mIsNewTask = args.getBoolean(ARG_IS_NEW_TASK); 232 mInitSelectedTab = mIsViewAsHome ? DuoTabs.TAB_SECONDARY : DuoTabs.TAB_PRIMARY; 233 Context appContext = requireContext().getApplicationContext(); 234 Injector injector = InjectorProvider.getInjector(); 235 236 mUserEventLogger = injector.getUserEventLogger(); 237 mWallpaperSetter = new WallpaperSetter(injector.getWallpaperPersister(appContext), 238 injector.getPreferences(appContext), mUserEventLogger, 239 injector.getCurrentWallpaperInfoFactory(appContext)); 240 mViewModelProvider = new ViewModelProvider(requireActivity()); 241 mSetWallpaperViewModel = mViewModelProvider.get(SetWallpaperViewModel.class); 242 mSetWallpaperViewModel.getStatus().observe(requireActivity(), setWallpaperStatus -> { 243 switch (setWallpaperStatus) { 244 case SUCCESS: 245 onSetWallpaperSuccess(); 246 break; 247 case ERROR: 248 showSetWallpaperErrorDialog(); 249 break; 250 default: 251 // Do nothing when UNKNOWN or PENDING 252 } 253 }); 254 255 mShortAnimTimeMillis = getResources().getInteger(android.R.integer.config_shortAnimTime); 256 setEnterTransition(new MaterialSharedAxis(MaterialSharedAxis.X, /* forward */ true)); 257 setReturnTransition(new MaterialSharedAxis(MaterialSharedAxis.X, /* forward */ false)); 258 setExitTransition(new MaterialSharedAxis(MaterialSharedAxis.X, /* forward */ true)); 259 setReenterTransition(new MaterialSharedAxis(MaterialSharedAxis.X, /* forward */ false)); 260 261 } 262 263 @Override 264 @CallSuper onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)265 public View onCreateView(LayoutInflater inflater, ViewGroup container, 266 Bundle savedInstanceState) { 267 View view = inflater.inflate(R.layout.fragment_wallpaper_preview, container, false); 268 // Progress indicator 269 mProgressBar = view.findViewById(R.id.action_progress); 270 // Toolbar 271 mToolbar = view.findViewById(R.id.toolbar); 272 setUpToolbar(); 273 // TouchForwardingLayout 274 mTouchForwardingLayout = view.findViewById(R.id.touch_forwarding_layout); 275 mTouchForwardingLayout.setOnClickAccessibilityDescription( 276 R.string.hide_preview_controls_action); 277 // Preview overlay 278 mWorkspaceSurface = view.findViewById(R.id.workspace_surface); 279 mWorkspaceSurfaceCallback = new WorkspaceSurfaceHolderCallback( 280 mWorkspaceSurface, 281 new PreviewUtils( 282 requireContext(), 283 getString(R.string.grid_control_metadata_name)), 284 shouldApplyWallpaperColors()); 285 // Hide the work space's bottom row initially to avoid overlapping with the overlay tabs. 286 mWorkspaceSurfaceCallback.setHideBottomRow(true); 287 mLockSurface = view.findViewById(R.id.lock_screen_overlay_surface); 288 mLockSurfaceCallback = new WorkspaceSurfaceHolderCallback( 289 mLockSurface, 290 new PreviewUtils( 291 requireContext().getApplicationContext(), 292 null, 293 getString(R.string.lock_screen_preview_provider_authority)), 294 shouldApplyWallpaperColors()); 295 setUpScreenPreviewOverlay(); 296 // Set wallpaper button 297 mSetWallpaperButtonContainer = view.findViewById(R.id.button_set_wallpaper_container); 298 mSetWallpaperButton = view.findViewById(R.id.button_set_wallpaper); 299 mSetWallpaperButtonContainer.setOnClickListener( 300 v -> showDestinationSelectionDialogForWallpaper(mWallpaper)); 301 // Overlay tabs 302 mOverlayTabs = view.findViewById(R.id.overlay_tabs); 303 mOverlayTabs.setTabText(getString(R.string.lock_screen_message), 304 getString(R.string.home_screen_message)); 305 mOverlayTabs.setOnTabSelectedListener(this::updateScreenPreviewOverlay); 306 mOverlayTabs.selectTab(mInitSelectedTab); 307 // Floating sheet and button control group 308 mFloatingSheet = view.findViewById(R.id.floating_sheet); 309 mHideFloatingSheetTouchLayout = view.findViewById(R.id.hide_floating_sheet_touch_layout); 310 mWallpaperControlButtonGroup = view.findViewById(R.id.wallpaper_control_button_group); 311 boolean shouldShowInformationFloatingSheet = shouldShowInformationFloatingSheet(mWallpaper); 312 setUpFloatingSheet(requireContext(), shouldShowInformationFloatingSheet); 313 if (shouldShowInformationFloatingSheet) { 314 mWallpaperControlButtonGroup.showButton( 315 WallpaperControlButtonGroup.INFORMATION, 316 getFloatingSheetControlButtonChangeListener( 317 WallpaperControlButtonGroup.INFORMATION, 318 INFORMATION)); 319 } 320 mPreviewScrim = view.findViewById(R.id.preview_scrim); 321 mExitFullPreviewButton = view.findViewById(R.id.exit_full_preview_button); 322 mExitFullPreviewButton.setOnClickListener(v -> toggleWallpaperPreviewControl()); 323 updateStatusBarColor(); 324 return view; 325 } 326 setUpToolbar()327 private void setUpToolbar() { 328 Activity activity = getActivity(); 329 if (activity == null) { 330 return; 331 } 332 mToolbar.setTitle(R.string.preview); 333 mToolbar.setTitleTextColor(getResources().getColor(R.color.preview_toolbar_text_light)); 334 mToolbar.setBackgroundResource(android.R.color.transparent); 335 activity.getWindow().setStatusBarColor( 336 getResources().getColor(android.R.color.transparent)); 337 activity.getWindow().setNavigationBarColor( 338 getResources().getColor(android.R.color.transparent)); 339 340 // The hosting activity needs to implement AppbarFragment.AppbarFragmentHost 341 AppbarFragment.AppbarFragmentHost host = (AppbarFragment.AppbarFragmentHost) activity; 342 if (host.isUpArrowSupported()) { 343 mToolbar.setNavigationIcon(getToolbarBackIcon()); 344 mToolbar.setNavigationContentDescription(R.string.bottom_action_bar_back); 345 mToolbar.setNavigationOnClickListener(view -> { 346 host.onUpArrowPressed(); 347 }); 348 } 349 } 350 351 @Nullable getToolbarBackIcon()352 private Drawable getToolbarBackIcon() { 353 Drawable backIcon = ResourcesCompat.getDrawable(getResources(), 354 R.drawable.material_ic_arrow_back_black_24, 355 null); 356 if (backIcon == null) { 357 return null; 358 } 359 backIcon.setAutoMirrored(true); 360 backIcon.setTint(getResources().getColor(R.color.preview_toolbar_text_light)); 361 return backIcon; 362 } 363 updateStatusBarColor()364 private void updateStatusBarColor() { 365 Activity activity = getActivity(); 366 if (activity == null) { 367 return; 368 } 369 Window window = activity.getWindow(); 370 WindowInsetsControllerCompat windowInsetsController = 371 WindowCompat.getInsetsController(window, window.getDecorView()); 372 boolean shouldUseLightText = 373 mPreviewScrim.getVisibility() == VISIBLE || (mWallpaperColors != null && ( 374 (mWallpaperColors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) 375 != WallpaperColors.HINT_SUPPORTS_DARK_TEXT)); 376 windowInsetsController.setAppearanceLightStatusBars(!shouldUseLightText); 377 } 378 setUpScreenPreviewOverlay()379 private void setUpScreenPreviewOverlay() { 380 int placeHolderColor = ResourceUtils.getColorAttr(requireContext(), 381 android.R.attr.colorBackground); 382 mWorkspaceSurface.setResizeBackgroundColor(placeHolderColor); 383 mWorkspaceSurface.setZOrderMediaOverlay(true); 384 mWorkspaceSurface.getHolder().addCallback(mWorkspaceSurfaceCallback); 385 mLockSurface.setResizeBackgroundColor(placeHolderColor); 386 mLockSurface.setZOrderMediaOverlay(true); 387 mLockSurface.getHolder().addCallback(mLockSurfaceCallback); 388 } 389 shouldShowInformationFloatingSheet(WallpaperInfo wallpaperInfo)390 private boolean shouldShowInformationFloatingSheet(WallpaperInfo wallpaperInfo) { 391 List<String> attributions = wallpaperInfo.getAttributions(requireContext()); 392 String actionUrl = wallpaperInfo.getActionUrl(requireContext()); 393 boolean showAttribution = false; 394 for (String attr : attributions) { 395 if (attr != null && !attr.isEmpty()) { 396 showAttribution = true; 397 break; 398 } 399 } 400 boolean showActionUrl = actionUrl != null && !actionUrl.isEmpty(); 401 return showAttribution || showActionUrl; 402 } 403 404 @SuppressLint("ClickableViewAccessibility") setUpFloatingSheet(Context context, boolean shouldShowInformationFloatingSheet)405 private void setUpFloatingSheet(Context context, boolean shouldShowInformationFloatingSheet) { 406 setHideFloatingSheetLayoutAccessibilityAction(); 407 mHideFloatingSheetTouchLayout.setContentDescription( 408 getString(R.string.preview_screen_description)); 409 mHideFloatingSheetTouchLayout.setOnClickListener(v -> mFloatingSheet.collapse()); 410 mHideFloatingSheetTouchLayout.setVisibility(View.GONE); 411 mFloatingSheet.addFloatingSheetCallback(mStandardFloatingSheetCallback); 412 mFloatingSheet.addFloatingSheetCallback(mShowOverlayOnHideFloatingSheetCallback); 413 if (shouldShowInformationFloatingSheet) { 414 mFloatingSheet.putFloatingSheetContent(INFORMATION, 415 new WallpaperInfoContent(context, mWallpaper)); 416 } 417 } 418 getFloatingSheetControlButtonChangeListener( @allpaperControlButtonGroup.WallpaperControlType int wallpaperType, @FloatingSheet.Companion.FloatingSheetContentType int floatingSheetType)419 protected CompoundButton.OnCheckedChangeListener getFloatingSheetControlButtonChangeListener( 420 @WallpaperControlButtonGroup.WallpaperControlType int wallpaperType, 421 @FloatingSheet.Companion.FloatingSheetContentType int floatingSheetType) { 422 return (buttonView, isChecked) -> { 423 if (isChecked) { 424 mWallpaperControlButtonGroup.deselectOtherFloatingSheetControlButtons( 425 wallpaperType); 426 if (mFloatingSheet.isFloatingSheetCollapsed()) { 427 if (floatingSheetType == INFORMATION) { 428 hideScreenPreviewOverlayKeepScrim(); 429 } else { 430 hideScreenPreviewOverlay(/* hide= */true); 431 } 432 mFloatingSheet.updateContentView(floatingSheetType); 433 mFloatingSheet.expand(); 434 } else { 435 mFloatingSheet.updateContentViewWithAnimation(floatingSheetType); 436 } 437 } else { 438 if (!mWallpaperControlButtonGroup.isFloatingSheetControlButtonSelected()) { 439 mFloatingSheet.collapse(); 440 } 441 } 442 }; 443 } 444 445 private void setHideFloatingSheetLayoutAccessibilityAction() { 446 ViewCompat.setAccessibilityDelegate(mHideFloatingSheetTouchLayout, 447 new AccessibilityDelegateCompat() { 448 @Override 449 public void onInitializeAccessibilityNodeInfo(View host, 450 AccessibilityNodeInfoCompat info) { 451 super.onInitializeAccessibilityNodeInfo(host, info); 452 CharSequence description = host.getResources().getString( 453 R.string.hide_wallpaper_info_action); 454 AccessibilityActionCompat clickAction = new AccessibilityActionCompat( 455 AccessibilityNodeInfoCompat.ACTION_CLICK, description); 456 info.addAction(clickAction); 457 } 458 }); 459 } 460 461 @Override 462 public void onDestroy() { 463 super.onDestroy(); 464 if (mWallpaperSetter != null) { 465 mWallpaperSetter.cleanUp(); 466 } 467 if (mWorkspaceSurfaceCallback != null) { 468 mWorkspaceSurfaceCallback.cleanUp(); 469 } 470 if (mLockSurfaceCallback != null) { 471 mLockSurfaceCallback.cleanUp(); 472 } 473 } 474 475 protected void onWallpaperColorsChanged(@Nullable WallpaperColors colors) { 476 // Early return to not block the instrumentation test. 477 if (InjectorProvider.getInjector().isInstrumentationTest()) { 478 return; 479 } 480 if (!shouldApplyWallpaperColors()) { 481 return; 482 } 483 mWallpaperColors = colors; 484 Context context = getContext(); 485 if (context == null || colors == null) { 486 return; 487 } 488 // Apply the wallpaper color resources to the fragment context. So the views created by 489 // the context will apply the given wallpaper color. 490 BuildersKt.launch( 491 LifecycleOwnerKt.getLifecycleScope(getViewLifecycleOwner()), 492 Dispatchers.getIO(), 493 CoroutineStart.DEFAULT, 494 (coroutineScope, continuation) -> 495 InjectorProvider.getInjector().getWallpaperColorResources(colors, 496 context).apply( 497 context, 498 () -> { 499 requireActivity().runOnUiThread(() -> { 500 mSetWallpaperButton.setBackground(null); 501 mSetWallpaperButton.setBackgroundResource( 502 R.drawable.set_wallpaper_button_background); 503 mExitFullPreviewButton.setForeground( 504 AppCompatResources.getDrawable(context, 505 R.drawable.exit_full_preview_cross)); 506 mWallpaperControlButtonGroup.updateBackgroundColor(); 507 mOverlayTabs.updateBackgroundColor(); 508 mFloatingSheet.setColor(context); 509 }); 510 return null; 511 }, continuation 512 ) 513 ); 514 515 // Update the color theme for the home screen overlay 516 updateWorkspacePreview(mWorkspaceSurface, mWorkspaceSurfaceCallback, colors, 517 /* hideBottomRow= */ mOverlayTabs.getVisibility() == VISIBLE); 518 // Update the color theme for the lock screen overlay 519 updateWorkspacePreview(mLockSurface, mLockSurfaceCallback, colors, 520 /* hideBottomRow= */ mOverlayTabs.getVisibility() == VISIBLE); 521 } 522 523 private void updateScreenPreviewOverlay(@DuoTabs.Tab int tab) { 524 if (mWorkspaceSurface != null) { 525 mWorkspaceSurface.setVisibility( 526 tab == DuoTabs.TAB_SECONDARY ? View.VISIBLE : View.INVISIBLE); 527 mWorkspaceSurface.setZOrderMediaOverlay(tab == DuoTabs.TAB_SECONDARY); 528 } 529 if (mLockSurface != null) { 530 mLockSurface.setVisibility( 531 tab == DuoTabs.TAB_PRIMARY ? View.VISIBLE : View.INVISIBLE); 532 mLockSurface.setZOrderMediaOverlay(tab == DuoTabs.TAB_PRIMARY); 533 } 534 } 535 536 protected void toggleWallpaperPreviewControl() { 537 boolean wasVisible = mPreviewScrim.getVisibility() == VISIBLE; 538 mTouchForwardingLayout.setOnClickAccessibilityDescription( 539 wasVisible ? R.string.show_preview_controls_action 540 : R.string.hide_preview_controls_action); 541 animateWallpaperPreviewControl(wasVisible); 542 } 543 544 private void animateWallpaperPreviewControl(boolean hide) { 545 // When hiding the preview control, we should show the workspace bottom row components 546 hideBottomRow(!hide); 547 mPreviewScrim.animate() 548 .alpha(hide ? 0f : 1f) 549 .setDuration(mShortAnimTimeMillis) 550 .setListener(new ViewAnimatorListener(mPreviewScrim, hide) { 551 @Override 552 public void onAnimationEnd(Animator animation) { 553 super.onAnimationEnd(animation); 554 updateStatusBarColor(); 555 } 556 }); 557 mWallpaperControlButtonGroup.animate().alpha(hide ? 0f : 1f) 558 .setDuration(mShortAnimTimeMillis) 559 .setListener(new ViewAnimatorListener(mWallpaperControlButtonGroup, hide)); 560 mOverlayTabs.animate().alpha(hide ? 0f : 1f) 561 .setDuration(mShortAnimTimeMillis) 562 .setListener(new ViewAnimatorListener(mOverlayTabs, hide)); 563 mSetWallpaperButtonContainer.animate().alpha(hide ? 0f : 1f) 564 .setDuration(mShortAnimTimeMillis) 565 .setListener(new ViewAnimatorListener(mSetWallpaperButtonContainer, hide)); 566 mToolbar.animate().alpha(hide ? 0f : 1f) 567 .setDuration(mShortAnimTimeMillis) 568 .setListener(new ViewAnimatorListener(mToolbar, hide)); 569 // The show and hide of the button is the opposite of the wallpaper preview control 570 mExitFullPreviewButton.animate().alpha(!hide ? 0f : 1f) 571 .setDuration(mShortAnimTimeMillis) 572 .setListener(new ViewAnimatorListener(mExitFullPreviewButton, !hide)); 573 } 574 575 private void hideBottomRow(boolean hide) { 576 if (mWorkspaceSurfaceCallback != null) { 577 Bundle data = new Bundle(); 578 data.putBoolean(WorkspaceSurfaceHolderCallback.KEY_HIDE_BOTTOM_ROW, hide); 579 mWorkspaceSurfaceCallback.send(WorkspaceSurfaceHolderCallback.MESSAGE_ID_UPDATE_PREVIEW, 580 data); 581 } 582 } 583 584 protected void hideScreenPreviewOverlay(boolean hide) { 585 mPreviewScrim.setVisibility(hide ? View.INVISIBLE : View.VISIBLE); 586 mOverlayTabs.setVisibility(hide ? View.INVISIBLE : View.VISIBLE); 587 boolean isLockSelected = mOverlayTabs.getSelectedTab() == DuoTabs.TAB_PRIMARY; 588 if (isLockSelected) { 589 mLockSurface.setVisibility(hide ? View.INVISIBLE : View.VISIBLE); 590 mLockSurface.setZOrderMediaOverlay(!hide); 591 } else { 592 mWorkspaceSurface.setVisibility(hide ? View.INVISIBLE : View.VISIBLE); 593 mWorkspaceSurface.setZOrderMediaOverlay(!hide); 594 } 595 } 596 597 /** 598 * Hides or shows the overlay but leaves the scrim always visible. 599 */ 600 private void hideScreenPreviewOverlayKeepScrim() { 601 mPreviewScrim.setVisibility(VISIBLE); 602 mOverlayTabs.setVisibility(View.INVISIBLE); 603 boolean isLockSelected = mOverlayTabs.getSelectedTab() == DuoTabs.TAB_PRIMARY; 604 SurfaceView targetSurface = isLockSelected ? mLockSurface : mWorkspaceSurface; 605 targetSurface.setVisibility(View.INVISIBLE); 606 targetSurface.setZOrderMediaOverlay(false); 607 } 608 609 protected void onSetWallpaperSuccess() { 610 Activity activity = getActivity(); 611 if (activity == null) { 612 return; 613 } 614 try { 615 Toast.makeText(activity, R.string.wallpaper_set_successfully_message, 616 Toast.LENGTH_SHORT).show(); 617 } catch (NotFoundException e) { 618 Log.e(TAG, "Could not show toast " + e); 619 } 620 activity.setResult(Activity.RESULT_OK); 621 finishActivityWithFadeTransition(); 622 623 // Start activity to go back to main screen. 624 if (mIsNewTask) { 625 Intent intent = new Intent(requireActivity(), TrampolinePickerActivity.class); 626 intent.putExtra(WALLPAPER_LAUNCH_SOURCE, 627 mIsViewAsHome ? LAUNCH_SOURCE_LAUNCHER : LAUNCH_SOURCE_SETTINGS_HOMEPAGE); 628 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); 629 startActivity(intent); 630 } 631 } 632 633 protected void finishActivityWithFadeTransition() { 634 Activity activity = getActivity(); 635 if (activity == null) { 636 return; 637 } 638 activity.finish(); 639 } 640 641 private void showDestinationSelectionDialogForWallpaper(WallpaperInfo wallpaperInfo) { 642 643 // This logic is implemented for the editing of live wallpapers. The purpose is to 644 // restrict users to set the edited creative wallpaper only to the destination from 645 // where they originally started the editing process. For instance, if they began editing 646 // by clicking on the homescreen preview, they would be allowed to set the wallpaper on the 647 // homescreen and both the homescreen and lockscreen. On the other hand, if they initiated 648 // editing by clicking on the lockscreen preview, they would only be allowed to set the 649 // wallpaper on the lockscreen and both the homescreen and lockscreen. It's essential to 650 // note that this restriction only applies when the editing process is started by tapping 651 // on the preview available on the wallpaper picker home page. 652 boolean isLockOption = true; 653 boolean isHomeOption = true; 654 if (wallpaperInfo instanceof LiveWallpaperInfo) { 655 if (!mIsAssetIdPresent) { 656 isHomeOption = mIsViewAsHome; 657 isLockOption = !mIsViewAsHome; 658 } 659 } 660 661 mWallpaperSetter.requestDestination(getActivity(), getParentFragmentManager(), 662 destination -> { 663 mSetWallpaperViewModel.setDestination(destination); 664 setWallpaper(destination); 665 }, 666 wallpaperInfo instanceof LiveWallpaperInfo, isHomeOption, isLockOption); 667 } 668 669 protected void showSetWallpaperErrorDialog() { 670 new AlertDialog.Builder(getActivity(), R.style.LightDialogTheme) 671 .setMessage(R.string.set_wallpaper_error_message) 672 .setPositiveButton(R.string.try_again, (dialogInterface, i) -> 673 setWallpaper(mSetWallpaperViewModel.getDestination()) 674 ) 675 .setNegativeButton(android.R.string.cancel, null) 676 .create() 677 .show(); 678 } 679 680 protected void showLoadWallpaperErrorDialog() { 681 new AlertDialog.Builder(getActivity(), R.style.LightDialogTheme) 682 .setMessage(R.string.load_wallpaper_error_message) 683 .setPositiveButton(android.R.string.ok, 684 (dialogInterface, i) -> finishFragmentActivity()) 685 .setOnDismissListener(dialog -> finishFragmentActivity()) 686 .create() 687 .show(); 688 } 689 690 private void finishFragmentActivity() { 691 FragmentActivity activity = getActivity(); 692 if (activity != null) { 693 activity.finish(); 694 } 695 } 696 697 private static class ViewAnimatorListener extends AnimatorListenerAdapter { 698 final View mView; 699 final boolean mHide; 700 701 private ViewAnimatorListener(View view, boolean hide) { 702 mView = view; 703 mHide = hide; 704 } 705 706 @Override 707 public void onAnimationStart(Animator animation) { 708 mView.setVisibility(VISIBLE); 709 } 710 711 @Override 712 public void onAnimationEnd(Animator animation) { 713 mView.setVisibility(mHide ? View.INVISIBLE : VISIBLE); 714 } 715 } 716 } 717