• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.keyguard;
2 
3 import android.animation.Animator;
4 import android.animation.AnimatorListenerAdapter;
5 import android.animation.AnimatorSet;
6 import android.animation.ObjectAnimator;
7 import android.content.Context;
8 import android.graphics.Rect;
9 import android.util.AttributeSet;
10 import android.view.View;
11 import android.view.ViewGroup;
12 import android.widget.FrameLayout;
13 import android.widget.RelativeLayout;
14 
15 import androidx.annotation.IntDef;
16 import androidx.annotation.VisibleForTesting;
17 
18 import com.android.keyguard.dagger.KeyguardStatusViewScope;
19 import com.android.systemui.R;
20 import com.android.systemui.animation.Interpolators;
21 import com.android.systemui.plugins.ClockController;
22 import com.android.systemui.plugins.log.LogBuffer;
23 import com.android.systemui.plugins.log.LogLevel;
24 
25 import java.io.PrintWriter;
26 import java.lang.annotation.Retention;
27 import java.lang.annotation.RetentionPolicy;
28 
29 import kotlin.Unit;
30 
31 /**
32  * Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
33  */
34 @KeyguardStatusViewScope
35 public class KeyguardClockSwitch extends RelativeLayout {
36 
37     private static final String TAG = "KeyguardClockSwitch";
38 
39     private static final long CLOCK_OUT_MILLIS = 150;
40     private static final long CLOCK_IN_MILLIS = 200;
41     private static final long STATUS_AREA_MOVE_MILLIS = 350;
42 
43     @IntDef({LARGE, SMALL})
44     @Retention(RetentionPolicy.SOURCE)
45     public @interface ClockSize { }
46 
47     public static final int LARGE = 0;
48     public static final int SMALL = 1;
49 
50     /** Returns a region for the large clock to position itself, based on the given parent. */
getLargeClockRegion(ViewGroup parent)51     public static Rect getLargeClockRegion(ViewGroup parent) {
52         int largeClockTopMargin = parent.getResources()
53                 .getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin);
54         int targetHeight = parent.getResources()
55                 .getDimensionPixelSize(R.dimen.large_clock_text_size) * 2;
56         int top = parent.getHeight() / 2 - targetHeight / 2
57                 + largeClockTopMargin / 2;
58         return new Rect(
59                 parent.getLeft(),
60                 top,
61                 parent.getRight(),
62                 top + targetHeight);
63     }
64 
65     /**
66      * Frame for small/large clocks
67      */
68     private FrameLayout mSmallClockFrame;
69     private FrameLayout mLargeClockFrame;
70     private ClockController mClock;
71 
72     private View mStatusArea;
73     private int mSmartspaceTopOffset;
74 
75     /**
76      * Maintain state so that a newly connected plugin can be initialized.
77      */
78     private float mDarkAmount;
79 
80     /**
81      * Indicates which clock is currently displayed - should be one of {@link ClockSize}.
82      * Use null to signify it is uninitialized.
83      */
84     @ClockSize private Integer mDisplayedClockSize = null;
85 
86     @VisibleForTesting AnimatorSet mClockInAnim = null;
87     @VisibleForTesting AnimatorSet mClockOutAnim = null;
88     private ObjectAnimator mStatusAreaAnim = null;
89 
90     private int mClockSwitchYAmount;
91     @VisibleForTesting boolean mChildrenAreLaidOut = false;
92     @VisibleForTesting boolean mAnimateOnLayout = true;
93     private LogBuffer mLogBuffer = null;
94 
KeyguardClockSwitch(Context context, AttributeSet attrs)95     public KeyguardClockSwitch(Context context, AttributeSet attrs) {
96         super(context, attrs);
97     }
98 
99     /**
100      * Apply dp changes on font/scale change
101      */
onDensityOrFontScaleChanged()102     public void onDensityOrFontScaleChanged() {
103         mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
104                 R.dimen.keyguard_clock_switch_y_shift);
105         mSmartspaceTopOffset = mContext.getResources().getDimensionPixelSize(
106                 R.dimen.keyguard_smartspace_top_offset);
107     }
108 
109     @Override
onFinishInflate()110     protected void onFinishInflate() {
111         super.onFinishInflate();
112 
113         mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
114         mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
115         mStatusArea = findViewById(R.id.keyguard_status_area);
116 
117         onDensityOrFontScaleChanged();
118     }
119 
setLogBuffer(LogBuffer logBuffer)120     public void setLogBuffer(LogBuffer logBuffer) {
121         mLogBuffer = logBuffer;
122     }
123 
getLogBuffer()124     public LogBuffer getLogBuffer() {
125         return mLogBuffer;
126     }
127 
setClock(ClockController clock, int statusBarState)128     void setClock(ClockController clock, int statusBarState) {
129         mClock = clock;
130 
131         // Disconnect from existing plugin.
132         mSmallClockFrame.removeAllViews();
133         mLargeClockFrame.removeAllViews();
134 
135         if (clock == null) {
136             if (mLogBuffer != null) {
137                 mLogBuffer.log(TAG, LogLevel.ERROR, "No clock being shown");
138             }
139             return;
140         }
141 
142         // Attach small and big clock views to hierarchy.
143         if (mLogBuffer != null) {
144             mLogBuffer.log(TAG, LogLevel.INFO, "Attached new clock views to switch");
145         }
146         mSmallClockFrame.addView(clock.getSmallClock().getView());
147         mLargeClockFrame.addView(clock.getLargeClock().getView());
148         updateClockTargetRegions();
149     }
150 
updateClockTargetRegions()151     void updateClockTargetRegions() {
152         if (mClock != null) {
153             if (mSmallClockFrame.isLaidOut()) {
154                 int targetHeight =  getResources()
155                         .getDimensionPixelSize(R.dimen.small_clock_text_size);
156                 mClock.getSmallClock().getEvents().onTargetRegionChanged(new Rect(
157                         mSmallClockFrame.getLeft(),
158                         mSmallClockFrame.getTop(),
159                         mSmallClockFrame.getRight(),
160                         mSmallClockFrame.getTop() + targetHeight));
161             }
162 
163             if (mLargeClockFrame.isLaidOut()) {
164                 mClock.getLargeClock().getEvents().onTargetRegionChanged(
165                         getLargeClockRegion(mLargeClockFrame));
166             }
167         }
168     }
169 
updateClockViews(boolean useLargeClock, boolean animate)170     private void updateClockViews(boolean useLargeClock, boolean animate) {
171         if (mLogBuffer != null) {
172             mLogBuffer.log(TAG, LogLevel.DEBUG, (msg) -> {
173                 msg.setBool1(useLargeClock);
174                 msg.setBool2(animate);
175                 msg.setBool3(mChildrenAreLaidOut);
176                 return Unit.INSTANCE;
177             }, (msg) -> "updateClockViews"
178                     + "; useLargeClock=" + msg.getBool1()
179                     + "; animate=" + msg.getBool2()
180                     + "; mChildrenAreLaidOut=" + msg.getBool3());
181         }
182 
183         if (mClockInAnim != null) mClockInAnim.cancel();
184         if (mClockOutAnim != null) mClockOutAnim.cancel();
185         if (mStatusAreaAnim != null) mStatusAreaAnim.cancel();
186 
187         mClockInAnim = null;
188         mClockOutAnim = null;
189         mStatusAreaAnim = null;
190 
191         View in, out;
192         int direction = 1;
193         float statusAreaYTranslation;
194         if (useLargeClock) {
195             out = mSmallClockFrame;
196             in = mLargeClockFrame;
197             if (indexOfChild(in) == -1) addView(in, 0);
198             direction = -1;
199             statusAreaYTranslation = mSmallClockFrame.getTop() - mStatusArea.getTop()
200                     + mSmartspaceTopOffset;
201         } else {
202             in = mSmallClockFrame;
203             out = mLargeClockFrame;
204             statusAreaYTranslation = 0f;
205 
206             // Must remove in order for notifications to appear in the proper place
207             removeView(out);
208         }
209 
210         if (!animate) {
211             out.setAlpha(0f);
212             in.setAlpha(1f);
213             in.setVisibility(VISIBLE);
214             mStatusArea.setTranslationY(statusAreaYTranslation);
215             return;
216         }
217 
218         mClockOutAnim = new AnimatorSet();
219         mClockOutAnim.setDuration(CLOCK_OUT_MILLIS);
220         mClockOutAnim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
221         mClockOutAnim.playTogether(
222                 ObjectAnimator.ofFloat(out, View.ALPHA, 0f),
223                 ObjectAnimator.ofFloat(out, View.TRANSLATION_Y, 0,
224                         direction * -mClockSwitchYAmount));
225         mClockOutAnim.addListener(new AnimatorListenerAdapter() {
226             public void onAnimationEnd(Animator animation) {
227                 mClockOutAnim = null;
228             }
229         });
230 
231         in.setAlpha(0);
232         in.setVisibility(View.VISIBLE);
233         mClockInAnim = new AnimatorSet();
234         mClockInAnim.setDuration(CLOCK_IN_MILLIS);
235         mClockInAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
236         mClockInAnim.playTogether(ObjectAnimator.ofFloat(in, View.ALPHA, 1f),
237                 ObjectAnimator.ofFloat(in, View.TRANSLATION_Y, direction * mClockSwitchYAmount, 0));
238         mClockInAnim.setStartDelay(CLOCK_OUT_MILLIS / 2);
239         mClockInAnim.addListener(new AnimatorListenerAdapter() {
240             public void onAnimationEnd(Animator animation) {
241                 mClockInAnim = null;
242             }
243         });
244 
245         mClockInAnim.start();
246         mClockOutAnim.start();
247 
248         mStatusAreaAnim = ObjectAnimator.ofFloat(mStatusArea, View.TRANSLATION_Y,
249                 statusAreaYTranslation);
250         mStatusAreaAnim.setDuration(STATUS_AREA_MOVE_MILLIS);
251         mStatusAreaAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
252         mStatusAreaAnim.addListener(new AnimatorListenerAdapter() {
253             public void onAnimationEnd(Animator animation) {
254                 mStatusAreaAnim = null;
255             }
256         });
257         mStatusAreaAnim.start();
258     }
259 
260     /**
261      * Display the desired clock and hide the other one
262      *
263      * @return true if desired clock appeared and false if it was already visible
264      */
switchToClock(@lockSize int clockSize, boolean animate)265     boolean switchToClock(@ClockSize int clockSize, boolean animate) {
266         if (mDisplayedClockSize != null && clockSize == mDisplayedClockSize) {
267             return false;
268         }
269 
270         // let's make sure clock is changed only after all views were laid out so we can
271         // translate them properly
272         if (mChildrenAreLaidOut) {
273             updateClockViews(clockSize == LARGE, animate);
274         }
275 
276         mDisplayedClockSize = clockSize;
277         return true;
278     }
279 
280     @Override
onLayout(boolean changed, int l, int t, int r, int b)281     protected void onLayout(boolean changed, int l, int t, int r, int b) {
282         super.onLayout(changed, l, t, r, b);
283 
284         if (changed) {
285             post(() -> updateClockTargetRegions());
286         }
287 
288         if (mDisplayedClockSize != null && !mChildrenAreLaidOut) {
289             post(() -> updateClockViews(mDisplayedClockSize == LARGE, mAnimateOnLayout));
290         }
291 
292         mChildrenAreLaidOut = true;
293     }
294 
dump(PrintWriter pw, String[] args)295     public void dump(PrintWriter pw, String[] args) {
296         pw.println("KeyguardClockSwitch:");
297         pw.println("  mSmallClockFrame: " + mSmallClockFrame);
298         pw.println("  mSmallClockFrame.alpha: " + mSmallClockFrame.getAlpha());
299         pw.println("  mLargeClockFrame: " + mLargeClockFrame);
300         pw.println("  mLargeClockFrame.alpha: " + mLargeClockFrame.getAlpha());
301         pw.println("  mStatusArea: " + mStatusArea);
302         pw.println("  mDisplayedClockSize: " + mDisplayedClockSize);
303     }
304 }
305