From ad587fc67ee3a120a4503cf9686f357ca6290a46 Mon Sep 17 00:00:00 2001 From: Bryce Lee Date: Mon, 11 Oct 2021 18:27:43 -0700 Subject: [PATCH 1/2] CommunalSourcePackageObserver Introduction. This changelist introduces the CommunalSourcePackageObserver, which handles notifying when the package is updated or added. Bug: 202784947 Test: atest CommunalSourcePackageObserverTest Change-Id: I945717af0dec15532bdd0a110d5f92859bbdf118 --- .../systemui/communal/PackageObserver.java | 101 ++++++++++++++++++ .../communal/dagger/CommunalModule.java | 22 ++++ .../communal/PackageObserverTest.java | 77 +++++++++++++ 3 files changed, 200 insertions(+) create mode 100644 packages/SystemUI/src/com/android/systemui/communal/PackageObserver.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/communal/PackageObserverTest.java diff --git a/packages/SystemUI/src/com/android/systemui/communal/PackageObserver.java b/packages/SystemUI/src/com/android/systemui/communal/PackageObserver.java new file mode 100644 index 0000000000000..3d25d126a2914 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/PackageObserver.java @@ -0,0 +1,101 @@ +/* + * 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> 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> 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); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java index 9b72655c9fb5b..fca842ade7bb2 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java @@ -16,18 +16,26 @@ 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 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.Optional; import java.util.Set; import javax.inject.Named; @@ -56,6 +64,20 @@ public interface CommunalModule { return view; } + /** */ + @Provides + static Optional 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. diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/PackageObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/PackageObserverTest.java new file mode 100644 index 0000000000000..1cd60698353bf --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/PackageObserverTest.java @@ -0,0 +1,77 @@ +/* + * 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 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()); + } +} From 0106a9f1b48adbbe4c7094e7ff1c8dfbefe7ed13 Mon Sep 17 00:00:00 2001 From: Bryce Lee Date: Mon, 8 Nov 2021 17:04:10 -0800 Subject: [PATCH 2/2] Load CommunalSource.Connector from config. This changelist moves CommunalSource.Connector dependencies behind a multi-binding provider. The selected connector is based on a configuration, which specifies the fully qualified class. Test: manual Bug: 205600269 Change-Id: I289800da19839897c57ae5205d76115fa3367468 --- packages/SystemUI/res/values/config.xml | 4 ++ .../communal/dagger/CommunalModule.java | 38 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index ff748a982b33b..df8fc22827b1a 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -742,4 +742,8 @@ false + + + + diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java index fca842ade7bb2..a0986660ebdca 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java @@ -23,6 +23,8 @@ 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; @@ -35,15 +37,19 @@ 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. @@ -97,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 provideCommunalSourceConnector( + @Main Resources resources, + Map, Provider> connectorCreators) { + final String className = resources.getString(R.string.config_communalSourceConnector); + + if (TextUtils.isEmpty(className)) { + return Optional.empty(); + } + + try { + Class clazz = Class.forName(className); + Provider provider = connectorCreators.get(clazz); + return provider != null ? Optional.of(provider.get()) : Optional.empty(); + } catch (ClassNotFoundException e) { + return Optional.empty(); + } + } }