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
17package com.android.managedprovisioning.analytics;
18
19import static android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED;
20import static com.android.managedprovisioning.common.Globals.ACTION_RESUME_PROVISIONING;
21import static java.nio.charset.StandardCharsets.UTF_8;
22
23import android.content.Context;
24import android.content.Intent;
25import android.nfc.NdefRecord;
26import android.os.SystemClock;
27import android.support.annotation.NonNull;
28import android.support.annotation.Nullable;
29
30import com.android.managedprovisioning.parser.PropertiesProvisioningDataParser;
31import com.android.managedprovisioning.task.AbstractProvisioningTask;
32
33import java.io.IOException;
34import java.io.StringReader;
35import java.util.ArrayList;
36import java.util.List;
37import java.util.Set;
38import java.util.Properties;
39
40/**
41 * Class containing various auxiliary methods used by provisioning analytics tracker.
42 */
43public class AnalyticsUtils {
44
45    public AnalyticsUtils() {}
46
47    private static final String PROVISIONING_EXTRA_PREFIX = "android.app.extra.PROVISIONING_";
48
49    /**
50     * Returns package name of the installer package, null if package is not present on the device
51     * and empty string if installer package is not present on the device.
52     *
53     * @param context Context used to get package manager
54     * @param packageName Package name of the installed package
55     */
56    @Nullable
57    public static String getInstallerPackageName(Context context, String packageName) {
58        try {
59            return context.getPackageManager().getInstallerPackageName(packageName);
60        } catch (IllegalArgumentException e) {
61            return null;
62        }
63    }
64
65    /**
66     * Returns elapsed real time.
67     */
68    public Long elapsedRealTime() {
69        return SystemClock.elapsedRealtime();
70    }
71
72    /**
73     * Returns list of all valid provisioning extras sent by the dpc.
74     *
75     * @param intent Intent that started provisioning
76     */
77    @NonNull
78    public static List<String> getAllProvisioningExtras(Intent intent) {
79        if (intent == null || ACTION_RESUME_PROVISIONING.equals(intent.getAction())) {
80            // Provisioning extras should have already been logged for resume case.
81            return new ArrayList<String>();
82        } else if (ACTION_NDEF_DISCOVERED.equals(intent.getAction())) {
83            return getExtrasFromProperties(intent);
84        } else {
85            return getExtrasFromBundle(intent);
86        }
87    }
88
89    /**
90     * Returns unique string for all provisioning task errors.
91     *
92     * @param task Provisioning task which threw error
93     * @param errorCode Unique code from class indicating the error
94     */
95    @Nullable
96    public static String getErrorString(AbstractProvisioningTask task, int errorCode) {
97        if (task == null) {
98            return null;
99        }
100        // We do not have definite codes for all provisioning errors yet. We just pass the task's
101        // class name and the internal task's error code to generate a unique error code.
102        return task.getClass().getSimpleName() + ":" + errorCode;
103    }
104
105    @NonNull
106    private static List<String> getExtrasFromBundle(Intent intent) {
107        List<String> provisioningExtras = new ArrayList<String>();
108        if (intent != null && intent.getExtras() != null) {
109            final Set<String> keys = intent.getExtras().keySet();
110            for (String key : keys) {
111                if (isValidProvisioningExtra(key)) {
112                    provisioningExtras.add(key);
113                }
114            }
115        }
116        return provisioningExtras;
117    }
118
119    @NonNull
120    private static List<String> getExtrasFromProperties(Intent intent) {
121        List<String> provisioningExtras = new ArrayList<String>();
122        NdefRecord firstRecord = PropertiesProvisioningDataParser.getFirstNdefRecord(intent);
123        if (firstRecord != null) {
124            try {
125                Properties props = new Properties();
126                props.load(new StringReader(new String(firstRecord.getPayload(), UTF_8)));
127                final Set<String> keys = props.stringPropertyNames();
128                for (String key : keys) {
129                    if (isValidProvisioningExtra(key)) {
130                        provisioningExtras.add(key);
131                    }
132                }
133            } catch (IOException e) {
134            }
135        }
136        return provisioningExtras;
137    }
138
139    /**
140     * Returns if a string is a valid provisioning extra.
141     */
142    private static boolean isValidProvisioningExtra(String provisioningExtra) {
143        // Currently it verifies using the prefix. We should further change this to verify using the
144        // actual DPM extras.
145        return provisioningExtra != null && provisioningExtra.startsWith(PROVISIONING_EXTRA_PREFIX);
146    }
147}
148