Replace DateView with VariableDateView (1/2)
VariableDateView adjusts its pattern (long, short, or empty) depending on how much space it has to show. This is done from a controller. Keep DateView for now and replace in next CL for compatibility Also, move `updateAnimators` out of `onMeasure`, as it creates new objects. Test: manual Test: atest VariableDateViewControllerTest Fixes: 192574271 Change-Id: I547a2f035cfc7f9eaa9bdea37846a3ba4fbe1475 Merged-In: I547a2f035cfc7f9eaa9bdea37846a3ba4fbe1475
This commit is contained in:
@@ -21,6 +21,9 @@
|
||||
<!-- Skeleton string format for displaying the date when an alarm is set. -->
|
||||
<string name="abbrev_wday_month_day_no_year_alarm">EEEMMMd</string>
|
||||
|
||||
<!-- Skeleton string format for displaying the date shorter. -->
|
||||
<string name="abbrev_month_day_no_year">MMMd</string>
|
||||
|
||||
<!-- Skeleton string format for displaying the time in 12-hour format. -->
|
||||
<string name="clock_12hr_format">hm</string>
|
||||
|
||||
|
||||
@@ -26,17 +26,38 @@
|
||||
android:focusable="true"
|
||||
android:theme="@style/Theme.SystemUI.QuickSettings.Header">
|
||||
|
||||
<com.android.systemui.statusbar.policy.Clock
|
||||
android:id="@+id/clock"
|
||||
<LinearLayout
|
||||
android:id="@+id/clock_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:minWidth="48dp"
|
||||
android:minHeight="@dimen/qs_header_row_min_height"
|
||||
android:orientation="horizontal"
|
||||
android:layout_gravity="center_vertical|start"
|
||||
android:gravity="center_vertical|start"
|
||||
android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
|
||||
android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.QS.Status" />
|
||||
>
|
||||
|
||||
<com.android.systemui.statusbar.policy.Clock
|
||||
android:id="@+id/clock"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:minWidth="48dp"
|
||||
android:minHeight="@dimen/qs_header_row_min_height"
|
||||
android:gravity="center_vertical|start"
|
||||
android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
|
||||
android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.QS.Status" />
|
||||
|
||||
<com.android.systemui.statusbar.policy.VariableDateView
|
||||
android:id="@+id/date_clock"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical|start"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.QS.Status"
|
||||
systemui:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm"
|
||||
systemui:shortDatePattern="@string/abbrev_month_day_no_year"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<include layout="@layout/qs_carrier_group"
|
||||
android:id="@+id/carrier_group"
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical|start" >
|
||||
|
||||
<com.android.systemui.statusbar.policy.DateView
|
||||
<com.android.systemui.statusbar.policy.VariableDateView
|
||||
android:id="@+id/date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -44,7 +44,9 @@
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.QS.Status"
|
||||
systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" />
|
||||
systemui:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm"
|
||||
systemui:shortDatePattern="@string/abbrev_month_day_no_year"
|
||||
/>
|
||||
</FrameLayout>
|
||||
|
||||
<android.widget.Space
|
||||
|
||||
@@ -69,6 +69,10 @@
|
||||
<declare-styleable name="DateView">
|
||||
<attr name="datePattern" format="string" />
|
||||
</declare-styleable>
|
||||
<declare-styleable name="VariableDateView">
|
||||
<attr name="longDatePattern" format="string" />
|
||||
<attr name="shortDatePattern" format="string" />
|
||||
</declare-styleable>
|
||||
<declare-styleable name="PseudoGridView">
|
||||
<attr name="numColumns" format="integer" />
|
||||
<attr name="verticalSpacing" format="dimension" />
|
||||
|
||||
@@ -42,6 +42,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconMa
|
||||
import com.android.systemui.statusbar.phone.StatusBarWindowView;
|
||||
import com.android.systemui.statusbar.phone.StatusIconContainer;
|
||||
import com.android.systemui.statusbar.policy.Clock;
|
||||
import com.android.systemui.statusbar.policy.VariableDateView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -61,11 +62,15 @@ public class QuickStatusBarHeader extends FrameLayout {
|
||||
|
||||
protected QuickQSPanel mHeaderQsPanel;
|
||||
private View mDatePrivacyView;
|
||||
private View mDateView;
|
||||
// DateView next to clock. Visible on QQS
|
||||
private VariableDateView mClockDateView;
|
||||
private View mSecurityHeaderView;
|
||||
private View mStatusIconsView;
|
||||
private View mContainer;
|
||||
|
||||
private View mQSCarriers;
|
||||
private ViewGroup mClockContainer;
|
||||
private Clock mClockView;
|
||||
private Space mDatePrivacySeparator;
|
||||
private View mClockIconsSeparator;
|
||||
@@ -120,12 +125,15 @@ public class QuickStatusBarHeader extends FrameLayout {
|
||||
mContainer = findViewById(R.id.qs_container);
|
||||
mIconContainer = findViewById(R.id.statusIcons);
|
||||
mPrivacyChip = findViewById(R.id.privacy_chip);
|
||||
mDateView = findViewById(R.id.date);
|
||||
mClockDateView = findViewById(R.id.date_clock);
|
||||
mSecurityHeaderView = findViewById(R.id.header_text_container);
|
||||
mClockIconsSeparator = findViewById(R.id.separator);
|
||||
mRightLayout = findViewById(R.id.rightLayout);
|
||||
mDateContainer = findViewById(R.id.date_container);
|
||||
mPrivacyContainer = findViewById(R.id.privacy_container);
|
||||
|
||||
mClockContainer = findViewById(R.id.clock_container);
|
||||
mClockView = findViewById(R.id.clock);
|
||||
mDatePrivacySeparator = findViewById(R.id.space);
|
||||
// Tint for the battery icons are handled in setupHost()
|
||||
@@ -172,7 +180,7 @@ public class QuickStatusBarHeader extends FrameLayout {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
if (mDatePrivacyView.getMeasuredHeight() != mTopViewMeasureHeight) {
|
||||
mTopViewMeasureHeight = mDatePrivacyView.getMeasuredHeight();
|
||||
updateAnimators();
|
||||
post(this::updateAnimators);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +288,8 @@ public class QuickStatusBarHeader extends FrameLayout {
|
||||
TouchAnimator.Builder builder = new TouchAnimator.Builder()
|
||||
.addFloat(mSecurityHeaderView, "alpha", 0, 1)
|
||||
// These views appear on expanding down
|
||||
.addFloat(mClockView, "alpha", 0, 1)
|
||||
.addFloat(mDateView, "alpha", 0, 0, 1)
|
||||
.addFloat(mClockDateView, "alpha", 1, 0, 0)
|
||||
.addFloat(mQSCarriers, "alpha", 0, 1)
|
||||
.setListener(new TouchAnimator.ListenerAdapter() {
|
||||
@Override
|
||||
@@ -289,10 +298,14 @@ public class QuickStatusBarHeader extends FrameLayout {
|
||||
if (!mIsSingleCarrier) {
|
||||
mIconContainer.addIgnoredSlots(mRssiIgnoredSlots);
|
||||
}
|
||||
// Make it gone so there's enough room for carrier names
|
||||
mClockDateView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStarted() {
|
||||
mClockDateView.setVisibility(View.VISIBLE);
|
||||
mClockDateView.setFreezeSwitching(true);
|
||||
setSeparatorVisibility(false);
|
||||
if (!mIsSingleCarrier) {
|
||||
mIconContainer.addIgnoredSlots(mRssiIgnoredSlots);
|
||||
@@ -302,6 +315,7 @@ public class QuickStatusBarHeader extends FrameLayout {
|
||||
@Override
|
||||
public void onAnimationAtStart() {
|
||||
super.onAnimationAtStart();
|
||||
mClockDateView.setFreezeSwitching(false);
|
||||
setSeparatorVisibility(mShowClockIconsSeparator);
|
||||
// In QQS we never ignore RSSI.
|
||||
mIconContainer.removeIgnoredSlots(mRssiIgnoredSlots);
|
||||
@@ -434,10 +448,11 @@ public class QuickStatusBarHeader extends FrameLayout {
|
||||
mClockIconsSeparator.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
mQSCarriers.setVisibility(visible ? View.GONE : View.VISIBLE);
|
||||
|
||||
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mClockView.getLayoutParams();
|
||||
LinearLayout.LayoutParams lp =
|
||||
(LinearLayout.LayoutParams) mClockContainer.getLayoutParams();
|
||||
lp.width = visible ? 0 : WRAP_CONTENT;
|
||||
lp.weight = visible ? 1f : 0f;
|
||||
mClockView.setLayoutParams(lp);
|
||||
mClockContainer.setLayoutParams(lp);
|
||||
|
||||
lp = (LinearLayout.LayoutParams) mRightLayout.getLayoutParams();
|
||||
lp.width = visible ? 0 : WRAP_CONTENT;
|
||||
|
||||
@@ -42,6 +42,7 @@ import com.android.systemui.qs.dagger.QSScope;
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController;
|
||||
import com.android.systemui.statusbar.phone.StatusIconContainer;
|
||||
import com.android.systemui.statusbar.policy.Clock;
|
||||
import com.android.systemui.statusbar.policy.VariableDateViewController;
|
||||
import com.android.systemui.util.ViewController;
|
||||
|
||||
import java.util.List;
|
||||
@@ -73,6 +74,9 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
|
||||
private final BatteryMeterViewController mBatteryMeterViewController;
|
||||
private final FeatureFlags mFeatureFlags;
|
||||
|
||||
private final VariableDateViewController mVariableDateViewControllerDateView;
|
||||
private final VariableDateViewController mVariableDateViewControllerClockDateView;
|
||||
|
||||
private boolean mListening;
|
||||
private boolean mMicCameraIndicatorsEnabled;
|
||||
private boolean mLocationIndicatorsEnabled;
|
||||
@@ -137,7 +141,8 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
|
||||
PrivacyDialogController privacyDialogController,
|
||||
QSExpansionPathInterpolator qsExpansionPathInterpolator,
|
||||
BatteryMeterViewController batteryMeterViewController,
|
||||
FeatureFlags featureFlags) {
|
||||
FeatureFlags featureFlags,
|
||||
VariableDateViewController.Factory variableDateViewControllerFactory) {
|
||||
super(view);
|
||||
mPrivacyItemController = privacyItemController;
|
||||
mActivityStarter = activityStarter;
|
||||
@@ -158,6 +163,12 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
|
||||
mPrivacyChip = mView.findViewById(R.id.privacy_chip);
|
||||
mClockView = mView.findViewById(R.id.clock);
|
||||
mIconContainer = mView.findViewById(R.id.statusIcons);
|
||||
mVariableDateViewControllerDateView = variableDateViewControllerFactory.create(
|
||||
mView.requireViewById(R.id.date)
|
||||
);
|
||||
mVariableDateViewControllerClockDateView = variableDateViewControllerFactory.create(
|
||||
mView.requireViewById(R.id.date_clock)
|
||||
);
|
||||
|
||||
mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer, featureFlags);
|
||||
mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
|
||||
@@ -215,6 +226,9 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
|
||||
mView.onAttach(mIconManager, mQSExpansionPathInterpolator, rssiIgnoredSlots);
|
||||
|
||||
mDemoModeController.addCallback(mDemoModeReceiver);
|
||||
|
||||
mVariableDateViewControllerDateView.init();
|
||||
mVariableDateViewControllerClockDateView.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.systemui.statusbar.policy
|
||||
|
||||
import android.content.Context
|
||||
import android.text.StaticLayout
|
||||
import android.util.AttributeSet
|
||||
import android.widget.TextView
|
||||
import com.android.systemui.R
|
||||
|
||||
/**
|
||||
* View for showing a date that can toggle between two different formats depending on size.
|
||||
*
|
||||
* If no pattern can fit, it will display empty.
|
||||
*
|
||||
* @see R.styleable.VariableDateView_longDatePattern
|
||||
* @see R.styleable.VariableDateView_shortDatePattern
|
||||
*/
|
||||
class VariableDateView(context: Context, attrs: AttributeSet) : TextView(context, attrs) {
|
||||
|
||||
val longerPattern: String
|
||||
val shorterPattern: String
|
||||
|
||||
init {
|
||||
val a = context.theme.obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.VariableDateView,
|
||||
0, 0)
|
||||
longerPattern = a.getString(R.styleable.VariableDateView_longDatePattern)
|
||||
?: context.getString(R.string.system_ui_date_pattern)
|
||||
shorterPattern = a.getString(R.styleable.VariableDateView_shortDatePattern)
|
||||
?: context.getString(R.string.abbrev_month_day_no_year)
|
||||
|
||||
a.recycle()
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze the pattern switching
|
||||
*
|
||||
* Use during animations if the container will change its size but this view should not change
|
||||
*/
|
||||
var freezeSwitching = false
|
||||
|
||||
private var onMeasureListener: OnMeasureListener? = null
|
||||
|
||||
fun onAttach(listener: OnMeasureListener?) {
|
||||
onMeasureListener = listener
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
val availableWidth = MeasureSpec.getSize(widthMeasureSpec) - paddingStart - paddingEnd
|
||||
if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED && !freezeSwitching) {
|
||||
onMeasureListener?.onMeasureAction(availableWidth)
|
||||
}
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
}
|
||||
|
||||
fun getDesiredWidthForText(text: CharSequence): Float {
|
||||
return StaticLayout.getDesiredWidth(text, paint)
|
||||
}
|
||||
|
||||
interface OnMeasureListener {
|
||||
fun onMeasureAction(availableWidth: Int)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.systemui.statusbar.policy
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.icu.text.DateFormat
|
||||
import android.icu.text.DisplayContext
|
||||
import android.icu.util.Calendar
|
||||
import android.os.Handler
|
||||
import android.os.HandlerExecutor
|
||||
import android.os.UserHandle
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.systemui.Dependency
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.util.ViewController
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
import java.text.FieldPosition
|
||||
import java.text.ParsePosition
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun getTextForFormat(date: Date?, format: DateFormat): String {
|
||||
return if (format === EMPTY_FORMAT) { // Check if same object
|
||||
""
|
||||
} else format.format(date)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun getFormatFromPattern(pattern: String?): DateFormat {
|
||||
if (TextUtils.equals(pattern, "")) {
|
||||
return EMPTY_FORMAT
|
||||
}
|
||||
val l = Locale.getDefault()
|
||||
val format = DateFormat.getInstanceForSkeleton(pattern, l)
|
||||
format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE)
|
||||
return format
|
||||
}
|
||||
|
||||
private val EMPTY_FORMAT: DateFormat = object : DateFormat() {
|
||||
override fun format(
|
||||
cal: Calendar,
|
||||
toAppendTo: StringBuffer,
|
||||
fieldPosition: FieldPosition
|
||||
): StringBuffer? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun parse(text: String, cal: Calendar, pos: ParsePosition) {}
|
||||
}
|
||||
|
||||
private const val DEBUG = false
|
||||
private const val TAG = "VariableDateViewController"
|
||||
|
||||
class VariableDateViewController(
|
||||
private val systemClock: SystemClock,
|
||||
private val broadcastDispatcher: BroadcastDispatcher,
|
||||
private val timeTickHandler: Handler,
|
||||
view: VariableDateView
|
||||
) : ViewController<VariableDateView>(view) {
|
||||
|
||||
private var dateFormat: DateFormat? = null
|
||||
private var datePattern = view.longerPattern
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
field = value
|
||||
dateFormat = null
|
||||
if (isAttachedToWindow) {
|
||||
post(::updateClock)
|
||||
}
|
||||
}
|
||||
private var lastWidth = Integer.MAX_VALUE
|
||||
private var lastText = ""
|
||||
private var currentTime = Date()
|
||||
|
||||
// View class easy accessors
|
||||
private val longerPattern: String
|
||||
get() = mView.longerPattern
|
||||
private val shorterPattern: String
|
||||
get() = mView.shorterPattern
|
||||
private fun post(block: () -> Unit) = mView.handler?.post(block)
|
||||
|
||||
private val intentReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
// If the handler is null, it means we received a broadcast while the view has not
|
||||
// finished being attached or in the process of being detached.
|
||||
// In that case, do not post anything.
|
||||
val handler = mView.handler ?: return
|
||||
val action = intent.action
|
||||
if (
|
||||
Intent.ACTION_TIME_TICK == action ||
|
||||
Intent.ACTION_TIME_CHANGED == action ||
|
||||
Intent.ACTION_TIMEZONE_CHANGED == action ||
|
||||
Intent.ACTION_LOCALE_CHANGED == action
|
||||
) {
|
||||
if (
|
||||
Intent.ACTION_LOCALE_CHANGED == action ||
|
||||
Intent.ACTION_TIMEZONE_CHANGED == action
|
||||
) {
|
||||
// need to get a fresh date format
|
||||
handler.post { dateFormat = null }
|
||||
}
|
||||
handler.post(::updateClock)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val onMeasureListener = object : VariableDateView.OnMeasureListener {
|
||||
override fun onMeasureAction(availableWidth: Int) {
|
||||
if (availableWidth != lastWidth) {
|
||||
// maybeChangeFormat will post if the pattern needs to change.
|
||||
maybeChangeFormat(availableWidth)
|
||||
lastWidth = availableWidth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewAttached() {
|
||||
val filter = IntentFilter().apply {
|
||||
addAction(Intent.ACTION_TIME_TICK)
|
||||
addAction(Intent.ACTION_TIME_CHANGED)
|
||||
addAction(Intent.ACTION_TIMEZONE_CHANGED)
|
||||
addAction(Intent.ACTION_LOCALE_CHANGED)
|
||||
}
|
||||
|
||||
broadcastDispatcher.registerReceiver(intentReceiver, filter,
|
||||
HandlerExecutor(timeTickHandler), UserHandle.SYSTEM)
|
||||
|
||||
post(::updateClock)
|
||||
mView.onAttach(onMeasureListener)
|
||||
}
|
||||
|
||||
override fun onViewDetached() {
|
||||
dateFormat = null
|
||||
mView.onAttach(null)
|
||||
broadcastDispatcher.unregisterReceiver(intentReceiver)
|
||||
}
|
||||
|
||||
private fun updateClock() {
|
||||
if (dateFormat == null) {
|
||||
dateFormat = getFormatFromPattern(datePattern)
|
||||
}
|
||||
|
||||
currentTime.time = systemClock.currentTimeMillis()
|
||||
|
||||
val text = getTextForFormat(currentTime, dateFormat!!)
|
||||
if (text != lastText) {
|
||||
mView.setText(text)
|
||||
lastText = text
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeChangeFormat(availableWidth: Int) {
|
||||
if (mView.freezeSwitching ||
|
||||
availableWidth > lastWidth && datePattern == longerPattern ||
|
||||
availableWidth < lastWidth && datePattern == ""
|
||||
) {
|
||||
// Nothing to do
|
||||
return
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, "Width changed. Maybe changing pattern")
|
||||
// Start with longer pattern and see what fits
|
||||
var text = getTextForFormat(currentTime, getFormatFromPattern(longerPattern))
|
||||
var length = mView.getDesiredWidthForText(text)
|
||||
if (length <= availableWidth) {
|
||||
changePattern(longerPattern)
|
||||
return
|
||||
}
|
||||
|
||||
text = getTextForFormat(currentTime, getFormatFromPattern(shorterPattern))
|
||||
length = mView.getDesiredWidthForText(text)
|
||||
if (length <= availableWidth) {
|
||||
changePattern(shorterPattern)
|
||||
return
|
||||
}
|
||||
|
||||
changePattern("")
|
||||
}
|
||||
|
||||
private fun changePattern(newPattern: String) {
|
||||
if (newPattern.equals(datePattern)) return
|
||||
if (DEBUG) Log.d(TAG, "Changing pattern to $newPattern")
|
||||
datePattern = newPattern
|
||||
}
|
||||
|
||||
class Factory @Inject constructor(
|
||||
private val systemClock: SystemClock,
|
||||
private val broadcastDispatcher: BroadcastDispatcher,
|
||||
@Named(Dependency.TIME_TICK_HANDLER_NAME) private val handler: Handler
|
||||
) {
|
||||
fun create(view: VariableDateView): VariableDateViewController {
|
||||
return VariableDateViewController(
|
||||
systemClock,
|
||||
broadcastDispatcher,
|
||||
handler,
|
||||
view
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,8 @@ import com.android.systemui.qs.carrier.QSCarrierGroupController
|
||||
import com.android.systemui.statusbar.phone.StatusBarIconController
|
||||
import com.android.systemui.statusbar.phone.StatusIconContainer
|
||||
import com.android.systemui.statusbar.policy.Clock
|
||||
import com.android.systemui.statusbar.policy.VariableDateView
|
||||
import com.android.systemui.statusbar.policy.VariableDateViewController
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.argumentCaptor
|
||||
import com.android.systemui.util.mockito.capture
|
||||
@@ -88,10 +90,16 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
|
||||
@Mock
|
||||
private lateinit var privacyDialogController: PrivacyDialogController
|
||||
@Mock
|
||||
private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory
|
||||
@Mock
|
||||
private lateinit var variableDateViewController: VariableDateViewController
|
||||
@Mock
|
||||
private lateinit var batteryMeterViewController: BatteryMeterViewController
|
||||
@Mock
|
||||
private lateinit var clock: Clock
|
||||
@Mock
|
||||
private lateinit var variableDateView: VariableDateView
|
||||
@Mock
|
||||
private lateinit var mockView: View
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private lateinit var context: Context
|
||||
@@ -112,6 +120,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
|
||||
stubViews()
|
||||
`when`(iconContainer.context).thenReturn(context)
|
||||
`when`(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
|
||||
`when`(variableDateViewControllerFactory.create(any()))
|
||||
.thenReturn(variableDateViewController)
|
||||
`when`(view.resources).thenReturn(mContext.resources)
|
||||
`when`(view.isAttachedToWindow).thenReturn(true)
|
||||
`when`(view.context).thenReturn(context)
|
||||
@@ -137,7 +147,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
|
||||
privacyDialogController,
|
||||
qsExpansionPathInterpolator,
|
||||
batteryMeterViewController,
|
||||
featureFlags
|
||||
featureFlags,
|
||||
variableDateViewControllerFactory
|
||||
)
|
||||
}
|
||||
|
||||
@@ -278,6 +289,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
|
||||
`when`(view.findViewById<StatusIconContainer>(R.id.statusIcons)).thenReturn(iconContainer)
|
||||
`when`(view.findViewById<OngoingPrivacyChip>(R.id.privacy_chip)).thenReturn(privacyChip)
|
||||
`when`(view.findViewById<Clock>(R.id.clock)).thenReturn(clock)
|
||||
`when`(view.requireViewById<VariableDateView>(R.id.date)).thenReturn(variableDateView)
|
||||
`when`(view.requireViewById<VariableDateView>(R.id.date_clock)).thenReturn(variableDateView)
|
||||
}
|
||||
|
||||
private fun setPrivacyController(micCamera: Boolean, location: Boolean) {
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.systemui.statusbar.policy
|
||||
|
||||
import android.os.Handler
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.capture
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Captor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.anyString
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
import java.util.Date
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@TestableLooper.RunWithLooper
|
||||
@SmallTest
|
||||
class VariableDateViewControllerTest : SysuiTestCase() {
|
||||
|
||||
companion object {
|
||||
private const val TIME_STAMP = 1_500_000_000_000
|
||||
private const val LONG_PATTERN = "EEEMMMd"
|
||||
private const val SHORT_PATTERN = "MMMd"
|
||||
private const val CHAR_WIDTH = 10f
|
||||
}
|
||||
|
||||
@Mock
|
||||
private lateinit var broadcastDispatcher: BroadcastDispatcher
|
||||
@Mock
|
||||
private lateinit var view: VariableDateView
|
||||
@Captor
|
||||
private lateinit var onMeasureListenerCaptor: ArgumentCaptor<VariableDateView.OnMeasureListener>
|
||||
|
||||
private var lastText: String? = null
|
||||
|
||||
private lateinit var systemClock: FakeSystemClock
|
||||
private lateinit var testableLooper: TestableLooper
|
||||
private lateinit var testableHandler: Handler
|
||||
private lateinit var controller: VariableDateViewController
|
||||
|
||||
private lateinit var longText: String
|
||||
private lateinit var shortText: String
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
testableLooper = TestableLooper.get(this)
|
||||
testableHandler = Handler(testableLooper.looper)
|
||||
|
||||
systemClock = FakeSystemClock()
|
||||
systemClock.setCurrentTimeMillis(TIME_STAMP)
|
||||
|
||||
`when`(view.longerPattern).thenReturn(LONG_PATTERN)
|
||||
`when`(view.shorterPattern).thenReturn(SHORT_PATTERN)
|
||||
`when`(view.handler).thenReturn(testableHandler)
|
||||
|
||||
`when`(view.setText(anyString())).thenAnswer {
|
||||
lastText = it.arguments[0] as? String
|
||||
Unit
|
||||
}
|
||||
`when`(view.isAttachedToWindow).thenReturn(true)
|
||||
|
||||
val date = Date(TIME_STAMP)
|
||||
longText = getTextForFormat(date, getFormatFromPattern(LONG_PATTERN))
|
||||
shortText = getTextForFormat(date, getFormatFromPattern(SHORT_PATTERN))
|
||||
|
||||
// Assume some sizes for the text, the controller doesn't need to know if these sizes are
|
||||
// the true ones
|
||||
`when`(view.getDesiredWidthForText(any())).thenAnswer {
|
||||
getTextLength(it.arguments[0] as CharSequence)
|
||||
}
|
||||
|
||||
controller = VariableDateViewController(
|
||||
systemClock,
|
||||
broadcastDispatcher,
|
||||
testableHandler,
|
||||
view
|
||||
)
|
||||
|
||||
controller.init()
|
||||
testableLooper.processAllMessages()
|
||||
|
||||
verify(view).onAttach(capture(onMeasureListenerCaptor))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testViewStartsWithLongText() {
|
||||
assertThat(lastText).isEqualTo(longText)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testListenerNotNull() {
|
||||
assertThat(onMeasureListenerCaptor.value).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLotsOfSpaceUseLongText() {
|
||||
onMeasureListenerCaptor.value.onMeasureAction(10000)
|
||||
|
||||
testableLooper.processAllMessages()
|
||||
assertThat(lastText).isEqualTo(longText)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSmallSpaceUseEmpty() {
|
||||
onMeasureListenerCaptor.value.onMeasureAction(1)
|
||||
testableLooper.processAllMessages()
|
||||
|
||||
assertThat(lastText).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSpaceInBetweenUseShortText() {
|
||||
val average = ((getTextLength(longText) + getTextLength(shortText)) / 2).toInt()
|
||||
|
||||
onMeasureListenerCaptor.value.onMeasureAction(average)
|
||||
testableLooper.processAllMessages()
|
||||
|
||||
assertThat(lastText).isEqualTo(shortText)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSwitchBackToLonger() {
|
||||
onMeasureListenerCaptor.value.onMeasureAction(1)
|
||||
testableLooper.processAllMessages()
|
||||
|
||||
onMeasureListenerCaptor.value.onMeasureAction(10000)
|
||||
testableLooper.processAllMessages()
|
||||
|
||||
assertThat(lastText).isEqualTo(longText)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoSwitchingWhenFrozen() {
|
||||
`when`(view.freezeSwitching).thenReturn(true)
|
||||
|
||||
val average = ((getTextLength(longText) + getTextLength(shortText)) / 2).toInt()
|
||||
onMeasureListenerCaptor.value.onMeasureAction(average)
|
||||
testableLooper.processAllMessages()
|
||||
assertThat(lastText).isEqualTo(longText)
|
||||
|
||||
onMeasureListenerCaptor.value.onMeasureAction(1)
|
||||
testableLooper.processAllMessages()
|
||||
assertThat(lastText).isEqualTo(longText)
|
||||
}
|
||||
|
||||
private fun getTextLength(text: CharSequence): Float {
|
||||
return text.length * CHAR_WIDTH
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user