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) {