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