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