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.common;
18 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.pm.PackageManager;
23 import android.content.pm.ResolveInfo;
24 import android.content.pm.ServiceInfo;
25 import android.os.Build;
26 import android.os.SystemProperties;
27 import android.util.Log;
28 
29 import com.android.compatibility.common.util.ShellUtils;
30 import com.android.modules.utils.build.SdkLevel;
31 
32 import java.util.List;
33 
34 /** Class to place Adservices CTS related helper method. */
35 public final class AdservicesTestHelper {
36     // Used to get the package name. Copied over from com.android.adservices.AdServicesCommon
37     private static final String MEASUREMENT_SERVICE_NAME = "android.adservices.MEASUREMENT_SERVICE";
38     private static final String DEFAULT_LOG_TAG = "adservices";
39     private static final String FORCE_KILL_PROCESS_COMMAND = "am force-stop";
40     // Used to differentiate between AdServices APK package name and AdExtServices APK package name.
41     private static final String ADSERVICES_APK_PACKAGE_NAME_SUFFIX = "android.adservices.api";
42 
43     /**
44      * Used to get the package name. Copied over from com.android.adservices.AndroidServiceBinder
45      *
46      * @param context the context
47      * @param logTag the tag used for logging
48      * @return Adservices package name
49      * @deprecated use {@link AdServicesSupportHelper#getAdServicesPackageName()} instead.
50      */
51     @Deprecated
getAdServicesPackageName( @onNull Context context, @NonNull String logTag)52     public static String getAdServicesPackageName(
53             @NonNull Context context, @NonNull String logTag) {
54         final Intent intent = new Intent(MEASUREMENT_SERVICE_NAME);
55         final List<ResolveInfo> resolveInfos =
56                 context.getPackageManager()
57                         .queryIntentServices(intent, PackageManager.MATCH_SYSTEM_ONLY);
58         final ServiceInfo serviceInfo =
59                 resolveAdServicesService(resolveInfos, MEASUREMENT_SERVICE_NAME, logTag);
60         if (serviceInfo == null) {
61             Log.e(logTag, "Failed to find serviceInfo for adServices service");
62             return null;
63         }
64 
65         return serviceInfo.packageName;
66     }
67 
68     /**
69      * Used to get the package name. An overloading method of {@code
70      * getAdservicesPackageName(context, logTag)} by using {@code DEFAULT_LOG_TAG}.
71      *
72      * @param context the context
73      * @return Adservices package name
74      * @deprecated use {@link AdServicesSupportHelper#getAdServicesPackageName()} instead.
75      */
76     @Deprecated
getAdServicesPackageName(@onNull Context context)77     public static String getAdServicesPackageName(@NonNull Context context) {
78         return getAdServicesPackageName(context, DEFAULT_LOG_TAG);
79     }
80 
81     /**
82      * Kill the Adservices process.
83      *
84      * @param context the context used to get Adservices package name.
85      * @param logTag the tag used for logging
86      */
killAdservicesProcess(@onNull Context context, @NonNull String logTag)87     public static void killAdservicesProcess(@NonNull Context context, @NonNull String logTag) {
88         ShellUtils.runShellCommand(
89                 "%s %s", FORCE_KILL_PROCESS_COMMAND, getAdServicesPackageName(context, logTag));
90 
91         try {
92             // Sleep 100 ms to allow AdServices process to recover
93             Thread.sleep(/* millis= */ 100);
94         } catch (InterruptedException ignored) {
95             Log.e(logTag, "Recovery from restarting AdServices process interrupted", ignored);
96         }
97     }
98 
99     /**
100      * Kill the Adservices process. An overloading method of {@code killAdservicesProcess(context,
101      * logTag)} by using {@code DEFAULT_LOG_TAG}.
102      *
103      * @param context the context used to get Adservices package name.
104      */
killAdservicesProcess(@onNull Context context)105     public static void killAdservicesProcess(@NonNull Context context) {
106         killAdservicesProcess(context, DEFAULT_LOG_TAG);
107     }
108 
109     /**
110      * Kill the Adservices process. An overloading method of {@code killAdservicesProcess(context,
111      * logTag)} by using Adservices package name directly.
112      *
113      * @param adservicesPackageName the Adservices package name.
114      */
killAdservicesProcess(@onNull String adservicesPackageName)115     public static void killAdservicesProcess(@NonNull String adservicesPackageName) {
116         ShellUtils.runShellCommand("%s %s", FORCE_KILL_PROCESS_COMMAND, adservicesPackageName);
117     }
118 
119     /**
120      * Check whether the device is supported. Adservices doesn't support non-phone device.
121      *
122      * @return if the device is supported.
123      * @deprecated use {@link AdServicesDeviceSupportedRule} instead.
124      */
125     @Deprecated
126     @SuppressWarnings("InlineMeSuggester")
isDeviceSupported()127     public static boolean isDeviceSupported() {
128         return AdServicesSupportHelper.getInstance().isDeviceSupported();
129     }
130 
131     /**
132      * Checks if the device is debuggable, as the {@code Build.isDebuggable()} was just added on
133      * Android S.
134      */
isDebuggable()135     public static boolean isDebuggable() {
136         if (SdkLevel.isAtLeastS()) {
137             return Build.isDebuggable();
138         }
139         return SystemProperties.getInt("ro.debuggable", 0) == 1;
140     }
141 
142     /**
143      * Resolve package name of the active AdServices APK on this device.
144      *
145      * <p>Copied from AdServicesCommon.
146      */
resolveAdServicesService( List<ResolveInfo> intentResolveInfos, String intentAction, String logTag)147     private static ServiceInfo resolveAdServicesService(
148             List<ResolveInfo> intentResolveInfos, String intentAction, String logTag) {
149         if (intentResolveInfos == null || intentResolveInfos.isEmpty()) {
150             Log.e(
151                     logTag,
152                     "Failed to find resolveInfo for adServices service. Intent action: "
153                             + intentAction);
154             return null;
155         }
156 
157         // On T+ devices, we may have two versions of the services present due to b/263904312.
158         if (intentResolveInfos.size() > 2) {
159             StringBuilder intents = new StringBuilder("");
160             for (ResolveInfo intentResolveInfo : intentResolveInfos) {
161                 if (intentResolveInfo != null && intentResolveInfo.serviceInfo != null) {
162                     intents.append(intentResolveInfo.serviceInfo.packageName);
163                 }
164             }
165             Log.e(logTag, "Found multiple services " + intents + " for " + intentAction);
166             return null;
167         }
168 
169         // On T+ devices, only use the service that comes from AdServices APK. The package name of
170         // AdService is com.[google.]android.adservices.api while the package name of ExtServices
171         // APK is com.[google.]android.ext.services.
172         ServiceInfo serviceInfo = null;
173 
174         // We have already checked if there are 0 OR more than 2 services returned.
175         switch (intentResolveInfos.size()) {
176             case 2:
177                 // In the case of 2, always use the one from AdServicesApk.
178                 if (intentResolveInfos.get(0) != null
179                         && intentResolveInfos.get(0).serviceInfo != null
180                         && intentResolveInfos.get(0).serviceInfo.packageName != null
181                         && intentResolveInfos
182                                 .get(0)
183                                 .serviceInfo
184                                 .packageName
185                                 .endsWith(ADSERVICES_APK_PACKAGE_NAME_SUFFIX)) {
186                     serviceInfo = intentResolveInfos.get(0).serviceInfo;
187                 } else if (intentResolveInfos.get(1) != null
188                         && intentResolveInfos.get(1).serviceInfo != null
189                         && intentResolveInfos.get(1).serviceInfo.packageName != null
190                         && intentResolveInfos
191                                 .get(1)
192                                 .serviceInfo
193                                 .packageName
194                                 .endsWith(ADSERVICES_APK_PACKAGE_NAME_SUFFIX)) {
195                     serviceInfo = intentResolveInfos.get(1).serviceInfo;
196                 }
197                 break;
198 
199             case 1:
200                 serviceInfo = intentResolveInfos.get(0).serviceInfo;
201                 break;
202         }
203         return serviceInfo;
204     }
205 }
206