1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.statusbar.policy;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.content.res.TypedArray;
24 import android.icu.text.DateFormat;
25 import android.icu.text.DisplayContext;
26 import android.os.Handler;
27 import android.text.TextUtils;
28 import android.util.AttributeSet;
29 import android.widget.TextView;
30 
31 import com.android.systemui.Dependency;
32 import com.android.systemui.R;
33 import com.android.systemui.broadcast.BroadcastDispatcher;
34 
35 import java.util.Date;
36 import java.util.Locale;
37 
38 public class DateView extends TextView {
39     private static final String TAG = "DateView";
40 
41     private final Date mCurrentTime = new Date();
42 
43     private DateFormat mDateFormat;
44     private String mLastText;
45     private String mDatePattern;
46     private final BroadcastDispatcher mBroadcastDispatcher;
47 
48     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
49         @Override
50         public void onReceive(Context context, Intent intent) {
51             // If the handler is null, it means we received a broadcast while the view has not
52             // finished being attached or in the process of being detached.
53             // In that case, do not post anything.
54             Handler handler = getHandler();
55             if (handler == null) return;
56 
57             final String action = intent.getAction();
58             if (Intent.ACTION_TIME_TICK.equals(action)
59                     || Intent.ACTION_TIME_CHANGED.equals(action)
60                     || Intent.ACTION_TIMEZONE_CHANGED.equals(action)
61                     || Intent.ACTION_LOCALE_CHANGED.equals(action)) {
62                 if (Intent.ACTION_LOCALE_CHANGED.equals(action)
63                         || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
64                     // need to get a fresh date format
65                     handler.post(() -> mDateFormat = null);
66                 }
67                 handler.post(() -> updateClock());
68             }
69         }
70     };
71 
DateView(Context context, AttributeSet attrs)72     public DateView(Context context, AttributeSet attrs) {
73         super(context, attrs);
74         TypedArray a = context.getTheme().obtainStyledAttributes(
75                 attrs,
76                 R.styleable.DateView,
77                 0, 0);
78 
79         try {
80             mDatePattern = a.getString(R.styleable.DateView_datePattern);
81         } finally {
82             a.recycle();
83         }
84         if (mDatePattern == null) {
85             mDatePattern = getContext().getString(R.string.system_ui_date_pattern);
86         }
87         mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
88     }
89 
90     @Override
onAttachedToWindow()91     protected void onAttachedToWindow() {
92         super.onAttachedToWindow();
93 
94         IntentFilter filter = new IntentFilter();
95         filter.addAction(Intent.ACTION_TIME_TICK);
96         filter.addAction(Intent.ACTION_TIME_CHANGED);
97         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
98         filter.addAction(Intent.ACTION_LOCALE_CHANGED);
99         mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter,
100                 Dependency.get(Dependency.TIME_TICK_HANDLER));
101 
102         updateClock();
103     }
104 
105     @Override
onDetachedFromWindow()106     protected void onDetachedFromWindow() {
107         super.onDetachedFromWindow();
108 
109         mDateFormat = null; // reload the locale next time
110         mBroadcastDispatcher.unregisterReceiver(mIntentReceiver);
111     }
112 
updateClock()113     protected void updateClock() {
114         if (mDateFormat == null) {
115             final Locale l = Locale.getDefault();
116             DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l);
117             // The use of CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE instead of
118             // CAPITALIZATION_FOR_STANDALONE is to address
119             // https://unicode-org.atlassian.net/browse/ICU-21631
120             // TODO(b/229287642): Switch back to CAPITALIZATION_FOR_STANDALONE
121             format.setContext(DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE);
122             mDateFormat = format;
123         }
124 
125         mCurrentTime.setTime(System.currentTimeMillis());
126 
127         final String text = mDateFormat.format(mCurrentTime);
128         if (!text.equals(mLastText)) {
129             setText(text);
130             mLastText = text;
131         }
132     }
133 
setDatePattern(String pattern)134     public void setDatePattern(String pattern) {
135         if (TextUtils.equals(pattern, mDatePattern)) {
136             return;
137         }
138         mDatePattern = pattern;
139         mDateFormat = null;
140         if (isAttachedToWindow()) {
141             updateClock();
142         }
143     }
144 }
145