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 com.android.adservices.service.common;
18 
19 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__BACK_COMPAT_INIT_CANCEL_JOB_FAILURE;
20 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__BACK_COMPAT_INIT_UPDATE_ACTIVITY_FAILURE;
21 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__BACK_COMPAT_INIT_UPDATE_SERVICE_FAILURE;
22 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__BACK_COMPAT_INIT_ENABLE_RECEIVER_FAILURE;
23 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__BACK_COMPAT_INIT_DISABLE_RECEIVER_FAILURE;
24 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__JOB_SCHEDULER_IS_UNAVAILABLE;
25 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON;
26 
27 import android.app.job.JobInfo;
28 import android.app.job.JobScheduler;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.content.pm.PackageManager;
32 import android.os.Build;
33 
34 import com.android.adservices.AdServicesCommon;
35 import com.android.adservices.LogUtil;
36 import com.android.adservices.errorlogging.ErrorLogUtil;
37 import com.android.adservices.service.Flags;
38 import com.android.adservices.service.FlagsFactory;
39 import com.android.adservices.service.common.compat.PackageManagerCompatUtils;
40 import com.android.adservices.shared.common.ApplicationContextSingleton;
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.modules.utils.build.SdkLevel;
43 
44 import java.util.List;
45 import java.util.Objects;
46 import java.util.stream.Collectors;
47 
48 /** Handles the Back Compat initialization for AdExtServices APK. */
49 public final class AdServicesBackCompatInit {
50     private final Context mContext;
51     private final Flags mFlags;
52 
53     @VisibleForTesting
AdServicesBackCompatInit(Context context)54     AdServicesBackCompatInit(Context context) {
55         this.mContext = Objects.requireNonNull(context);
56         this.mFlags = FlagsFactory.getFlags();
57     }
58 
59     /** Gets an instance of {@link AdServicesBackCompatInit}. */
getInstance()60     public static AdServicesBackCompatInit getInstance() {
61         return new AdServicesBackCompatInit(ApplicationContextSingleton.get());
62     }
63 
64     /**
65      * Initialize back compat components in ExtServices package. Skips execution if executed within
66      * the AdServices package.
67      */
initializeComponents()68     public void initializeComponents() {
69         String packageName = mContext.getPackageName();
70         if (isNullOrAdServicesPackageName(packageName)) {
71             // Package name is null or running within the AdServices package, so don't do anything.
72             LogUtil.d("Running within package %s, not changing component state", packageName);
73             return;
74         }
75 
76         // On T+ devices, always disable the AdExtServices activities and services.
77         if (SdkLevel.isAtLeastT()) {
78             // If this is not an S- device, disable the activities, services, unregister the
79             // broadcast receivers, and unschedule any background jobs.
80             unregisterPackageChangedBroadcastReceivers();
81             updateAdExtServicesActivities(/* shouldEnable= */ false);
82             updateAdExtServicesServices(/* shouldEnable= */ false);
83             disableScheduledBackgroundJobs();
84             return;
85         }
86 
87         // If this is an S- device but the flags are disabled, do nothing.
88         if (mFlags.getEnableBackCompat()
89                 && mFlags.getAdServicesEnabled()
90                 && !mFlags.getGlobalKillSwitch()) {
91             registerPackagedChangedBroadcastReceivers();
92             updateAdExtServicesActivities(/* shouldEnable= */ true);
93             updateAdExtServicesServices(/* shouldEnable= */ true);
94             return;
95         }
96 
97         LogUtil.d("Exiting AdServicesBackCompatInit because flags are disabled");
98     }
99 
100     /**
101      * Cancels all scheduled jobs if running within the ExtServices APK. Needed because we could
102      * have some persistent jobs that were scheduled on S before an OTA to T.
103      */
disableScheduledBackgroundJobs()104     private void disableScheduledBackgroundJobs() {
105         try {
106             JobScheduler scheduler = mContext.getSystemService(JobScheduler.class);
107             if (scheduler == null) {
108                 LogUtil.e("Could not retrieve JobScheduler instance, so not cancelling jobs");
109                 ErrorLogUtil.e(
110                         AD_SERVICES_ERROR_REPORTED__ERROR_CODE__JOB_SCHEDULER_IS_UNAVAILABLE,
111                         AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON);
112                 return;
113             }
114             for (JobInfo jobInfo : scheduler.getAllPendingJobs()) {
115                 String jobClassName = jobInfo.getService().getClassName();
116                 // Cancel jobs from AdServices only
117                 if (jobClassName.startsWith(AdServicesCommon.ADSERVICES_CLASS_PATH_PREFIX)) {
118                     int jobId = jobInfo.getId();
119                     LogUtil.d("Deleting ext AdServices job %d %s", jobId, jobClassName);
120                     scheduler.cancel(jobId);
121                 }
122             }
123             LogUtil.d(
124                     "All AdServices scheduled jobs cancelled on package %s",
125                     mContext.getPackageName());
126         } catch (Exception e) {
127             LogUtil.e(e, "Error when cancelling scheduled jobs");
128             ErrorLogUtil.e(
129                     e,
130                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__BACK_COMPAT_INIT_CANCEL_JOB_FAILURE,
131                     AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON);
132         }
133     }
134 
135     /**
136      * Registers a receiver for any broadcasts regarding changes to any packages for all users on
137      * the device at boot up. After receiving the broadcast, send an explicit broadcast to the
138      * AdServices module as that user.
139      */
140     @SuppressWarnings("NewApi")
registerPackagedChangedBroadcastReceivers()141     private void registerPackagedChangedBroadcastReceivers() {
142         boolean result = PackageChangedReceiver.enableReceiver(mContext, mFlags);
143         LogUtil.d(
144                 "Package Change Receiver registration: Success=%s, Package=%s",
145                 result, mContext.getPackageName());
146         if (!result) {
147             ErrorLogUtil.e(
148                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__BACK_COMPAT_INIT_ENABLE_RECEIVER_FAILURE,
149                     AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON);
150         }
151     }
152 
153     @SuppressWarnings("NewApi")
unregisterPackageChangedBroadcastReceivers()154     private void unregisterPackageChangedBroadcastReceivers() {
155         boolean result = PackageChangedReceiver.disableReceiver(mContext, mFlags);
156         LogUtil.d(
157                 "Package Change Receiver unregistration: Success=%s, Package=%s",
158                 result, mContext.getPackageName());
159         if (!result) {
160             ErrorLogUtil.e(
161                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__BACK_COMPAT_INIT_DISABLE_RECEIVER_FAILURE,
162                     AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON);
163         }
164     }
165 
166     /**
167      * Activities for user consent and control are disabled by default. Only on S- devices, after
168      * the flag is enabled, we enable the activities.
169      */
updateAdExtServicesActivities(boolean shouldEnable)170     private void updateAdExtServicesActivities(boolean shouldEnable) {
171         try {
172             updateComponents(PackageManagerCompatUtils.CONSENT_ACTIVITIES_CLASSES, shouldEnable);
173             LogUtil.d("Updated state of AdExtServices activities: [enabled=%s]", shouldEnable);
174         } catch (IllegalArgumentException e) {
175             LogUtil.e("Error when updating activities: %s", e.getMessage());
176             ErrorLogUtil.e(
177                     e,
178                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__BACK_COMPAT_INIT_UPDATE_ACTIVITY_FAILURE,
179                     AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON);
180         }
181     }
182 
183     /**
184      * Disables services that have intent filters specified in the AdExtServicesManifest to prevent
185      * duplicates on T+ devices, Conversely, it enables these services on devices running versions S
186      * and below.
187      */
updateAdExtServicesServices(boolean shouldEnable)188     private void updateAdExtServicesServices(boolean shouldEnable) {
189         try {
190             int currentSdkInt = getSdkLevelInt();
191             List<String> servicesToUpdate =
192                     PackageManagerCompatUtils.SERVICE_CLASSES_AND_MIN_SDK_SUPPORT_PAIRS.stream()
193                             .filter(p -> p.second <= currentSdkInt)
194                             .map(p -> p.first)
195                             .collect(Collectors.toList());
196             updateComponents(servicesToUpdate, shouldEnable);
197             LogUtil.d("Updated state of AdExtServices services: [enable=%s]", shouldEnable);
198         } catch (IllegalArgumentException e) {
199             LogUtil.e("Error when updating services: %s", e.getMessage());
200             ErrorLogUtil.e(
201                     e,
202                     AD_SERVICES_ERROR_REPORTED__ERROR_CODE__BACK_COMPAT_INIT_UPDATE_SERVICE_FAILURE,
203                     AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__COMMON);
204         }
205     }
206 
207     @VisibleForTesting
updateComponents(List<String> components, boolean shouldEnable)208     void updateComponents(List<String> components, boolean shouldEnable) {
209         PackageManager packageManager = mContext.getPackageManager();
210         String packageName = mContext.getPackageName();
211         for (String component : components) {
212             packageManager.setComponentEnabledSetting(
213                     new ComponentName(packageName, component),
214                     shouldEnable
215                             ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
216                             : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
217                     PackageManager.DONT_KILL_APP);
218         }
219     }
220 
isNullOrAdServicesPackageName(String packageName)221     private boolean isNullOrAdServicesPackageName(String packageName) {
222         return packageName == null
223                 || packageName.endsWith(AdServicesCommon.ADSERVICES_APK_PACKAGE_NAME_SUFFIX);
224     }
225 
226     @VisibleForTesting
getSdkLevelInt()227     static int getSdkLevelInt() {
228         return Build.VERSION.SDK_INT;
229     }
230 }
231