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

Commit e02d3ec2 authored by Bryce Lee's avatar Bryce Lee Committed by Android (Google) Code Review
Browse files

Merge "Introduce PersistentConnectionManager."

parents b72556f4 dc24107b
Loading
Loading
Loading
Loading
+45 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2022 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.systemui.util.service;

/**
 * The {@link Observer} interface specifies an entity which listeners
 * can be informed of changes to the source, which will require updating. Note that this deals
 * with changes to the source itself, not content which will be updated through the interface.
 */
public interface Observer {
    /**
     * Callback for receiving updates from the {@link Observer}.
     */
    interface Callback {
        /**
         * Invoked when the source has changed.
         */
        void onSourceChanged();
    }

    /**
     * Adds a callback to receive future updates from the {@link Observer}.
     */
    void addCallback(Callback callback);

    /**
     * Removes a callback from receiving further updates.
     * @param callback
     */
    void removeCallback(Callback callback);
}
+107 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2022 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.systemui.util.service;

import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.PatternMatcher;
import android.util.Log;

import com.android.systemui.communal.CommunalSource;

import com.google.android.collect.Lists;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;

import javax.inject.Inject;

/**
 * {@link PackageObserver} allows for monitoring the system for changes relating to a particular
 * package. This can be used by {@link CommunalSource} clients to detect when a related package
 * has changed and reloading is necessary.
 */
public class PackageObserver implements Observer {
    private static final String TAG = "PackageObserver";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private final ArrayList<WeakReference<Callback>> mCallbacks = Lists.newArrayList();

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (DEBUG) {
                Log.d(TAG, "package added receiver - onReceive");
            }

            final Iterator<WeakReference<Callback>> iter = mCallbacks.iterator();
            while (iter.hasNext()) {
                final Callback callback = iter.next().get();
                if (callback != null) {
                    callback.onSourceChanged();
                } else {
                    iter.remove();
                }
            }
        }
    };

    private final String mPackageName;
    private final Context mContext;

    @Inject
    public PackageObserver(Context context, ComponentName component) {
        mContext = context;
        mPackageName = component.getPackageName();
    }

    @Override
    public void addCallback(Callback callback) {
        if (DEBUG) {
            Log.d(TAG, "addCallback:" + callback);
        }
        mCallbacks.add(new WeakReference<>(callback));

        // Only register for listening to package additions on first callback.
        if (mCallbacks.size() > 1) {
            return;
        }

        final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
        filter.addDataScheme("package");
        filter.addDataSchemeSpecificPart(mPackageName, PatternMatcher.PATTERN_LITERAL);
        // Note that we directly register the receiver here as data schemes are not supported by
        // BroadcastDispatcher.
        mContext.registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED);
    }

    @Override
    public void removeCallback(Callback callback) {
        if (DEBUG) {
            Log.d(TAG, "removeCallback:" + callback);
        }
        final boolean removed = mCallbacks.removeIf(el -> el.get() == callback);

        if (removed && mCallbacks.isEmpty()) {
            mContext.unregisterReceiver(mReceiver);
        }
    }
}
+155 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2022 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.systemui.util.service;

import static com.android.systemui.util.service.dagger.ObservableServiceModule.BASE_RECONNECT_DELAY_MS;
import static com.android.systemui.util.service.dagger.ObservableServiceModule.MAX_RECONNECT_ATTEMPTS;
import static com.android.systemui.util.service.dagger.ObservableServiceModule.MIN_CONNECTION_DURATION_MS;
import static com.android.systemui.util.service.dagger.ObservableServiceModule.OBSERVER;
import static com.android.systemui.util.service.dagger.ObservableServiceModule.SERVICE_CONNECTION;

import android.util.Log;

import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;

import javax.inject.Inject;
import javax.inject.Named;

/**
 * The {@link PersistentConnectionManager} is responsible for maintaining a connection to a
 * {@link ObservableServiceConnection}.
 * @param <T> The transformed connection type handled by the service.
 */
public class PersistentConnectionManager<T> {
    private static final String TAG = "PersistentConnManager";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private final SystemClock mSystemClock;
    private final DelayableExecutor mMainExecutor;
    private final int mBaseReconnectDelayMs;
    private final int mMaxReconnectAttempts;
    private final int mMinConnectionDuration;
    private final Observer mObserver;

    private int mReconnectAttempts = 0;
    private Runnable mCurrentReconnectCancelable;

    private final ObservableServiceConnection<T> mConnection;

    private final Runnable mConnectRunnable = new Runnable() {
        @Override
        public void run() {
            mCurrentReconnectCancelable = null;
            mConnection.bind();
        }
    };

    private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt();

