• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.ext.services.common;
18 
19 import android.annotation.SuppressLint;
20 import android.content.BroadcastReceiver;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.PackageInfo;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ResolveInfo;
27 import android.os.Build;
28 import android.provider.DeviceConfig;
29 import android.util.Log;
30 
31 import androidx.annotation.ChecksSdkIntAtLeast;
32 import androidx.annotation.NonNull;
33 import androidx.annotation.VisibleForTesting;
34 
35 import java.util.Arrays;
36 import java.util.List;
37 import java.util.Objects;
38 
39 /**
40  * Handles the BootCompleted initialization for AdExtServices APK on S-.
41  * The BootCompleted receiver re-broadcasts a different intent that is handled by the
42  * AdExtBootCompletedReceiver within the AdServices apk. The reason for doing this here instead of
43  * within the AdServices APK is due to problematic platform modifications (b/286070595).
44  */
45 public class BootCompletedReceiver extends BroadcastReceiver {
46     private static final String TAG = "extservices";
47     private static final String KEY_PRIVACY_EXCLUDE_LIST = "privacy_exclude_list";
48     private static final String KEY_EXTSERVICES_BOOT_COMPLETE_RECEIVER =
49             "extservices_bootcomplete_enabled";
50 
51     private static final String ADEXTBOOTCOMPLETEDRECEIVER_CLASS_NAME =
52             "com.android.adservices.service.common.AdExtBootCompletedReceiver";
53     private static final String REBROADCAST_INTENT_ACTION =
54             "android.adservices.action.INIT_EXT_SERVICES";
55     private static final String ADSERVICES_SETTINGS_MAINACTIVITY =
56             "com.android.adservices.ui.settings.activities.AdServicesSettingsMainActivity";
57 
58     @SuppressLint("MissingPermission")
59     @Override
onReceive(Context context, Intent intent)60     public void onReceive(Context context, Intent intent) {
61         Log.i(TAG, "BootCompletedReceiver received BOOT_COMPLETED broadcast (f): "
62                 + Build.FINGERPRINT);
63 
64         // Check if the feature is enabled, otherwise exit without doing anything.
65         if (!isReceiverEnabled()) {
66             Log.d(TAG, "BootCompletedReceiver not enabled in config, exiting");
67             return;
68         }
69 
70         String adServicesPackageName = getAdExtServicesPackageName(context);
71         if (adServicesPackageName == null) {
72             Log.d(TAG, "AdServices package was not present, exiting BootCompletedReceiver");
73             return;
74         }
75 
76         // No need to run this on every boot if we're on T+ and the AdExtServices components have
77         // already been disabled.
78         if (shouldDisableReceiver(context, adServicesPackageName)) {
79             context.getPackageManager().setComponentEnabledSetting(
80                     new ComponentName(context.getPackageName(), this.getClass().getName()),
81                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
82                     0);
83             Log.d(TAG, "Disabled BootCompletedReceiver as AdServices is already initialized.");
84             return;
85         }
86 
87         // Check if this device is among a list of excluded devices
88         String excludeList = getExcludedFingerprints();
89         Log.d(TAG, "Read BOOT_COMPLETED broadcast exclude list: " + excludeList);
90         if (Arrays.stream(excludeList.split(","))
91                 .map(String::trim)
92                 .filter(s -> !s.isEmpty())
93                 .anyMatch(Build.FINGERPRINT::startsWith)) {
94             Log.d(TAG, "Device is present in the exclude list, exiting BootCompletedReceiver");
95             return;
96         }
97 
98         // Re-broadcast the intent
99         Intent intentToSend = new Intent(REBROADCAST_INTENT_ACTION);
100         intentToSend.setComponent(
101                 new ComponentName(adServicesPackageName, ADEXTBOOTCOMPLETEDRECEIVER_CLASS_NAME));
102         intentToSend.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
103         context.sendBroadcast(intentToSend);
104         Log.i(TAG, "BootCompletedReceiver sending init broadcast: " + intentToSend);
105     }
106 
107     @SuppressLint("MissingPermission")
108     @VisibleForTesting
isReceiverEnabled()109     public boolean isReceiverEnabled() {
110         return DeviceConfig.getBoolean(
111                 DeviceConfig.NAMESPACE_ADSERVICES,
112                 /* flagName */ KEY_EXTSERVICES_BOOT_COMPLETE_RECEIVER,
113                 /* defaultValue */ false);
114     }
115 
116     @SuppressLint("MissingPermission")
117     @VisibleForTesting
getExcludedFingerprints()118     public String getExcludedFingerprints() {
119         return DeviceConfig.getString(
120                 DeviceConfig.NAMESPACE_ADSERVICES,
121                 /* flagName */ KEY_PRIVACY_EXCLUDE_LIST,
122                 /* defaultValue */ "");
123     }
124 
125     @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
126     @VisibleForTesting
isAtLeastT()127     public boolean isAtLeastT() {
128         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
129     }
130 
shouldDisableReceiver(@onNull Context context, @NonNull String adServicesPackageName)131     private boolean shouldDisableReceiver(@NonNull Context context,
132             @NonNull String adServicesPackageName) {
133         Objects.requireNonNull(context);
134         Objects.requireNonNull(adServicesPackageName);
135         return isAtLeastT() && !isExtServicesInitialized(context, adServicesPackageName);
136     }
137 
isExtServicesInitialized(Context context, String adServicesPackageName)138     private boolean isExtServicesInitialized(Context context, String adServicesPackageName) {
139         Intent intent = new Intent();
140         intent.setComponent(
141                 new ComponentName(adServicesPackageName, ADSERVICES_SETTINGS_MAINACTIVITY));
142         List<ResolveInfo> list = context.getPackageManager().queryIntentActivities(intent,
143                 PackageManager.MATCH_DEFAULT_ONLY);
144         Log.d(TAG, "Components matching AdServicesSettingsMainActivity: " + list);
145         return list != null && !list.isEmpty();
146     }
147 
getAdExtServicesPackageName(@onNull Context context)148     private String getAdExtServicesPackageName(@NonNull Context context) {
149         Objects.requireNonNull(context);
150 
151         List<PackageInfo> installedPackages =
152                 context.getPackageManager().getInstalledPackages(PackageManager.MATCH_SYSTEM_ONLY);
153 
154         return installedPackages.stream()
155                 .filter(s -> s.packageName.endsWith("android.ext.adservices.api"))
156                 .map(s -> s.packageName)
157                 .findFirst()
158                 .orElse(null);
159     }
160 }
161