Add SDK 29 sources.

Test: N/A
Change-Id: Iedb7a31029e003928eb16f7e69ed147e72bb6235
diff --git a/android/util/HashedStringCache.java b/android/util/HashedStringCache.java
new file mode 100644
index 0000000..1f2b956
--- /dev/null
+++ b/android/util/HashedStringCache.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Environment;
+import android.os.storage.StorageManager;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+/**
+ * HashedStringCache provides hashing functionality with an underlying LRUCache and expiring salt.
+ * Salt and expiration time are being stored under the tag passed in by the calling package --
+ * intended usage is the calling package name.
+ * @hide
+ */
+public class HashedStringCache {
+    private static HashedStringCache sHashedStringCache = null;
+    private static final Charset UTF_8 = Charset.forName("UTF-8");
+    private static final int HASH_CACHE_SIZE = 100;
+    private static final int HASH_LENGTH = 8;
+    @VisibleForTesting
+    static final String HASH_SALT = "_hash_salt";
+    @VisibleForTesting
+    static final String HASH_SALT_DATE = "_hash_salt_date";
+    @VisibleForTesting
+    static final String HASH_SALT_GEN = "_hash_salt_gen";
+    // For privacy we need to rotate the salt regularly
+    private static final long DAYS_TO_MILLIS = 1000 * 60 * 60 * 24;
+    private static final int MAX_SALT_DAYS = 100;
+    private final LruCache<String, String> mHashes;
+    private final SecureRandom mSecureRandom;
+    private final Object mPreferenceLock = new Object();
+    private final MessageDigest mDigester;
+    private byte[] mSalt;
+    private int mSaltGen;
+    private SharedPreferences mSharedPreferences;
+
+    private static final String TAG = "HashedStringCache";
+    private static final boolean DEBUG = false;
+
+    private HashedStringCache() {
+        mHashes = new LruCache<>(HASH_CACHE_SIZE);
+        mSecureRandom = new SecureRandom();
+        try {
+            mDigester = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException impossible) {
+            // this can't happen - MD5 is always present
+            throw new RuntimeException(impossible);
+        }
+    }
+
+    /**
+     * @return - instance of the HashedStringCache
+     * @hide
+     */
+    public static HashedStringCache getInstance() {
+        if (sHashedStringCache == null) {
+            sHashedStringCache = new HashedStringCache();
+        }
+        return sHashedStringCache;
+    }
+
+    /**
+     * Take the string and context and create a hash of the string. Trigger refresh on salt if salt
+     * is more than 7 days old
+     * @param context - callers context to retrieve SharedPreferences
+     * @param clearText - string that needs to be hashed
+     * @param tag - class name to use for storing values in shared preferences
+     * @param saltExpirationDays - number of days we may keep the same salt
+     *                           special value -1 will short-circuit and always return null.
+     * @return - HashResult containing the hashed string and the generation of the hash salt, null
+     *      if clearText string is empty
+     *
+     * @hide
+     */
+    public HashResult hashString(Context context, String tag, String clearText,
+            int saltExpirationDays) {
+        if (saltExpirationDays == -1 || context == null
+                || TextUtils.isEmpty(clearText) || TextUtils.isEmpty(tag)) {
+            return null;
+        }
+
+        populateSaltValues(context, tag, saltExpirationDays);
+        String hashText = mHashes.get(clearText);
+        if (hashText != null) {
+            return new HashResult(hashText, mSaltGen);
+        }
+
+        mDigester.reset();
+        mDigester.update(mSalt);
+        mDigester.update(clearText.getBytes(UTF_8));
+        byte[] bytes = mDigester.digest();
+        int len = Math.min(HASH_LENGTH, bytes.length);
+        hashText = Base64.encodeToString(bytes, 0, len, Base64.NO_PADDING | Base64.NO_WRAP);
+        mHashes.put(clearText, hashText);
+
+        return new HashResult(hashText, mSaltGen);
+    }
+
+    /**
+     * Populates the mSharedPreferences and checks if there is a salt present and if it's older than
+     * 7 days
+     * @param tag - class name to use for storing values in shared preferences
+     * @param saltExpirationDays - number of days we may keep the same salt
+     * @param saltDate - the date retrieved from configuration
+     * @return - true if no salt or salt is older than 7 days
+     */
+    private boolean checkNeedsNewSalt(String tag, int saltExpirationDays, long saltDate) {
+        if (saltDate == 0 || saltExpirationDays < -1) {
+            return true;
+        }
+        if (saltExpirationDays > MAX_SALT_DAYS) {
+            saltExpirationDays = MAX_SALT_DAYS;
+        }
+        long now = System.currentTimeMillis();
+        long delta = now - saltDate;
+        // Check for delta < 0 to make sure we catch if someone puts their phone far in the
+        // future and then goes back to normal time.
+        return delta >= saltExpirationDays * DAYS_TO_MILLIS || delta < 0;
+    }
+
+    /**
+     * Populate the salt and saltGen member variables if they aren't already set / need refreshing.
+     * @param context - to get sharedPreferences
+     * @param tag - class name to use for storing values in shared preferences
+     * @param saltExpirationDays - number of days we may keep the same salt
+     */
+    private void populateSaltValues(Context context, String tag, int saltExpirationDays) {
+        synchronized (mPreferenceLock) {
+            // check if we need to refresh the salt
+            mSharedPreferences = getHashSharedPreferences(context);
+            long saltDate = mSharedPreferences.getLong(tag + HASH_SALT_DATE, 0);
+            boolean needsNewSalt = checkNeedsNewSalt(tag, saltExpirationDays, saltDate);
+            if (needsNewSalt) {
+                mHashes.evictAll();
+            }
+            if (mSalt == null || needsNewSalt) {
+                String saltString = mSharedPreferences.getString(tag + HASH_SALT, null);
+                mSaltGen = mSharedPreferences.getInt(tag + HASH_SALT_GEN, 0);
+                if (saltString == null || needsNewSalt) {
+                    mSaltGen++;
+                    byte[] saltBytes = new byte[16];
+                    mSecureRandom.nextBytes(saltBytes);
+                    saltString = Base64.encodeToString(saltBytes,
+                            Base64.NO_PADDING | Base64.NO_WRAP);
+                    mSharedPreferences.edit()
+                            .putString(tag + HASH_SALT, saltString)
+                            .putInt(tag + HASH_SALT_GEN, mSaltGen)
+                            .putLong(tag + HASH_SALT_DATE, System.currentTimeMillis()).apply();
+                    if (DEBUG) {
+                        Log.d(TAG, "created a new salt: " + saltString);
+                    }
+                }
+                mSalt = saltString.getBytes(UTF_8);
+            }
+        }
+    }
+
+    /**
+     * Android:ui doesn't have persistent preferences, so need to fall back on this hack originally
+     * from ChooserActivity.java
+     * @param context
+     * @return
+     */
+    private SharedPreferences getHashSharedPreferences(Context context) {
+        final File prefsFile = new File(new File(
+                Environment.getDataUserCePackageDirectory(
+                        StorageManager.UUID_PRIVATE_INTERNAL,
+                        context.getUserId(), context.getPackageName()),
+                "shared_prefs"),
+                "hashed_cache.xml");
+        return context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
+    }
+
+    /**
+     * Helper class to hold hashed string and salt generation.
+     */
+    public class HashResult {
+        public String hashedString;
+        public int saltGeneration;
+
+        public HashResult(String hString, int saltGen) {
+            hashedString = hString;
+            saltGeneration = saltGen;
+        }
+    }
+}