1 /*
2  * Copyright 2016, 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.managedprovisioning.analytics;
18 
19 import static android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED;
20 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_COPY_ACCOUNT_TASK_MS;
21 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_CREATE_PROFILE_TASK_MS;
22 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_DOWNLOAD_PACKAGE_TASK_MS;
23 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENCRYPT_DEVICE_ACTIVITY_TIME_MS;
24 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_INSTALL_PACKAGE_TASK_MS;
25 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_PREPROVISIONING_ACTIVITY_TIME_MS;
26 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_PROVISIONING_ACTIVITY_TIME_MS;
27 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_START_PROFILE_TASK_MS;
28 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_TERMS_ACTIVITY_TIME_MS;
29 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_TOTAL_TASK_TIME_MS;
30 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_WEB_ACTIVITY_TIME_MS;
31 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.VIEW_UNKNOWN;
32 import static com.android.managedprovisioning.common.Globals.ACTION_RESUME_PROVISIONING;
33 import static java.nio.charset.StandardCharsets.UTF_8;
34 
35 import android.content.Context;
36 import android.content.Intent;
37 import android.icu.util.TimeUnit;
38 import android.nfc.NdefRecord;
39 import android.os.SystemClock;
40 import android.stats.devicepolicy.DevicePolicyEnums;
41 import androidx.annotation.NonNull;
42 import androidx.annotation.Nullable;
43 
44 import com.android.internal.annotations.VisibleForTesting;
45 import com.android.managedprovisioning.analytics.TimeLogger.TimeCategory;
46 import com.android.managedprovisioning.common.ManagedProvisioningSharedPreferences;
47 import com.android.managedprovisioning.parser.PropertiesProvisioningDataParser;
48 import com.android.managedprovisioning.task.AbstractProvisioningTask;
49 
50 import java.io.IOException;
51 import java.io.StringReader;
52 import java.time.Clock;
53 import java.util.ArrayList;
54 import java.util.List;
55 import java.util.Properties;
56 import java.util.Set;
57 import java.util.function.Function;
58 import java.util.function.LongSupplier;
59 
60 /**
61  * Class containing various auxiliary methods used by provisioning analytics tracker.
62  */
63 public class AnalyticsUtils {
64 
65     final static int CATEGORY_VIEW_UNKNOWN = -1;
66 
AnalyticsUtils()67     public AnalyticsUtils() {}
68 
69     private static final String PROVISIONING_EXTRA_PREFIX = "android.app.extra.PROVISIONING_";
70 
71     /**
72      * Returns package name of the installer package, null if package is not present on the device
73      * and empty string if installer package is not present on the device.
74      *
75      * @param context Context used to get package manager
76      * @param packageName Package name of the installed package
77      */
78     @Nullable
getInstallerPackageName(Context context, String packageName)79     public static String getInstallerPackageName(Context context, String packageName) {
80         try {
81             return context.getPackageManager().getInstallerPackageName(packageName);
82         } catch (IllegalArgumentException e) {
83             return null;
84         }
85     }
86 
87     /**
88      * Returns elapsed real time.
89      */
elapsedRealTime()90     public Long elapsedRealTime() {
91         return SystemClock.elapsedRealtime();
92     }
93 
94     /**
95      * Returns list of all valid provisioning extras sent by the dpc.
96      *
97      * @param intent Intent that started provisioning
98      */
99     @NonNull
getAllProvisioningExtras(Intent intent)100     public static List<String> getAllProvisioningExtras(Intent intent) {
101         if (intent == null || ACTION_RESUME_PROVISIONING.equals(intent.getAction())) {
102             // Provisioning extras should have already been logged for resume case.
103             return new ArrayList<String>();
104         } else if (ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
105             return getExtrasFromProperties(intent);
106         } else {
107             return getExtrasFromBundle(intent);
108         }
109     }
110 
111     /**
112      * Returns unique string for all provisioning task errors.
113      *
114      * @param task Provisioning task which threw error
115      * @param errorCode Unique code from class indicating the error
116      */
117     @Nullable
getErrorString(AbstractProvisioningTask task, int errorCode)118     public static String getErrorString(AbstractProvisioningTask task, int errorCode) {
119         if (task == null) {
120             return null;
121         }
122         // We do not have definite codes for all provisioning errors yet. We just pass the task's
123         // class name and the internal task's error code to generate a unique error code.
124         return task.getClass().getSimpleName() + ":" + errorCode;
125     }
126 
127     @NonNull
getExtrasFromBundle(Intent intent)128     private static List<String> getExtrasFromBundle(Intent intent) {
129         List<String> provisioningExtras = new ArrayList<String>();
130         if (intent != null && intent.getExtras() != null) {
131             final Set<String> keys = intent.getExtras().keySet();
132             for (String key : keys) {
133                 if (isValidProvisioningExtra(key)) {
134                     provisioningExtras.add(key);
135                 }
136             }
137         }
138         return provisioningExtras;
139     }
140 
141     @NonNull
getExtrasFromProperties(Intent intent)142     private static List<String> getExtrasFromProperties(Intent intent) {
143         List<String> provisioningExtras = new ArrayList<String>();
144         NdefRecord firstRecord = PropertiesProvisioningDataParser.getFirstNdefRecord(intent);
145         if (firstRecord != null) {
146             try {
147                 Properties props = new Properties();
148                 props.load(new StringReader(new String(firstRecord.getPayload(), UTF_8)));
149                 final Set<String> keys = props.stringPropertyNames();
150                 for (String key : keys) {
151                     if (isValidProvisioningExtra(key)) {
152                         provisioningExtras.add(key);
153                     }
154                 }
155             } catch (IOException e) {
156             }
157         }
158         return provisioningExtras;
159     }
160 
161     /**
162      * Returns if a string is a valid provisioning extra.
163      */
isValidProvisioningExtra(String provisioningExtra)164     private static boolean isValidProvisioningExtra(String provisioningExtra) {
165         // Currently it verifies using the prefix. We should further change this to verify using the
166         // actual DPM extras.
167         return provisioningExtra != null && provisioningExtra.startsWith(PROVISIONING_EXTRA_PREFIX);
168     }
169 
170     /**
171      * Converts from {@link MetricsEvent} constants to {@link DevicePolicyEnums} constants.
172      * <p>If such a {@link MetricsEvent} does not exist, the metric is assumed
173      * to belong to {@link DevicePolicyEnums}.
174      */
getDevicePolicyEventForCategory(@imeCategory int metricsEvent)175     static int getDevicePolicyEventForCategory(@TimeCategory int metricsEvent) {
176         switch (metricsEvent) {
177             case PROVISIONING_COPY_ACCOUNT_TASK_MS:
178                 return DevicePolicyEnums.PROVISIONING_COPY_ACCOUNT_TASK_MS;
179             case PROVISIONING_CREATE_PROFILE_TASK_MS:
180                 return DevicePolicyEnums.PROVISIONING_CREATE_PROFILE_TASK_MS;
181             case PROVISIONING_DOWNLOAD_PACKAGE_TASK_MS:
182                 return DevicePolicyEnums.PROVISIONING_DOWNLOAD_PACKAGE_TASK_MS;
183             case PROVISIONING_ENCRYPT_DEVICE_ACTIVITY_TIME_MS:
184                 return DevicePolicyEnums.PROVISIONING_ENCRYPT_DEVICE_ACTIVITY_TIME_MS;
185             case PROVISIONING_INSTALL_PACKAGE_TASK_MS:
186                 return DevicePolicyEnums.PROVISIONING_INSTALL_PACKAGE_TASK_MS;
187             case PROVISIONING_PREPROVISIONING_ACTIVITY_TIME_MS:
188                 return DevicePolicyEnums.PROVISIONING_PREPROVISIONING_ACTIVITY_TIME_MS;
189             case PROVISIONING_PROVISIONING_ACTIVITY_TIME_MS:
190                 return DevicePolicyEnums.PROVISIONING_PROVISIONING_ACTIVITY_TIME_MS;
191             case PROVISIONING_START_PROFILE_TASK_MS:
192                 return DevicePolicyEnums.PROVISIONING_START_PROFILE_TASK_MS;
193             case PROVISIONING_WEB_ACTIVITY_TIME_MS:
194                 return DevicePolicyEnums.PROVISIONING_WEB_ACTIVITY_TIME_MS;
195             case PROVISIONING_TERMS_ACTIVITY_TIME_MS:
196                 return DevicePolicyEnums.PROVISIONING_TERMS_ACTIVITY_TIME_MS;
197             case PROVISIONING_TOTAL_TASK_TIME_MS:
198                 return DevicePolicyEnums.PROVISIONING_TOTAL_TASK_TIME_MS;
199             case VIEW_UNKNOWN:
200                 return -1;
201             default:
202                 return metricsEvent;
203         }
204     }
205 
206     /**
207      * Returns the time passed since provisioning started, in milliseconds.
208      * Returns <code>-1</code> if the provisioning start time was not specified via
209      * {@link ManagedProvisioningSharedPreferences#writeProvisioningStartedTimestamp(long)}.
210      */
getProvisioningTime(ManagedProvisioningSharedPreferences sharedPreferences)211     static long getProvisioningTime(ManagedProvisioningSharedPreferences sharedPreferences) {
212         return getProvisioningTime(sharedPreferences, SystemClock::elapsedRealtime);
213     }
214 
215     @VisibleForTesting
getProvisioningTime(ManagedProvisioningSharedPreferences sharedPreferences, LongSupplier getTimeFunction)216     static long getProvisioningTime(ManagedProvisioningSharedPreferences sharedPreferences,
217             LongSupplier getTimeFunction) {
218         if (sharedPreferences.getProvisioningStartedTimestamp() == 0) {
219             return -1;
220         }
221         return getTimeFunction.getAsLong() - sharedPreferences.getProvisioningStartedTimestamp();
222     }
223 }
224