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

Commit dc24107b authored by Bryce Lee's avatar Bryce Lee
Browse files

Introduce PersistentConnectionManager.

PersistentConnectionManager monitors and re-establishes an ObservableServiceConnection on disconnect.

Test: atest PackageObserverTest PersistentConnectionManagerTest ObservableServiceConnectionTest
Bug: 213906355
Change-Id: I51fb79a2c05cf2f86e7c5e4a3ceecddd162c0ec8
parent 12ae0523
Loading
Loading
Loading
Loading
+45 −0
Original line number 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 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 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 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 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