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