Merge "Use the PlatformLogger in the platform" into sc-dev
This commit is contained in:
@@ -23,6 +23,7 @@ import android.app.appsearch.util.SchemaMigrationUtil;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.ParcelableException;
|
import android.os.ParcelableException;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.util.ArrayMap;
|
import android.util.ArrayMap;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -270,8 +271,8 @@ public final class AppSearchSession implements Closeable {
|
|||||||
documentBundles.add(documents.get(i).getBundle());
|
documentBundles.add(documents.get(i).getBundle());
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// TODO(b/173532925) a timestamp needs to be sent here to calculate binder latency
|
|
||||||
mService.putDocuments(mPackageName, mDatabaseName, documentBundles, mUserId,
|
mService.putDocuments(mPackageName, mDatabaseName, documentBundles, mUserId,
|
||||||
|
/*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(),
|
||||||
new IAppSearchBatchResultCallback.Stub() {
|
new IAppSearchBatchResultCallback.Stub() {
|
||||||
public void onResult(AppSearchBatchResult result) {
|
public void onResult(AppSearchBatchResult result) {
|
||||||
executor.execute(() -> callback.onResult(result));
|
executor.execute(() -> callback.onResult(result));
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ interface IAppSearchManager {
|
|||||||
* @param databaseName The name of the database where this document lives.
|
* @param databaseName The name of the database where this document lives.
|
||||||
* @param documentBundes List of GenericDocument bundles.
|
* @param documentBundes List of GenericDocument bundles.
|
||||||
* @param userId Id of the calling user
|
* @param userId Id of the calling user
|
||||||
|
* @param binderCallStartTimeMillis start timestamp of binder call in Millis
|
||||||
* @param callback
|
* @param callback
|
||||||
* If the call fails to start, {@link IAppSearchBatchResultCallback#onSystemError}
|
* If the call fails to start, {@link IAppSearchBatchResultCallback#onSystemError}
|
||||||
* will be called with the cause throwable. Otherwise,
|
* will be called with the cause throwable. Otherwise,
|
||||||
@@ -106,6 +107,7 @@ interface IAppSearchManager {
|
|||||||
in String databaseName,
|
in String databaseName,
|
||||||
in List<Bundle> documentBundles,
|
in List<Bundle> documentBundles,
|
||||||
in int userId,
|
in int userId,
|
||||||
|
in long binderCallStartTimeMillis,
|
||||||
in IAppSearchBatchResultCallback callback);
|
in IAppSearchBatchResultCallback callback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package com.android.server.appsearch;
|
|||||||
import static android.app.appsearch.AppSearchResult.throwableToFailedResult;
|
import static android.app.appsearch.AppSearchResult.throwableToFailedResult;
|
||||||
import static android.os.UserHandle.USER_NULL;
|
import static android.os.UserHandle.USER_NULL;
|
||||||
|
|
||||||
|
import android.annotation.ElapsedRealtimeLong;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.UserIdInt;
|
import android.annotation.UserIdInt;
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
@@ -45,6 +46,7 @@ import android.os.Bundle;
|
|||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.os.ParcelableException;
|
import android.os.ParcelableException;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
import android.util.ArrayMap;
|
import android.util.ArrayMap;
|
||||||
@@ -57,6 +59,9 @@ import com.android.internal.util.Preconditions;
|
|||||||
import com.android.server.LocalServices;
|
import com.android.server.LocalServices;
|
||||||
import com.android.server.SystemService;
|
import com.android.server.SystemService;
|
||||||
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
|
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.DataInputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
@@ -80,6 +85,7 @@ public class AppSearchManagerService extends SystemService {
|
|||||||
private PackageManagerInternal mPackageManagerInternal;
|
private PackageManagerInternal mPackageManagerInternal;
|
||||||
private ImplInstanceManager mImplInstanceManager;
|
private ImplInstanceManager mImplInstanceManager;
|
||||||
private UserManager mUserManager;
|
private UserManager mUserManager;
|
||||||
|
private LoggerInstanceManager mLoggerInstanceManager;
|
||||||
|
|
||||||
// Never call shutdownNow(). It will cancel the futures it's returned. And since
|
// 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.
|
// 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);
|
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
|
||||||
mImplInstanceManager = ImplInstanceManager.getInstance(mContext);
|
mImplInstanceManager = ImplInstanceManager.getInstance(mContext);
|
||||||
mUserManager = mContext.getSystemService(UserManager.class);
|
mUserManager = mContext.getSystemService(UserManager.class);
|
||||||
|
mLoggerInstanceManager = LoggerInstanceManager.getInstance();
|
||||||
registerReceivers();
|
registerReceivers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +154,7 @@ public class AppSearchManagerService extends SystemService {
|
|||||||
private void handleUserRemoved(@UserIdInt int userId) {
|
private void handleUserRemoved(@UserIdInt int userId) {
|
||||||
try {
|
try {
|
||||||
mImplInstanceManager.removeAppSearchImplForUser(userId);
|
mImplInstanceManager.removeAppSearchImplForUser(userId);
|
||||||
|
mLoggerInstanceManager.removePlatformLoggerForUser(userId);
|
||||||
Slog.i(TAG, "Removed AppSearchImpl instance for user: " + userId);
|
Slog.i(TAG, "Removed AppSearchImpl instance for user: " + userId);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
Slog.e(TAG, "Unable to remove data for user: " + userId, 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 String databaseName,
|
||||||
@NonNull List<Bundle> documentBundles,
|
@NonNull List<Bundle> documentBundles,
|
||||||
@UserIdInt int userId,
|
@UserIdInt int userId,
|
||||||
|
@ElapsedRealtimeLong long binderCallStartTimeMillis,
|
||||||
@NonNull IAppSearchBatchResultCallback callback) {
|
@NonNull IAppSearchBatchResultCallback callback) {
|
||||||
Preconditions.checkNotNull(packageName);
|
Preconditions.checkNotNull(packageName);
|
||||||
Preconditions.checkNotNull(databaseName);
|
Preconditions.checkNotNull(databaseName);
|
||||||
@@ -282,6 +291,11 @@ public class AppSearchManagerService extends SystemService {
|
|||||||
int callingUid = Binder.getCallingUid();
|
int callingUid = Binder.getCallingUid();
|
||||||
int callingUserId = handleIncomingUser(userId, callingUid);
|
int callingUserId = handleIncomingUser(userId, callingUid);
|
||||||
EXECUTOR.execute(() -> {
|
EXECUTOR.execute(() -> {
|
||||||
|
long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
|
||||||
|
@AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
|
||||||
|
PlatformLogger logger = null;
|
||||||
|
int operationSuccessCount = 0;
|
||||||
|
int operationFailureCount = 0;
|
||||||
try {
|
try {
|
||||||
verifyUserUnlocked(callingUserId);
|
verifyUserUnlocked(callingUserId);
|
||||||
verifyCallingPackage(callingUid, packageName);
|
verifyCallingPackage(callingUid, packageName);
|
||||||
@@ -289,20 +303,46 @@ public class AppSearchManagerService extends SystemService {
|
|||||||
new AppSearchBatchResult.Builder<>();
|
new AppSearchBatchResult.Builder<>();
|
||||||
AppSearchImpl impl =
|
AppSearchImpl impl =
|
||||||
mImplInstanceManager.getAppSearchImpl(callingUserId);
|
mImplInstanceManager.getAppSearchImpl(callingUserId);
|
||||||
|
logger = mLoggerInstanceManager.getPlatformLogger(callingUserId);
|
||||||
for (int i = 0; i < documentBundles.size(); i++) {
|
for (int i = 0; i < documentBundles.size(); i++) {
|
||||||
GenericDocument document = new GenericDocument(documentBundles.get(i));
|
GenericDocument document = new GenericDocument(documentBundles.get(i));
|
||||||
try {
|
try {
|
||||||
impl.putDocument(packageName, databaseName, document,
|
impl.putDocument(packageName, databaseName, document, logger);
|
||||||
/*logger=*/ null);
|
|
||||||
resultBuilder.setSuccess(document.getUri(), /*result=*/ null);
|
resultBuilder.setSuccess(document.getUri(), /*result=*/ null);
|
||||||
|
++operationSuccessCount;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
resultBuilder.setResult(document.getUri(),
|
resultBuilder.setResult(document.getUri(),
|
||||||
throwableToFailedResult(t));
|
throwableToFailedResult(t));
|
||||||
|
AppSearchResult<Void> 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());
|
invokeCallbackOnResult(callback, resultBuilder.build());
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
invokeCallbackOnError(callback, 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 {
|
try {
|
||||||
verifyUserUnlocked(callingUserId);
|
verifyUserUnlocked(callingUserId);
|
||||||
mImplInstanceManager.getOrCreateAppSearchImpl(mContext, callingUserId);
|
mImplInstanceManager.getOrCreateAppSearchImpl(mContext, callingUserId);
|
||||||
|
mLoggerInstanceManager.getOrCreatePlatformLogger(getContext(), callingUserId);
|
||||||
invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
|
invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
invokeCallbackOnError(callback, t);
|
invokeCallbackOnError(callback, t);
|
||||||
|
|||||||
@@ -106,7 +106,6 @@ public final class ImplInstanceManager {
|
|||||||
*
|
*
|
||||||
* @param userId The multi-user userId of the user that need to be removed.
|
* @param userId The multi-user userId of the user that need to be removed.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
|
||||||
public void removeAppSearchImplForUser(@UserIdInt int userId) {
|
public void removeAppSearchImplForUser(@UserIdInt int userId) {
|
||||||
synchronized (mInstancesLocked) {
|
synchronized (mInstancesLocked) {
|
||||||
mInstancesLocked.remove(userId);
|
mInstancesLocked.remove(userId);
|
||||||
|
|||||||
@@ -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}.
|
||||||
|
*
|
||||||
|
* <p>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<PlatformLogger> mInstancesLocked = new SparseArray<>();
|
||||||
|
|
||||||
|
private LoggerInstanceManager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an instance of {@link LoggerInstanceManager} to be used.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,21 +17,26 @@
|
|||||||
package com.android.server.appsearch.stats;
|
package com.android.server.appsearch.stats;
|
||||||
|
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.app.appsearch.exceptions.AppSearchException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.ArrayMap;
|
import android.util.ArrayMap;
|
||||||
|
import android.util.Log;
|
||||||
import android.util.SparseIntArray;
|
import android.util.SparseIntArray;
|
||||||
|
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
|
import com.android.internal.util.FrameworkStatsLog;
|
||||||
import com.android.internal.util.Preconditions;
|
import com.android.internal.util.Preconditions;
|
||||||
import com.android.server.appsearch.external.localstorage.AppSearchLogger;
|
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.CallStats;
|
||||||
import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
|
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.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
@@ -120,19 +125,18 @@ public final class PlatformLogger implements AppSearchLogger {
|
|||||||
* @param minTimeIntervalBetweenSamplesMillis minimum time interval apart in Milliseconds
|
* @param minTimeIntervalBetweenSamplesMillis minimum time interval apart in Milliseconds
|
||||||
* required for two consecutive stats logged
|
* required for two consecutive stats logged
|
||||||
* @param defaultSamplingRatio default sampling ratio
|
* @param defaultSamplingRatio default sampling ratio
|
||||||
* @param samplingRatios SparseArray to customize sampling ratio for
|
* @param samplingRatios SparseArray to customize sampling ratio for
|
||||||
* different stat types
|
* different stat types
|
||||||
*/
|
*/
|
||||||
public Config(long minTimeIntervalBetweenSamplesMillis,
|
public Config(long minTimeIntervalBetweenSamplesMillis,
|
||||||
int defaultSamplingRatio,
|
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;
|
mMinTimeIntervalBetweenSamplesMillis = minTimeIntervalBetweenSamplesMillis;
|
||||||
mDefaultSamplingRatio = defaultSamplingRatio;
|
mDefaultSamplingRatio = defaultSamplingRatio;
|
||||||
if (samplingRatios != null) {
|
mSamplingRatios = samplingRatios;
|
||||||
mSamplingRatios = samplingRatios;
|
|
||||||
} else {
|
|
||||||
mSamplingRatios = new SparseIntArray();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +173,7 @@ public final class PlatformLogger implements AppSearchLogger {
|
|||||||
Preconditions.checkNotNull(stats);
|
Preconditions.checkNotNull(stats);
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
if (shouldLogForTypeLocked(stats.getCallType())) {
|
if (shouldLogForTypeLocked(stats.getCallType())) {
|
||||||
logToWestworldLocked(stats);
|
logStatsImplLocked(stats);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,7 +184,7 @@ public final class PlatformLogger implements AppSearchLogger {
|
|||||||
Preconditions.checkNotNull(stats);
|
Preconditions.checkNotNull(stats);
|
||||||
synchronized (mLock) {
|
synchronized (mLock) {
|
||||||
if (shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)) {
|
if (shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)) {
|
||||||
logToWestworldLocked(stats);
|
logStatsImplLocked(stats);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,25 +205,103 @@ public final class PlatformLogger implements AppSearchLogger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private void logToWestworldLocked(@NonNull CallStats stats) {
|
private void logStatsImplLocked(@NonNull CallStats stats) {
|
||||||
mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
|
mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
|
||||||
ExtraStats extraStats = createExtraStatsLocked(stats.getGeneralStats().getPackageName(),
|
ExtraStats extraStats = createExtraStatsLocked(stats.getGeneralStats().getPackageName(),
|
||||||
stats.getCallType());
|
stats.getCallType());
|
||||||
/* TODO(b/173532925) Log the CallStats to Westworld
|
String database = stats.getGeneralStats().getDatabase();
|
||||||
stats.log(..., samplingRatio, skippedSampleCount, ...)
|
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")
|
@GuardedBy("mLock")
|
||||||
private void logToWestworldLocked(@NonNull PutDocumentStats stats) {
|
private void logStatsImplLocked(@NonNull PutDocumentStats stats) {
|
||||||
mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
|
mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
|
||||||
ExtraStats extraStats = createExtraStatsLocked(stats.getGeneralStats().getPackageName(),
|
ExtraStats extraStats = createExtraStatsLocked(stats.getGeneralStats().getPackageName(),
|
||||||
CallStats.CALL_TYPE_PUT_DOCUMENT);
|
CallStats.CALL_TYPE_PUT_DOCUMENT);
|
||||||
/* TODO(b/173532925) Log the PutDocumentStats to Westworld
|
String database = stats.getGeneralStats().getDatabase();
|
||||||
stats.log(..., samplingRatio, skippedSampleCount, ...)
|
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.
|
||||||
|
*
|
||||||
|
* <p>This method is called by most of logToWestworldLocked functions to reduce code
|
||||||
|
* duplication.
|
||||||
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.eq;
|
|||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.ContextWrapper;
|
import android.content.ContextWrapper;
|
||||||
import android.content.pm.PackageManager;
|
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.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
public class PlatformLoggerTest {
|
public class PlatformLoggerTest {
|
||||||
private static final int TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 100;
|
private static final int TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 100;
|
||||||
private static final int TEST_DEFAULT_SAMPLING_RATIO = 10;
|
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
|
@Test
|
||||||
public void testcreateExtraStatsLocked_nullSamplingRatioMap_returnsDefaultSamplingRatio() {
|
public void testCreateExtraStatsLocked_nullSamplingRatioMap_returnsDefaultSamplingRatio() {
|
||||||
PlatformLogger logger = new PlatformLogger(
|
PlatformLogger logger = new PlatformLogger(
|
||||||
ApplicationProvider.getApplicationContext(),
|
ApplicationProvider.getApplicationContext(),
|
||||||
UserHandle.USER_NULL,
|
UserHandle.USER_NULL,
|
||||||
new PlatformLogger.Config(
|
new PlatformLogger.Config(
|
||||||
TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
|
TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
|
||||||
TEST_DEFAULT_SAMPLING_RATIO,
|
TEST_DEFAULT_SAMPLING_RATIO,
|
||||||
/*samplingRatioMap=*/ null));
|
/*samplingRatios=*/ new SparseIntArray()));
|
||||||
|
|
||||||
// Make sure default sampling ratio is used if samplingMap is not provided.
|
// Make sure default sampling ratio is used if samplingMap is not provided.
|
||||||
assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
|
assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
|
||||||
@@ -84,7 +98,7 @@ public class PlatformLoggerTest {
|
|||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testcreateExtraStatsLocked_with_samplingRatioMap_returnsConfiguredSamplingRatio() {
|
public void testCreateExtraStatsLocked_with_samplingRatioMap_returnsConfiguredSamplingRatio() {
|
||||||
int putDocumentSamplingRatio = 1;
|
int putDocumentSamplingRatio = 1;
|
||||||
int querySamplingRatio = 2;
|
int querySamplingRatio = 2;
|
||||||
final SparseIntArray samplingRatios = new SparseIntArray();
|
final SparseIntArray samplingRatios = new SparseIntArray();
|
||||||
@@ -98,8 +112,8 @@ public class PlatformLoggerTest {
|
|||||||
TEST_DEFAULT_SAMPLING_RATIO,
|
TEST_DEFAULT_SAMPLING_RATIO,
|
||||||
samplingRatios));
|
samplingRatios));
|
||||||
|
|
||||||
// The default sampling ratio should be used if no sampling ratio is
|
// The default sampling ratio should be used if no sampling ratio is
|
||||||
// provided for certain call type.
|
// provided for certain call type.
|
||||||
assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
|
assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
|
||||||
CallStats.CALL_TYPE_INITIALIZE).mSamplingRatio).isEqualTo(
|
CallStats.CALL_TYPE_INITIALIZE).mSamplingRatio).isEqualTo(
|
||||||
TEST_DEFAULT_SAMPLING_RATIO);
|
TEST_DEFAULT_SAMPLING_RATIO);
|
||||||
@@ -117,6 +131,70 @@ public class PlatformLoggerTest {
|
|||||||
querySamplingRatio);
|
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
|
@Test
|
||||||
public void testShouldLogForTypeLocked_trueWhenSampleRatioIsOne() {
|
public void testShouldLogForTypeLocked_trueWhenSampleRatioIsOne() {
|
||||||
final int samplingRatio = 1;
|
final int samplingRatio = 1;
|
||||||
@@ -127,7 +205,7 @@ public class PlatformLoggerTest {
|
|||||||
new PlatformLogger.Config(
|
new PlatformLogger.Config(
|
||||||
TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
|
TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
|
||||||
samplingRatio,
|
samplingRatio,
|
||||||
/* samplingMap=*/ null));
|
/*samplingRatios=*/ new SparseIntArray()));
|
||||||
|
|
||||||
// Sample should always be logged for the first time if sampling is disabled(value is one).
|
// 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();
|
assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isTrue();
|
||||||
@@ -145,7 +223,7 @@ public class PlatformLoggerTest {
|
|||||||
new PlatformLogger.Config(
|
new PlatformLogger.Config(
|
||||||
TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
|
TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
|
||||||
samplingRatio,
|
samplingRatio,
|
||||||
/* samplingMap=*/ null));
|
/*samplingRatios=*/ new SparseIntArray()));
|
||||||
|
|
||||||
// Makes sure sample will be excluded due to sampling if sample ratio is negative.
|
// Makes sure sample will be excluded due to sampling if sample ratio is negative.
|
||||||
assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isFalse();
|
assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isFalse();
|
||||||
@@ -167,7 +245,7 @@ public class PlatformLoggerTest {
|
|||||||
new PlatformLogger.Config(
|
new PlatformLogger.Config(
|
||||||
minTimeIntervalBetweenSamplesMillis,
|
minTimeIntervalBetweenSamplesMillis,
|
||||||
samplingRatio,
|
samplingRatio,
|
||||||
/* samplingMap=*/ null));
|
/*samplingRatios=*/ new SparseIntArray()));
|
||||||
logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
|
logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
|
||||||
|
|
||||||
// Makes sure sample will be excluded due to rate limiting if samples are too close.
|
// 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(
|
new PlatformLogger.Config(
|
||||||
minTimeIntervalBetweenSamplesMillis,
|
minTimeIntervalBetweenSamplesMillis,
|
||||||
samplingRatio,
|
samplingRatio,
|
||||||
/* samplingMap=*/ null));
|
/*samplingRatios=*/ new SparseIntArray()));
|
||||||
logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
|
logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
|
||||||
|
|
||||||
// Makes sure sample will be logged if it is not too close to previous sample.
|
// 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(
|
new PlatformLogger.Config(
|
||||||
TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
|
TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
|
||||||
TEST_DEFAULT_SAMPLING_RATIO,
|
TEST_DEFAULT_SAMPLING_RATIO,
|
||||||
/* samplingMap=*/ null));
|
/*samplingRatios=*/ new SparseIntArray()));
|
||||||
mMockPackageManager.mockGetPackageUidAsUser(testPackageName, mContext.getUserId(), testUid);
|
mMockPackageManager.mockGetPackageUidAsUser(testPackageName, mContext.getUserId(), testUid);
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -691,7 +691,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putDocuments(String packageName, String databaseName,
|
public void putDocuments(String packageName, String databaseName,
|
||||||
List<Bundle> documentBundles, int userId, IAppSearchBatchResultCallback callback)
|
List<Bundle> documentBundles, int userId, long binderCallStartTimeMillis,
|
||||||
|
IAppSearchBatchResultCallback callback)
|
||||||
throws RemoteException {
|
throws RemoteException {
|
||||||
final List<GenericDocument> docs = new ArrayList<>(documentBundles.size());
|
final List<GenericDocument> docs = new ArrayList<>(documentBundles.size());
|
||||||
for (Bundle bundle : documentBundles) {
|
for (Bundle bundle : documentBundles) {
|
||||||
|
|||||||
Reference in New Issue
Block a user