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