1 /*
2  * Copyright (C) 2023 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.wm.shell.compatui;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.ObjectAnimator;
23 import android.annotation.IdRes;
24 import android.annotation.NonNull;
25 import android.content.Context;
26 import android.util.AttributeSet;
27 import android.view.View;
28 import android.view.animation.Interpolator;
29 import android.view.animation.LinearInterpolator;
30 import android.view.animation.PathInterpolator;
31 import android.widget.ImageButton;
32 import android.widget.LinearLayout;
33 import android.widget.TextView;
34 
35 import com.android.wm.shell.R;
36 
37 /**
38  * Layout for the user aspect ratio button which opens the app list page in settings
39  * and allows users to change apps aspect ratio.
40  */
41 public class UserAspectRatioSettingsLayout extends LinearLayout {
42 
43     private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
44 
45     private static final Interpolator PATH_INTERPOLATOR =
46             new PathInterpolator(0.2f, 0f, 0f, 1f);
47 
48     private static final float ALPHA_FULL_TRANSPARENT = 0f;
49 
50     private static final float ALPHA_FULL_OPAQUE = 1f;
51 
52     private static final float SCALE_START = 0.8f;
53 
54     private static final float SCALE_END = 1f;
55 
56     private static final long FADE_ANIMATION_DURATION_MS = 167;
57 
58     private static final long SCALE_ANIMATION_DURATION_MS = 300;
59 
60     private static final String ALPHA_PROPERTY_NAME = "alpha";
61 
62     private static final String SCALE_X_PROPERTY_NAME = "scaleX";
63 
64     private static final String SCALE_Y_PROPERTY_NAME = "scaleY";
65 
66     private UserAspectRatioSettingsWindowManager mWindowManager;
67 
UserAspectRatioSettingsLayout(Context context)68     public UserAspectRatioSettingsLayout(Context context) {
69         this(context, null);
70     }
71 
UserAspectRatioSettingsLayout(Context context, AttributeSet attrs)72     public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs) {
73         this(context, attrs, 0);
74     }
75 
UserAspectRatioSettingsLayout(Context context, AttributeSet attrs, int defStyleAttr)76     public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs, int defStyleAttr) {
77         this(context, attrs, defStyleAttr, 0);
78     }
79 
UserAspectRatioSettingsLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)80     public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs, int defStyleAttr,
81             int defStyleRes) {
82         super(context, attrs, defStyleAttr, defStyleRes);
83     }
84 
inject(@onNull UserAspectRatioSettingsWindowManager windowManager)85     void inject(@NonNull UserAspectRatioSettingsWindowManager windowManager) {
86         mWindowManager = windowManager;
87     }
88 
setUserAspectRatioSettingsHintVisibility(boolean show)89     void setUserAspectRatioSettingsHintVisibility(boolean show) {
90         setViewVisibility(R.id.user_aspect_ratio_settings_hint, show);
91     }
92 
setUserAspectRatioButtonVisibility(boolean show)93     void setUserAspectRatioButtonVisibility(boolean show) {
94         setViewVisibility(R.id.user_aspect_ratio_settings_button, show);
95         // Hint should never be visible without button.
96         if (!show) {
97             setUserAspectRatioSettingsHintVisibility(/* show= */ false);
98         }
99     }
100 
setViewVisibility(@dRes int resId, boolean show)101     private void setViewVisibility(@IdRes int resId, boolean show) {
102         final View view = findViewById(resId);
103         int visibility = show ? View.VISIBLE : View.GONE;
104         if (view.getVisibility() == visibility) {
105             return;
106         }
107         if (show) {
108             showItem(view);
109         } else {
110             hideItem(view);
111         }
112     }
113 
114     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)115     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
116         super.onLayout(changed, left, top, right, bottom);
117         // Need to relayout after changes like hiding / showing a hint since they affect size.
118         // Doing this directly in setUserAspectRatioButtonVisibility can result in flaky animation.
119         mWindowManager.relayout();
120     }
121 
122     @Override
onFinishInflate()123     protected void onFinishInflate() {
124         super.onFinishInflate();
125 
126         final ImageButton userAspectRatioButton =
127                 findViewById(R.id.user_aspect_ratio_settings_button);
128         userAspectRatioButton.setOnClickListener(
129                 view -> mWindowManager.onUserAspectRatioSettingsButtonClicked());
130         userAspectRatioButton.setOnLongClickListener(view -> {
131             mWindowManager.onUserAspectRatioSettingsButtonLongClicked();
132             return true;
133         });
134 
135         final LinearLayout sizeCompatHint = findViewById(R.id.user_aspect_ratio_settings_hint);
136         ((TextView) sizeCompatHint.findViewById(R.id.compat_mode_hint_text))
137                 .setText(R.string.user_aspect_ratio_settings_button_hint);
138         sizeCompatHint.setOnClickListener(
139                 view -> setUserAspectRatioSettingsHintVisibility(/* show= */ false));
140     }
141 
showItem(@onNull View view)142     private void showItem(@NonNull View view) {
143         final AnimatorSet animatorSet = new AnimatorSet();
144         final ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME,
145                 ALPHA_FULL_TRANSPARENT, ALPHA_FULL_OPAQUE);
146         fadeIn.setDuration(FADE_ANIMATION_DURATION_MS);
147         fadeIn.setInterpolator(LINEAR_INTERPOLATOR);
148         final ObjectAnimator scaleY =
149                 ObjectAnimator.ofFloat(view, SCALE_Y_PROPERTY_NAME, SCALE_START, SCALE_END);
150         final ObjectAnimator scaleX =
151                 ObjectAnimator.ofFloat(view, SCALE_X_PROPERTY_NAME, SCALE_START, SCALE_END);
152         scaleX.setDuration(SCALE_ANIMATION_DURATION_MS);
153         scaleX.setInterpolator(PATH_INTERPOLATOR);
154         scaleY.setDuration(SCALE_ANIMATION_DURATION_MS);
155         scaleY.setInterpolator(PATH_INTERPOLATOR);
156         animatorSet.addListener(new AnimatorListenerAdapter() {
157             @Override
158             public void onAnimationStart(Animator animation) {
159                 view.setVisibility(View.VISIBLE);
160             }
161         });
162         animatorSet.playTogether(fadeIn, scaleY, scaleX);
163         animatorSet.start();
164     }
165 
hideItem(@onNull View view)166     private void hideItem(@NonNull View view) {
167         final ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME,
168                 ALPHA_FULL_OPAQUE, ALPHA_FULL_TRANSPARENT);
169         fadeOut.setDuration(FADE_ANIMATION_DURATION_MS);
170         fadeOut.setInterpolator(LINEAR_INTERPOLATOR);
171         fadeOut.addListener(new AnimatorListenerAdapter() {
172             @Override
173             public void onAnimationEnd(Animator animation) {
174                 view.setVisibility(View.GONE);
175             }
176         });
177         fadeOut.start();
178     }
179 }
180