Snap for 11219529 from a8b7fc7f57ea68153a9fc8b5e0c9cfa6c8e38820 to mainline-tzdata4-release

Change-Id: I5c12599d741306a562d6008482a8bb24ad955faa
diff --git a/Android.bp b/Android.bp
index 668be85..2f2f9d9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -34,14 +34,17 @@
     srcs: [
         "java/src/**/*.java",
     ],
+
+    // Need to use empty manifest to avoid getting INSTALL_FAILED_DUPLICATE_PERMISSION when running
+    // unit tests, because the manifest is included in this library which is statically included in
+    // the test module. The actual manifest is included in the android_app target below.
+    manifest: "EmptyManifest.xml",
+
     sdk_version: "module_current",
     min_sdk_version: "30",
     resource_dirs: [
         "java/res",
     ],
-
-    manifest: "AndroidManifest.xml",
-
     static_libs: [
         "androidx.annotation_annotation",
         "androidx.autofill_autofill",
@@ -63,6 +66,7 @@
     name: "ExtServices",
     sdk_version: "module_current",
     min_sdk_version: "30",
+    manifest: "AndroidManifest.xml",
     optimize: {
         optimize: true,
         proguard_compatibility: false,
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 5426117..354ea51 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -36,7 +36,11 @@
     <!-- Remove unused permissions merged from WorkManager library -->
     <uses-permission android:name="android.permission.WAKE_LOCK" tools:node="remove" />
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" tools:node="remove" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
 
+    <permission android:name="android.permission.INIT_EXT_SERVICES"
+        android:protectionLevel="signature"/>
+    <uses-permission android:name="android.permission.INIT_EXT_SERVICES" />
     <application
         android:name=".ExtServicesApplication"
         android:label="@string/app_name"
@@ -157,6 +161,13 @@
             android:authorities="${applicationId}.androidx-startup"
             tools:node="remove" />
 
+        <!-- Boot completed receiver sends privileged startup broadcast. -->
+        <receiver android:name=".common.BootCompletedReceiver"
+                  android:enabled="@bool/enableBootCompletedReceiver"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED"/>
+            </intent-filter>
+        </receiver>
     </application>
-
 </manifest>
diff --git a/EmptyManifest.xml b/EmptyManifest.xml
new file mode 100644
index 0000000..4c6b790
--- /dev/null
+++ b/EmptyManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.ext.services">
+</manifest>
diff --git a/java/res/values-v31/bools.xml b/java/res/values-v31/bools.xml
new file mode 100644
index 0000000..f39c88f
--- /dev/null
+++ b/java/res/values-v31/bools.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <bool name="enableBootCompletedReceiver">true</bool>
+</resources>
diff --git a/java/res/values/bools.xml b/java/res/values/bools.xml
new file mode 100644
index 0000000..d21ed12
--- /dev/null
+++ b/java/res/values/bools.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <bool name="enableBootCompletedReceiver">false</bool>
+</resources>
diff --git a/java/src/android/ext/services/common/BootCompletedReceiver.java b/java/src/android/ext/services/common/BootCompletedReceiver.java
new file mode 100644
index 0000000..af28dd7
--- /dev/null
+++ b/java/src/android/ext/services/common/BootCompletedReceiver.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2023 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 android.ext.services.common;
+
+import android.annotation.SuppressLint;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
+import android.provider.DeviceConfig;
+import android.util.Log;
+
+import androidx.annotation.ChecksSdkIntAtLeast;
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Handles the BootCompleted initialization for AdExtServices APK on S-.
+ * The BootCompleted receiver re-broadcasts a different intent that is handled by the
+ * AdExtBootCompletedReceiver within the AdServices apk. The reason for doing this here instead of
+ * within the AdServices APK is due to problematic platform modifications (b/286070595).
+ */
+public class BootCompletedReceiver extends BroadcastReceiver {
+    private static final String TAG = "extservices";
+    private static final String KEY_PRIVACY_EXCLUDE_LIST = "privacy_exclude_list";
+    private static final String KEY_EXTSERVICES_BOOT_COMPLETE_RECEIVER =
+            "extservices_bootcomplete_enabled";
+
+    private static final String ADEXTBOOTCOMPLETEDRECEIVER_CLASS_NAME =
+            "com.android.adservices.service.common.AdExtBootCompletedReceiver";
+    private static final String REBROADCAST_INTENT_ACTION =
+            "android.adservices.action.INIT_EXT_SERVICES";
+    private static final String ADSERVICES_SETTINGS_MAINACTIVITY =
+            "com.android.adservices.ui.settings.activities.AdServicesSettingsMainActivity";
+
+    @SuppressLint("MissingPermission")
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.i(TAG, "BootCompletedReceiver received BOOT_COMPLETED broadcast (f): "
+                + Build.FINGERPRINT);
+
+        // Check if the feature is enabled, otherwise exit without doing anything.
+        if (!isReceiverEnabled()) {
+            Log.d(TAG, "BootCompletedReceiver not enabled in config, exiting");
+            return;
+        }
+
+        String adServicesPackageName = getAdExtServicesPackageName(context);
+        if (adServicesPackageName == null) {
+            Log.d(TAG, "AdServices package was not present, exiting BootCompletedReceiver");
+            return;
+        }
+
+        // No need to run this on every boot if we're on T+ and the AdExtServices components have
+        // already been disabled.
+        if (shouldDisableReceiver(context, adServicesPackageName)) {
+            context.getPackageManager().setComponentEnabledSetting(
+                    new ComponentName(context.getPackageName(), this.getClass().getName()),
+                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                    0);
+            Log.d(TAG, "Disabled BootCompletedReceiver as AdServices is already initialized.");
+            return;
+        }
+
+        // Check if this device is among a list of excluded devices
+        String excludeList = getExcludedFingerprints();
+        Log.d(TAG, "Read BOOT_COMPLETED broadcast exclude list: " + excludeList);
+        if (Arrays.stream(excludeList.split(","))
+                .map(String::trim)
+                .filter(s -> !s.isEmpty())
+                .anyMatch(Build.FINGERPRINT::startsWith)) {
+            Log.d(TAG, "Device is present in the exclude list, exiting BootCompletedReceiver");
+            return;
+        }
+
+        // Re-broadcast the intent
+        Intent intentToSend = new Intent(REBROADCAST_INTENT_ACTION);
+        intentToSend.setComponent(
+                new ComponentName(adServicesPackageName, ADEXTBOOTCOMPLETEDRECEIVER_CLASS_NAME));
+        intentToSend.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        context.sendBroadcast(intentToSend);
+        Log.i(TAG, "BootCompletedReceiver sending init broadcast: " + intentToSend);
+    }
+
+    @SuppressLint("MissingPermission")
+    @VisibleForTesting
+    public boolean isReceiverEnabled() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_ADSERVICES,
+                /* flagName */ KEY_EXTSERVICES_BOOT_COMPLETE_RECEIVER,
+                /* defaultValue */ false);
+    }
+
+    @SuppressLint("MissingPermission")
+    @VisibleForTesting
+    public String getExcludedFingerprints() {
+        return DeviceConfig.getString(
+                DeviceConfig.NAMESPACE_ADSERVICES,
+                /* flagName */ KEY_PRIVACY_EXCLUDE_LIST,
+                /* defaultValue */ "");
+    }
+
+    @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
+    @VisibleForTesting
+    public boolean isAtLeastT() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
+    }
+
+    private boolean shouldDisableReceiver(@NonNull Context context,
+            @NonNull String adServicesPackageName) {
+        Objects.requireNonNull(context);
+        Objects.requireNonNull(adServicesPackageName);
+        return isAtLeastT() && !isExtServicesInitialized(context, adServicesPackageName);
+    }
+
+    private boolean isExtServicesInitialized(Context context, String adServicesPackageName) {
+        Intent intent = new Intent();
+        intent.setComponent(
+                new ComponentName(adServicesPackageName, ADSERVICES_SETTINGS_MAINACTIVITY));
+        List<ResolveInfo> list = context.getPackageManager().queryIntentActivities(intent,
+                PackageManager.MATCH_DEFAULT_ONLY);
+        Log.d(TAG, "Components matching AdServicesSettingsMainActivity: " + list);
+        return list != null && !list.isEmpty();
+    }
+
+    private String getAdExtServicesPackageName(@NonNull Context context) {
+        Objects.requireNonNull(context);
+
+        List<PackageInfo> installedPackages =
+                context.getPackageManager().getInstalledPackages(PackageManager.MATCH_SYSTEM_ONLY);
+
+        return installedPackages.stream()
+                .filter(s -> s.packageName.endsWith("android.ext.adservices.api"))
+                .map(s -> s.packageName)
+                .findFirst()
+                .orElse(null);
+    }
+}
diff --git a/java/tests/src/android/ext/services/common/BootCompletedReceiverTest.java b/java/tests/src/android/ext/services/common/BootCompletedReceiverTest.java
new file mode 100644
index 0000000..8a58a9f
--- /dev/null
+++ b/java/tests/src/android/ext/services/common/BootCompletedReceiverTest.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2023 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 android.ext.services.common;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.Spy;
+import org.mockito.quality.Strictness;
+
+import java.util.List;
+
+public class BootCompletedReceiverTest {
+    private static final String ADSERVICES_EXT_PACKAGE_NAME = "com.android.ext.adservices.api";
+
+    private MockitoSession mMockitoSession;
+
+    @Spy
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+
+    @Spy
+    private BootCompletedReceiver mReceiver;
+
+    @Mock
+    private PackageManager mPackageManager;
+
+    @Before
+    public void setup() {
+        mMockitoSession = ExtendedMockito.mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.WARN)
+                .startMocking();
+
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockitoSession != null) {
+            mMockitoSession.finishMocking();
+        }
+    }
+
+    @Test
+    public void testReceiverSkipsBroadcastIfDisabled() {
+        mockReceiverEnabled(false);
+
+        mReceiver.onReceive(mContext, null);
+
+        verify(mContext, never()).getPackageManager();
+        verify(mContext, never()).sendBroadcast(any());
+    }
+
+    @Test
+    public void testReceiverSkipsBroadcastIfNoPackages() {
+        mockReceiverEnabled(true);
+        doReturn(List.of()).when(mPackageManager).getInstalledPackages(anyInt());
+
+        mReceiver.onReceive(mContext, null);
+
+        verify(mContext, never()).sendBroadcast(any());
+        verify(mPackageManager, never()).setComponentEnabledSetting(any(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void testReceiverSkipsBroadcastIfNoPackagesMatchingAdServices() {
+        mockReceiverEnabled(true);
+
+        PackageInfo one = new PackageInfo();
+        one.packageName = "one";
+        PackageInfo two = new PackageInfo();
+        two.packageName = "external.adservices.invalid.api";
+        doReturn(List.of(one, two)).when(mPackageManager).getInstalledPackages(anyInt());
+
+        mReceiver.onReceive(mContext, null);
+
+        verify(mContext, never()).sendBroadcast(any());
+        verify(mPackageManager, never()).setComponentEnabledSetting(any(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void testReceiverResendsBroadcast() {
+        mockReceiverEnabled(true);
+        mockAdServicesPackageName();
+        doReturn(false).when(mReceiver).isAtLeastT();
+        mockExcludedDevices("");
+
+        mReceiver.onReceive(mContext, null);
+
+        verifyBroadcastSent();
+    }
+
+    @Test
+    public void testReceiverDisablesItselfOnTPlusIfAdServicesDisabled() {
+        mockReceiverEnabled(true);
+        mockAdServicesPackageName();
+        doReturn(true).when(mReceiver).isAtLeastT();
+        doReturn(List.of()).when(mPackageManager).queryIntentActivities(any(), anyInt());
+
+        mReceiver.onReceive(mContext, null);
+
+        verify(mPackageManager).setComponentEnabledSetting(any(),
+                eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED), eq(0));
+        verify(mContext, never()).sendBroadcast(any());
+    }
+
+    @Test
+    public void testReceiverDoesNotDisableItselfOnTPlusIfAdServicesEnabled() {
+        mockReceiverEnabled(true);
+        mockAdServicesPackageName();
+        doReturn(true).when(mReceiver).isAtLeastT();
+        mockExcludedDevices("");
+
+        ResolveInfo info = new ResolveInfo();
+        info.activityInfo = new ActivityInfo();
+        info.activityInfo.packageName = "test";
+        info.activityInfo.name = "test2";
+        doReturn(List.of(info)).when(mPackageManager).queryIntentActivities(any(), anyInt());
+
+        mReceiver.onReceive(mContext, null);
+
+        verify(mPackageManager, never()).setComponentEnabledSetting(any(), anyInt(), anyInt());
+        verifyBroadcastSent();
+    }
+
+    @Test
+    public void testReceiverShouldNotDisableItselfOnSMinus() {
+        mockReceiverEnabled(true);
+        mockAdServicesPackageName();
+        doReturn(false).when(mReceiver).isAtLeastT();
+        mockExcludedDevices("");
+
+        mReceiver.onReceive(mContext, null);
+
+        verify(mPackageManager, never()).queryIntentActivities(any(), anyInt());
+        verify(mPackageManager, never()).setComponentEnabledSetting(any(), anyInt(), anyInt());
+        verifyBroadcastSent();
+    }
+
+    @Test
+    public void testReceiverSkipsBroadcastIfFingerprintExcludedExactly() {
+        mockReceiverEnabled(true);
+        mockAdServicesPackageName();
+        doReturn(false).when(mReceiver).isAtLeastT();
+        mockExcludedDevices(Build.FINGERPRINT);
+
+        mReceiver.onReceive(mContext, null);
+
+        verify(mContext, never()).sendBroadcast(any());
+    }
+
+    @Test
+    public void testReceiverSkipsBroadcastIfFingerprintExcludedPrefix() {
+        mockReceiverEnabled(true);
+        mockAdServicesPackageName();
+        doReturn(false).when(mReceiver).isAtLeastT();
+
+        String currentBuild = Build.FINGERPRINT;
+        if (currentBuild.length() > 1) {
+            currentBuild = currentBuild.substring(0, currentBuild.length() - 2);
+        }
+        mockExcludedDevices(currentBuild);
+
+        mReceiver.onReceive(mContext, null);
+
+        verify(mContext, never()).sendBroadcast(any());
+    }
+
+    @Test
+    public void testReceiverSkipsBroadcastIfFingerprintExcludedTrim() {
+        mockReceiverEnabled(true);
+        mockAdServicesPackageName();
+        doReturn(false).when(mReceiver).isAtLeastT();
+        mockExcludedDevices("  " + Build.FINGERPRINT + "  ");
+
+        mReceiver.onReceive(mContext, null);
+
+        verify(mContext, never()).sendBroadcast(any());
+    }
+
+    @Test
+    public void testReceiverSkipsBroadcastIfFingerprintExcludedInList() {
+        mockReceiverEnabled(true);
+        mockAdServicesPackageName();
+        doReturn(false).when(mReceiver).isAtLeastT();
+        mockExcludedDevices("one, " + Build.FINGERPRINT + ", two");
+
+        mReceiver.onReceive(mContext, null);
+
+        verify(mContext, never()).sendBroadcast(any());
+    }
+
+    private void mockAdServicesPackageName() {
+        PackageInfo pkg = new PackageInfo();
+        pkg.packageName = ADSERVICES_EXT_PACKAGE_NAME;
+        doReturn(List.of(pkg)).when(mPackageManager).getInstalledPackages(anyInt());
+    }
+
+    private void mockReceiverEnabled(boolean value) {
+        doReturn(value).when(mReceiver).isReceiverEnabled();
+    }
+
+    private void mockExcludedDevices(String value) {
+        doReturn(value).when(mReceiver).getExcludedFingerprints();
+    }
+
+    private void verifyBroadcastSent() {
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendBroadcast(captor.capture());
+        verify(mPackageManager, never()).setComponentEnabledSetting(any(), anyInt(), anyInt());
+
+        ComponentName componentName = captor.getValue().getComponent();
+        assertThat(componentName.getPackageName()).isEqualTo(ADSERVICES_EXT_PACKAGE_NAME);
+        assertThat(componentName.getShortClassName()).isEqualTo(
+                "com.android.adservices.service.common.AdExtBootCompletedReceiver");
+    }
+}