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