Loading packages/SystemUI/res/values/config.xml +4 −0 Original line number Diff line number Diff line Loading @@ -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> packages/SystemUI/src/com/android/systemui/communal/PackageObserver.java 0 → 100644 +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); } } } packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java +60 −0 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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. Loading @@ -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(); } } } packages/SystemUI/tests/src/com/android/systemui/communal/PackageObserverTest.java 0 → 100644 +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()); } } Loading
packages/SystemUI/res/values/config.xml +4 −0 Original line number Diff line number Diff line Loading @@ -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>
packages/SystemUI/src/com/android/systemui/communal/PackageObserver.java 0 → 100644 +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); } } }
packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java +60 −0 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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. Loading @@ -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(); } } }
packages/SystemUI/tests/src/com/android/systemui/communal/PackageObserverTest.java 0 → 100644 +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()); } }