From 79119ac9b124964c9c8d78c20d4485b16f7e0dda Mon Sep 17 00:00:00 2001 From: Xiaoyu Jin Date: Fri, 12 Mar 2021 11:00:54 -0800 Subject: [PATCH] Use the PlatformLogger in the platform 1) Add LoggerInstanceManager (similar to ImplInstanceManager) 2) Call statsd api to log atoms in PlatformLogger 3) Added BinderCallStartMillis to calculate estimated binder latency 4) Log CallStats for putDocuments Bug: b/173532925 Test: go/westworld-create-atom#step-3-test-your-atom, atest CtsAppSearchTestCases FrameworksCoreTests:android.app.appsearch FrameworksServicesTests:AppSearchImplTest FrameworksServicesTests:com.android.server.appsearch.stats.PlatformLoggerTest Change-Id: Ief158aee2d79008fecc9e1df52188765b8036c02 --- .../app/appsearch/AppSearchSession.java | 3 +- .../app/appsearch/IAppSearchManager.aidl | 2 + .../appsearch/AppSearchManagerService.java | 45 +++++- .../server/appsearch/ImplInstanceManager.java | 1 - .../stats/LoggerInstanceManager.java | 131 ++++++++++++++++++ .../appsearch/stats/PlatformLogger.java | 118 +++++++++++++--- .../appsearch/stats/PlatformLoggerTest.java | 98 +++++++++++-- .../server/pm/BaseShortcutManagerTest.java | 3 +- 8 files changed, 368 insertions(+), 33 deletions(-) create mode 100644 apex/appsearch/service/java/com/android/server/appsearch/stats/LoggerInstanceManager.java diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java index 3677489519022..ac91bdb2dce75 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java @@ -23,6 +23,7 @@ import android.app.appsearch.util.SchemaMigrationUtil; import android.os.Bundle; import android.os.ParcelableException; import android.os.RemoteException; +import android.os.SystemClock; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -270,8 +271,8 @@ public final class AppSearchSession implements Closeable { documentBundles.add(documents.get(i).getBundle()); } try { - // TODO(b/173532925) a timestamp needs to be sent here to calculate binder latency mService.putDocuments(mPackageName, mDatabaseName, documentBundles, mUserId, + /*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(), new IAppSearchBatchResultCallback.Stub() { public void onResult(AppSearchBatchResult result) { executor.execute(() -> callback.onResult(result)); diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl index 4d05ad71a121c..0b8f0523ab560 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl @@ -94,6 +94,7 @@ interface IAppSearchManager { * @param databaseName The name of the database where this document lives. * @param documentBundes List of GenericDocument bundles. * @param userId Id of the calling user + * @param binderCallStartTimeMillis start timestamp of binder call in Millis * @param callback * If the call fails to start, {@link IAppSearchBatchResultCallback#onSystemError} * will be called with the cause throwable. Otherwise, @@ -106,6 +107,7 @@ interface IAppSearchManager { in String databaseName, in List documentBundles, in int userId, + in long binderCallStartTimeMillis, in IAppSearchBatchResultCallback callback); /** diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java index b46e85d1d4800..35c441e9eb98b 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -18,6 +18,7 @@ package com.android.server.appsearch; import static android.app.appsearch.AppSearchResult.throwableToFailedResult; import static android.os.UserHandle.USER_NULL; +import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager; @@ -45,6 +46,7 @@ import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.ParcelableException; import android.os.RemoteException; +import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.util.ArrayMap; @@ -57,6 +59,9 @@ import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.appsearch.external.localstorage.AppSearchImpl; +import com.android.server.appsearch.external.localstorage.stats.CallStats; +import com.android.server.appsearch.stats.LoggerInstanceManager; +import com.android.server.appsearch.stats.PlatformLogger; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -80,6 +85,7 @@ public class AppSearchManagerService extends SystemService { private PackageManagerInternal mPackageManagerInternal; private ImplInstanceManager mImplInstanceManager; private UserManager mUserManager; + private LoggerInstanceManager mLoggerInstanceManager; // Never call shutdownNow(). It will cancel the futures it's returned. And since // Executor#execute won't return anything, we will hang forever waiting for the execution. @@ -106,6 +112,7 @@ public class AppSearchManagerService extends SystemService { mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mImplInstanceManager = ImplInstanceManager.getInstance(mContext); mUserManager = mContext.getSystemService(UserManager.class); + mLoggerInstanceManager = LoggerInstanceManager.getInstance(); registerReceivers(); } @@ -147,6 +154,7 @@ public class AppSearchManagerService extends SystemService { private void handleUserRemoved(@UserIdInt int userId) { try { mImplInstanceManager.removeAppSearchImplForUser(userId); + mLoggerInstanceManager.removePlatformLoggerForUser(userId); Slog.i(TAG, "Removed AppSearchImpl instance for user: " + userId); } catch (Throwable t) { Slog.e(TAG, "Unable to remove data for user: " + userId, t); @@ -274,6 +282,7 @@ public class AppSearchManagerService extends SystemService { @NonNull String databaseName, @NonNull List documentBundles, @UserIdInt int userId, + @ElapsedRealtimeLong long binderCallStartTimeMillis, @NonNull IAppSearchBatchResultCallback callback) { Preconditions.checkNotNull(packageName); Preconditions.checkNotNull(databaseName); @@ -282,6 +291,11 @@ public class AppSearchManagerService extends SystemService { int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { + long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime(); + @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK; + PlatformLogger logger = null; + int operationSuccessCount = 0; + int operationFailureCount = 0; try { verifyUserUnlocked(callingUserId); verifyCallingPackage(callingUid, packageName); @@ -289,20 +303,46 @@ public class AppSearchManagerService extends SystemService { new AppSearchBatchResult.Builder<>(); AppSearchImpl impl = mImplInstanceManager.getAppSearchImpl(callingUserId); + logger = mLoggerInstanceManager.getPlatformLogger(callingUserId); for (int i = 0; i < documentBundles.size(); i++) { GenericDocument document = new GenericDocument(documentBundles.get(i)); try { - impl.putDocument(packageName, databaseName, document, - /*logger=*/ null); + impl.putDocument(packageName, databaseName, document, logger); resultBuilder.setSuccess(document.getUri(), /*result=*/ null); + ++operationSuccessCount; } catch (Throwable t) { resultBuilder.setResult(document.getUri(), throwableToFailedResult(t)); + AppSearchResult result = throwableToFailedResult(t); + resultBuilder.setResult(document.getUri(), result); + // for failures, we would just log the one for last failure + statusCode = result.getResultCode(); + ++operationFailureCount; } } invokeCallbackOnResult(callback, resultBuilder.build()); } catch (Throwable t) { invokeCallbackOnError(callback, t); + } finally { + if (logger != null) { + CallStats.Builder cBuilder = new CallStats.Builder(packageName, + databaseName) + .setCallType(CallStats.CALL_TYPE_PUT_DOCUMENTS) + // TODO(b/173532925) check the existing binder call latency chart + // is good enough for us: + // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4 + .setEstimatedBinderLatencyMillis( + 2 * (int) (totalLatencyStartTimeMillis + - binderCallStartTimeMillis)) + .setNumOperationsSucceeded(operationSuccessCount) + .setNumOperationsFailed(operationFailureCount); + cBuilder.getGeneralStatsBuilder() + .setStatusCode(statusCode) + .setTotalLatencyMillis( + (int) (SystemClock.elapsedRealtime() + - totalLatencyStartTimeMillis)); + logger.logStats(cBuilder.build()); + } } }); } @@ -717,6 +757,7 @@ public class AppSearchManagerService extends SystemService { try { verifyUserUnlocked(callingUserId); mImplInstanceManager.getOrCreateAppSearchImpl(mContext, callingUserId); + mLoggerInstanceManager.getOrCreatePlatformLogger(getContext(), callingUserId); invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null)); } catch (Throwable t) { invokeCallbackOnError(callback, t); diff --git a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java index e82dd9ad5df3a..af39b790168c8 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/ImplInstanceManager.java @@ -106,7 +106,6 @@ public final class ImplInstanceManager { * * @param userId The multi-user userId of the user that need to be removed. */ - @NonNull public void removeAppSearchImplForUser(@UserIdInt int userId) { synchronized (mInstancesLocked) { mInstancesLocked.remove(userId); diff --git a/apex/appsearch/service/java/com/android/server/appsearch/stats/LoggerInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/stats/LoggerInstanceManager.java new file mode 100644 index 0000000000000..45440633cff45 --- /dev/null +++ b/apex/appsearch/service/java/com/android/server/appsearch/stats/LoggerInstanceManager.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2021 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 com.android.server.appsearch.stats; + +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.content.Context; +import android.util.SparseArray; +import android.util.SparseIntArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.server.appsearch.AppSearchManagerService; + +/** + * Manages the lifecycle of instances of {@link PlatformLogger}. + * + *

