1 /*
2  * Copyright (C) 2023 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 package com.android.quickstep.util;
17 
18 import static android.content.Intent.ACTION_TIMEZONE_CHANGED;
19 import static android.content.Intent.ACTION_TIME_CHANGED;
20 
21 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
22 
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.database.ContentObserver;
27 import android.net.Uri;
28 import android.os.Handler;
29 import android.provider.Settings;
30 import android.util.ArrayMap;
31 import android.widget.TextClock.ClockEventDelegate;
32 
33 import androidx.annotation.WorkerThread;
34 
35 import com.android.launcher3.util.MainThreadInitializedObject;
36 import com.android.launcher3.util.SafeCloseable;
37 import com.android.launcher3.util.SettingsCache;
38 import com.android.launcher3.util.SettingsCache.OnChangeListener;
39 import com.android.launcher3.util.SimpleBroadcastReceiver;
40 
41 import java.util.ArrayList;
42 import java.util.List;
43 
44 /**
45  * Extension of {@link ClockEventDelegate} to support async event registration
46  */
47 public class AsyncClockEventDelegate extends ClockEventDelegate
48         implements OnChangeListener, SafeCloseable {
49 
50     public static final MainThreadInitializedObject<AsyncClockEventDelegate> INSTANCE =
51             new MainThreadInitializedObject<>(AsyncClockEventDelegate::new);
52 
53     private final Context mContext;
54     private final SimpleBroadcastReceiver mReceiver =
55             new SimpleBroadcastReceiver(this::onClockEventReceived);
56 
57     private final ArrayMap<BroadcastReceiver, Handler> mTimeEventReceivers = new ArrayMap<>();
58     private final List<ContentObserver> mFormatObservers = new ArrayList<>();
59     private final Uri mFormatUri = Settings.System.getUriFor(Settings.System.TIME_12_24);
60 
61     private boolean mFormatRegistered = false;
62     private boolean mDestroyed = false;
63 
AsyncClockEventDelegate(Context context)64     private AsyncClockEventDelegate(Context context) {
65         super(context);
66         mContext = context;
67 
68         UI_HELPER_EXECUTOR.execute(() ->
69                 mReceiver.register(mContext, ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGED));
70     }
71 
72     @Override
registerTimeChangeReceiver(BroadcastReceiver receiver, Handler handler)73     public void registerTimeChangeReceiver(BroadcastReceiver receiver, Handler handler) {
74         synchronized (mTimeEventReceivers) {
75             mTimeEventReceivers.put(receiver, handler == null ? new Handler() : handler);
76         }
77     }
78 
79     @Override
unregisterTimeChangeReceiver(BroadcastReceiver receiver)80     public void unregisterTimeChangeReceiver(BroadcastReceiver receiver) {
81         synchronized (mTimeEventReceivers) {
82             mTimeEventReceivers.remove(receiver);
83         }
84     }
85 
86     @Override
registerFormatChangeObserver(ContentObserver observer, int userHandle)87     public void registerFormatChangeObserver(ContentObserver observer, int userHandle) {
88         if (mDestroyed) {
89             return;
90         }
91         synchronized (mFormatObservers) {
92             if (!mFormatRegistered && !mDestroyed) {
93                 SettingsCache.INSTANCE.get(mContext).register(mFormatUri, this);
94                 mFormatRegistered = true;
95             }
96             mFormatObservers.add(observer);
97         }
98     }
99 
100     @Override
unregisterFormatChangeObserver(ContentObserver observer)101     public void unregisterFormatChangeObserver(ContentObserver observer) {
102         synchronized (mFormatObservers) {
103             mFormatObservers.remove(observer);
104         }
105     }
106 
107     @Override
onSettingsChanged(boolean isEnabled)108     public void onSettingsChanged(boolean isEnabled) {
109         if (mDestroyed) {
110             return;
111         }
112         synchronized (mFormatObservers) {
113             mFormatObservers.forEach(o -> o.dispatchChange(false, mFormatUri));
114         }
115     }
116     @WorkerThread
onClockEventReceived(Intent intent)117     private void onClockEventReceived(Intent intent) {
118         if (mDestroyed) {
119             return;
120         }
121         synchronized (mReceiver) {
122             mTimeEventReceivers.forEach((r, h) -> h.post(() -> r.onReceive(mContext, intent)));
123         }
124     }
125 
126     @Override
close()127     public void close() {
128         mDestroyed = true;
129         SettingsCache.INSTANCE.get(mContext).unregister(mFormatUri, this);
130         UI_HELPER_EXECUTOR.execute(() -> mReceiver.unregisterReceiverSafely(mContext));
131     }
132 }
133