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