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