1 /*
2  * Copyright (C) 2021 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 package com.android.server.am;
17 
18 import android.app.ActivityThread;
19 import android.content.ContentResolver;
20 import android.content.Intent;
21 import android.provider.Settings;
22 import android.util.ArrayMap;
23 
24 import com.android.internal.annotations.GuardedBy;
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.internal.util.FrameworkStatsLog;
27 
28 import java.security.MessageDigest;
29 import java.security.NoSuchAlgorithmException;
30 
31 /**
32  * To store random utility methods...
33  */
34 public class ActivityManagerUtils {
ActivityManagerUtils()35     private ActivityManagerUtils() {
36     }
37 
38     private static Integer sAndroidIdHash;
39 
40     @GuardedBy("sHashCache")
41     private static final ArrayMap<String, Integer> sHashCache = new ArrayMap<>();
42 
43     private static String sInjectedAndroidId;
44 
45     /** Used by the unit tests to inject an android ID. Do not set in the prod code. */
46     @VisibleForTesting
injectAndroidIdForTest(String androidId)47     static void injectAndroidIdForTest(String androidId) {
48         sInjectedAndroidId = androidId;
49         sAndroidIdHash = null;
50     }
51 
52     /**
53      * Return a hash between [0, MAX_VALUE] generated from the android ID.
54      */
55     @VisibleForTesting
getAndroidIdHash()56     static int getAndroidIdHash() {
57         // No synchronization is required. Double-initialization is fine here.
58         if (sAndroidIdHash == null) {
59             final ContentResolver resolver = ActivityThread.currentApplication()
60                                              .getContentResolver();
61             final String androidId = Settings.Secure.getStringForUser(
62                     resolver,
63                     Settings.Secure.ANDROID_ID,
64                     resolver.getUserId());
65             sAndroidIdHash = getUnsignedHashUnCached(
66                     sInjectedAndroidId != null ? sInjectedAndroidId : androidId);
67         }
68         return sAndroidIdHash;
69     }
70 
71     /**
72      * Return a hash between [0, MAX_VALUE] generated from a package name, using a cache.
73      *
74      * Because all the results are cached, do not use it for dynamically generated strings.
75      */
76     @VisibleForTesting
getUnsignedHashCached(String s)77     static int getUnsignedHashCached(String s) {
78         synchronized (sHashCache) {
79             final Integer cached = sHashCache.get(s);
80             if (cached != null) {
81                 return cached;
82             }
83             final int hash = getUnsignedHashUnCached(s);
84             sHashCache.put(s.intern(), hash);
85             return hash;
86         }
87     }
88 
89     /**
90      * Return a hash between [0, MAX_VALUE] generated from a package name.
91      */
getUnsignedHashUnCached(String s)92     private static int getUnsignedHashUnCached(String s) {
93         try {
94             final MessageDigest digest = MessageDigest.getInstance("SHA-1");
95             digest.update(s.getBytes());
96             return unsignedIntFromBytes(digest.digest());
97         } catch (NoSuchAlgorithmException e) {
98             throw new RuntimeException(e);
99         }
100     }
101 
102     @VisibleForTesting
unsignedIntFromBytes(byte[] longEnoughBytes)103     static int unsignedIntFromBytes(byte[] longEnoughBytes) {
104         return (extractByte(longEnoughBytes, 0)
105                 | extractByte(longEnoughBytes, 1)
106                 | extractByte(longEnoughBytes, 2)
107                 | extractByte(longEnoughBytes, 3))
108                 & 0x7FFF_FFFF;
109     }
110 
extractByte(byte[] bytes, int index)111     private static int extractByte(byte[] bytes, int index) {
112         return (((int) bytes[index]) & 0xFF) << (index * 8);
113     }
114 
115     /**
116      * @return whether a package should be logged, using a random value based on the ANDROID_ID,
117      * with a given sampling rate.
118      */
shouldSamplePackageForAtom(String packageName, float rate)119     public static boolean shouldSamplePackageForAtom(String packageName, float rate) {
120         if (rate <= 0) {
121             return false;
122         }
123         if (rate >= 1) {
124             return true;
125         }
126         final int hash = getUnsignedHashCached(packageName) ^ getAndroidIdHash();
127 
128         return (((double) hash) / Integer.MAX_VALUE) <= rate;
129     }
130 
131     /**
132      * @param shortInstanceName {@link ServiceRecord#shortInstanceName}.
133      * @return hash of the ServiceRecord's shortInstanceName, combined with ANDROID_ID.
134      */
hashComponentNameForAtom(String shortInstanceName)135     public static int hashComponentNameForAtom(String shortInstanceName) {
136         return getUnsignedHashUnCached(shortInstanceName) ^ getAndroidIdHash();
137     }
138 
139     /**
140      * Helper method to log an unsafe intent event.
141      */
logUnsafeIntentEvent(int event, int callingUid, Intent intent, String resolvedType, boolean blocked)142     public static void logUnsafeIntentEvent(int event, int callingUid,
143             Intent intent, String resolvedType, boolean blocked) {
144         String[] categories = intent.getCategories() == null ? new String[0]
145                 : intent.getCategories().toArray(String[]::new);
146         String component = intent.getComponent() == null ? null
147                 : intent.getComponent().flattenToString();
148         FrameworkStatsLog.write(FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED,
149                 event,
150                 callingUid,
151                 component,
152                 intent.getPackage(),
153                 intent.getAction(),
154                 categories,
155                 resolvedType,
156                 intent.getScheme(),
157                 blocked);
158     }
159 }
160