    private final ObservableServiceConnection.Callback mConnectionCallback =
            new ObservableServiceConnection.Callback() {
        private long mStartTime;

        @Override
        public void onConnected(ObservableServiceConnection connection, Object proxy) {
            mStartTime = mSystemClock.currentTimeMillis();
        }

        @Override
        public void onDisconnected(ObservableServiceConnection connection, int reason) {
            if (mSystemClock.currentTimeMillis() - mStartTime > mMinConnectionDuration) {
                initiateConnectionAttempt();
            } else {
                scheduleConnectionAttempt();
            }
        }
    };

    @Inject
    public PersistentConnectionManager(
            SystemClock clock,
            DelayableExecutor mainExecutor,
            @Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection,
            @Named(MAX_RECONNECT_ATTEMPTS) int maxReconnectAttempts,
            @Named(BASE_RECONNECT_DELAY_MS) int baseReconnectDelayMs,
            @Named(MIN_CONNECTION_DURATION_MS) int minConnectionDurationMs,
            @Named(OBSERVER) Observer observer) {
        mSystemClock = clock;
        mMainExecutor = mainExecutor;
        mConnection = serviceConnection;
        mObserver = observer;

        mMaxReconnectAttempts = maxReconnectAttempts;
        mBaseReconnectDelayMs = baseReconnectDelayMs;
        mMinConnectionDuration = minConnectionDurationMs;
    }

    /**
     * Begins the {@link PersistentConnectionManager} by connecting to the associated service.
     */
    public void start() {
        mConnection.addCallback(mConnectionCallback);
        mObserver.addCallback(mObserverCallback);
        initiateConnectionAttempt();
    }

    /**
     * Brings down the {@link PersistentConnectionManager}, disconnecting from the service.
     */
    public void stop() {
        mConnection.removeCallback(mConnectionCallback);
        mObserver.removeCallback(mObserverCallback);
        mConnection.unbind();
    }

    private void initiateConnectionAttempt() {
        // Reset attempts
        mReconnectAttempts = 0;

        // The first attempt is always a direct invocation rather than delayed.
        mConnection.bind();
    }

    private void scheduleConnectionAttempt() {
        // always clear cancelable if present.
        if (mCurrentReconnectCancelable != null) {
            mCurrentReconnectCancelable.run();
            mCurrentReconnectCancelable = null;
        }

        if (mReconnectAttempts >= mMaxReconnectAttempts) {
            if (DEBUG) {
                Log.d(TAG, "exceeded max connection attempts.");
            }
            return;
        }

        final long reconnectDelayMs =
                (long) Math.scalb(mBaseReconnectDelayMs, mReconnectAttempts);

        if (DEBUG) {
            Log.d(TAG,
                    "scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
        }

        mCurrentReconnectCancelable = mMainExecutor.executeDelayed(mConnectRunnable,
                reconnectDelayMs);

        mReconnectAttempts++;
    }
}
+65 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2022 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.systemui.util.service.dagger;

import android.content.res.Resources;

import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;

import javax.inject.Named;

import dagger.Module;
import dagger.Provides;

/**
 * Module containing components and parameters for
 * {@link com.android.systemui.util.service.ObservableServiceConnection}
 * and {@link com.android.systemui.util.service.PersistentConnectionManager}.
 */
@Module(subcomponents = {
        PackageObserverComponent.class,
})
public class ObservableServiceModule {
    public static final String MAX_RECONNECT_ATTEMPTS = "max_reconnect_attempts";
    public static final String BASE_RECONNECT_DELAY_MS = "base_reconnect_attempts";
    public static final String MIN_CONNECTION_DURATION_MS = "min_connection_duration_ms";
    public static final String SERVICE_CONNECTION = "service_connection";
    public static final String OBSERVER = "observer";

    @Provides
    @Named(MAX_RECONNECT_ATTEMPTS)
    static int providesMaxReconnectAttempts(@Main Resources resources) {
        return resources.getInteger(
                R.integer.config_communalSourceMaxReconnectAttempts);
    }

    @Provides
    @Named(BASE_RECONNECT_DELAY_MS)
    static int provideBaseReconnectDelayMs(@Main Resources resources) {
        return resources.getInteger(
                R.integer.config_communalSourceReconnectBaseDelay);
    }

    @Provides
    @Named(MIN_CONNECTION_DURATION_MS)
    static int providesMinConnectionDuration(@Main Resources resources) {
        return resources.getInteger(
                R.integer.config_connectionMinDuration);
    }
}
+42 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2022 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.systemui.util.service.dagger;

import android.content.ComponentName;

import com.android.systemui.util.service.PackageObserver;

import dagger.BindsInstance;
import dagger.Subcomponent;

/**
 * Generates a scoped {@link PackageObserver}.
 */
@Subcomponent
public interface PackageObserverComponent {
    /**
     * Generates a {@link PackageObserverComponent} instance.
     */
    @Subcomponent.Factory
    interface Factory {
        PackageObserverComponent create(@BindsInstance ComponentName component);
    }

    /**
     * Creates a {@link PackageObserver}.
     */
    PackageObserver getPackageObserver();
}
Loading