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

Commit c9b86af7 authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Cleaning up some synchronous event registration in Views

> Removing broadcast receiver in ViewFlipper and AdapterViewFlipper
  as those are handled by windowvisibility
> Adding a delegate for event registration in TextClock and AnalogClock
  so that they can easily be overriden

Bug: 294352799
Test: Verified on device
Flag: N/A
Change-Id: Ib80f8c938019378fea3e243dc67ce6ff0765b256
parent bae52482
Loading
Loading
Loading
Loading
+2 −39
Original line number Diff line number Diff line
@@ -16,10 +16,7 @@

package android.widget;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.TypedArray;
import android.os.Message;
import android.util.AttributeSet;
@@ -48,7 +45,6 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
    private boolean mRunning = false;
    private boolean mStarted = false;
    private boolean mVisible = false;
    private boolean mUserPresent = true;
    private boolean mAdvancedByHost = false;

    public AdapterViewFlipper(Context context) {
@@ -82,40 +78,10 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
        a.recycle();
    }

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                mUserPresent = false;
                updateRunning();
            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
                mUserPresent = true;
                updateRunning(false);
            }
        }
    };

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        // Listen for broadcasts related to user-presence
        final IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_USER_PRESENT);

        // OK, this is gross but needed. This class is supported by the
        // remote views machanism and as a part of that the remote views
        // can be inflated by a context for another user without the app
        // having interact users permission - just for loading resources.
        // For exmaple, when adding widgets from a user profile to the
        // home screen. Therefore, we register the receiver as the current
        // user not the one the context is for.
        getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(),
                filter, null, getHandler());


        if (mAutoStart) {
            // Automatically start when requested
            startFlipping();
@@ -126,8 +92,6 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mVisible = false;

        getContext().unregisterReceiver(mReceiver);
        updateRunning();
    }

@@ -235,8 +199,7 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
     *            true.
     */
    private void updateRunning(boolean flipNow) {
        boolean running = !mAdvancedByHost && mVisible && mStarted && mUserPresent
                && mAdapter != null;
        boolean running = !mAdvancedByHost && mVisible && mStarted && mAdapter != null;
        if (running != mRunning) {
            if (running) {
                showOnly(mWhichChild, flipNow);
@@ -248,7 +211,7 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
        }
        if (LOGD) {
            Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
                    + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
                    + ", mRunning=" + mRunning);
        }
    }

+18 −15
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.BlendMode;
@@ -37,6 +36,9 @@ import android.view.RemotableViewMethod;
import android.view.View;
import android.view.inspector.InspectableProperty;
import android.widget.RemoteViews.RemoteView;
import android.widget.TextClock.ClockEventDelegate;

import com.android.internal.util.Preconditions;