These instances are managed per unique device-user. + */ +public final class LoggerInstanceManager { + // TODO(b/173532925) flags to control those three + // So probably we can't pass those three in the constructor but need to fetch the latest value + // every time we need them in the logger. + private static final int MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 100; + private static final int DEFAULT_SAMPLING_RATIO = 10; + + private static volatile LoggerInstanceManager sLoggerInstanceManager; + + @GuardedBy("mInstancesLocked") + private final SparseArray mInstancesLocked = new SparseArray<>(); + + private LoggerInstanceManager() { + } + + /** + * Gets an instance of {@link LoggerInstanceManager} to be used. + * + *

If no instance has been initialized yet, a new one will be created. Otherwise, the + * existing instance will be returned. + */ + @NonNull + public static LoggerInstanceManager getInstance() { + if (sLoggerInstanceManager == null) { + synchronized (LoggerInstanceManager.class) { + if (sLoggerInstanceManager == null) { + sLoggerInstanceManager = + new LoggerInstanceManager(); + } + } + } + return sLoggerInstanceManager; + } + + /** + * Gets an instance of PlatformLogger for the given user, or creates one if none exists. + * + * @param context The context + * @param userId The multi-user userId of the device user calling AppSearch + * @return An initialized {@link PlatformLogger} for this user + */ + @NonNull + public PlatformLogger getOrCreatePlatformLogger( + @NonNull Context context, @UserIdInt int userId) { + synchronized (mInstancesLocked) { + PlatformLogger instance = mInstancesLocked.get(userId); + if (instance == null) { + instance = new PlatformLogger(context, userId, new PlatformLogger.Config( + MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS, + DEFAULT_SAMPLING_RATIO, + // TODO(b/173532925) re-enable sampling ratios for different stats types + // once we have P/H flag manager setup in ag/13977824 + /*samplingRatios=*/ new SparseIntArray())); + mInstancesLocked.put(userId, instance); + } + return instance; + } + } + + + /** + * Gets an instance of PlatformLogger for the given user. + * + *

This method should only be called by an initialized SearchSession, which has been already + * created the PlatformLogger instance for the given user. + * + * @param userId The multi-user userId of the device user calling AppSearch + * @return An initialized {@link PlatformLogger} for this user + * @throws IllegalStateException if {@link PlatformLogger} haven't created for the given user. + */ + @NonNull + public PlatformLogger getPlatformLogger(@UserIdInt int userId) { + synchronized (mInstancesLocked) { + PlatformLogger instance = mInstancesLocked.get(userId); + if (instance == null) { + // Impossible scenario, user cannot call an uninitialized SearchSession, + // getInstance should always find the instance for the given user and never try to + // create an instance for this user again. + throw new IllegalStateException( + "PlatformLogger has never been created for this user: " + userId); + } + return instance; + } + } + + /** + * Remove an instance of {@link PlatformLogger} for the given user. + * + *

This method should only be called if {@link AppSearchManagerService} receives an + * ACTION_USER_REMOVED, which the logger instance of given user should be removed. + * + * @param userId The multi-user userId of the user that need to be removed. + */ + public void removePlatformLoggerForUser(@UserIdInt int userId) { + synchronized (mInstancesLocked) { + mInstancesLocked.remove(userId); + } + } +} diff --git a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java index aeb66d90b5bec..1c04d99ac2c74 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java @@ -17,21 +17,26 @@ package com.android.server.appsearch.stats; import android.annotation.NonNull; -import android.annotation.Nullable; +import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; import android.content.pm.PackageManager; import android.os.Process; import android.os.SystemClock; import android.util.ArrayMap; +import android.util.Log; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.Preconditions; import com.android.server.appsearch.external.localstorage.AppSearchLogger; import com.android.server.appsearch.external.localstorage.stats.CallStats; import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Map; import java.util.Random; @@ -120,19 +125,18 @@ public final class PlatformLogger implements AppSearchLogger { * @param minTimeIntervalBetweenSamplesMillis minimum time interval apart in Milliseconds * required for two consecutive stats logged * @param defaultSamplingRatio default sampling ratio - * @param samplingRatios SparseArray to customize sampling ratio for + * @param samplingRatios SparseArray to customize sampling ratio for * different stat types */ public Config(long minTimeIntervalBetweenSamplesMillis, int defaultSamplingRatio, - @Nullable SparseIntArray samplingRatios) { + @NonNull SparseIntArray samplingRatios) { + // TODO(b/173532925) Probably we can get rid of those three after we have p/h flags + // for them. + // e.g. we can just call DeviceConfig.get(SAMPLING_RATIO_FOR_PUT_DOCUMENTS). mMinTimeIntervalBetweenSamplesMillis = minTimeIntervalBetweenSamplesMillis; mDefaultSamplingRatio = defaultSamplingRatio; - if (samplingRatios != null) { - mSamplingRatios = samplingRatios; - } else { - mSamplingRatios = new SparseIntArray(); - } + mSamplingRatios = samplingRatios; } } @@ -169,7 +173,7 @@ public final class PlatformLogger implements AppSearchLogger { Preconditions.checkNotNull(stats); synchronized (mLock) { if (shouldLogForTypeLocked(stats.getCallType())) { - logToWestworldLocked(stats); + logStatsImplLocked(stats); } } } @@ -180,7 +184,7 @@ public final class PlatformLogger implements AppSearchLogger { Preconditions.checkNotNull(stats); synchronized (mLock) { if (shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)) { - logToWestworldLocked(stats); + logStatsImplLocked(stats); } } } @@ -201,25 +205,103 @@ public final class PlatformLogger implements AppSearchLogger { } @GuardedBy("mLock") - private void logToWestworldLocked(@NonNull CallStats stats) { + private void logStatsImplLocked(@NonNull CallStats stats) { mLastPushTimeMillisLocked = SystemClock.elapsedRealtime(); ExtraStats extraStats = createExtraStatsLocked(stats.getGeneralStats().getPackageName(), stats.getCallType()); - /* TODO(b/173532925) Log the CallStats to Westworld - stats.log(..., samplingRatio, skippedSampleCount, ...) - */ + String database = stats.getGeneralStats().getDatabase(); + try { + int hashCodeForDatabase = calculateHashCodeMd5(database); + FrameworkStatsLog.write(FrameworkStatsLog.APP_SEARCH_CALL_STATS_REPORTED, + extraStats.mSamplingRatio, + extraStats.mSkippedSampleCount, + extraStats.mPackageUid, + hashCodeForDatabase, + stats.getGeneralStats().getStatusCode(), + stats.getGeneralStats().getTotalLatencyMillis(), + stats.getCallType(), + stats.getEstimatedBinderLatencyMillis(), + stats.getNumOperationsSucceeded(), + stats.getNumOperationsFailed()); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { + // TODO(b/184204720) report hashing error to Westworld + // We need to set a special value(e.g. 0xFFFFFFFF) for the hashing of the database, + // so in the dashboard we know there is some error for hashing. + // + // Something is wrong while calculating the hash code for database + // this shouldn't happen since we always use "MD5" and "UTF-8" + Log.e(TAG, "Error calculating hash code for database " + database, e); + } } @GuardedBy("mLock") - private void logToWestworldLocked(@NonNull PutDocumentStats stats) { + private void logStatsImplLocked(@NonNull PutDocumentStats stats) { mLastPushTimeMillisLocked = SystemClock.elapsedRealtime(); ExtraStats extraStats = createExtraStatsLocked(stats.getGeneralStats().getPackageName(), CallStats.CALL_TYPE_PUT_DOCUMENT); - /* TODO(b/173532925) Log the PutDocumentStats to Westworld - stats.log(..., samplingRatio, skippedSampleCount, ...) - */ + String database = stats.getGeneralStats().getDatabase(); + try { + int hashCodeForDatabase = calculateHashCodeMd5(database); + FrameworkStatsLog.write(FrameworkStatsLog.APP_SEARCH_PUT_DOCUMENT_STATS_REPORTED, + extraStats.mSamplingRatio, + extraStats.mSkippedSampleCount, + extraStats.mPackageUid, + hashCodeForDatabase, + stats.getGeneralStats().getStatusCode(), + stats.getGeneralStats().getTotalLatencyMillis(), + stats.getGenerateDocumentProtoLatencyMillis(), + stats.getRewriteDocumentTypesLatencyMillis(), + stats.getNativeLatencyMillis(), + stats.getNativeDocumentStoreLatencyMillis(), + stats.getNativeIndexLatencyMillis(), + stats.getNativeIndexMergeLatencyMillis(), + stats.getNativeDocumentSizeBytes(), + stats.getNativeNumTokensIndexed(), + stats.getNativeExceededMaxNumTokens()); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { + // TODO(b/184204720) report hashing error to Westworld + // We need to set a special value(e.g. 0xFFFFFFFF) for the hashing of the database, + // so in the dashboard we know there is some error for hashing. + // + // Something is wrong while calculating the hash code for database + // this shouldn't happen since we always use "MD5" and "UTF-8" + Log.e(TAG, "Error calculating hash code for database " + database, e); + } } + /** + * Calculate the hash code as an integer by returning the last four bytes of its MD5. + * + * @param str a string + * @return hash code as an integer + * @throws AppSearchException if either algorithm or encoding does not exist. + */ + @VisibleForTesting + @NonNull + static int calculateHashCodeMd5(@NonNull String str) throws + NoSuchAlgorithmException, UnsupportedEncodingException { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(str.getBytes(/*charsetName=*/ "UTF-8")); + byte[] digest = md.digest(); + + // Since MD5 generates 16 bytes digest, we don't need to check the length here to see + // if it is smaller than sizeof(int)(4). + // + // We generate the same value as BigInteger(digest).intValue(). + // BigInteger takes bytes[] and treat it as big endian. And its intValue() would get the + // lower 4 bytes. So here we take the last 4 bytes and treat them as big endian. + return (digest[12] & 0xFF) << 24 + | (digest[13] & 0xFF) << 16 + | (digest[14] & 0xFF) << 8 + | (digest[15] & 0xFF); + } + + /** + * Creates {@link ExtraStats} to hold additional information generated for logging. + * + *

This method is called by most of logToWestworldLocked functions to reduce code + * duplication. + */ @VisibleForTesting @GuardedBy("mLock") @NonNull diff --git a/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java b/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java index 7afcbf7ead1cc..5de8a7abc90a9 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.annotation.NonNull; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.PackageManager; @@ -38,6 +39,11 @@ import com.android.server.appsearch.external.localstorage.stats.CallStats; import org.junit.Before; import org.junit.Test; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + public class PlatformLoggerTest { private static final int TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 100; private static final int TEST_DEFAULT_SAMPLING_RATIO = 10; @@ -57,15 +63,23 @@ public class PlatformLoggerTest { }; } + static int calculateHashCodeMd5withBigInteger(@NonNull String str) throws + NoSuchAlgorithmException, UnsupportedEncodingException { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(str.getBytes(/*charsetName=*/ "UTF-8")); + byte[] digest = md.digest(); + return new BigInteger(digest).intValue(); + } + @Test - public void testcreateExtraStatsLocked_nullSamplingRatioMap_returnsDefaultSamplingRatio() { + public void testCreateExtraStatsLocked_nullSamplingRatioMap_returnsDefaultSamplingRatio() { PlatformLogger logger = new PlatformLogger( ApplicationProvider.getApplicationContext(), UserHandle.USER_NULL, new PlatformLogger.Config( TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS, TEST_DEFAULT_SAMPLING_RATIO, - /*samplingRatioMap=*/ null)); + /*samplingRatios=*/ new SparseIntArray())); // Make sure default sampling ratio is used if samplingMap is not provided. assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME, @@ -84,7 +98,7 @@ public class PlatformLoggerTest { @Test - public void testcreateExtraStatsLocked_with_samplingRatioMap_returnsConfiguredSamplingRatio() { + public void testCreateExtraStatsLocked_with_samplingRatioMap_returnsConfiguredSamplingRatio() { int putDocumentSamplingRatio = 1; int querySamplingRatio = 2; final SparseIntArray samplingRatios = new SparseIntArray(); @@ -98,8 +112,8 @@ public class PlatformLoggerTest { TEST_DEFAULT_SAMPLING_RATIO, samplingRatios)); - // The default sampling ratio should be used if no sampling ratio is - // provided for certain call type. + // The default sampling ratio should be used if no sampling ratio is + // provided for certain call type. assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME, CallStats.CALL_TYPE_INITIALIZE).mSamplingRatio).isEqualTo( TEST_DEFAULT_SAMPLING_RATIO); @@ -117,6 +131,70 @@ public class PlatformLoggerTest { querySamplingRatio); } + @Test + public void testCalculateHashCode_MD5_int32_shortString() + throws NoSuchAlgorithmException, UnsupportedEncodingException { + final String str1 = "d1"; + final String str2 = "d2"; + + int hashCodeForStr1 = PlatformLogger.calculateHashCodeMd5(str1); + + // hashing should be stable + assertThat(hashCodeForStr1).isEqualTo( + PlatformLogger.calculateHashCodeMd5(str1)); + assertThat(hashCodeForStr1).isNotEqualTo( + PlatformLogger.calculateHashCodeMd5(str2)); + } + + @Test + public void testGetCalculateCode_MD5_int32_mediumString() + throws NoSuchAlgorithmException, UnsupportedEncodingException { + final String str1 = "Siblings"; + final String str2 = "Teheran"; + + int hashCodeForStr1 = PlatformLogger.calculateHashCodeMd5(str1); + + // hashing should be stable + assertThat(hashCodeForStr1).isEqualTo( + PlatformLogger.calculateHashCodeMd5(str1)); + assertThat(hashCodeForStr1).isNotEqualTo( + PlatformLogger.calculateHashCodeMd5(str2)); + } + + @Test + public void testCalculateHashCode_MD5_int32_longString() throws NoSuchAlgorithmException, + UnsupportedEncodingException { + final String str1 = "abcdefghijkl-mnopqrstuvwxyz"; + final String str2 = "abcdefghijkl-mnopqrstuvwxy123"; + + int hashCodeForStr1 = PlatformLogger.calculateHashCodeMd5(str1); + + // hashing should be stable + assertThat(hashCodeForStr1).isEqualTo( + PlatformLogger.calculateHashCodeMd5(str1)); + assertThat(hashCodeForStr1).isNotEqualTo( + PlatformLogger.calculateHashCodeMd5(str2)); + } + + @Test + public void testCalculateHashCode_MD5_int32_sameAsBigInteger_intValue() throws + NoSuchAlgorithmException, UnsupportedEncodingException { + final String emptyStr = ""; + final String shortStr = "a"; + final String mediumStr = "Teheran"; + final String longStr = "abcd-efgh-ijkl-mnop-qrst-uvwx-yz"; + + int emptyHashCode = PlatformLogger.calculateHashCodeMd5(emptyStr); + int shortHashCode = PlatformLogger.calculateHashCodeMd5(shortStr); + int mediumHashCode = PlatformLogger.calculateHashCodeMd5(mediumStr); + int longHashCode = PlatformLogger.calculateHashCodeMd5(longStr); + + assertThat(emptyHashCode).isEqualTo(calculateHashCodeMd5withBigInteger(emptyStr)); + assertThat(shortHashCode).isEqualTo(calculateHashCodeMd5withBigInteger(shortStr)); + assertThat(mediumHashCode).isEqualTo(calculateHashCodeMd5withBigInteger(mediumStr)); + assertThat(longHashCode).isEqualTo(calculateHashCodeMd5withBigInteger(longStr)); + } + @Test public void testShouldLogForTypeLocked_trueWhenSampleRatioIsOne() { final int samplingRatio = 1; @@ -127,7 +205,7 @@ public class PlatformLoggerTest { new PlatformLogger.Config( TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS, samplingRatio, - /* samplingMap=*/ null)); + /*samplingRatios=*/ new SparseIntArray())); // Sample should always be logged for the first time if sampling is disabled(value is one). assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isTrue(); @@ -145,7 +223,7 @@ public class PlatformLoggerTest { new PlatformLogger.Config( TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS, samplingRatio, - /* samplingMap=*/ null)); + /*samplingRatios=*/ new SparseIntArray())); // Makes sure sample will be excluded due to sampling if sample ratio is negative. assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isFalse(); @@ -167,7 +245,7 @@ public class PlatformLoggerTest { new PlatformLogger.Config( minTimeIntervalBetweenSamplesMillis, samplingRatio, - /* samplingMap=*/ null)); + /*samplingRatios=*/ new SparseIntArray())); logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime()); // Makes sure sample will be excluded due to rate limiting if samples are too close. @@ -189,7 +267,7 @@ public class PlatformLoggerTest { new PlatformLogger.Config( minTimeIntervalBetweenSamplesMillis, samplingRatio, - /* samplingMap=*/ null)); + /*samplingRatios=*/ new SparseIntArray())); logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime()); // Makes sure sample will be logged if it is not too close to previous sample. @@ -209,7 +287,7 @@ public class PlatformLoggerTest { new PlatformLogger.Config( TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS, TEST_DEFAULT_SAMPLING_RATIO, - /* samplingMap=*/ null)); + /*samplingRatios=*/ new SparseIntArray())); mMockPackageManager.mockGetPackageUidAsUser(testPackageName, mContext.getUserId(), testUid); // diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index 589b3b403e6ce..bbcf527a0af78 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -691,7 +691,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { @Override public void putDocuments(String packageName, String databaseName, - List documentBundles, int userId, IAppSearchBatchResultCallback callback) + List documentBundles, int userId, long binderCallStartTimeMillis, + IAppSearchBatchResultCallback callback) throws RemoteException { final List docs = new ArrayList<>(documentBundles.size()); for (Bundle bundle : documentBundles) {