Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5b861628 authored by Sunny Goyal's avatar Sunny Goyal
Browse files

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
parent 2316dc47
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
@@ -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);
    }
}
+125 −0
Original line number Diff line number Diff line
/*
 * 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));
    }
}