Overriding the default TextClock and AnalogClock behavior to avoid RPCs on main thread

during onAttachToWindow

Bug: 294352799
Test: Verified on device
Flag: N/A
Change-Id: I3cce6900cd62a6e9a57c155b74c15c2340c6011b
This commit is contained in:
Sunny Goyal
2023-09-18 12:49:12 -07:00
parent 2316dc4750
commit 5b86162853
2 changed files with 158 additions and 0 deletions

View File

@@ -73,10 +73,13 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemProperties;
import android.os.Trace;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.View;
import android.widget.AnalogClock;
import android.widget.TextClock;
import android.window.BackEvent;
import android.window.OnBackAnimationCallback;
import android.window.OnBackInvokedDispatcher;
@@ -152,6 +155,7 @@ import com.android.quickstep.RecentsModel;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TouchInteractionService.TISBinder;
import com.android.quickstep.util.AsyncClockEventDelegate;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LauncherUnfoldAnimationController;
import com.android.quickstep.util.QuickstepOnboardingPrefs;
@@ -213,6 +217,8 @@ public class QuickstepLauncher extends Launcher {
private SplitWithKeyboardShortcutController mSplitWithKeyboardShortcutController;
private SplitToWorkspaceController mSplitToWorkspaceController;
private AsyncClockEventDelegate mAsyncClockEventDelegate;
/**
* If Launcher restarted while in the middle of an Overview split select, it needs this data to
* recover. In all other cases this will remain null.
@@ -478,6 +484,10 @@ public class QuickstepLauncher extends Launcher {
mSplitSelectStateController.onDestroy();
}
if (mAsyncClockEventDelegate != null) {
mAsyncClockEventDelegate.onDestroy();
}
super.onDestroy();
mHotseatPredictionController.destroy();
mSplitWithKeyboardShortcutController.onDestroy();
@@ -1305,4 +1315,27 @@ public class QuickstepLauncher extends Launcher {
mHotseatPredictionController.dump(prefix, writer);
}
}
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
switch (name) {
case "TextClock", "android.widget.TextClock" -> {
TextClock tc = new TextClock(context, attrs);
if (mAsyncClockEventDelegate == null) {
mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
}
tc.setClockEventDelegate(mAsyncClockEventDelegate);
return tc;
}
case "AnalogClock", "android.widget.AnalogClock" -> {
AnalogClock ac = new AnalogClock(context, attrs);
if (mAsyncClockEventDelegate == null) {
mAsyncClockEventDelegate = new AsyncClockEventDelegate(this);
}
ac.setClockEventDelegate(mAsyncClockEventDelegate);
return ac;
}
}
return super.onCreateView(parent, name, context, attrs);
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright (C) 2023 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.quickstep.util;
import static android.content.Intent.ACTION_TIMEZONE_CHANGED;
import static android.content.Intent.ACTION_TIME_CHANGED;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
import android.util.ArrayMap;
import android.widget.TextClock.ClockEventDelegate;
import androidx.annotation.WorkerThread;
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.SettingsCache.OnChangeListener;
import com.android.launcher3.util.SimpleBroadcastReceiver;
import java.util.ArrayList;
import java.util.List;
/**
* Extension of {@link ClockEventDelegate} to support async event registration
*/
public class AsyncClockEventDelegate extends ClockEventDelegate implements OnChangeListener {
private final Context mContext;
private final SimpleBroadcastReceiver mReceiver =
new SimpleBroadcastReceiver(this::onClockEventReceived);
private final ArrayMap<BroadcastReceiver, Handler> mTimeEventReceivers = new ArrayMap<>();
private final List<ContentObserver> mFormatObservers = new ArrayList<>();
private final Uri mFormatUri = Settings.System.getUriFor(Settings.System.TIME_12_24);
private boolean mFormatRegistered = false;
private boolean mDestroyed = false;
public AsyncClockEventDelegate(Context context) {
super(context);
mContext = context;
UI_HELPER_EXECUTOR.execute(() ->
mReceiver.register(mContext, ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGED));
}
@Override
public void registerTimeChangeReceiver(BroadcastReceiver receiver, Handler handler) {
synchronized (mTimeEventReceivers) {
mTimeEventReceivers.put(receiver, handler == null ? new Handler() : handler);
}
}
@Override
public void unregisterTimeChangeReceiver(BroadcastReceiver receiver) {
synchronized (mTimeEventReceivers) {
mTimeEventReceivers.remove(receiver);
}
}
@Override
public void registerFormatChangeObserver(ContentObserver observer, int userHandle) {
synchronized (mFormatObservers) {
if (!mFormatRegistered && !mDestroyed) {
SettingsCache.INSTANCE.get(mContext).register(mFormatUri, this);
mFormatRegistered = true;
}
mFormatObservers.add(observer);
}
}
@Override
public void unregisterFormatChangeObserver(ContentObserver observer) {
synchronized (mFormatObservers) {
mFormatObservers.remove(observer);
}
}
@Override
public void onSettingsChanged(boolean isEnabled) {
if (mDestroyed) {
return;
}
synchronized (mFormatObservers) {
mFormatObservers.forEach(o -> o.dispatchChange(false, mFormatUri));
}
}
@WorkerThread
private void onClockEventReceived(Intent intent) {
if (mDestroyed) {
return;
}
synchronized (mReceiver) {
mTimeEventReceivers.forEach((r, h) -> h.post(() -> r.onReceive(mContext, intent)));
}
}
/**
* Unregisters all system callbacks and destroys this delegate
*/
public void onDestroy() {
mDestroyed = true;
SettingsCache.INSTANCE.get(mContext).unregister(mFormatUri, this);
UI_HELPER_EXECUTOR.execute(() -> mReceiver.unregisterReceiverSafely(mContext));
}
}