• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 
17 package com.android.wallpaper.util;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorSet;
21 import android.animation.ObjectAnimator;
22 import android.animation.ValueAnimator;
23 import android.content.res.TypedArray;
24 import android.graphics.Insets;
25 import android.graphics.Point;
26 import android.graphics.Rect;
27 import android.view.Gravity;
28 import android.view.SurfaceView;
29 import android.view.View;
30 import android.view.WindowInsets;
31 import android.widget.Button;
32 import android.widget.FrameLayout;
33 import android.widget.ImageButton;
34 import android.widget.TextView;
35 import android.widget.Toolbar;
36 
37 import androidx.cardview.widget.CardView;
38 
39 import com.android.wallpaper.R;
40 import com.android.wallpaper.picker.TouchForwardingLayout;
41 
42 /**
43  * A class storing information about a preview fragment's full-screen layout.
44  *
45  * Used for {@code ImagePreviewFragment} and {@code LivePreviewFragment}.
46  */
47 public class FullScreenAnimation {
48 
49     private final View mView;
50     private final TouchForwardingLayout mTouchForwardingLayout;
51     private final SurfaceView mWorkspaceSurface;
52     private boolean mIsFullScreen = false;
53     private boolean mShowInFullScreen = false;
54 
55     private boolean mScaleIsSet = false;
56     private boolean mWorkspaceVisibility = true;
57     private float mOffsetY;
58     private float mScale;
59     private float mDefaultRadius;
60     private int mWorkspaceWidth;
61     private int mWorkspaceHeight;
62     private float mBottomActionBarTranslation;
63     private float mFullScreenButtonsTranslation;
64     private int mStatusBarHeight;
65     private int mNavigationBarHeight;
66     private FullScreenStatusListener mFullScreenStatusListener;
67 
68     private static final float HIDE_ICONS_TOP_RATIO = 0.2f;
69 
70     private boolean mIsHomeSelected = true;
71 
72     /**
73      * Options for the full-screen text color.
74      *
75      * {@code DEFAULT} represents the default text color.
76      * {@code DARK} represents a text color that is dark, and should be used when the wallpaper
77      *              supports dark text.
78      * {@code LIGHT} represents a text color that is light, and should be used when the wallpaper
79      *               does not support dark text.
80      */
81     public enum FullScreenTextColor {
82         DEFAULT,
83         DARK,
84         LIGHT
85     }
86 
87     FullScreenTextColor mFullScreenTextColor = FullScreenTextColor.DEFAULT;
88     private int mCurrentTextColor;
89 
90     /** Callback for full screen status. */
91     public interface FullScreenStatusListener {
92         /** Gets called at animation end when full screen status gets changed. */
onFullScreenStatusChange(boolean isFullScreen)93         void onFullScreenStatusChange(boolean isFullScreen);
94     }
95 
96     /**
97      * Constructor.
98      *
99      * @param view The view containing all relevant UI elements. Equal to {@code mRootView}.
100      */
FullScreenAnimation(View view)101     public FullScreenAnimation(View view) {
102         mView = view;
103         mTouchForwardingLayout = view.findViewById(R.id.touch_forwarding_layout);
104         mWorkspaceSurface = view.findViewById(R.id.workspace_surface);
105         mCurrentTextColor = ResourceUtils.getColorAttr(
106                 view.getContext(),
107                 android.R.attr.textColorPrimary);
108     }
109 
110     /**
111      * Returns if the preview layout is currently in full screen.
112      *
113      * @return whether the preview layout is currently in full screen.
114      */
isFullScreen()115     public boolean isFullScreen() {
116         return mIsFullScreen;
117     }
118 
119     /**
120      * Informs this object whether the home tab is selected.
121      *
122      * Used to determine the visibility of {@code lock_screen_preview_container}.
123      *
124      * @param isHomeSelected whether the home tab is selected.
125      */
setIsHomeSelected(boolean isHomeSelected)126     public void setIsHomeSelected(boolean isHomeSelected) {
127         mIsHomeSelected = isHomeSelected;
128     }
129 
130     /**
131      * Informs this object whether the full screen is separate activity.
132      *
133      * Used to determine the height of workspace.
134      *
135      * @param isShowInFullScreen whether the full screen is separate activity.
136      */
setShowInFullScreen(boolean isShowInFullScreen)137     public void setShowInFullScreen(boolean isShowInFullScreen) {
138         mShowInFullScreen = isShowInFullScreen;
139     }
140 
141     /**
142      * Returns the height of status bar.
143      *
144      * @return height of status bar.
145      */
getStatusBarHeight()146     public int getStatusBarHeight() {
147         return mStatusBarHeight;
148     }
149 
getNavigationBarHeight()150     private int getNavigationBarHeight() {
151         return mNavigationBarHeight;
152     }
153 
getAttributeDimension(int resId)154     private int getAttributeDimension(int resId) {
155         final TypedArray attributes = mView.getContext().getTheme().obtainStyledAttributes(
156                 new int[]{resId});
157         int dimension = attributes.getDimensionPixelSize(0, 0);
158         attributes.recycle();
159         return dimension;
160     }
161 
setViewMargins(int viewId, float marginTop, float marginBottom, boolean separatedTabs)162     private void setViewMargins(int viewId, float marginTop, float marginBottom,
163             boolean separatedTabs) {
164         FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
165                 FrameLayout.LayoutParams.MATCH_PARENT,
166                 separatedTabs ? FrameLayout.LayoutParams.WRAP_CONTENT
167                         : FrameLayout.LayoutParams.MATCH_PARENT);
168 
169         layoutParams.setMargins(0, Math.round(marginTop), 0, Math.round(marginBottom));
170 
171         if (separatedTabs) {
172             layoutParams.gravity = Gravity.BOTTOM;
173         }
174 
175         mView.findViewById(viewId).setLayoutParams(layoutParams);
176     }
177 
178     /** Sets a {@param listener} to listen full screen state changes. */
setFullScreenStatusListener(FullScreenStatusListener listener)179     public void setFullScreenStatusListener(FullScreenStatusListener listener) {
180         mFullScreenStatusListener = listener;
181     }
182 
183     /**
184      * Informs the {@code FullScreenAnimation} object about the window insets of the current
185      * window.
186      *
187      * Called by a {@code View.OnApplyWindowInsetsListener} defined in {@code PreviewFragment}.
188      *
189      * @param windowInsets the window insets of the current window.
190      */
setWindowInsets(WindowInsets windowInsets)191     public void setWindowInsets(WindowInsets windowInsets) {
192         Insets insets = windowInsets.getInsetsIgnoringVisibility(
193                 WindowInsets.Type.systemBars()
194         );
195 
196         mStatusBarHeight = insets.top;
197         mNavigationBarHeight = insets.bottom;
198     }
199 
200     /**
201      * Place UI elements in the correct locations.
202      *
203      * Takes status bar and navigation bar into account.
204      * @param view view is used to show preview fragment.
205      */
placeViews(View view)206     public void placeViews(View view) {
207         // If is already full screen we do not do anything here.
208         if (mIsFullScreen) {
209             return;
210         }
211         if (mShowInFullScreen) {
212             View container = view.findViewById(R.id.container);
213             container.setPadding(0, 0, 0, 0);
214             setViewMargins(R.id.screen_preview_layout, 0, 0, false);
215         } else {
216             setViewMargins(R.id.screen_preview_layout,
217                     (float) getStatusBarHeight() + mView.findViewById(
218                             R.id.preview_header).getPaddingBottom(),
219                     getNavigationBarHeight()
220                             + mView.getResources().getDimension(R.dimen.bottom_actions_height)
221                             + mView.getResources().getDimension(R.dimen.separated_tabs_height),
222                     false);
223         }
224         setViewMargins(R.id.bottom_action_bar_container,
225                 0,
226                 getNavigationBarHeight(),
227                 false);
228         setViewMargins(R.id.separated_tabs_container,
229                 0,
230                 getNavigationBarHeight()
231                         + mView.getResources().getDimension(R.dimen.bottom_actions_height),
232                 true);
233         ensureToolbarIsCorrectlyLocated();
234     }
235 
236     /**
237      * Ensures that the bottom action bar is in the correct location.
238      *
239      * Called by {@code onBottomActionBarReady}, so that the bottom action bar is correctly located
240      * when it is redrawn.
241      */
ensureBottomActionBarIsCorrectlyLocated()242     public void ensureBottomActionBarIsCorrectlyLocated() {
243         float targetTranslation = mIsFullScreen ? mBottomActionBarTranslation : 0;
244         mView.findViewById(R.id.bottom_actionbar).setTranslationY(targetTranslation);
245     }
246 
247     /**
248      * Ensures that the toolbar is in the correct location.
249      *
250      * Called by {@code placeViews}, {@code ImageWallpaperColorThemePreviewFragment#updateToolBar},
251      * and @{code LiveWallpaperColorThemePreviewFragment#updateToolBar}, so that the toolbar is
252      * correctly located when it is redrawn.
253      */
ensureToolbarIsCorrectlyLocated()254     public void ensureToolbarIsCorrectlyLocated() {
255         FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
256                 FrameLayout.LayoutParams.MATCH_PARENT,
257                 FrameLayout.LayoutParams.MATCH_PARENT);
258 
259         layoutParams.setMargins(0, getStatusBarHeight(), 0, 0);
260         mView.findViewById(R.id.section_header_container).setLayoutParams(layoutParams);
261     }
262 
263     /**
264      * Ensures that the text and the navigation button on the toolbar is given the correct color.
265      *
266      * Called by {@code updateToolBar}.
267      */
ensureToolbarIsCorrectlyColored()268     public void ensureToolbarIsCorrectlyColored() {
269         TextView textView = mView.findViewById(R.id.custom_toolbar_title);
270         if (textView != null) {
271             textView.setTextColor(mCurrentTextColor);
272         }
273 
274         Toolbar toolbar = mView.findViewById(R.id.toolbar);
275         // It may be null because there's no back arrow in some cases. For example: no back arrow
276         // for Photos launching case.
277         ImageButton button = (ImageButton) toolbar.getNavigationView();
278         if (button != null) {
279             button.setColorFilter(mCurrentTextColor);
280         }
281     }
282 
283     /**
284      * Sets the text color used for the "Preview" caption in full screen mode.
285      *
286      * @param fullScreenTextColor The desired color for the "Preview" caption in full screen mode.
287      */
setFullScreenTextColor(FullScreenTextColor fullScreenTextColor)288     public void setFullScreenTextColor(FullScreenTextColor fullScreenTextColor) {
289         mFullScreenTextColor = fullScreenTextColor;
290 
291         animateColor(mIsFullScreen);
292     }
293 
294     /**
295      * Sets the visibility of the workspace surface (containing icons from the home screen) and
296      * the elements unique to the lock screen (date and time).
297      *
298      * Called when the "Hide UI Preview" button is clicked.
299      *
300      * @param visible {@code true} if the icons should be shown;
301      *                {@code false} if they should be hidden.
302      */
setWorkspaceVisibility(boolean visible)303     public void setWorkspaceVisibility(boolean visible) {
304         // Not using [setVisibility], because it creates a "jump".
305         if (visible) {
306             mWorkspaceSurface.setClipBounds(new Rect(
307                     0,
308                     Math.round(mWorkspaceHeight * HIDE_ICONS_TOP_RATIO),
309                     mWorkspaceWidth,
310                     mShowInFullScreen ? mWorkspaceHeight : mWorkspaceHeight + Math.round(
311                             mFullScreenButtonsTranslation / mScale)));
312             mView.findViewById(R.id.lock_screen_preview_container).setVisibility(View.VISIBLE);
313         } else {
314             mWorkspaceSurface.setClipBounds(new Rect(
315                     mWorkspaceWidth - 1,
316                     mWorkspaceHeight - 1,
317                     mWorkspaceWidth,
318                     mWorkspaceHeight));
319             mView.findViewById(R.id.lock_screen_preview_container).setVisibility(View.INVISIBLE);
320         }
321         if (mIsHomeSelected) {
322             mView.findViewById(R.id.lock_screen_preview_container).setVisibility(View.INVISIBLE);
323         }
324         mWorkspaceVisibility = visible;
325     }
326 
327     /**
328      * Returns the visibility of the workspace surface (containing icons from the home screen).
329      *
330      * @return the visibility of the workspace surface.
331      */
getWorkspaceVisibility()332     public boolean getWorkspaceVisibility() {
333         return mWorkspaceVisibility;
334     }
335 
animateColor(boolean toFullScreen)336     private void animateColor(boolean toFullScreen) {
337         TextView textView = mView.findViewById(R.id.custom_toolbar_title);
338 
339         int targetColor;
340         if (!toFullScreen || mFullScreenTextColor == FullScreenTextColor.DEFAULT) {
341             targetColor = ResourceUtils.getColorAttr(
342                     mView.getContext(),
343                     android.R.attr.textColorPrimary);
344         } else if (mFullScreenTextColor == FullScreenTextColor.DARK) {
345             targetColor = mView.getContext().getColor(android.R.color.black);
346         } else {
347             targetColor = mView.getContext().getColor(android.R.color.white);
348         }
349 
350         if (targetColor == mCurrentTextColor) {
351             return;
352         }
353 
354         Toolbar toolbar = mView.findViewById(R.id.toolbar);
355         ImageButton button = (ImageButton) toolbar.getNavigationView();
356 
357         ValueAnimator colorAnimator = ValueAnimator.ofArgb(mCurrentTextColor, targetColor);
358         colorAnimator.addUpdateListener(animation -> {
359             int color = (int) animation.getAnimatedValue();
360             if (textView != null) {
361                 textView.setTextColor(color);
362             }
363             // It may be null because there's no back arrow in some cases. For example: no back
364             // arrow for Photos launching case.
365             if (button != null) {
366                 button.setColorFilter(color);
367             }
368         });
369         colorAnimator.start();
370 
371         mCurrentTextColor = targetColor;
372     }
373 
374     /**
375      * Animates the layout to or from fullscreen.
376      *
377      * @param toFullScreen {@code true} if animating into the full screen layout;
378      *                     {@code false} if animating out of the full screen layout.
379      */
startAnimation(boolean toFullScreen)380     public void startAnimation(boolean toFullScreen) {
381         // If there is no need to animate, return.
382         if (toFullScreen == mIsFullScreen) {
383             return;
384         }
385 
386         // If the scale is not set, compute the location and size of frame layout.
387         if (!mScaleIsSet) {
388             int[] loc = new int[2];
389             mTouchForwardingLayout.getLocationInWindow(loc);
390 
391             ScreenSizeCalculator screenSizeCalculator = ScreenSizeCalculator.getInstance();
392             Point screenSize = screenSizeCalculator.getScreenSize(mView.getDisplay());
393             int screenWidth = screenSize.x;
394             int screenHeight = screenSize.y;
395 
396             mOffsetY = (float) (screenHeight / 2.0
397                     - (loc[1] + mTouchForwardingLayout.getHeight() / 2.0));
398 
399             mScale = Math.max(
400                     screenWidth / (float) mTouchForwardingLayout.getWidth(),
401                     screenHeight / (float) mTouchForwardingLayout.getHeight());
402 
403             mDefaultRadius = ((CardView) mWorkspaceSurface.getParent()).getRadius();
404 
405             mWorkspaceSurface.setEnableSurfaceClipping(true);
406 
407             mWorkspaceWidth = mWorkspaceSurface.getWidth();
408             mWorkspaceHeight = mWorkspaceSurface.getHeight();
409 
410             mBottomActionBarTranslation = getNavigationBarHeight()
411                     + mView.getResources().getDimension(R.dimen.bottom_actions_height)
412                     + mView.getResources().getDimension(R.dimen.separated_tabs_height);
413 
414             mFullScreenButtonsTranslation = -(getNavigationBarHeight()
415                     + mView.getResources().getDimension(
416                             R.dimen.fullscreen_preview_button_margin_bottom)
417                     + mView.getResources().getDimension(R.dimen.separated_tabs_height));
418 
419             mScaleIsSet = true;
420         }
421 
422         // Perform animations.
423 
424         // Rounding animation.
425         // Animated version of ((CardView) mWorkspaceSurface.getParent()).setRadius(0);
426         float fromRadius = toFullScreen ? mDefaultRadius : 0f;
427         float toRadius = toFullScreen ? 0f : mDefaultRadius;
428 
429         ValueAnimator animationRounding = ValueAnimator.ofFloat(fromRadius, toRadius);
430         animationRounding.addUpdateListener(animation -> {
431             ((CardView) mWorkspaceSurface.getParent()).setRadius(
432                     (float) animation.getAnimatedValue());
433         });
434 
435         // Animation to hide some of the home screen icons.
436         float fromTop = toFullScreen ? 0f : HIDE_ICONS_TOP_RATIO;
437         float toTop = toFullScreen ? HIDE_ICONS_TOP_RATIO : 0f;
438         float fromBottom = toFullScreen ? 0 : mFullScreenButtonsTranslation / mScale;
439         float toBottom = toFullScreen ? mFullScreenButtonsTranslation / mScale : 0;
440 
441         ValueAnimator animationHide = ValueAnimator.ofFloat(0f, 1f);
442         animationHide.addUpdateListener(animation -> {
443             float t = (float) animation.getAnimatedValue();
444             float top = fromTop + t * (toTop - fromTop);
445             float bottom = fromBottom + t * (toBottom - fromBottom);
446             mWorkspaceSurface.setClipBounds(new Rect(
447                     0,
448                     Math.round(mWorkspaceHeight * top),
449                     mWorkspaceWidth,
450                     mShowInFullScreen ? mWorkspaceHeight : mWorkspaceHeight + Math.round(bottom)));
451         });
452 
453         // Other animations.
454         float scale = toFullScreen ? mScale : 1f;
455         float offsetY = toFullScreen ? mOffsetY : 0f;
456         float bottomActionBarTranslation = toFullScreen ? mBottomActionBarTranslation : 0;
457         float fullScreenButtonsTranslation = toFullScreen ? mFullScreenButtonsTranslation : 0;
458         View frameLayout = mView.findViewById(R.id.screen_preview_layout);
459 
460         AnimatorSet animatorSet = new AnimatorSet();
461         animatorSet.playTogether(
462                 ObjectAnimator.ofFloat(frameLayout, "scaleX", scale),
463                 ObjectAnimator.ofFloat(frameLayout, "scaleY", scale),
464                 ObjectAnimator.ofFloat(frameLayout, "translationY", offsetY),
465                 ObjectAnimator.ofFloat(mView.findViewById(R.id.bottom_actionbar),
466                         "translationY", bottomActionBarTranslation),
467                 ObjectAnimator.ofFloat(mView.findViewById(R.id.separated_tabs_container),
468                         "translationY", bottomActionBarTranslation),
469                 ObjectAnimator.ofFloat(mView.findViewById(R.id.fullscreen_buttons_container),
470                         "translationY", fullScreenButtonsTranslation),
471                 animationRounding,
472                 animationHide
473         );
474         animatorSet.addListener(new Animator.AnimatorListener() {
475             @Override
476             public void onAnimationCancel(Animator animator) {}
477 
478             @Override
479             public void onAnimationEnd(Animator animator) {
480                 if (mFullScreenStatusListener != null) {
481                     mFullScreenStatusListener.onFullScreenStatusChange(toFullScreen);
482                 }
483             }
484 
485             @Override
486             public void onAnimationRepeat(Animator animator) {}
487 
488             @Override
489             public void onAnimationStart(Animator animator) {}
490         });
491         animatorSet.start();
492 
493         animateColor(toFullScreen);
494 
495         // Changes appearances of some elements.
496         mWorkspaceVisibility = true;
497 
498         if (toFullScreen) {
499             ((Button) mView.findViewById(R.id.hide_ui_preview_button)).setText(
500                     R.string.hide_ui_preview_text
501             );
502         }
503 
504         mView.findViewById(R.id.lock_screen_preview_container).setVisibility(View.VISIBLE);
505         if (mIsHomeSelected) {
506             mView.findViewById(R.id.lock_screen_preview_container)
507                     .setVisibility(View.INVISIBLE);
508         }
509 
510         mIsFullScreen = toFullScreen;
511     }
512 }
513