import java.time.Clock;
import java.time.DateTimeException;
@@ -112,6 +114,7 @@ public class AnalogClock extends View {
    public AnalogClock(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        mClockEventDelegate = new ClockEventDelegate(context);
        mSecondsHandFps = AppGlobals.getIntCoreSetting(
                WidgetFlags.KEY_ANALOG_CLOCK_SECONDS_HAND_FPS,
                context.getResources()
@@ -584,21 +587,9 @@ public class AnalogClock extends View {
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        IntentFilter filter = new IntentFilter();

        if (!mReceiverAttached) {
            filter.addAction(Intent.ACTION_TIME_CHANGED);
            filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);

            // OK, this is gross but needed. This class is supported by the
            // remote views mechanism and as a part of that the remote views
            // can be inflated by a context for another user without the app
            // having interact users permission - just for loading resources.
            // For example, when adding widgets from a user profile to the
            // home screen. Therefore, we register the receiver as the current
            // user not the one the context is for.
            getContext().registerReceiverAsUser(mIntentReceiver,
                    android.os.Process.myUserHandle(), filter, null, getHandler());
            mClockEventDelegate.registerTimeChangeReceiver(mIntentReceiver, getHandler());
            mReceiverAttached = true;
        }

@@ -615,12 +606,23 @@ public class AnalogClock extends View {
    @Override
    protected void onDetachedFromWindow() {
        if (mReceiverAttached) {
            getContext().unregisterReceiver(mIntentReceiver);
            mClockEventDelegate.unregisterTimeChangeReceiver(mIntentReceiver);
            mReceiverAttached = false;
        }
        super.onDetachedFromWindow();
    }

    /**
     * Sets a delegate to handle clock event registration. This must be called before the view is
     * attached to the window
     *
     * @hide
     */
    public void setClockEventDelegate(ClockEventDelegate delegate) {
        Preconditions.checkState(!mReceiverAttached, "Clock events already registered");
        mClockEventDelegate = delegate;
    }

    private void onVisible() {
        if (!mVisible) {
            mVisible = true;
@@ -797,6 +799,7 @@ public class AnalogClock extends View {
        }
    };
    private boolean mReceiverAttached;
    private ClockEventDelegate mClockEventDelegate;

    private final Runnable mTick = new Runnable() {
        @Override
+85 −45
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.widget;

import static android.os.Process.myUserHandle;
import static android.view.ViewDebug.ExportedProperty;
import static android.widget.RemoteViews.RemoteView;

@@ -24,7 +25,6 @@ import android.annotation.TestApi;
import android.app.ActivityManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -43,6 +43,7 @@ import android.view.ViewHierarchyEncoder;
import android.view.inspector.InspectableProperty;

import com.android.internal.R;
import com.android.internal.util.Preconditions;

import java.time.Duration;
import java.time.Instant;
@@ -141,6 +142,8 @@ public class TextClock extends TextView {
    private boolean mRegistered;
    private boolean mShouldRunTicker;

    private ClockEventDelegate mClockEventDelegate;

    private Calendar mTime;
    private String mTimeZone;

@@ -178,8 +181,7 @@ public class TextClock extends TextView {
            if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
                final String timeZone = intent.getStringExtra(Intent.EXTRA_TIMEZONE);
                createTime(timeZone);
            } else if (!mShouldRunTicker && (Intent.ACTION_TIME_TICK.equals(intent.getAction())
                    || Intent.ACTION_TIME_CHANGED.equals(intent.getAction()))) {
            } else if (!mShouldRunTicker && Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) {
                return;
            }
            onTimeChanged();
@@ -282,6 +284,7 @@ public class TextClock extends TextView {
        if (mFormat24 == null) {
            mFormat24 = getBestDateTimePattern("Hm");
        }
        mClockEventDelegate = new ClockEventDelegate(getContext());

        createTime(mTimeZone);
        chooseFormat();
@@ -430,6 +433,17 @@ public class TextClock extends TextView {
        registerObserver();
    }

    /**
     * Sets a delegate to handle clock event registration. This must be called before the view is
     * attached to the window
     *
     * @hide
     */
    public void setClockEventDelegate(ClockEventDelegate delegate) {
        Preconditions.checkState(!mRegistered, "Clock events already registered");
        mClockEventDelegate = delegate;
    }

    /**
     * Update the displayed time if necessary and invalidate the view.
     */
@@ -557,7 +571,7 @@ public class TextClock extends TextView {
        if (!mRegistered) {
            mRegistered = true;

            registerReceiver();
            mClockEventDelegate.registerTimeChangeReceiver(mIntentReceiver, getHandler());
            registerObserver();

            createTime(mTimeZone);
@@ -582,7 +596,7 @@ public class TextClock extends TextView {
        super.onDetachedFromWindow();

        if (mRegistered) {
            unregisterReceiver();
            mClockEventDelegate.unregisterTimeChangeReceiver(mIntentReceiver);
            unregisterObserver();

            mRegistered = false;
@@ -598,34 +612,11 @@ public class TextClock extends TextView {
        mStopTicking = true;
    }

    private void registerReceiver() {
        final IntentFilter filter = new IntentFilter();

        filter.addAction(Intent.ACTION_TIME_CHANGED);
        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);

        // OK, this is gross but needed. This class is supported by the
        // remote views mechanism and as a part of that the remote views
        // can be inflated by a context for another user without the app
        // having interact users permission - just for loading resources.
        // For example, when adding widgets from a managed profile to the
        // home screen. Therefore, we register the receiver as the user
        // the app is running as not the one the context is for.
        getContext().registerReceiverAsUser(mIntentReceiver, android.os.Process.myUserHandle(),
                filter, null, getHandler());
    }

    private void registerObserver() {
        if (mRegistered) {
            if (mFormatChangeObserver == null) {
                mFormatChangeObserver = new FormatChangeObserver(getHandler());
            }
            final ContentResolver resolver = getContext().getContentResolver();
            Uri uri = Settings.System.getUriFor(Settings.System.TIME_12_24);
            if (mShowCurrentUserTime) {
                resolver.registerContentObserver(uri, true,
                        mFormatChangeObserver, UserHandle.USER_ALL);
            } else {
            // UserHandle.myUserId() is needed. This class is supported by the
            // remote views mechanism and as a part of that the remote views
            // can be inflated by a context for another user without the app
@@ -634,20 +625,14 @@ public class TextClock extends TextView {
            // home screen. Therefore, we register the ContentObserver with the user
            // the app is running (e.g. the launcher) and not the user of the
            // context (e.g. the widget's profile).
                resolver.registerContentObserver(uri, true,
                        mFormatChangeObserver, UserHandle.myUserId());
            int userHandle = mShowCurrentUserTime ? UserHandle.USER_ALL : UserHandle.myUserId();
            mClockEventDelegate.registerFormatChangeObserver(mFormatChangeObserver, userHandle);
        }
    }
    }

    private void unregisterReceiver() {
        getContext().unregisterReceiver(mIntentReceiver);
    }

    private void unregisterObserver() {
        if (mFormatChangeObserver != null) {
            final ContentResolver resolver = getContext().getContentResolver();
            resolver.unregisterContentObserver(mFormatChangeObserver);
            mClockEventDelegate.unregisterFormatChangeObserver(mFormatChangeObserver);
        }
    }

@@ -674,4 +659,59 @@ public class TextClock extends TextView {
        stream.addProperty("format", mFormat == null ? null : mFormat.toString());
        stream.addProperty("hasSeconds", mHasSeconds);
    }

    /**
     * Utility class to delegate some system event handling to allow overring the default behavior
     *
     * @hide
     */
    public static class ClockEventDelegate {

        private final Context mContext;

        public ClockEventDelegate(Context context) {
            mContext = context;
        }

        /**
         * Registers a receiver for actions {@link Intent#ACTION_TIME_CHANGED} and
         * {@link Intent#ACTION_TIMEZONE_CHANGED}
         *
         * OK, this is gross but needed. This class is supported by the remote views mechanism and
         * as a part of that the remote views can be inflated by a context for another user without
         * the app having interact users permission - just for loading resources. For example,
         * when adding widgets from a managed profile to the home screen. Therefore, we register
         * the receiver as the user the app is running as not the one the context is for.
         */
        public void registerTimeChangeReceiver(BroadcastReceiver receiver, Handler handler) {
            final IntentFilter filter = new IntentFilter();

            filter.addAction(Intent.ACTION_TIME_CHANGED);
            filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);

            mContext.registerReceiverAsUser(receiver, myUserHandle(), filter, null, handler);
        }

        /**
         * Unregisters a previously registered receiver
         */
        public void unregisterTimeChangeReceiver(BroadcastReceiver receiver) {
            mContext.unregisterReceiver(receiver);
        }

        /**
         * Registers an observer for time format changes
         */
        public void registerFormatChangeObserver(ContentObserver observer, int userHandle) {
            Uri uri = Settings.System.getUriFor(Settings.System.TIME_12_24);
            mContext.getContentResolver().registerContentObserver(uri, true, observer, userHandle);
        }

        /**
         * Unregisters a previously registered observer
         */
        public void unregisterFormatChangeObserver(ContentObserver observer) {
            mContext.getContentResolver().unregisterContentObserver(observer);
        }
    }
}
+2 −38
Original line number Diff line number Diff line
@@ -18,10 +18,7 @@ package android.widget;

import android.annotation.IntRange;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Message;
@@ -51,8 +48,6 @@ public class ViewFlipper extends ViewAnimator {
    private boolean mRunning = false;
    private boolean mStarted = false;
    private boolean mVisible = false;
    @UnsupportedAppUsage
    private boolean mUserPresent = true;

    public ViewFlipper(Context context) {
        super(context);
@@ -70,39 +65,10 @@ public class ViewFlipper extends ViewAnimator {
        a.recycle();
    }

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (Intent.ACTION_SCREEN_OFF.equals(action)) {
                mUserPresent = false;
                updateRunning();
            } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
                mUserPresent = true;
                updateRunning(false);
            }
        }
    };

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        // Listen for broadcasts related to user-presence
        final IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_USER_PRESENT);

        // OK, this is gross but needed. This class is supported by the
        // remote views machanism and as a part of that the remote views
        // can be inflated by a context for another user without the app
        // having interact users permission - just for loading resources.
        // For exmaple, when adding widgets from a user profile to the
        // home screen. Therefore, we register the receiver as the current
        // user not the one the context is for.
        getContext().registerReceiverAsUser(mReceiver, android.os.Process.myUserHandle(),
                filter, null, getHandler());

        if (mAutoStart) {
            // Automatically start when requested
            startFlipping();
@@ -113,8 +79,6 @@ public class ViewFlipper extends ViewAnimator {
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mVisible = false;

        getContext().unregisterReceiver(mReceiver);
        updateRunning();
    }

@@ -186,7 +150,7 @@ public class ViewFlipper extends ViewAnimator {
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private void updateRunning(boolean flipNow) {
        boolean running = mVisible && mStarted && mUserPresent;
        boolean running = mVisible && mStarted;
        if (running != mRunning) {
            if (running) {
                showOnly(mWhichChild, flipNow);
@@ -198,7 +162,7 @@ public class ViewFlipper extends ViewAnimator {
        }
        if (LOGD) {
            Log.d(TAG, "updateRunning() mVisible=" + mVisible + ", mStarted=" + mStarted
                    + ", mUserPresent=" + mUserPresent + ", mRunning=" + mRunning);
                    + ", mRunning=" + mRunning);
        }
    }