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

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

Merge changes from topic "primer-refactor"

* changes:
  Load CommunalSource.Connector from config.
  CommunalSourcePackageObserver Introduction.
parents 9d8a6151 0106a9f1
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -742,4 +742,8 @@

    <!-- Flag to enable dream overlay service and its registration -->
    <bool name="config_dreamOverlayServiceEnabled">false</bool>

    <!-- Class for the communal source connector to be used -->
    <string name="config_communalSourceConnector" translatable="false"></string>

</resources>
+101 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.communal;

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

import com.google.android.collect.Lists;

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

/**
 * {@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 CommunalSource.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;

    public PackageObserver(Context context, String packageName) {
        mContext = context;
        mPackageName = packageName;
    }

    @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);
        }
    }
}
+60 −0
Original line number Diff line number Diff line
@@ -16,26 +16,40 @@

package com.android.systemui.communal.dagger;

import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.text.TextUtils;
import android.view.View;
import android.widget.FrameLayout;

import androidx.annotation.Nullable;

import com.android.systemui.R;
import com.android.systemui.communal.CommunalSource;
import com.android.systemui.communal.PackageObserver;
import com.android.systemui.communal.conditions.CommunalCondition;
import com.android.systemui.communal.conditions.CommunalSettingCondition;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.idle.AmbientLightModeMonitor;
import com.android.systemui.idle.LightSensorEventsDebounceAlgorithm;
import com.android.systemui.idle.dagger.IdleViewComponent;

import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import javax.inject.Named;
import javax.inject.Provider;

import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;
import dagger.multibindings.IntoMap;
import dagger.multibindings.StringKey;

/**
 * Dagger Module providing Communal-related functionality.
@@ -56,6 +70,20 @@ public interface CommunalModule {
        return view;
    }

    /** */
    @Provides
    static Optional<CommunalSource.Observer> provideCommunalSourcePackageObserver(
            Context context, @Main Resources resources) {
        final String componentName = resources.getString(R.string.config_communalSourceComponent);

        if (TextUtils.isEmpty(componentName)) {
            return Optional.empty();
        }

        return Optional.of(new PackageObserver(context,
                ComponentName.unflattenFromString(componentName).getPackageName()));
    }

    /**
     * Provides LightSensorEventsDebounceAlgorithm as an instance to DebounceAlgorithm interface.
     * @param algorithm the instance of algorithm that is bound to the interface.
@@ -75,4 +103,36 @@ public interface CommunalModule {
            CommunalSettingCondition communalSettingCondition) {
        return new HashSet<>(Collections.singletonList(communalSettingCondition));
    }

    /**
     * TODO(b/205638389): Remove when there is a base implementation of
     * {@link CommunalSource.Connector}. Currently a place holder to allow a map to be present.
     */
    @Provides
    @IntoMap
    @Nullable
    @StringKey("empty")
    static CommunalSource.Connector provideEmptyCommunalSourceConnector() {
        return null;
    }

    /** */
    @Provides
    static Optional<CommunalSource.Connector> provideCommunalSourceConnector(
            @Main Resources resources,
            Map<Class<?>, Provider<CommunalSource.Connector>> connectorCreators) {
        final String className = resources.getString(R.string.config_communalSourceConnector);

        if (TextUtils.isEmpty(className)) {
            return Optional.empty();
        }

        try {
            Class<?> clazz = Class.forName(className);
            Provider<CommunalSource.Connector> provider = connectorCreators.get(clazz);
            return provider != null ? Optional.of(provider.get()) : Optional.empty();
        } catch (ClassNotFoundException e) {
            return Optional.empty();
        }
    }
}
+77 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.communal;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.verify;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.testing.AndroidTestingRunner;

import androidx.test.filters.SmallTest;

import com.android.systemui.SysuiTestCase;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@SmallTest
@RunWith(AndroidTestingRunner.class)
public class PackageObserverTest extends SysuiTestCase {
    private static final String PACKAGE_NAME = "com.foo.bar";

    @Mock
    Context mContext;

    @Mock
    CommunalSource.Observer.Callback mCallback;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testChange() {
        final PackageObserver observer = new PackageObserver(mContext, PACKAGE_NAME);
        final ArgumentCaptor<BroadcastReceiver> receiverCapture =
                ArgumentCaptor.forClass(BroadcastReceiver.class);

        observer.addCallback(mCallback);

        // Verify broadcast receiver registered.
        verify(mContext).registerReceiver(receiverCapture.capture(), any(), anyInt());

        // Simulate package change.
        receiverCapture.getValue().onReceive(mContext, new Intent());

        // Check that callback was informed.
        verify(mCallback).onSourceChanged();

        observer.removeCallback(mCallback);

        // Make sure receiver is unregistered on last callback removal
        verify(mContext).unregisterReceiver(receiverCapture.getValue());
    }
}