• 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.Paint;
9 import android.graphics.Paint.Style;
10 import android.util.AttributeSet;
11 import android.util.TypedValue;
12 import android.view.View;
13 import android.view.ViewGroup;
14 import android.widget.FrameLayout;
15 import android.widget.RelativeLayout;
16 
17 import com.android.internal.colorextraction.ColorExtractor;
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.ClockPlugin;
22 
23 import java.io.FileDescriptor;
24 import java.io.PrintWriter;
25 import java.util.Arrays;
26 import java.util.TimeZone;
27 
28 /**
29  * Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
30  */
31 @KeyguardStatusViewScope
32 public class KeyguardClockSwitch extends RelativeLayout {
33 
34     private static final String TAG = "KeyguardClockSwitch";
35 
36     private static final long CLOCK_OUT_MILLIS = 150;
37     private static final long CLOCK_IN_MILLIS = 200;
38     private static final long SMARTSPACE_MOVE_MILLIS = 350;
39 
40     /**
41      * Optional/alternative clock injected via plugin.
42      */
43     private ClockPlugin mClockPlugin;
44 
45     /**
46      * Frame for small/large clocks
47      */
48     private FrameLayout mClockFrame;
49     private FrameLayout mLargeClockFrame;
50     private AnimatableClockView mClockView;
51     private AnimatableClockView mLargeClockView;
52 
53     /**
54      * Status area (date and other stuff) shown below the clock. Plugin can decide whether or not to
55      * show it below the alternate clock.
56      */
57     private View mKeyguardStatusArea;
58     /** Mutually exclusive with mKeyguardStatusArea */
59     private View mSmartspaceView;
60     private int mSmartspaceTopOffset;
61 
62     /**
63      * Maintain state so that a newly connected plugin can be initialized.
64      */
65     private float mDarkAmount;
66 
67     /**
68      * Boolean value indicating if notifications are visible on lock screen. Use null to signify
69      * it is uninitialized.
70      */
71     private Boolean mHasVisibleNotifications = null;
72 
73     private AnimatorSet mClockInAnim = null;
74     private AnimatorSet mClockOutAnim = null;
75     private ObjectAnimator mSmartspaceAnim = null;
76 
77     /**
78      * If the Keyguard Slice has a header (big center-aligned text.)
79      */
80     private boolean mSupportsDarkText;
81     private int[] mColorPalette;
82 
83     private int mClockSwitchYAmount;
84 
KeyguardClockSwitch(Context context, AttributeSet attrs)85     public KeyguardClockSwitch(Context context, AttributeSet attrs) {
86         super(context, attrs);
87     }
88 
89     /**
90      * Apply dp changes on font/scale change
91      */
onDensityOrFontScaleChanged()92     public void onDensityOrFontScaleChanged() {
93         mLargeClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources()
94                 .getDimensionPixelSize(R.dimen.large_clock_text_size));
95         mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mContext.getResources()
96                 .getDimensionPixelSize(R.dimen.clock_text_size));
97 
98         mClockSwitchYAmount = mContext.getResources().getDimensionPixelSize(
99                 R.dimen.keyguard_clock_switch_y_shift);
100 
101         mSmartspaceTopOffset = mContext.getResources().getDimensionPixelSize(
102                 R.dimen.keyguard_smartspace_top_offset);
103     }
104 
105     /**
106      * Returns if this view is presenting a custom clock, or the default implementation.
107      */
hasCustomClock()108     public boolean hasCustomClock() {
109         return mClockPlugin != null;
110     }
111 
112     @Override
onFinishInflate()113     protected void onFinishInflate() {
114         super.onFinishInflate();
115 
116         mClockFrame = findViewById(R.id.lockscreen_clock_view);
117         mClockView = findViewById(R.id.animatable_clock_view);
118         mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
119         mLargeClockView = findViewById(R.id.animatable_clock_view_large);
120         mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
121 
122         onDensityOrFontScaleChanged();
123     }
124 
setClockPlugin(ClockPlugin plugin, int statusBarState)125     void setClockPlugin(ClockPlugin plugin, int statusBarState) {
126         // Disconnect from existing plugin.
127         if (mClockPlugin != null) {
128             View smallClockView = mClockPlugin.getView();
129             if (smallClockView != null && smallClockView.getParent() == mClockFrame) {
130                 mClockFrame.removeView(smallClockView);
131             }
132             View bigClockView = mClockPlugin.getBigClockView();
133             if (bigClockView != null && bigClockView.getParent() == mLargeClockFrame) {
134                 mLargeClockFrame.removeView(bigClockView);
135             }
136             mClockPlugin.onDestroyView();
137             mClockPlugin = null;
138         }
139         if (plugin == null) {
140             mClockView.setVisibility(View.VISIBLE);
141             mLargeClockView.setVisibility(View.VISIBLE);
142             return;
143         }
144         // Attach small and big clock views to hierarchy.
145         View smallClockView = plugin.getView();
146         if (smallClockView != null) {
147             mClockFrame.addView(smallClockView, -1,
148                     new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
149                             ViewGroup.LayoutParams.WRAP_CONTENT));
150             mClockView.setVisibility(View.GONE);
151         }
152         View bigClockView = plugin.getBigClockView();
153         if (bigClockView != null) {
154             mLargeClockFrame.addView(bigClockView);
155             mLargeClockView.setVisibility(View.GONE);
156         }
157 
158         // Initialize plugin parameters.
159         mClockPlugin = plugin;
160         mClockPlugin.setStyle(getPaint().getStyle());
161         mClockPlugin.setTextColor(getCurrentTextColor());
162         mClockPlugin.setDarkAmount(mDarkAmount);
163         if (mColorPalette != null) {
164             mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
165         }
166     }
167 
168     /**
169      * It will also update plugin setStyle if plugin is connected.
170      */
setStyle(Style style)171     public void setStyle(Style style) {
172         if (mClockPlugin != null) {
173             mClockPlugin.setStyle(style);
174         }
175     }
176 
177     /**
178      * It will also update plugin setTextColor if plugin is connected.
179      */
setTextColor(int color)180     public void setTextColor(int color) {
181         if (mClockPlugin != null) {
182             mClockPlugin.setTextColor(color);
183         }
184     }
185 
animateClockChange(boolean useLargeClock)186     private void animateClockChange(boolean useLargeClock) {
187         if (mClockInAnim != null) mClockInAnim.cancel();
188         if (mClockOutAnim != null) mClockOutAnim.cancel();
189         if (mSmartspaceAnim != null) mSmartspaceAnim.cancel();
190 
191         View in, out;
192         int direction = 1;
193         float smartspaceYTranslation;
194         if (useLargeClock) {
195             out = mClockFrame;
196             in = mLargeClockFrame;
197             if (indexOfChild(in) == -1) addView(in);
198             direction = -1;
199             smartspaceYTranslation = mSmartspaceView == null ? 0
200                     : mClockFrame.getTop() - mSmartspaceView.getTop() + mSmartspaceTopOffset;
201         } else {
202             in = mClockFrame;
203             out = mLargeClockFrame;
204             smartspaceYTranslation = 0f;
205 
206             // Must remove in order for notifications to appear in the proper place
207             removeView(out);
208         }
209 
210         mClockOutAnim = new AnimatorSet();
211         mClockOutAnim.setDuration(CLOCK_OUT_MILLIS);
212         mClockOutAnim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
213         mClockOutAnim.playTogether(
214                 ObjectAnimator.ofFloat(out, View.ALPHA, 0f),
215                 ObjectAnimator.ofFloat(out, View.TRANSLATION_Y, 0,
216                         direction * -mClockSwitchYAmount));
217         mClockOutAnim.addListener(new AnimatorListenerAdapter() {
218             public void onAnimationEnd(Animator animation) {
219                 mClockOutAnim = null;
220             }
221         });
222 
223         in.setAlpha(0);
224         in.setVisibility(View.VISIBLE);
225         mClockInAnim = new AnimatorSet();
226         mClockInAnim.setDuration(CLOCK_IN_MILLIS);
227         mClockInAnim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
228         mClockInAnim.playTogether(ObjectAnimator.ofFloat(in, View.ALPHA, 1f),
229                 ObjectAnimator.ofFloat(in, View.TRANSLATION_Y, direction * mClockSwitchYAmount, 0));
230         mClockInAnim.setStartDelay(CLOCK_OUT_MILLIS / 2);
231         mClockInAnim.addListener(new AnimatorListenerAdapter() {
232             public void onAnimationEnd(Animator animation) {
233                 mClockInAnim = null;
234             }
235         });
236 
237         mClockInAnim.start();
238         mClockOutAnim.start();
239 
240         if (mSmartspaceView != null) {
241             mSmartspaceAnim = ObjectAnimator.ofFloat(mSmartspaceView, View.TRANSLATION_Y,
242                     smartspaceYTranslation);
243             mSmartspaceAnim.setDuration(SMARTSPACE_MOVE_MILLIS);
244             mSmartspaceAnim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
245             mSmartspaceAnim.addListener(new AnimatorListenerAdapter() {
246                 public void onAnimationEnd(Animator animation) {
247                     mSmartspaceAnim = null;
248                 }
249             });
250             mSmartspaceAnim.start();
251         }
252     }
253 
254     /**
255      * Set the amount (ratio) that the device has transitioned to doze.
256      *
257      * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
258      */
setDarkAmount(float darkAmount)259     public void setDarkAmount(float darkAmount) {
260         mDarkAmount = darkAmount;
261         if (mClockPlugin != null) {
262             mClockPlugin.setDarkAmount(darkAmount);
263         }
264     }
265 
266     /**
267      * Based upon whether notifications are showing or not, display/hide the large clock and
268      * the smaller version.
269      */
willSwitchToLargeClock(boolean hasVisibleNotifications)270     boolean willSwitchToLargeClock(boolean hasVisibleNotifications) {
271         if (mHasVisibleNotifications != null
272                 && hasVisibleNotifications == mHasVisibleNotifications) {
273             return false;
274         }
275         boolean useLargeClock = !hasVisibleNotifications;
276         animateClockChange(useLargeClock);
277 
278         mHasVisibleNotifications = hasVisibleNotifications;
279         return useLargeClock;
280     }
281 
getPaint()282     public Paint getPaint() {
283         return mClockView.getPaint();
284     }
285 
getCurrentTextColor()286     public int getCurrentTextColor() {
287         return mClockView.getCurrentTextColor();
288     }
289 
getTextSize()290     public float getTextSize() {
291         return mClockView.getTextSize();
292     }
293 
294     /**
295      * Refresh the time of the clock, due to either time tick broadcast or doze time tick alarm.
296      */
refresh()297     public void refresh() {
298         if (mClockPlugin != null) {
299             mClockPlugin.onTimeTick();
300         }
301     }
302 
303     /**
304      * Notifies that the time zone has changed.
305      */
onTimeZoneChanged(TimeZone timeZone)306     public void onTimeZoneChanged(TimeZone timeZone) {
307         if (mClockPlugin != null) {
308             mClockPlugin.onTimeZoneChanged(timeZone);
309         }
310     }
311 
312     /**
313      * Notifies that the time format has changed.
314      *
315      * @param timeFormat "12" for 12-hour format, "24" for 24-hour format
316      */
onTimeFormatChanged(String timeFormat)317     public void onTimeFormatChanged(String timeFormat) {
318         if (mClockPlugin != null) {
319             mClockPlugin.onTimeFormatChanged(timeFormat);
320         }
321     }
322 
setSmartspaceView(View smartspaceView)323     void setSmartspaceView(View smartspaceView) {
324         mSmartspaceView = smartspaceView;
325     }
326 
updateColors(ColorExtractor.GradientColors colors)327     void updateColors(ColorExtractor.GradientColors colors) {
328         mSupportsDarkText = colors.supportsDarkText();
329         mColorPalette = colors.getColorPalette();
330         if (mClockPlugin != null) {
331             mClockPlugin.setColorPalette(mSupportsDarkText, mColorPalette);
332         }
333     }
334 
dump(FileDescriptor fd, PrintWriter pw, String[] args)335     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
336         pw.println("KeyguardClockSwitch:");
337         pw.println("  mClockPlugin: " + mClockPlugin);
338         pw.println("  mClockFrame: " + mClockFrame);
339         pw.println("  mLargeClockFrame: " + mLargeClockFrame);
340         pw.println("  mKeyguardStatusArea: " + mKeyguardStatusArea);
341         pw.println("  mSmartspaceView: " + mSmartspaceView);
342         pw.println("  mDarkAmount: " + mDarkAmount);
343         pw.println("  mSupportsDarkText: " + mSupportsDarkText);
344         pw.println("  mColorPalette: " + Arrays.toString(mColorPalette));
345     }
346 }
347