Add custom bubble clock face.
Bug: 122301289
Test: Using adb to set the settings value switches to bubble clock.
Change-Id: I9b5ab62796204cfdce7b9beb147f34f80b0db167
diff --git a/packages/SystemUI/res-keyguard/drawable/bubble_hour_hand.xml b/packages/SystemUI/res-keyguard/drawable/bubble_hour_hand.xml
new file mode 100644
index 0000000..d3c3a51
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bubble_hour_hand.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="200dp"
+ android:width="200dp"
+ android:viewportHeight="100"
+ android:viewportWidth="100">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M50.082,14.199m-13.985,0a13.985,13.985 0,1 1,27.97 0a13.985,13.985 0,1 1,-27.97 0"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/bubble_minute_hand.xml b/packages/SystemUI/res-keyguard/drawable/bubble_minute_hand.xml
new file mode 100644
index 0000000..a4417fb
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bubble_minute_hand.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="200dp"
+ android:width="200dp"
+ android:viewportHeight="100"
+ android:viewportWidth="100" >
+ <path
+ android:fillColor="#000000"
+ android:pathData="M50.082,0.025L50.082,0.025A13.985,15.63 0,0 1,64.067 15.656L64.067,49.029A13.985,15.63 0,0 1,50.082 64.659L50.082,64.659A13.985,15.63 0,0 1,36.097 49.029L36.097,15.656A13.985,15.63 0,0 1,50.082 0.025z"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/layout/bubble_clock.xml b/packages/SystemUI/res-keyguard/layout/bubble_clock.xml
new file mode 100644
index 0000000..0d72657
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/bubble_clock.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<com.android.keyguard.clock.ClockLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+ <TextClock
+ android:id="@+id/digital_clock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:letterSpacing="0.03"
+ android:singleLine="true"
+ style="@style/widget_big"
+ android:format12Hour="@string/keyguard_widget_12_hours_format"
+ android:format24Hour="@string/keyguard_widget_24_hours_format"
+ />
+ <com.android.keyguard.clock.ImageClock
+ android:id="@+id/analog_clock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ >
+ <ImageView
+ android:id="@+id/minute_hand"
+ android:layout_width="300dp"
+ android:layout_height="300dp"
+ android:src="@drawable/bubble_minute_hand"
+ android:tint="@color/bubbleMinuteHandColor"
+ />
+ <ImageView
+ android:id="@+id/hour_hand"
+ android:layout_width="300dp"
+ android:layout_height="300dp"
+ android:src="@drawable/bubble_hour_hand"
+ android:tint="@color/bubbleHourHandColor"
+ />
+ </com.android.keyguard.clock.ImageClock>
+</com.android.keyguard.clock.ClockLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/digital_clock.xml b/packages/SystemUI/res-keyguard/layout/digital_clock.xml
new file mode 100644
index 0000000..cf4a573
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/digital_clock.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_alignParentTop="true">
+ <TextClock
+ android:id="@+id/lock_screen_clock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:letterSpacing="0.03"
+ android:singleLine="true"
+ style="@style/widget_big"
+ android:format12Hour="@string/keyguard_widget_12_hours_format"
+ android:format24Hour="@string/keyguard_widget_24_hours_format" />
+ />
+</FrameLayout>
+
diff --git a/packages/SystemUI/res-keyguard/values/colors.xml b/packages/SystemUI/res-keyguard/values/colors.xml
new file mode 100644
index 0000000..7a849eb
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/values/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<resources>
+ <!-- Default color for hour hand of Bubble clock. -->
+ <color name="bubbleHourHandColor">#C97343</color>
+ <!-- Default color for minute hand of Bubble clock. -->
+ <color name="bubbleMinuteHandColor">#F5C983</color>
+</resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 17cc1d5..efa43c6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -1,9 +1,15 @@
package com.android.keyguard;
+import android.content.ContentResolver;
import android.content.Context;
+import android.database.ContentObserver;
import android.graphics.Paint;
import android.graphics.Paint.Style;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
import android.util.AttributeSet;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -12,6 +18,7 @@
import androidx.annotation.VisibleForTesting;
+import com.android.keyguard.clock.BubbleClockController;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.statusbar.StatusBarState;
@@ -19,13 +26,19 @@
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.ExtensionController.Extension;
+import java.util.Objects;
import java.util.TimeZone;
import java.util.function.Consumer;
+import java.util.function.Supplier;
/**
* Switch to show plugin clock when plugin is connected, otherwise it will show default clock.
*/
public class KeyguardClockSwitch extends RelativeLayout {
+
+ private LayoutInflater mLayoutInflater;
+
+ private final ContentResolver mContentResolver;
/**
* Optional/alternative clock injected via plugin.
*/
@@ -79,12 +92,25 @@
}
};
+ private final ContentObserver mContentObserver =
+ new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ if (mClockExtension != null) {
+ mClockExtension.reload();
+ }
+ }
+ };
+
public KeyguardClockSwitch(Context context) {
this(context, null);
}
public KeyguardClockSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
+ mLayoutInflater = LayoutInflater.from(context);
+ mContentResolver = context.getContentResolver();
}
@Override
@@ -101,7 +127,22 @@
mClockExtension = Dependency.get(ExtensionController.class).newExtension(ClockPlugin.class)
.withPlugin(ClockPlugin.class)
.withCallback(mClockPluginConsumer)
+ // Using withDefault even though this isn't the default as a workaround.
+ // ExtensionBulider doesn't provide the ability to supply a ClockPlugin
+ // instance based off of the value of a setting. Since multiple "default"
+ // can be provided, using a supplier that changes the settings value.
+ // A null return will cause Extension#reload to look at the next "default"
+ // supplier.
+ .withDefault(
+ new SettingsGattedSupplier(
+ mContentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
+ BubbleClockController.class.getName(),
+ () -> BubbleClockController.build(mLayoutInflater)))
.build();
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
+ false, mContentObserver);
Dependency.get(StatusBarStateController.class).addCallback(mStateListener);
}
@@ -109,6 +150,7 @@
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mClockExtension.destroy();
+ mContentResolver.unregisterContentObserver(mContentObserver);
Dependency.get(StatusBarStateController.class).removeCallback(mStateListener);
}
@@ -262,4 +304,44 @@
StatusBarStateController.StateListener getStateListener() {
return mStateListener;
}
+
+ /**
+ * Supplier that only gets an instance when a settings value matches expected value.
+ */
+ private static class SettingsGattedSupplier implements Supplier<ClockPlugin> {
+
+ private final ContentResolver mContentResolver;
+ private final String mKey;
+ private final String mValue;
+ private final Supplier<ClockPlugin> mSupplier;
+
+ /**
+ * Constructs a supplier that changes secure setting key against value.
+ *
+ * @param contentResolver Used to look up settings value.
+ * @param key Settings key.
+ * @param value If the setting matches this values that get supplies a ClockPlugin
+ * instance.
+ * @param supplier Supplier of ClockPlugin instance, only used if the setting
+ * matches value.
+ */
+ SettingsGattedSupplier(ContentResolver contentResolver, String key, String value,
+ Supplier<ClockPlugin> supplier) {
+ mContentResolver = contentResolver;
+ mKey = key;
+ mValue = value;
+ mSupplier = supplier;
+ }
+
+ /**
+ * Returns null if the settings value doesn't match the expected value.
+ *
+ * A null return causes Extension#reload to skip this supplier and move to the next.
+ */
+ @Override
+ public ClockPlugin get() {
+ final String currentValue = Settings.Secure.getString(mContentResolver, mKey);
+ return Objects.equals(currentValue, mValue) ? mSupplier.get() : null;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
new file mode 100644
index 0000000..db6127f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard.clock;
+
+import android.graphics.Paint.Style;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextClock;
+
+import com.android.keyguard.R;
+import com.android.systemui.plugins.ClockPlugin;
+
+import java.util.TimeZone;
+
+/**
+ * Controller for Bubble clock that can appear on lock screen and AOD.
+ */
+public class BubbleClockController implements ClockPlugin {
+
+ /**
+ * Custom clock shown on AOD screen and behind stack scroller on lock.
+ */
+ private View mView;
+ private TextClock mDigitalClock;
+ private ImageClock mAnalogClock;
+
+ /**
+ * Small clock shown on lock screen above stack scroller.
+ */
+ private View mLockClockContainer;
+ private TextClock mLockClock;
+
+ /**
+ * Controller for transition to dark state.
+ */
+ private CrossFadeDarkController mDarkController;
+
+ private BubbleClockController() { }
+
+ /**
+ * Create a BubbleClockController instance.
+ *
+ * @param layoutInflater Inflater used to inflate custom clock views.
+ */
+ public static BubbleClockController build(LayoutInflater layoutInflater) {
+ BubbleClockController controller = new BubbleClockController();
+ controller.createViews(layoutInflater);
+ return controller;
+ }
+
+ private void createViews(LayoutInflater layoutInflater) {
+ mView = layoutInflater.inflate(R.layout.bubble_clock, null);
+ mDigitalClock = (TextClock) mView.findViewById(R.id.digital_clock);
+ mAnalogClock = (ImageClock) mView.findViewById(R.id.analog_clock);
+
+ mLockClockContainer = layoutInflater.inflate(R.layout.digital_clock, null);
+ mLockClock = (TextClock) mLockClockContainer.findViewById(R.id.lock_screen_clock);
+ mLockClock.setVisibility(View.GONE);
+
+ mDarkController = new CrossFadeDarkController(mDigitalClock, mLockClock);
+ }
+
+ @Override
+ public View getView() {
+ return mLockClockContainer;
+ }
+
+ @Override
+ public View getBigClockView() {
+ return mView;
+ }
+
+ @Override
+ public void setStyle(Style style) {}
+
+ @Override
+ public void setTextColor(int color) {
+ mLockClock.setTextColor(color);
+ mDigitalClock.setTextColor(color);
+ }
+
+ @Override
+ public void dozeTimeTick() {
+ mAnalogClock.onTimeChanged();
+ }
+
+ @Override
+ public void setDarkAmount(float darkAmount) {
+ mDarkController.setDarkAmount(darkAmount);
+ }
+
+ @Override
+ public void onTimeZoneChanged(TimeZone timeZone) {
+ mAnalogClock.onTimeZoneChanged(timeZone);
+ }
+
+ @Override
+ public boolean shouldShowStatusArea() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java
new file mode 100644
index 0000000..5aa5668
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockLayout.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard.clock;
+
+import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.keyguard.R;
+
+/**
+ * Positions clock faces (analog, digital, typographic) and handles pixel shifting
+ * to prevent screen burn-in.
+ */
+public class ClockLayout extends FrameLayout {
+
+ /**
+ * Clock face views.
+ */
+ private View mDigitalClock;
+ private View mAnalogClock;
+
+ /**
+ * Pixel shifting amplitidues used to prevent screen burn-in.
+ */
+ private int mBurnInPreventionOffsetX;
+ private int mBurnInPreventionOffsetY;
+
+ public ClockLayout(Context context) {
+ this(context, null);
+ }
+
+ public ClockLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ClockLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mDigitalClock = findViewById(R.id.digital_clock);
+ mAnalogClock = findViewById(R.id.analog_clock);
+
+ // Get pixel shifting X, Y amplitudes from resources.
+ Resources resources = getResources();
+ mBurnInPreventionOffsetX = resources.getDimensionPixelSize(
+ R.dimen.burn_in_prevention_offset_x);
+ mBurnInPreventionOffsetY = resources.getDimensionPixelSize(
+ R.dimen.burn_in_prevention_offset_y);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ final float offsetX = getBurnInOffset(mBurnInPreventionOffsetX, true);
+ final float offsetY = getBurnInOffset(mBurnInPreventionOffsetY, false);
+
+ // Put digital clock in two left corner of the screen.
+ if (mDigitalClock != null) {
+ mDigitalClock.setX(0.1f * getWidth() + offsetX);
+ mDigitalClock.setY(0.1f * getHeight() + offsetY);
+ }
+
+ // Put the analog clock in the middle of the screen.
+ if (mAnalogClock != null) {
+ mAnalogClock.setX(Math.max(0f, 0.5f * (getWidth() - mAnalogClock.getWidth()))
+ + offsetX);
+ mAnalogClock.setY(Math.max(0f, 0.5f * (getHeight() - mAnalogClock.getHeight()))
+ + offsetY);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java b/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java
new file mode 100644
index 0000000..3c3f475
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/CrossFadeDarkController.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard.clock;
+
+import android.view.View;
+
+/**
+ * Controls transition to dark state by cross fading between views.
+ */
+final class CrossFadeDarkController {
+
+ private final View mFadeInView;
+ private final View mFadeOutView;
+
+ /**
+ * Creates a new controller that fades between views.
+ *
+ * @param fadeInView View to fade in when transitioning to AOD.
+ * @param fadeOutView View to fade out when transitioning to AOD.
+ */
+ CrossFadeDarkController(View fadeInView, View fadeOutView) {
+ mFadeInView = fadeInView;
+ mFadeOutView = fadeOutView;
+ }
+
+ /**
+ * Sets the amount the system has transitioned to the dark state.
+ *
+ * @param darkAmount Amount of transition to dark state: 1f for AOD and 0f for lock screen.
+ */
+ void setDarkAmount(float darkAmount) {
+ mFadeInView.setAlpha(Math.max(0f, 2f * darkAmount - 1f));
+ if (darkAmount == 0f) {
+ mFadeInView.setVisibility(View.GONE);
+ } else {
+ if (mFadeInView.getVisibility() == View.GONE) {
+ mFadeInView.setVisibility(View.VISIBLE);
+ }
+ }
+ mFadeOutView.setAlpha(Math.max(0f, 1f - 2f * darkAmount));
+ if (darkAmount == 1f) {
+ mFadeOutView.setVisibility(View.GONE);
+ } else {
+ if (mFadeOutView.getVisibility() == View.GONE) {
+ mFadeOutView.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java b/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java
new file mode 100644
index 0000000..2c709e0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ImageClock.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard.clock;
+
+import android.content.Context;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.keyguard.R;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * Clock composed of two images that rotate with the time.
+ *
+ * The images are the clock hands. ImageClock expects two child ImageViews
+ * with ids hour_hand and minute_hand.
+ */
+public class ImageClock extends FrameLayout {
+
+ private ImageView mHourHand;
+ private ImageView mMinuteHand;
+ private Calendar mTime;
+ private String mDescFormat;
+ private TimeZone mTimeZone;
+
+ public ImageClock(Context context) {
+ this(context, null);
+ }
+
+ public ImageClock(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ImageClock(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mTime = Calendar.getInstance();
+ mDescFormat = ((SimpleDateFormat) DateFormat.getTimeFormat(context)).toLocalizedPattern();
+ }
+
+ /**
+ * Call when the time changes to update the rotation of the clock hands.
+ */
+ public void onTimeChanged() {
+ mTime.setTimeInMillis(System.currentTimeMillis());
+ final float hourAngle = mTime.get(Calendar.HOUR) * 30f;
+ mHourHand.setRotation(hourAngle);
+ final float minuteAngle = mTime.get(Calendar.MINUTE) * 6f;
+ mMinuteHand.setRotation(minuteAngle);
+ setContentDescription(DateFormat.format(mDescFormat, mTime));
+ invalidate();
+ }
+
+ /**
+ * Call when the time zone has changed to update clock hands.
+ *
+ * @param timeZone The updated time zone that will be used.
+ */
+ public void onTimeZoneChanged(TimeZone timeZone) {
+ mTimeZone = timeZone;
+ mTime.setTimeZone(timeZone);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mHourHand = findViewById(R.id.hour_hand);
+ mMinuteHand = findViewById(R.id.minute_hand);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mTime = Calendar.getInstance(mTimeZone != null ? mTimeZone : TimeZone.getDefault());
+ onTimeChanged();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java
new file mode 100644
index 0000000..9e946fa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/BubbleClockControllerTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard.clock;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public final class BubbleClockControllerTest extends SysuiTestCase {
+
+ private BubbleClockController mClockController;
+
+ @Before
+ public void setUp() {
+ LayoutInflater layoutInflater = LayoutInflater.from(getContext());
+ mClockController = BubbleClockController.build(layoutInflater);
+ }
+
+ @Test
+ public void setDarkAmount_fadeIn() {
+ ViewGroup smallClockFrame = (ViewGroup) mClockController.getView();
+ View smallClock = smallClockFrame.getChildAt(0);
+ // WHEN dark amount is set to AOD
+ mClockController.setDarkAmount(1f);
+ // THEN small clock should not be shown.
+ assertThat(smallClock.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void setTextColor_setDigitalClock() {
+ ViewGroup smallClock = (ViewGroup) mClockController.getView();
+ // WHEN text color is set
+ mClockController.setTextColor(42);
+ // THEN child of small clock should have text color set.
+ TextView digitalClock = (TextView) smallClock.getChildAt(0);
+ assertThat(digitalClock.getCurrentTextColor()).isEqualTo(42);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/CrossFadeDarkControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/CrossFadeDarkControllerTest.java
new file mode 100644
index 0000000..fd7657f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/CrossFadeDarkControllerTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.keyguard.clock;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public final class CrossFadeDarkControllerTest extends SysuiTestCase {
+
+ private View mViewFadeIn;
+ private View mViewFadeOut;
+ private CrossFadeDarkController mDarkController;
+
+ @Before
+ public void setUp() {
+ mViewFadeIn = new TextView(getContext());
+ mViewFadeIn.setVisibility(View.VISIBLE);
+ mViewFadeIn.setAlpha(1f);
+ mViewFadeOut = new TextView(getContext());
+ mViewFadeOut.setVisibility(View.VISIBLE);
+ mViewFadeOut.setAlpha(1f);
+
+ mDarkController = new CrossFadeDarkController(mViewFadeIn, mViewFadeOut);
+ }
+
+ @Test
+ public void setDarkAmount_fadeIn() {
+ // WHEN dark amount corresponds to AOD
+ mDarkController.setDarkAmount(1f);
+ // THEN fade in view should be faded in and fade out view faded out.
+ assertThat(mViewFadeIn.getAlpha()).isEqualTo(1f);
+ assertThat(mViewFadeIn.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewFadeOut.getAlpha()).isEqualTo(0f);
+ assertThat(mViewFadeOut.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void setDarkAmount_fadeOut() {
+ // WHEN dark amount corresponds to lock screen
+ mDarkController.setDarkAmount(0f);
+ // THEN fade out view should bed faded out and fade in view faded in.
+ assertThat(mViewFadeIn.getAlpha()).isEqualTo(0f);
+ assertThat(mViewFadeIn.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mViewFadeOut.getAlpha()).isEqualTo(1f);
+ assertThat(mViewFadeOut.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void setDarkAmount_partialFadeIn() {
+ // WHEN dark amount corresponds to a partial transition
+ mDarkController.setDarkAmount(0.9f);
+ // THEN views should have intermediate alpha value.
+ assertThat(mViewFadeIn.getAlpha()).isGreaterThan(0f);
+ assertThat(mViewFadeIn.getAlpha()).isLessThan(1f);
+ assertThat(mViewFadeIn.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void setDarkAmount_partialFadeOut() {
+ // WHEN dark amount corresponds to a partial transition
+ mDarkController.setDarkAmount(0.1f);
+ // THEN views should have intermediate alpha value.
+ assertThat(mViewFadeOut.getAlpha()).isGreaterThan(0f);
+ assertThat(mViewFadeOut.getAlpha()).isLessThan(1f);
+ assertThat(mViewFadeOut.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+}