• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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