diff options
author | Binh Nguyen <binhnguyen@google.com> | 2022-09-19 21:33:04 -0700 |
---|---|---|
committer | Binh Nguyen <binhnguyen@google.com> | 2022-09-19 22:30:35 -0700 |
commit | 192e66ef4e382aae3d4021a8175ad3ab5c75e229 (patch) | |
tree | b9a6f7e26b413c469edb2fb7f8c22c71563698c5 | |
parent | a6cabbab369e5566fa95b0ebe5b07d51512bd83b (diff) | |
download | AdServices-192e66ef4e382aae3d4021a8175ad3ab5c75e229.tar.gz |
Add a feature flag for AdServices System Service receiver registration
- Add Flags and PhFlags that uses DeviceConfig
- Define a flag to enable the AdServiceSystemServiceBroadcastReceiver
- This will make sure that when the flag is disabled, we will not
register any Receiver.
Bug: 247581193
Test: presubmit
Change-Id: I24582e19909241001f1665c73eab36b31e89fea4
7 files changed, 360 insertions, 48 deletions
diff --git a/adservices/service/java/com/android/server/adservices/AdServicesManagerService.java b/adservices/service/java/com/android/server/adservices/AdServicesManagerService.java index 1f87123c9e..ccffefa5ea 100644 --- a/adservices/service/java/com/android/server/adservices/AdServicesManagerService.java +++ b/adservices/service/java/com/android/server/adservices/AdServicesManagerService.java @@ -24,6 +24,7 @@ import android.content.pm.ResolveInfo; import android.os.Handler; import android.os.HandlerThread; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -57,20 +58,30 @@ public class AdServicesManagerService { private static final String PACKAGE_DATA_CLEARED = "package_data_cleared"; private final Context mContext; - private final Handler mHandler; + + BroadcastReceiver mSystemServicePackageChangedReceiver; + + // This will be triggered when there is a flag change. + private final DeviceConfig.OnPropertiesChangedListener mOnFlagsChangedListener = + properties -> { + if (!properties.getNamespace().equals(DeviceConfig.NAMESPACE_ADSERVICES)) { + return; + } + registerPackagedChangedBroadcastReceivers(); + }; @VisibleForTesting AdServicesManagerService(Context context) { mContext = context; - // Start the handler thread. - HandlerThread handlerThread = new HandlerThread("AdServicesManagerServiceHandler"); - handlerThread.start(); - mHandler = new Handler(handlerThread.getLooper()); + DeviceConfig.addOnPropertiesChangedListener( + DeviceConfig.NAMESPACE_ADSERVICES, + mContext.getMainExecutor(), + mOnFlagsChangedListener); + registerPackagedChangedBroadcastReceivers(); } - /** @hide */ public static class Lifecycle extends SystemService { private AdServicesManagerService mService; @@ -93,28 +104,60 @@ public class AdServicesManagerService { * the device at boot up. After receiving the broadcast, send an explicit broadcast to the * AdServices module as that user. */ - private void registerPackagedChangedBroadcastReceivers() { - final IntentFilter packageChangedIntentFilter = new IntentFilter(); - - packageChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); - packageChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); - packageChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); - packageChangedIntentFilter.addDataScheme("package"); - - BroadcastReceiver systemServicePackageChangedReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - UserHandle user = getSendingUser(); - mHandler.post(() -> onPackageChange(intent, user)); - } - }; - mContext.registerReceiverForAllUsers( - systemServicePackageChangedReceiver, - packageChangedIntentFilter, - /* broadcastPermission */ null, - mHandler); - Log.d(TAG, "Package changed broadcast receivers registered."); + @VisibleForTesting + void registerPackagedChangedBroadcastReceivers() { + // There could be race condition between registerPackagedChangedBroadcastReceivers call + // in the AdServicesManagerService constructor and the mOnFlagsChangedListener. + synchronized (AdServicesManagerService.class) { + if (FlagsFactory.getFlags().getAdServicesSystemServiceEnabled()) { + if (mSystemServicePackageChangedReceiver != null) { + // We already register the receiver. + Log.d(TAG, "SystemServicePackageChangedReceiver is already registered."); + return; + } + + // mSystemServicePackageChangedReceiver == null + // We haven't registered the receiver. + // Start the handler thread. + HandlerThread handlerThread = new HandlerThread("AdServicesManagerServiceHandler"); + handlerThread.start(); + Handler handler = new Handler(handlerThread.getLooper()); + + final IntentFilter packageChangedIntentFilter = new IntentFilter(); + + packageChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); + packageChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); + packageChangedIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + packageChangedIntentFilter.addDataScheme("package"); + + mSystemServicePackageChangedReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + UserHandle user = getSendingUser(); + handler.post(() -> onPackageChange(intent, user)); + } + }; + mContext.registerReceiverForAllUsers( + mSystemServicePackageChangedReceiver, + packageChangedIntentFilter, + /* broadcastPermission */ null, + handler); + Log.d(TAG, "Package changed broadcast receivers registered."); + } else { + // FlagsFactory.getFlags().getAdServicesSystemServiceEnabled() == false + Log.d(TAG, "AdServicesSystemServiceEnabled is FALSE."); + + // If there is a SystemServicePackageChangeReceiver, unregister it. + if (mSystemServicePackageChangedReceiver != null) { + Log.d(TAG, "Unregistering the existing SystemServicePackageChangeReceiver"); + mContext.unregisterReceiver(mSystemServicePackageChangedReceiver); + mSystemServicePackageChangedReceiver = null; + } + + return; + } + } } /** Sends an explicit broadcast to the AdServices module when a package change occurs. */ diff --git a/adservices/service/java/com/android/server/adservices/Flags.java b/adservices/service/java/com/android/server/adservices/Flags.java new file mode 100644 index 0000000000..925f88db29 --- /dev/null +++ b/adservices/service/java/com/android/server/adservices/Flags.java @@ -0,0 +1,35 @@ +/* + * 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.server.adservices; + +/** + * AdServices System Service Feature Flags interface. This Flags interface hold the default values + * of AdServices System Service Flags. + * + * @hide + */ +interface Flags { + /** + * Whether to enable the AdServices System Service. By default, the AdServices System Service is + * disabled. + */ + boolean ADSERVICES_SYSTEM_SERVICE_ENABLED = false; + + default boolean getAdServicesSystemServiceEnabled() { + return ADSERVICES_SYSTEM_SERVICE_ENABLED; + } +} diff --git a/adservices/service/java/com/android/server/adservices/FlagsFactory.java b/adservices/service/java/com/android/server/adservices/FlagsFactory.java new file mode 100644 index 0000000000..e6c4030aed --- /dev/null +++ b/adservices/service/java/com/android/server/adservices/FlagsFactory.java @@ -0,0 +1,30 @@ +/* + * 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.server.adservices; + +/** + * Factory class to create AdServices System Service Flags + * + * @hide + */ +public class FlagsFactory { + /** AdServices System Service Flags backed by PH. */ + public static Flags getFlags() { + // Use the Flags backed by PH. + return PhFlags.getInstance(); + } +} diff --git a/adservices/service/java/com/android/server/adservices/PhFlags.java b/adservices/service/java/com/android/server/adservices/PhFlags.java new file mode 100644 index 0000000000..89ed912ed0 --- /dev/null +++ b/adservices/service/java/com/android/server/adservices/PhFlags.java @@ -0,0 +1,50 @@ +/* + * 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.server.adservices; + +import android.annotation.NonNull; +import android.provider.DeviceConfig; + +/** + * Flags Implementation that delegates to DeviceConfig. + * + * @hide + */ +public final class PhFlags implements Flags { + + private static final PhFlags sSingleton = new PhFlags(); + + /** Returns the singleton instance of the PhFlags. */ + @NonNull + public static PhFlags getInstance() { + return sSingleton; + } + + /* + * Keys for ALL the flags stored in DeviceConfig. + */ + // Adservices System Service enable status keys. + static final String KEY_ADSERVICES_SYSTEM_SERVICE_ENABLED = "adservice_system_service_enabled"; + + @Override + public boolean getAdServicesSystemServiceEnabled() { + // The priority of applying the flag values: PH (DeviceConfig) and then hard-coded value. + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_ADSERVICES, + /* flagName */ KEY_ADSERVICES_SYSTEM_SERVICE_ENABLED, + /* defaultValue */ ADSERVICES_SYSTEM_SERVICE_ENABLED); + } +} diff --git a/adservices/tests/unittest/system-service/Android.bp b/adservices/tests/unittest/system-service/Android.bp index 8d69bff677..a3757ad410 100644 --- a/adservices/tests/unittest/system-service/Android.bp +++ b/adservices/tests/unittest/system-service/Android.bp @@ -22,6 +22,7 @@ android_test { "src/com/android/server/adservices/*.java", "src/**/*.java", ], + defaults: ["modules-utils-testable-device-config-defaults"], manifest: "AndroidManifest.xml", test_config: "AndroidTest.xml", static_libs: [ diff --git a/adservices/tests/unittest/system-service/src/com/android/server/adservices/AdServicesManagerServiceTest.java b/adservices/tests/unittest/system-service/src/com/android/server/adservices/AdServicesManagerServiceTest.java index aab7236ed4..9e168b124c 100644 --- a/adservices/tests/unittest/system-service/src/com/android/server/adservices/AdServicesManagerServiceTest.java +++ b/adservices/tests/unittest/system-service/src/com/android/server/adservices/AdServicesManagerServiceTest.java @@ -16,23 +16,39 @@ package com.android.server.adservices; +import static com.android.server.adservices.PhFlags.KEY_ADSERVICES_SYSTEM_SERVICE_ENABLED; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; + import android.Manifest; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.net.Uri; +import android.os.Handler; import android.os.UserHandle; +import android.provider.DeviceConfig; import androidx.test.platform.app.InstrumentationRegistry; -import com.google.common.truth.Truth; +import com.android.modules.utils.testing.TestableDeviceConfig; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; /** Tests for {@link AdServicesManagerService} */ public class AdServicesManagerServiceTest { + @Rule + public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule = + new TestableDeviceConfig.TestableDeviceConfigRule(); + private AdServicesManagerService mService; private Context mSpyContext; private static final String PACKAGE_NAME = "com.package.example"; @@ -45,18 +61,100 @@ public class AdServicesManagerServiceTest { @Before public void setup() { + MockitoAnnotations.initMocks(this); + Context context = InstrumentationRegistry.getInstrumentation().getContext(); mSpyContext = Mockito.spy(context); InstrumentationRegistry.getInstrumentation() .getUiAutomation() .adoptShellPermissionIdentity(Manifest.permission.INTERACT_ACROSS_USERS_FULL); + } + @Test + public void testAdServicesSystemService_enabled_then_disabled() { + // First enable the flag. + DeviceConfig.setProperty( + DeviceConfig.NAMESPACE_ADSERVICES, + KEY_ADSERVICES_SYSTEM_SERVICE_ENABLED, + Boolean.toString(Boolean.TRUE), + /* makeDefault */ false); + + // This will trigger the registration of the Receiver. mService = new AdServicesManagerService(mSpyContext); + + ArgumentCaptor<BroadcastReceiver> argumentReceiver = + ArgumentCaptor.forClass(BroadcastReceiver.class); + ArgumentCaptor<IntentFilter> argumentIntentFilter = + ArgumentCaptor.forClass(IntentFilter.class); + ArgumentCaptor<String> argumentPermission = ArgumentCaptor.forClass(String.class); + ArgumentCaptor<Handler> argumentHandler = ArgumentCaptor.forClass(Handler.class); + + // Calling the second time will not register again. + mService.registerPackagedChangedBroadcastReceivers(); + + // The flag is enabled so we call registerReceiverForAllUsers + Mockito.verify(mSpyContext, Mockito.times(1)) + .registerReceiverForAllUsers( + argumentReceiver.capture(), + argumentIntentFilter.capture(), + argumentPermission.capture(), + argumentHandler.capture()); + + BroadcastReceiver receiver = argumentReceiver.getValue(); + assertThat(receiver).isNotNull(); + + assertThat(argumentIntentFilter.getValue().getAction(0)) + .isEqualTo(Intent.ACTION_PACKAGE_FULLY_REMOVED); + assertThat(argumentIntentFilter.getValue().getAction(1)) + .isEqualTo(Intent.ACTION_PACKAGE_DATA_CLEARED); + assertThat(argumentIntentFilter.getValue().getAction(2)) + .isEqualTo(Intent.ACTION_PACKAGE_ADDED); + assertThat(argumentIntentFilter.getValue().getDataScheme(0)).isEqualTo("package"); + + assertThat(argumentPermission.getValue()).isNull(); + assertThat(argumentHandler.getValue()).isNotNull(); + + // Now disable the flag. + DeviceConfig.setProperty( + DeviceConfig.NAMESPACE_ADSERVICES, + KEY_ADSERVICES_SYSTEM_SERVICE_ENABLED, + Boolean.toString(Boolean.FALSE), + /* makeDefault */ false); + + // Calling when the flag is disabled will unregister the Receiver! + mService.registerPackagedChangedBroadcastReceivers(); + Mockito.verify(mSpyContext, Mockito.times(1)) + .unregisterReceiver(argumentReceiver.capture()); + + // The unregistered is called on the same receiver when registered above. + assertThat(argumentReceiver.getValue()).isSameInstanceAs(receiver); + } + + @Test + public void testAdServicesSystemService_disabled() { + // Disable the flag. + DeviceConfig.setProperty( + DeviceConfig.NAMESPACE_ADSERVICES, + KEY_ADSERVICES_SYSTEM_SERVICE_ENABLED, + Boolean.toString(Boolean.FALSE), + /* makeDefault */ false); + + mService = new AdServicesManagerService(mSpyContext); + + // The flag is disabled so there is no registerReceiverForAllUsers + Mockito.verify(mSpyContext, Mockito.times(0)) + .registerReceiverForAllUsers( + any(BroadcastReceiver.class), + any(IntentFilter.class), + any(String.class), + any(Handler.class)); } @Test public void testSendBroadcastForPackageFullyRemoved() { + mService = new AdServicesManagerService(mSpyContext); + Intent i = new Intent(Intent.ACTION_PACKAGE_FULLY_REMOVED); i.setData(Uri.parse("package:" + PACKAGE_NAME)); i.putExtra(Intent.EXTRA_UID, PACKAGE_UID); @@ -70,18 +168,19 @@ public class AdServicesManagerServiceTest { Mockito.verify(mSpyContext, Mockito.times(1)) .sendBroadcastAsUser(argumentIntent.capture(), argumentUser.capture()); - Truth.assertThat(argumentIntent.getValue().getAction()) - .isEqualTo(PACKAGE_CHANGED_BROADCAST); - Truth.assertThat(argumentIntent.getValue().getData()).isEqualTo(i.getData()); - Truth.assertThat(argumentIntent.getValue().getStringExtra("action")) + assertThat(argumentIntent.getValue().getAction()).isEqualTo(PACKAGE_CHANGED_BROADCAST); + assertThat(argumentIntent.getValue().getData()).isEqualTo(i.getData()); + assertThat(argumentIntent.getValue().getStringExtra("action")) .isEqualTo(PACKAGE_FULLY_REMOVED); - Truth.assertThat(argumentIntent.getValue().getIntExtra(Intent.EXTRA_UID, -1)) + assertThat(argumentIntent.getValue().getIntExtra(Intent.EXTRA_UID, -1)) .isEqualTo(PACKAGE_UID); - Truth.assertThat(argumentUser.getValue()).isEqualTo(mSpyContext.getUser()); + assertThat(argumentUser.getValue()).isEqualTo(mSpyContext.getUser()); } @Test public void testSendBroadcastForPackageAdded() { + mService = new AdServicesManagerService(mSpyContext); + Intent i = new Intent(Intent.ACTION_PACKAGE_ADDED); i.setData(Uri.parse("package:" + PACKAGE_NAME)); i.putExtra(Intent.EXTRA_UID, PACKAGE_UID); @@ -96,18 +195,18 @@ public class AdServicesManagerServiceTest { Mockito.verify(mSpyContext, Mockito.times(1)) .sendBroadcastAsUser(argumentIntent.capture(), argumentUser.capture()); - Truth.assertThat(argumentIntent.getValue().getAction()) - .isEqualTo(PACKAGE_CHANGED_BROADCAST); - Truth.assertThat(argumentIntent.getValue().getData()).isEqualTo(i.getData()); - Truth.assertThat(argumentIntent.getValue().getStringExtra("action")) - .isEqualTo(PACKAGE_ADDED); - Truth.assertThat(argumentIntent.getValue().getIntExtra(Intent.EXTRA_UID, -1)) + assertThat(argumentIntent.getValue().getAction()).isEqualTo(PACKAGE_CHANGED_BROADCAST); + assertThat(argumentIntent.getValue().getData()).isEqualTo(i.getData()); + assertThat(argumentIntent.getValue().getStringExtra("action")).isEqualTo(PACKAGE_ADDED); + assertThat(argumentIntent.getValue().getIntExtra(Intent.EXTRA_UID, -1)) .isEqualTo(PACKAGE_UID); - Truth.assertThat(argumentUser.getValue()).isEqualTo(mSpyContext.getUser()); + assertThat(argumentUser.getValue()).isEqualTo(mSpyContext.getUser()); } @Test public void testSendBroadcastForPackageDataCleared() { + mService = new AdServicesManagerService(mSpyContext); + Intent i = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED); i.setData(Uri.parse("package:" + PACKAGE_NAME)); i.putExtra(Intent.EXTRA_UID, PACKAGE_UID); @@ -121,13 +220,12 @@ public class AdServicesManagerServiceTest { Mockito.verify(mSpyContext, Mockito.times(1)) .sendBroadcastAsUser(argumentIntent.capture(), argumentUser.capture()); - Truth.assertThat(argumentIntent.getValue().getAction()) - .isEqualTo(PACKAGE_CHANGED_BROADCAST); - Truth.assertThat(argumentIntent.getValue().getData()).isEqualTo(i.getData()); - Truth.assertThat(argumentIntent.getValue().getStringExtra("action")) + assertThat(argumentIntent.getValue().getAction()).isEqualTo(PACKAGE_CHANGED_BROADCAST); + assertThat(argumentIntent.getValue().getData()).isEqualTo(i.getData()); + assertThat(argumentIntent.getValue().getStringExtra("action")) .isEqualTo(PACKAGE_DATA_CLEARED); - Truth.assertThat(argumentIntent.getValue().getIntExtra(Intent.EXTRA_UID, -1)) + assertThat(argumentIntent.getValue().getIntExtra(Intent.EXTRA_UID, -1)) .isEqualTo(PACKAGE_UID); - Truth.assertThat(argumentUser.getValue()).isEqualTo(mSpyContext.getUser()); + assertThat(argumentUser.getValue()).isEqualTo(mSpyContext.getUser()); } } diff --git a/adservices/tests/unittest/system-service/src/com/android/server/adservices/PhFlagsTest.java b/adservices/tests/unittest/system-service/src/com/android/server/adservices/PhFlagsTest.java new file mode 100644 index 0000000000..dfd8b80b0a --- /dev/null +++ b/adservices/tests/unittest/system-service/src/com/android/server/adservices/PhFlagsTest.java @@ -0,0 +1,55 @@ +/* + * 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.server.adservices; + +import static com.android.server.adservices.Flags.ADSERVICES_SYSTEM_SERVICE_ENABLED; +import static com.android.server.adservices.PhFlags.KEY_ADSERVICES_SYSTEM_SERVICE_ENABLED; + +import android.provider.DeviceConfig; + +import com.android.modules.utils.testing.TestableDeviceConfig; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Rule; +import org.junit.Test; + +public class PhFlagsTest { + + @Rule + public final TestableDeviceConfig.TestableDeviceConfigRule mDeviceConfigRule = + new TestableDeviceConfig.TestableDeviceConfigRule(); + + @Test + public void testAdServicesSystemServiceEnabled() { + // Without any overriding, the value is the hard coded constant. + assertThat(FlagsFactory.getFlags().getAdServicesSystemServiceEnabled()) + .isEqualTo(ADSERVICES_SYSTEM_SERVICE_ENABLED); + + // Now overriding with the value from PH. + final boolean phOverridingValue = true; + + DeviceConfig.setProperty( + DeviceConfig.NAMESPACE_ADSERVICES, + KEY_ADSERVICES_SYSTEM_SERVICE_ENABLED, + Boolean.toString(phOverridingValue), + /* makeDefault */ false); + + Flags phFlags = FlagsFactory.getFlags(); + assertThat(phFlags.getAdServicesSystemServiceEnabled()).isEqualTo(phOverridingValue); + } +} |