Merge "Update VisibilityStore to handle role-based system visibility." into sc-dev
This commit is contained in:
@@ -16,18 +16,15 @@
|
||||
|
||||
package com.android.server.appsearch;
|
||||
|
||||
import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY;
|
||||
|
||||
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.Environment;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
|
||||
import com.android.server.appsearch.external.localstorage.AppSearchLogger;
|
||||
@@ -49,12 +46,6 @@ public final class ImplInstanceManager {
|
||||
@GuardedBy("mInstancesLocked")
|
||||
private final Map<UserHandle, AppSearchImpl> mInstancesLocked = new ArrayMap<>();
|
||||
|
||||
private final String mGlobalQuerierPackage;
|
||||
|
||||
private ImplInstanceManager(@NonNull String globalQuerierPackage) {
|
||||
mGlobalQuerierPackage = globalQuerierPackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an instance of ImplInstanceManager to be used.
|
||||
*
|
||||
@@ -66,9 +57,7 @@ public final class ImplInstanceManager {
|
||||
if (sImplInstanceManager == null) {
|
||||
synchronized (ImplInstanceManager.class) {
|
||||
if (sImplInstanceManager == null) {
|
||||
sImplInstanceManager =
|
||||
new ImplInstanceManager(
|
||||
getGlobalAppSearchDataQuerierPackageName(context));
|
||||
sImplInstanceManager = new ImplInstanceManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,7 +80,7 @@ public final class ImplInstanceManager {
|
||||
* <p>If no AppSearchImpl instance exists for the unlocked user, Icing will be initialized and
|
||||
* one will be created.
|
||||
*
|
||||
* @param context The context
|
||||
* @param context The system context
|
||||
* @param userHandle The multi-user handle of the device user calling AppSearch
|
||||
* @return An initialized {@link AppSearchImpl} for this user
|
||||
*/
|
||||
@@ -106,7 +95,8 @@ public final class ImplInstanceManager {
|
||||
synchronized (mInstancesLocked) {
|
||||
AppSearchImpl instance = mInstancesLocked.get(userHandle);
|
||||
if (instance == null) {
|
||||
instance = createImpl(context, userHandle, logger);
|
||||
Context userContext = context.createContextAsUser(userHandle, /*flags=*/ 0);
|
||||
instance = createImpl(userContext, userHandle, logger);
|
||||
mInstancesLocked.put(userHandle, instance);
|
||||
}
|
||||
return instance;
|
||||
@@ -177,7 +167,7 @@ public final class ImplInstanceManager {
|
||||
}
|
||||
|
||||
private AppSearchImpl createImpl(
|
||||
@NonNull Context context,
|
||||
@NonNull Context userContext,
|
||||
@NonNull UserHandle userHandle,
|
||||
@Nullable AppSearchLogger logger)
|
||||
throws AppSearchException {
|
||||
@@ -185,33 +175,8 @@ public final class ImplInstanceManager {
|
||||
// TODO(b/181787682): Swap AppSearchImpl and VisibilityStore to accept a UserHandle too
|
||||
return AppSearchImpl.create(
|
||||
appSearchDir,
|
||||
context,
|
||||
userContext,
|
||||
userHandle.getIdentifier(),
|
||||
mGlobalQuerierPackage,
|
||||
/*logger=*/ null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the global querier package if it's a system package. Otherwise, empty string.
|
||||
*
|
||||
* @param context Context of the system service.
|
||||
*/
|
||||
@NonNull
|
||||
private static String getGlobalAppSearchDataQuerierPackageName(@NonNull Context context) {
|
||||
String globalAppSearchDataQuerierPackage =
|
||||
context.getString(R.string.config_globalAppSearchDataQuerierPackage);
|
||||
try {
|
||||
if (context.getPackageManager()
|
||||
.getPackageInfoAsUser(
|
||||
globalAppSearchDataQuerierPackage,
|
||||
MATCH_FACTORY_ONLY,
|
||||
UserHandle.USER_SYSTEM)
|
||||
== null) {
|
||||
return "";
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return "";
|
||||
}
|
||||
return globalAppSearchDataQuerierPackage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.server.appsearch.external.localstorage;
|
||||
|
||||
import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.addPrefixToDocument;
|
||||
import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.createPackagePrefix;
|
||||
import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.createPrefix;
|
||||
import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.getDatabaseName;
|
||||
import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.getPackageName;
|
||||
@@ -60,6 +59,7 @@ import com.android.server.appsearch.external.localstorage.stats.InitializeStats;
|
||||
import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
|
||||
import com.android.server.appsearch.external.localstorage.stats.RemoveStats;
|
||||
import com.android.server.appsearch.external.localstorage.stats.SearchStats;
|
||||
import com.android.server.appsearch.visibilitystore.VisibilityStore;
|
||||
|
||||
import com.google.android.icing.IcingSearchEngine;
|
||||
import com.google.android.icing.proto.DeleteByQueryResultProto;
|
||||
@@ -98,6 +98,7 @@ import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -199,14 +200,12 @@ public final class AppSearchImpl implements Closeable {
|
||||
@NonNull
|
||||
public static AppSearchImpl create(
|
||||
@NonNull File icingDir,
|
||||
@NonNull Context context,
|
||||
@NonNull Context userContext,
|
||||
int userId,
|
||||
@NonNull String globalQuerierPackage,
|
||||
@Nullable AppSearchLogger logger)
|
||||
throws AppSearchException {
|
||||
Objects.requireNonNull(icingDir);
|
||||
Objects.requireNonNull(context);
|
||||
Objects.requireNonNull(globalQuerierPackage);
|
||||
Objects.requireNonNull(userContext);
|
||||
|
||||
long totalLatencyStartMillis = SystemClock.elapsedRealtime();
|
||||
InitializeStats.Builder initStatsBuilder = null;
|
||||
@@ -215,8 +214,7 @@ public final class AppSearchImpl implements Closeable {
|
||||
}
|
||||
|
||||
AppSearchImpl appSearchImpl =
|
||||
new AppSearchImpl(
|
||||
icingDir, context, userId, globalQuerierPackage, initStatsBuilder);
|
||||
new AppSearchImpl(icingDir, userContext, userId, initStatsBuilder);
|
||||
|
||||
long prepareVisibilityStoreLatencyStartMillis = SystemClock.elapsedRealtime();
|
||||
appSearchImpl.initializeVisibilityStore();
|
||||
@@ -239,9 +237,8 @@ public final class AppSearchImpl implements Closeable {
|
||||
/** @param initStatsBuilder collects stats for initialization if provided. */
|
||||
private AppSearchImpl(
|
||||
@NonNull File icingDir,
|
||||
@NonNull Context context,
|
||||
@NonNull Context userContext,
|
||||
int userId,
|
||||
@NonNull String globalQuerierPackage,
|
||||
@Nullable InitializeStats.Builder initStatsBuilder)
|
||||
throws AppSearchException {
|
||||
mReadWriteLock.writeLock().lock();
|
||||
@@ -259,8 +256,7 @@ public final class AppSearchImpl implements Closeable {
|
||||
"Constructing IcingSearchEngine, response",
|
||||
Objects.hashCode(mIcingSearchEngineLocked));
|
||||
|
||||
mVisibilityStoreLocked =
|
||||
new VisibilityStore(this, context, userId, globalQuerierPackage);
|
||||
mVisibilityStoreLocked = new VisibilityStore(this, userContext, userId);
|
||||
|
||||
// The core initialization procedure. If any part of this fails, we bail into
|
||||
// resetLocked(), deleting all data (but hopefully allowing AppSearchImpl to come up).
|
||||
@@ -495,7 +491,8 @@ public final class AppSearchImpl implements Closeable {
|
||||
}
|
||||
|
||||
mVisibilityStoreLocked.setVisibility(
|
||||
prefix,
|
||||
packageName,
|
||||
databaseName,
|
||||
prefixedSchemasNotPlatformSurfaceable,
|
||||
prefixedSchemasPackageAccessible);
|
||||
|
||||
@@ -844,17 +841,17 @@ public final class AppSearchImpl implements Closeable {
|
||||
try {
|
||||
throwIfClosedLocked();
|
||||
|
||||
// Convert package filters to prefix filters
|
||||
Set<String> packageFilters = new ArraySet<>(searchSpec.getFilterPackageNames());
|
||||
Set<String> prefixFilters = new ArraySet<>();
|
||||
Set<String> allPrefixes = mNamespaceMapLocked.keySet();
|
||||
if (packageFilters.isEmpty()) {
|
||||
// Client didn't restrict their search over packages. Try to query over all
|
||||
// packages/prefixes
|
||||
prefixFilters = allPrefixes;
|
||||
prefixFilters = mNamespaceMapLocked.keySet();
|
||||
} else {
|
||||
// Client did restrict their search over packages. Only include the prefixes that
|
||||
// belong to the specified packages.
|
||||
for (String prefix : allPrefixes) {
|
||||
for (String prefix : mNamespaceMapLocked.keySet()) {
|
||||
String packageName = getPackageName(prefix);
|
||||
if (packageFilters.contains(packageName)) {
|
||||
prefixFilters.add(prefix);
|
||||
@@ -862,41 +859,50 @@ public final class AppSearchImpl implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
// Find which schemas the client is allowed to query over.
|
||||
Set<String> allowedPrefixedSchemas = new ArraySet<>();
|
||||
List<String> schemaFilters = searchSpec.getFilterSchemas();
|
||||
// Convert schema filters to prefixed schema filters
|
||||
ArraySet<String> prefixedSchemaFilters = new ArraySet<>();
|
||||
for (String prefix : prefixFilters) {
|
||||
String packageName = getPackageName(prefix);
|
||||
|
||||
if (!schemaFilters.isEmpty()) {
|
||||
for (String schema : schemaFilters) {
|
||||
// Client specified some schemas to search over, check each one
|
||||
String prefixedSchema = prefix + schema;
|
||||
if (packageName.equals(callerPackageName)
|
||||
|| mVisibilityStoreLocked.isSchemaSearchableByCaller(
|
||||
prefix, prefixedSchema, callerUid)) {
|
||||
allowedPrefixedSchemas.add(prefixedSchema);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
List<String> schemaFilters = searchSpec.getFilterSchemas();
|
||||
if (schemaFilters.isEmpty()) {
|
||||
// Client didn't specify certain schemas to search over, check all schemas
|
||||
Map<String, SchemaTypeConfigProto> prefixedSchemas =
|
||||
mSchemaMapLocked.get(prefix);
|
||||
if (prefixedSchemas != null) {
|
||||
for (String prefixedSchema : prefixedSchemas.keySet()) {
|
||||
if (packageName.equals(callerPackageName)
|
||||
|| mVisibilityStoreLocked.isSchemaSearchableByCaller(
|
||||
prefix, prefixedSchema, callerUid)) {
|
||||
allowedPrefixedSchemas.add(prefixedSchema);
|
||||
}
|
||||
}
|
||||
prefixedSchemaFilters.addAll(mSchemaMapLocked.get(prefix).keySet());
|
||||
} else {
|
||||
// Client specified some schemas to search over, check each one
|
||||
for (int i = 0; i < schemaFilters.size(); i++) {
|
||||
prefixedSchemaFilters.add(prefix + schemaFilters.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the schemas the client is not allowed to search over
|
||||
Iterator<String> prefixedSchemaIt = prefixedSchemaFilters.iterator();
|
||||
while (prefixedSchemaIt.hasNext()) {
|
||||
String prefixedSchema = prefixedSchemaIt.next();
|
||||
String packageName = getPackageName(prefixedSchema);
|
||||
|
||||
boolean allow;
|
||||
if (packageName.equals(callerPackageName)) {
|
||||
// Callers can always retrieve their own data
|
||||
allow = true;
|
||||
} else {
|
||||
String databaseName = getDatabaseName(prefixedSchema);
|
||||
allow =
|
||||
mVisibilityStoreLocked.isSchemaSearchableByCaller(
|
||||
packageName,
|
||||
databaseName,
|
||||
prefixedSchema,
|
||||
callerPackageName,
|
||||
callerUid);
|
||||
}
|
||||
|
||||
if (!allow) {
|
||||
prefixedSchemaIt.remove();
|
||||
}
|
||||
}
|
||||
|
||||
return doQueryLocked(
|
||||
prefixFilters,
|
||||
allowedPrefixedSchemas,
|
||||
prefixedSchemaFilters,
|
||||
queryExpression,
|
||||
searchSpec,
|
||||
sStatsBuilder);
|
||||
@@ -1404,20 +1410,47 @@ public final class AppSearchImpl implements Closeable {
|
||||
mReadWriteLock.writeLock().lock();
|
||||
try {
|
||||
throwIfClosedLocked();
|
||||
Set<String> existingPackages = getPackageToDatabases().keySet();
|
||||
if (existingPackages.contains(packageName)) {
|
||||
existingPackages.remove(packageName);
|
||||
prunePackageData(existingPackages);
|
||||
}
|
||||
} finally {
|
||||
mReadWriteLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all {@link AppSearchSchema}s and {@link GenericDocument}s that doesn't belong to any
|
||||
* of the given installed packages
|
||||
*
|
||||
* @param installedPackages The name of all installed package.
|
||||
* @throws AppSearchException if we cannot remove the data.
|
||||
*/
|
||||
public void prunePackageData(@NonNull Set<String> installedPackages) throws AppSearchException {
|
||||
mReadWriteLock.writeLock().lock();
|
||||
try {
|
||||
throwIfClosedLocked();
|
||||
Map<String, Set<String>> packageToDatabases = getPackageToDatabases();
|
||||
if (installedPackages.containsAll(packageToDatabases.keySet())) {
|
||||
// No package got removed. We are good.
|
||||
return;
|
||||
}
|
||||
|
||||
// Prune schema proto
|
||||
SchemaProto existingSchema = getSchemaProtoLocked();
|
||||
SchemaProto.Builder newSchemaBuilder = SchemaProto.newBuilder();
|
||||
|
||||
String prefix = createPackagePrefix(packageName);
|
||||
for (int i = 0; i < existingSchema.getTypesCount(); i++) {
|
||||
if (!existingSchema.getTypes(i).getSchemaType().startsWith(prefix)) {
|
||||
String packageName = getPackageName(existingSchema.getTypes(i).getSchemaType());
|
||||
if (installedPackages.contains(packageName)) {
|
||||
newSchemaBuilder.addTypes(existingSchema.getTypes(i));
|
||||
}
|
||||
}
|
||||
|
||||
SchemaProto finalSchema = newSchemaBuilder.build();
|
||||
|
||||
// Apply schema, set force override to true to remove all schemas and documents under
|
||||
// that package.
|
||||
// Apply schema, set force override to true to remove all schemas and documents that
|
||||
// doesn't belong to any of these installed packages.
|
||||
mLogUtil.piiTrace(
|
||||
"clearPackageData.setSchema, request",
|
||||
finalSchema.getTypesCount(),
|
||||
@@ -1432,6 +1465,20 @@ public final class AppSearchImpl implements Closeable {
|
||||
|
||||
// Determine whether it succeeded.
|
||||
checkSuccess(setSchemaResultProto.getStatus());
|
||||
|
||||
// Prune cached maps
|
||||
for (Map.Entry<String, Set<String>> entry : packageToDatabases.entrySet()) {
|
||||
String packageName = entry.getKey();
|
||||
Set<String> databaseNames = entry.getValue();
|
||||
if (!installedPackages.contains(packageName) && databaseNames != null) {
|
||||
for (String databaseName : databaseNames) {
|
||||
String removedPrefix = createPrefix(packageName, databaseName);
|
||||
mSchemaMapLocked.remove(removedPrefix);
|
||||
mNamespaceMapLocked.remove(removedPrefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO(b/145759910) clear visibility setting for package.
|
||||
} finally {
|
||||
mReadWriteLock.writeLock().unlock();
|
||||
}
|
||||
@@ -1857,14 +1904,6 @@ public final class AppSearchImpl implements Closeable {
|
||||
return schemaProto.getSchema();
|
||||
}
|
||||
|
||||
/** Returns a set of all prefixes AppSearchImpl knows about. */
|
||||
// TODO(b/180058203): Remove this method once platform has switched away from using this method.
|
||||
@GuardedBy("mReadWriteLock")
|
||||
@NonNull
|
||||
Set<String> getPrefixesLocked() {
|
||||
return mSchemaMapLocked.keySet();
|
||||
}
|
||||
|
||||
private static void addToMap(
|
||||
Map<String, Set<String>> map, String prefix, String prefixedValue) {
|
||||
Set<String> values = map.get(prefix);
|
||||
|
||||
@@ -26,36 +26,52 @@ import java.util.Set;
|
||||
* {@link android.app.appsearch.SetSchemaRequest.Builder#setSchemaTypeDisplayedBySystem} API.
|
||||
*
|
||||
* This object is not thread safe.
|
||||
* @hide
|
||||
*/
|
||||
public class NotPlatformSurfaceableMap {
|
||||
class NotPlatformSurfaceableMap {
|
||||
/**
|
||||
* Maps prefixes to the set of prefixed schemas that are platform-hidden within that prefix.
|
||||
* Maps packages to databases to the set of prefixed schemas that are platform-hidden within
|
||||
* that database.
|
||||
*/
|
||||
private final Map<String, Set<String>> mMap = new ArrayMap<>();
|
||||
private final Map<String, Map<String, Set<String>>> mMap = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* Sets the prefixed schemas that are opted out of platform surfacing for the prefix.
|
||||
* Sets the prefixed schemas that are opted out of platform surfacing for the database.
|
||||
*
|
||||
* <p>Any existing mappings for this prefix are overwritten.
|
||||
*/
|
||||
public void setNotPlatformSurfaceable(@NonNull String prefix, @NonNull Set<String> schemas) {
|
||||
mMap.put(prefix, schemas);
|
||||
public void setNotPlatformSurfaceable(
|
||||
@NonNull String packageName,
|
||||
@NonNull String databaseName,
|
||||
@NonNull Set<String> prefixedSchemas) {
|
||||
Map<String, Set<String>> databaseToSchemas = mMap.get(packageName);
|
||||
if (databaseToSchemas == null) {
|
||||
databaseToSchemas = new ArrayMap<>();
|
||||
mMap.put(packageName, databaseToSchemas);
|
||||
}
|
||||
databaseToSchemas.put(databaseName, prefixedSchemas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given prefixed schema is platform surfaceable (has not opted out) in the
|
||||
* given prefix.
|
||||
* given database.
|
||||
*/
|
||||
public boolean isSchemaPlatformSurfaceable(@NonNull String prefix, @NonNull String schemaType) {
|
||||
Set<String> schemaTypes = mMap.get(prefix);
|
||||
public boolean isSchemaPlatformSurfaceable(
|
||||
@NonNull String packageName,
|
||||
@NonNull String databaseName,
|
||||
@NonNull String prefixedSchema) {
|
||||
Map<String, Set<String>> databaseToSchemaType = mMap.get(packageName);
|
||||
if (databaseToSchemaType == null) {
|
||||
// No opt-outs for this package
|
||||
return true;
|
||||
}
|
||||
Set<String> schemaTypes = databaseToSchemaType.get(databaseName);
|
||||
if (schemaTypes == null) {
|
||||
// No opt-outs for this prefix
|
||||
// No opt-outs for this database
|
||||
return true;
|
||||
}
|
||||
// Some schemas were opted out of being platform-surfaced. As long as this schema
|
||||
// isn't one of those opt-outs, it's surfaceable.
|
||||
return !schemaTypes.contains(schemaType);
|
||||
return !schemaTypes.contains(prefixedSchema);
|
||||
}
|
||||
|
||||
/** Discards all data in the map. */
|
||||
|
||||
@@ -26,9 +26,8 @@ import androidx.annotation.Nullable;
|
||||
* Holds configuration about a package+cert that can access a schema.
|
||||
*
|
||||
* @see android.app.appsearch.SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage
|
||||
* @hide
|
||||
*/
|
||||
public class PackageAccessibleDocument extends GenericDocument {
|
||||
class PackageAccessibleDocument extends GenericDocument {
|
||||
/** Schema type for nested documents that hold package accessible information. */
|
||||
public static final String SCHEMA_TYPE = "PackageAccessibleType";
|
||||
|
||||
|
||||
@@ -28,23 +28,31 @@ import java.util.Set;
|
||||
* {@link android.app.appsearch.SetSchemaRequest.Builder#setSchemaTypeVisibilityForPackage} API.
|
||||
*
|
||||
* This object is not thread safe.
|
||||
* @hide
|
||||
*/
|
||||
public class PackageAccessibleMap {
|
||||
class PackageAccessibleMap {
|
||||
/**
|
||||
* Maps prefixes to prefixed schema types to PackageIdentifiers that have access to that schema.
|
||||
* Maps packages to databases to prefixed schemas to PackageIdentifiers that have access to that
|
||||
* schema.
|
||||
*/
|
||||
private final Map<String, Map<String, Set<PackageIdentifier>>> mMap = new ArrayMap<>();
|
||||
private final Map<String, Map<String, Map<String, Set<PackageIdentifier>>>> mMap =
|
||||
new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* Sets the prefixed schemas that have package visibility in the given prefix.
|
||||
* Sets the prefixed schemas that have package visibility in the given database.
|
||||
*
|
||||
* <p>Any existing mappings for this prefix are overwritten.
|
||||
*/
|
||||
public void setPackageAccessible(
|
||||
@NonNull String prefix,
|
||||
@NonNull String packageName,
|
||||
@NonNull String databaseName,
|
||||
@NonNull Map<String, Set<PackageIdentifier>> schemaToPackageIdentifier) {
|
||||
mMap.put(prefix, schemaToPackageIdentifier);
|
||||
Map<String, Map<String, Set<PackageIdentifier>>> databaseToSchemaTypeToVisibility =
|
||||
mMap.get(packageName);
|
||||
if (databaseToSchemaTypeToVisibility == null) {
|
||||
databaseToSchemaTypeToVisibility = new ArrayMap<>();
|
||||
mMap.put(packageName, databaseToSchemaTypeToVisibility);
|
||||
}
|
||||
databaseToSchemaTypeToVisibility.put(databaseName, schemaToPackageIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,12 +63,20 @@ public class PackageAccessibleMap {
|
||||
*/
|
||||
@NonNull
|
||||
public Set<PackageIdentifier> getAccessiblePackages(
|
||||
@NonNull String prefix, @NonNull String schemaType) {
|
||||
Map<String, Set<PackageIdentifier>> schemaTypeToVisibility = mMap.get(prefix);
|
||||
@NonNull String packageName,
|
||||
@NonNull String databaseName,
|
||||
@NonNull String prefixedSchema) {
|
||||
Map<String, Map<String, Set<PackageIdentifier>>> databaseToSchemaTypeToVisibility =
|
||||
mMap.get(packageName);
|
||||
if (databaseToSchemaTypeToVisibility == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Map<String, Set<PackageIdentifier>> schemaTypeToVisibility =
|
||||
databaseToSchemaTypeToVisibility.get(databaseName);
|
||||
if (schemaTypeToVisibility == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<PackageIdentifier> accessiblePackages = schemaTypeToVisibility.get(schemaType);
|
||||
Set<PackageIdentifier> accessiblePackages = schemaTypeToVisibility.get(prefixedSchema);
|
||||
if (accessiblePackages == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@@ -21,11 +21,8 @@ import android.app.appsearch.GenericDocument;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Holds the visibility settings that apply to a package's databases.
|
||||
* @hide
|
||||
*/
|
||||
public class VisibilityDocument extends GenericDocument {
|
||||
/** Holds the visibility settings that apply to a package's databases. */
|
||||
class VisibilityDocument extends GenericDocument {
|
||||
/** Schema type for documents that hold AppSearch's metadata, e.g. visibility settings */
|
||||
public static final String SCHEMA_TYPE = "VisibilityType";
|
||||
|
||||
|
||||
@@ -13,11 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.server.appsearch.visibilitystore;
|
||||
|
||||
// TODO(b/169883602): This is purposely a different package from the path so that it can access
|
||||
// AppSearchImpl's methods without having to make them public. This should be moved into a proper
|
||||
// package once AppSearchImpl-VisibilityStore's dependencies are refactored.
|
||||
package com.android.server.appsearch.external.localstorage;
|
||||
import static android.Manifest.permission.READ_GLOBAL_APP_SEARCH_DATA;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
@@ -33,13 +31,10 @@ import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
|
||||
import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
|
||||
import com.android.server.appsearch.visibilitystore.NotPlatformSurfaceableMap;
|
||||
import com.android.server.appsearch.visibilitystore.PackageAccessibleDocument;
|
||||
import com.android.server.appsearch.visibilitystore.PackageAccessibleMap;
|
||||
import com.android.server.appsearch.visibilitystore.VisibilityDocument;
|
||||
|
||||
import com.google.android.icing.proto.PersistType;
|
||||
|
||||
@@ -85,38 +80,24 @@ public class VisibilityStore {
|
||||
* These cannot have any of the special characters used by AppSearchImpl (e.g. {@code
|
||||
* AppSearchImpl#PACKAGE_DELIMITER} or {@code AppSearchImpl#DATABASE_DELIMITER}.
|
||||
*/
|
||||
static final String PACKAGE_NAME = "VS#Pkg";
|
||||
@VisibleForTesting public static final String PACKAGE_NAME = "VS#Pkg";
|
||||
|
||||
static final String DATABASE_NAME = "VS#Db";
|
||||
|
||||
/**
|
||||
* Prefix that AppSearchImpl creates for the VisibilityStore based on our package name and
|
||||
* database name. Tracked here to tell when we're looking at our own prefix when looking through
|
||||
* AppSearchImpl.
|
||||
*/
|
||||
static final String VISIBILITY_STORE_PREFIX =
|
||||
PrefixUtil.createPrefix(PACKAGE_NAME, DATABASE_NAME);
|
||||
@VisibleForTesting public static final String DATABASE_NAME = "VS#Db";
|
||||
|
||||
/** Namespace of documents that contain visibility settings */
|
||||
private static final String NAMESPACE = "";
|
||||
|
||||
/**
|
||||
* Prefix to add to all visibility document ids. IcingSearchEngine doesn't allow empty ids.
|
||||
*/
|
||||
/** Prefix to add to all visibility document ids. IcingSearchEngine doesn't allow empty ids. */
|
||||
private static final String ID_PREFIX = "uri:";
|
||||
|
||||
private final AppSearchImpl mAppSearchImpl;
|
||||
|
||||
// Context of the system service.
|
||||
private final Context mContext;
|
||||
// Context of the user that the call is being made as.
|
||||
private final Context mUserContext;
|
||||
|
||||
// User ID of the caller who we're checking visibility settings for.
|
||||
private final int mUserId;
|
||||
|
||||
// UID of the package that has platform-query privileges, i.e. can query for all
|
||||
// platform-surfaceable content.
|
||||
private int mGlobalQuerierUid;
|
||||
|
||||
/** Stores the schemas that are platform-hidden. All values are prefixed. */
|
||||
private final NotPlatformSurfaceableMap mNotPlatformSurfaceableMap =
|
||||
new NotPlatformSurfaceableMap();
|
||||
@@ -129,16 +110,15 @@ public class VisibilityStore {
|
||||
* before using the object.
|
||||
*
|
||||
* @param appSearchImpl AppSearchImpl instance
|
||||
* @param userContext Context of the user that the call is being made as
|
||||
*/
|
||||
public VisibilityStore(
|
||||
@NonNull AppSearchImpl appSearchImpl,
|
||||
@NonNull Context context,
|
||||
@UserIdInt int userId,
|
||||
@NonNull String globalQuerierPackage) {
|
||||
@NonNull Context userContext,
|
||||
@UserIdInt int userId) {
|
||||
mAppSearchImpl = appSearchImpl;
|
||||
mContext = context;
|
||||
mUserContext = userContext;
|
||||
mUserId = userId;
|
||||
mGlobalQuerierUid = getGlobalQuerierUid(globalQuerierPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -181,28 +161,44 @@ public class VisibilityStore {
|
||||
|
||||
// Populate visibility settings set
|
||||
mNotPlatformSurfaceableMap.clear();
|
||||
for (String prefix : mAppSearchImpl.getPrefixesLocked()) {
|
||||
if (prefix.equals(VISIBILITY_STORE_PREFIX)) {
|
||||
// Our own prefix. Skip
|
||||
continue;
|
||||
for (Map.Entry<String, Set<String>> entry :
|
||||
mAppSearchImpl.getPackageToDatabases().entrySet()) {
|
||||
String packageName = entry.getKey();
|
||||
if (packageName.equals(PACKAGE_NAME)) {
|
||||
continue; // Our own package. Skip.
|
||||
}
|
||||
|
||||
try {
|
||||
// Note: We use the other clients' prefixed names as ids
|
||||
VisibilityDocument visibilityDocument = new VisibilityDocument(
|
||||
mAppSearchImpl.getDocument(
|
||||
PACKAGE_NAME,
|
||||
DATABASE_NAME,
|
||||
NAMESPACE,
|
||||
/*id=*/ addIdPrefix(prefix),
|
||||
/*typePropertyPaths=*/ Collections.emptyMap()));
|
||||
for (String databaseName : entry.getValue()) {
|
||||
VisibilityDocument visibilityDocument;
|
||||
try {
|
||||
// Note: We use the other clients' prefixed names as ids
|
||||
visibilityDocument =
|
||||
new VisibilityDocument(
|
||||
mAppSearchImpl.getDocument(
|
||||
PACKAGE_NAME,
|
||||
DATABASE_NAME,
|
||||
NAMESPACE,
|
||||
/*id=*/ getVisibilityDocumentId(
|
||||
packageName, databaseName),
|
||||
/*typePropertyPaths=*/ Collections.emptyMap()));
|
||||
} catch (AppSearchException e) {
|
||||
if (e.getResultCode() == AppSearchResult.RESULT_NOT_FOUND) {
|
||||
// TODO(b/172068212): This indicates some desync error. We were expecting a
|
||||
// document, but didn't find one. Should probably reset AppSearch instead
|
||||
// of ignoring it.
|
||||
continue;
|
||||
}
|
||||
// Otherwise, this is some other error we should pass up.
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Update platform visibility settings
|
||||
String[] notPlatformSurfaceableSchemas =
|
||||
visibilityDocument.getNotPlatformSurfaceableSchemas();
|
||||
if (notPlatformSurfaceableSchemas != null) {
|
||||
mNotPlatformSurfaceableMap.setNotPlatformSurfaceable(
|
||||
prefix,
|
||||
packageName,
|
||||
databaseName,
|
||||
new ArraySet<>(notPlatformSurfaceableSchemas));
|
||||
}
|
||||
|
||||
@@ -226,25 +222,18 @@ public class VisibilityStore {
|
||||
schemaToPackageIdentifierMap.put(prefixedSchema, packageIdentifiers);
|
||||
}
|
||||
}
|
||||
mPackageAccessibleMap.setPackageAccessible(prefix, schemaToPackageIdentifierMap);
|
||||
} catch (AppSearchException e) {
|
||||
if (e.getResultCode() == AppSearchResult.RESULT_NOT_FOUND) {
|
||||
// TODO(b/172068212): This indicates some desync error. We were expecting a
|
||||
// document, but didn't find one. Should probably reset AppSearch instead of
|
||||
// ignoring it.
|
||||
continue;
|
||||
}
|
||||
// Otherwise, this is some other error we should pass up.
|
||||
throw e;
|
||||
mPackageAccessibleMap.setPackageAccessible(
|
||||
packageName, databaseName, schemaToPackageIdentifierMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets visibility settings for {@code prefix}. Any previous visibility settings will be
|
||||
* Sets visibility settings for the given database. Any previous visibility settings will be
|
||||
* overwritten.
|
||||
*
|
||||
* @param prefix Prefix that identifies who owns the {@code schemasNotPlatformSurfaceable}.
|
||||
* @param packageName Package of app that owns the {@code schemasNotPlatformSurfaceable}.
|
||||
* @param databaseName Database that owns the {@code schemasNotPlatformSurfaceable}.
|
||||
* @param schemasNotPlatformSurfaceable Set of prefixed schemas that should be hidden from the
|
||||
* platform.
|
||||
* @param schemasPackageAccessible Map of prefixed schemas to a list of package identifiers that
|
||||
@@ -252,17 +241,20 @@ public class VisibilityStore {
|
||||
* @throws AppSearchException on AppSearchImpl error.
|
||||
*/
|
||||
public void setVisibility(
|
||||
@NonNull String prefix,
|
||||
@NonNull String packageName,
|
||||
@NonNull String databaseName,
|
||||
@NonNull Set<String> schemasNotPlatformSurfaceable,
|
||||
@NonNull Map<String, List<PackageIdentifier>> schemasPackageAccessible)
|
||||
throws AppSearchException {
|
||||
Objects.requireNonNull(prefix);
|
||||
Objects.requireNonNull(packageName);
|
||||
Objects.requireNonNull(databaseName);
|
||||
Objects.requireNonNull(schemasNotPlatformSurfaceable);
|
||||
Objects.requireNonNull(schemasPackageAccessible);
|
||||
|
||||
// Persist the document
|
||||
VisibilityDocument.Builder visibilityDocument =
|
||||
new VisibilityDocument.Builder(NAMESPACE, /*id=*/ addIdPrefix(prefix));
|
||||
new VisibilityDocument.Builder(
|
||||
NAMESPACE, /*id=*/ getVisibilityDocumentId(packageName, databaseName));
|
||||
if (!schemasNotPlatformSurfaceable.isEmpty()) {
|
||||
visibilityDocument.setSchemasNotPlatformSurfaceable(
|
||||
schemasNotPlatformSurfaceable.toArray(new String[0]));
|
||||
@@ -293,29 +285,50 @@ public class VisibilityStore {
|
||||
mAppSearchImpl.persistToDisk(PersistType.Code.LITE);
|
||||
|
||||
// Update derived data structures.
|
||||
mNotPlatformSurfaceableMap.setNotPlatformSurfaceable(prefix, schemasNotPlatformSurfaceable);
|
||||
mPackageAccessibleMap.setPackageAccessible(prefix, schemaToPackageIdentifierMap);
|
||||
mNotPlatformSurfaceableMap.setNotPlatformSurfaceable(
|
||||
packageName, databaseName, schemasNotPlatformSurfaceable);
|
||||
mPackageAccessibleMap.setPackageAccessible(
|
||||
packageName, databaseName, schemaToPackageIdentifierMap);
|
||||
}
|
||||
|
||||
/** Checks whether {@code prefixedSchema} can be searched over by the {@code callerUid}. */
|
||||
/**
|
||||
* Checks whether {@code prefixedSchema} can be searched over by the {@code callerUid}.
|
||||
*
|
||||
* @param packageName Package that owns the schema.
|
||||
* @param databaseName Database within the package that owns the schema.
|
||||
* @param prefixedSchema Prefixed schema type the caller is trying to access.
|
||||
* @param callerPackageName Package name of the caller.
|
||||
* @param callerUid Uid of the caller.
|
||||
*/
|
||||
public boolean isSchemaSearchableByCaller(
|
||||
@NonNull String prefix, @NonNull String prefixedSchema, int callerUid) {
|
||||
Objects.requireNonNull(prefix);
|
||||
@NonNull String packageName,
|
||||
@NonNull String databaseName,
|
||||
@NonNull String prefixedSchema,
|
||||
@NonNull String callerPackageName,
|
||||
int callerUid) {
|
||||
Objects.requireNonNull(packageName);
|
||||
Objects.requireNonNull(databaseName);
|
||||
Objects.requireNonNull(prefixedSchema);
|
||||
Objects.requireNonNull(callerPackageName);
|
||||
|
||||
if (prefix.equals(VISIBILITY_STORE_PREFIX)) {
|
||||
if (packageName.equals(PACKAGE_NAME)) {
|
||||
return false; // VisibilityStore schemas are for internal bookkeeping.
|
||||
}
|
||||
|
||||
// We compare appIds here rather than direct uids because the package's uid may change based
|
||||
// on the user that's running.
|
||||
if (UserHandle.isSameApp(mGlobalQuerierUid, callerUid)
|
||||
&& mNotPlatformSurfaceableMap.isSchemaPlatformSurfaceable(prefix, prefixedSchema)) {
|
||||
// TODO(b/180058203): If we can cache or pass in that a caller has the
|
||||
// READ_GLOBAL_SEARCH_DATA permission, then we can save this package manager lookup for
|
||||
// each schema we may check in the loop.
|
||||
if (mNotPlatformSurfaceableMap.isSchemaPlatformSurfaceable(
|
||||
packageName, databaseName, prefixedSchema)
|
||||
&& mUserContext
|
||||
.getPackageManager()
|
||||
.checkPermission(READ_GLOBAL_APP_SEARCH_DATA, callerPackageName)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// May not be platform surfaceable, but might still be accessible through 3p access.
|
||||
return isSchemaPackageAccessible(prefix, prefixedSchema, callerUid);
|
||||
return isSchemaPackageAccessible(packageName, databaseName, prefixedSchema, callerUid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -328,20 +341,32 @@ public class VisibilityStore {
|
||||
* does not handle packages that have been signed by multiple certificates.
|
||||
*/
|
||||
private boolean isSchemaPackageAccessible(
|
||||
@NonNull String prefix, @NonNull String prefixedSchema, int callerUid) {
|
||||
@NonNull String packageName,
|
||||
@NonNull String databaseName,
|
||||
@NonNull String prefixedSchema,
|
||||
int callerUid) {
|
||||
Set<PackageIdentifier> packageIdentifiers =
|
||||
mPackageAccessibleMap.getAccessiblePackages(prefix, prefixedSchema);
|
||||
mPackageAccessibleMap.getAccessiblePackages(
|
||||
packageName, databaseName, prefixedSchema);
|
||||
for (PackageIdentifier packageIdentifier : packageIdentifiers) {
|
||||
// Check that the caller uid matches this allowlisted PackageIdentifier.
|
||||
// TODO(b/169883602): Consider caching the UIDs of packages. Looking this up in the
|
||||
// package manager could be costly. We would also need to update the cache on
|
||||
// package-removals.
|
||||
if (getPackageUidAsUser(packageIdentifier.getPackageName()) != callerUid) {
|
||||
|
||||
// 'callerUid' is the uid of the caller. The 'user' doesn't have to be the same one as
|
||||
// the callerUid since clients can createContextAsUser with some other user, and then
|
||||
// make calls to us. So just check if the appId portion of the uid is the same. This is
|
||||
// essentially UserHandle.isSameApp, but that's not a system API for us to use.
|
||||
int callerAppId = UserHandle.getAppId((callerUid));
|
||||
int userAppId =
|
||||
UserHandle.getAppId(getPackageUidAsUser(packageIdentifier.getPackageName()));
|
||||
if (callerAppId != userAppId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check that the package also has the matching certificate
|
||||
if (mContext.getPackageManager()
|
||||
if (mUserContext
|
||||
.getPackageManager()
|
||||
.hasSigningCertificate(
|
||||
packageIdentifier.getPackageName(),
|
||||
packageIdentifier.getSha256Certificate(),
|
||||
@@ -367,36 +392,14 @@ public class VisibilityStore {
|
||||
/**
|
||||
* Adds a prefix to create a visibility store document's id.
|
||||
*
|
||||
* @param id Non-prefixed id
|
||||
* @param packageName Package to which the visibility doc refers
|
||||
* @param databaseName Database to which the visibility doc refers
|
||||
* @return Prefixed id
|
||||
*/
|
||||
private static String addIdPrefix(String id) {
|
||||
return ID_PREFIX + id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the uid of the {@code globalQuerierPackage}. {@code globalQuerierPackage} must be a
|
||||
* pre-installed, system app. Returns {@link Process#INVALID_UID} if unable to find the UID.
|
||||
*/
|
||||
private int getGlobalQuerierUid(@NonNull String globalQuerierPackage) {
|
||||
try {
|
||||
int flags =
|
||||
PackageManager.MATCH_DISABLED_COMPONENTS
|
||||
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
|
||||
| PackageManager.MATCH_SYSTEM_ONLY;
|
||||
// It doesn't matter that we're using the caller's userId here. We'll eventually check
|
||||
// that the two uids in question belong to the same appId.
|
||||
return mContext.getPackageManager()
|
||||
.getPackageUidAsUser(globalQuerierPackage, flags, mUserId);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// Global querier doesn't exist.
|
||||
Log.i(
|
||||
TAG,
|
||||
"AppSearch global querier package not found on device: '"
|
||||
+ globalQuerierPackage
|
||||
+ "'");
|
||||
}
|
||||
return Process.INVALID_UID;
|
||||
@NonNull
|
||||
private static String getVisibilityDocumentId(
|
||||
@NonNull String packageName, @NonNull String databaseName) {
|
||||
return ID_PREFIX + PrefixUtil.createPrefix(packageName, databaseName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -405,7 +408,7 @@ public class VisibilityStore {
|
||||
*/
|
||||
private int getPackageUidAsUser(@NonNull String packageName) {
|
||||
try {
|
||||
return mContext.getPackageManager().getPackageUidAsUser(packageName, mUserId);
|
||||
return mUserContext.getPackageManager().getPackageUidAsUser(packageName, mUserId);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// Package doesn't exist, continue
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
be6d5138cbd64d3fd401a83d30bf9ad22a6c2d17
|
||||
c35ced970a63a6c7b1d17f9706160579540850d6
|
||||
|
||||
@@ -19,19 +19,21 @@
|
||||
// global query integration tests that can test AppSearchImpl-VisibilityStore integration logic.
|
||||
package com.android.server.appsearch.external.localstorage;
|
||||
|
||||
import static android.Manifest.permission.READ_GLOBAL_APP_SEARCH_DATA;
|
||||
import static android.content.pm.PackageManager.PERMISSION_DENIED;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.app.appsearch.AppSearchSchema;
|
||||
import android.app.appsearch.GenericDocument;
|
||||
import android.app.appsearch.PackageIdentifier;
|
||||
import android.app.appsearch.SearchResultPage;
|
||||
import android.app.appsearch.SearchSpec;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.compatibility.common.util.SystemUtil;
|
||||
import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -43,7 +45,6 @@ import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** This tests AppSearchImpl when it's running with a platform-backed VisibilityStore. */
|
||||
public class AppSearchImplPlatformTest {
|
||||
@@ -70,126 +71,11 @@ public class AppSearchImplPlatformTest {
|
||||
mTemporaryFolder.newFolder(),
|
||||
mContext,
|
||||
mContext.getUserId(),
|
||||
mContext.getPackageName(),
|
||||
/*logger=*/ null);
|
||||
|
||||
mGlobalQuerierUid =
|
||||
mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0);
|
||||
}
|
||||
/**
|
||||
* TODO(b/169883602): This should be an integration test at the cts-level. This is a short-term
|
||||
* test until we have official support for multiple-apps indexing at once.
|
||||
*/
|
||||
@Test
|
||||
public void testGlobalQueryWithMultiplePackages_noPackageFilters() throws Exception {
|
||||
// Insert package1 schema
|
||||
List<AppSearchSchema> schema1 =
|
||||
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
|
||||
mAppSearchImpl.setSchema(
|
||||
"package1",
|
||||
"database1",
|
||||
schema1,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false,
|
||||
/*schemaVersion=*/ 0);
|
||||
|
||||
// Insert package2 schema
|
||||
List<AppSearchSchema> schema2 =
|
||||
ImmutableList.of(new AppSearchSchema.Builder("schema2").build());
|
||||
mAppSearchImpl.setSchema(
|
||||
"package2",
|
||||
"database2",
|
||||
schema2,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false,
|
||||
/*schemaVersion=*/ 0);
|
||||
|
||||
// Insert package1 document
|
||||
GenericDocument document1 =
|
||||
new GenericDocument.Builder<>("namespace", "uri", "schema1").build();
|
||||
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
|
||||
|
||||
// Insert package2 document
|
||||
GenericDocument document2 =
|
||||
new GenericDocument.Builder<>("namespace", "uri", "schema2").build();
|
||||
mAppSearchImpl.putDocument("package2", "database2", document2, /*logger=*/ null);
|
||||
|
||||
// No query filters specified, global query can retrieve all documents.
|
||||
SearchSpec searchSpec =
|
||||
new SearchSpec.Builder().setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY).build();
|
||||
SearchResultPage searchResultPage = mAppSearchImpl.globalQuery(
|
||||
"", searchSpec, mContext.getPackageName(), mGlobalQuerierUid, /*logger=*/ null);
|
||||
assertThat(searchResultPage.getResults()).hasSize(2);
|
||||
|
||||
// Document2 will be first since it got indexed later and has a "better", aka more recent
|
||||
// score.
|
||||
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
|
||||
assertThat(searchResultPage.getResults().get(1).getGenericDocument()).isEqualTo(document1);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO(b/169883602): This should be an integration test at the cts-level. This is a short-term
|
||||
* test until we have official support for multiple-apps indexing at once.
|
||||
*/
|
||||
@Test
|
||||
public void testGlobalQueryWithMultiplePackages_withPackageFilters() throws Exception {
|
||||
// Insert package1 schema
|
||||
List<AppSearchSchema> schema1 =
|
||||
ImmutableList.of(new AppSearchSchema.Builder("schema1").build());
|
||||
mAppSearchImpl.setSchema(
|
||||
"package1",
|
||||
"database1",
|
||||
schema1,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false,
|
||||
/*schemaVersion=*/ 0);
|
||||
|
||||
// Insert package2 schema
|
||||
List<AppSearchSchema> schema2 =
|
||||
ImmutableList.of(new AppSearchSchema.Builder("schema2").build());
|
||||
mAppSearchImpl.setSchema(
|
||||
"package2",
|
||||
"database2",
|
||||
schema2,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false,
|
||||
/*schemaVersion=*/ 0);
|
||||
|
||||
// Insert package1 document
|
||||
GenericDocument document1 =
|
||||
new GenericDocument.Builder<>("namespace", "uri", "schema1").build();
|
||||
mAppSearchImpl.putDocument("package1", "database1", document1, /*logger=*/ null);
|
||||
|
||||
// Insert package2 document
|
||||
GenericDocument document2 =
|
||||
new GenericDocument.Builder<>("namespace", "uri", "schema2").build();
|
||||
mAppSearchImpl.putDocument("package2", "database2", document2, /*logger=*/ null);
|
||||
|
||||
// "package1" filter specified
|
||||
SearchSpec searchSpec =
|
||||
new SearchSpec.Builder()
|
||||
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
|
||||
.addFilterPackageNames("package1")
|
||||
.build();
|
||||
SearchResultPage searchResultPage = mAppSearchImpl.globalQuery(
|
||||
"", searchSpec, mContext.getPackageName(), mGlobalQuerierUid, /*logger=*/ null);
|
||||
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1);
|
||||
|
||||
// "package2" filter specified
|
||||
searchSpec =
|
||||
new SearchSpec.Builder()
|
||||
.setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
|
||||
.addFilterPackageNames("package2")
|
||||
.build();
|
||||
searchResultPage = mAppSearchImpl.globalQuery(
|
||||
"", searchSpec, mContext.getPackageName(), mGlobalQuerierUid, /*logger=*/ null);
|
||||
assertThat(searchResultPage.getResults()).hasSize(1);
|
||||
assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSchema_existingSchemaRetainsVisibilitySetting() throws Exception {
|
||||
@@ -202,6 +88,12 @@ public class AppSearchImplPlatformTest {
|
||||
mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
|
||||
|
||||
// Make sure we have global query privileges and "foo" doesn't
|
||||
mMockPackageManager.mockCheckPermission(
|
||||
READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName(), PERMISSION_GRANTED);
|
||||
mMockPackageManager.mockCheckPermission(
|
||||
READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo, PERMISSION_DENIED);
|
||||
|
||||
// Set schema1
|
||||
String prefix = PrefixUtil.createPrefix("package", "database");
|
||||
mAppSearchImpl.setSchema(
|
||||
@@ -220,12 +112,22 @@ public class AppSearchImplPlatformTest {
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "schema1", mGlobalQuerierUid))
|
||||
"package",
|
||||
"database",
|
||||
prefix + "schema1",
|
||||
mContext.getPackageName(),
|
||||
mGlobalQuerierUid))
|
||||
.isFalse();
|
||||
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo))
|
||||
.isSchemaSearchableByCaller(
|
||||
"package",
|
||||
"database",
|
||||
prefix + "schema1",
|
||||
packageNameFoo,
|
||||
uidFoo))
|
||||
.isTrue();
|
||||
|
||||
// Add a new schema, and include the already-existing "schema1"
|
||||
@@ -243,29 +145,57 @@ public class AppSearchImplPlatformTest {
|
||||
/*schemaVersion=*/ 0);
|
||||
|
||||
// Check that "schema1" still has the same visibility settings
|
||||
SystemUtil.runWithShellPermissionIdentity(
|
||||
() -> {
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
"package",
|
||||
"database",
|
||||
prefix + "schema1",
|
||||
mContext.getPackageName(),
|
||||
mGlobalQuerierUid))
|
||||
.isFalse();
|
||||
},
|
||||
READ_GLOBAL_APP_SEARCH_DATA);
|
||||
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "schema1", mGlobalQuerierUid))
|
||||
.isFalse();
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo))
|
||||
"package",
|
||||
"database",
|
||||
prefix + "schema1",
|
||||
packageNameFoo,
|
||||
uidFoo))
|
||||
.isTrue();
|
||||
|
||||
// "schema2" has default visibility settings
|
||||
SystemUtil.runWithShellPermissionIdentity(
|
||||
() -> {
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
"package",
|
||||
"database",
|
||||
prefix + "schema2",
|
||||
mContext.getPackageName(),
|
||||
mGlobalQuerierUid))
|
||||
.isTrue();
|
||||
},
|
||||
READ_GLOBAL_APP_SEARCH_DATA);
|
||||
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "schema2", mGlobalQuerierUid))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(prefix, prefix + "schema2", uidFoo))
|
||||
"package",
|
||||
"database",
|
||||
prefix + "schema2",
|
||||
packageNameFoo,
|
||||
uidFoo))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@@ -280,6 +210,12 @@ public class AppSearchImplPlatformTest {
|
||||
mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
|
||||
|
||||
// Make sure we have global query privileges and "foo" doesn't
|
||||
mMockPackageManager.mockCheckPermission(
|
||||
READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName(), PERMISSION_GRANTED);
|
||||
mMockPackageManager.mockCheckPermission(
|
||||
READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo, PERMISSION_DENIED);
|
||||
|
||||
String prefix = PrefixUtil.createPrefix("package", "database");
|
||||
mAppSearchImpl.setSchema(
|
||||
"package",
|
||||
@@ -297,12 +233,22 @@ public class AppSearchImplPlatformTest {
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "schema1", mGlobalQuerierUid))
|
||||
"package",
|
||||
"database",
|
||||
prefix + "schema1",
|
||||
mContext.getPackageName(),
|
||||
mGlobalQuerierUid))
|
||||
.isFalse();
|
||||
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo))
|
||||
.isSchemaSearchableByCaller(
|
||||
"package",
|
||||
"database",
|
||||
prefix + "schema1",
|
||||
packageNameFoo,
|
||||
uidFoo))
|
||||
.isTrue();
|
||||
|
||||
// Remove "schema1" by force overriding
|
||||
@@ -320,12 +266,22 @@ public class AppSearchImplPlatformTest {
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "schema1", mGlobalQuerierUid))
|
||||
"package",
|
||||
"database",
|
||||
prefix + "schema1",
|
||||
mContext.getPackageName(),
|
||||
mGlobalQuerierUid))
|
||||
.isTrue();
|
||||
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo))
|
||||
.isSchemaSearchableByCaller(
|
||||
"package",
|
||||
"database",
|
||||
prefix + "schema1",
|
||||
packageNameFoo,
|
||||
uidFoo))
|
||||
.isFalse();
|
||||
|
||||
// Add "schema1" back, it gets default visibility settings which means it's not platform
|
||||
@@ -338,21 +294,35 @@ public class AppSearchImplPlatformTest {
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false,
|
||||
/*schemaVersion=*/ 0);
|
||||
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "schema1", mGlobalQuerierUid))
|
||||
"package",
|
||||
"database",
|
||||
prefix + "schema1",
|
||||
mContext.getPackageName(),
|
||||
mGlobalQuerierUid))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(prefix, prefix + "schema1", uidFoo))
|
||||
.isSchemaSearchableByCaller(
|
||||
"package",
|
||||
"database",
|
||||
prefix + "schema1",
|
||||
packageNameFoo,
|
||||
uidFoo))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSchema_defaultPlatformVisible() throws Exception {
|
||||
// Make sure we have global query privileges
|
||||
mMockPackageManager.mockCheckPermission(
|
||||
READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName(), PERMISSION_GRANTED);
|
||||
|
||||
String prefix = PrefixUtil.createPrefix("package", "database");
|
||||
mAppSearchImpl.setSchema(
|
||||
"package",
|
||||
@@ -362,16 +332,25 @@ public class AppSearchImplPlatformTest {
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false,
|
||||
/*schemaVersion=*/ 0);
|
||||
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "Schema", mGlobalQuerierUid))
|
||||
"package",
|
||||
"database",
|
||||
prefix + "Schema",
|
||||
mContext.getPackageName(),
|
||||
mGlobalQuerierUid))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSchema_platformHidden() throws Exception {
|
||||
// Make sure we have global query privileges
|
||||
mMockPackageManager.mockCheckPermission(
|
||||
READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName(), PERMISSION_GRANTED);
|
||||
|
||||
String prefix = PrefixUtil.createPrefix("package", "database");
|
||||
mAppSearchImpl.setSchema(
|
||||
"package",
|
||||
@@ -381,16 +360,27 @@ public class AppSearchImplPlatformTest {
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false,
|
||||
/*schemaVersion=*/ 0);
|
||||
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "Schema", mGlobalQuerierUid))
|
||||
"package",
|
||||
"database",
|
||||
prefix + "Schema",
|
||||
mContext.getPackageName(),
|
||||
mGlobalQuerierUid))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSchema_defaultNotPackageAccessible() throws Exception {
|
||||
String packageName = "com.package";
|
||||
|
||||
// Make sure package doesn't global query privileges
|
||||
mMockPackageManager.mockCheckPermission(
|
||||
READ_GLOBAL_APP_SEARCH_DATA, packageName, PERMISSION_DENIED);
|
||||
|
||||
String prefix = PrefixUtil.createPrefix("package", "database");
|
||||
mAppSearchImpl.setSchema(
|
||||
"package",
|
||||
@@ -404,7 +394,11 @@ public class AppSearchImplPlatformTest {
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(
|
||||
prefix, prefix + "Schema", /*callerUid=*/ 42))
|
||||
"package",
|
||||
"database",
|
||||
prefix + "Schema",
|
||||
packageName,
|
||||
/*callerUid=*/ 42))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@@ -419,6 +413,10 @@ public class AppSearchImplPlatformTest {
|
||||
mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
|
||||
|
||||
// Make sure foo doesn't have global query privileges
|
||||
mMockPackageManager.mockCheckPermission(
|
||||
READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo, PERMISSION_DENIED);
|
||||
|
||||
String prefix = PrefixUtil.createPrefix("package", "database");
|
||||
mAppSearchImpl.setSchema(
|
||||
"package",
|
||||
@@ -433,7 +431,12 @@ public class AppSearchImplPlatformTest {
|
||||
assertThat(
|
||||
mAppSearchImpl
|
||||
.getVisibilityStoreLocked()
|
||||
.isSchemaSearchableByCaller(prefix, prefix + "Schema", uidFoo))
|
||||
.isSchemaSearchableByCaller(
|
||||
"package",
|
||||
"database",
|
||||
prefix + "Schema",
|
||||
packageNameFoo,
|
||||
uidFoo))
|
||||
.isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,12 @@ public class MockPackageManager {
|
||||
return mMockPackageManager;
|
||||
}
|
||||
|
||||
/** Mock a checkPermission call. */
|
||||
public void mockCheckPermission(String permission, String packageName, int permissionResult) {
|
||||
when(mMockPackageManager.checkPermission(permission, packageName))
|
||||
.thenReturn(permissionResult);
|
||||
}
|
||||
|
||||
/** Mock a NameNotFoundException if the package name isn't installed. */
|
||||
public void mockThrowsNameNotFoundException(String packageName) {
|
||||
try {
|
||||
|
||||
@@ -15,10 +15,14 @@
|
||||
*/
|
||||
|
||||
// TODO(b/169883602): This is purposely a different package from the path so that it can access
|
||||
// AppSearchImpl and VisibilityStore methods without having to make methods public. This should be
|
||||
// moved into a proper package once AppSearchImpl-VisibilityStore's dependencies are refactored.
|
||||
// AppSearchImpl methods without having to make methods public. This should be moved into a proper
|
||||
// package once AppSearchImpl-VisibilityStore's dependencies are refactored.
|
||||
package com.android.server.appsearch.external.localstorage;
|
||||
|
||||
import static android.Manifest.permission.READ_GLOBAL_APP_SEARCH_DATA;
|
||||
import static android.content.pm.PackageManager.PERMISSION_DENIED;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.app.appsearch.PackageIdentifier;
|
||||
@@ -29,6 +33,7 @@ import android.content.pm.PackageManager;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
|
||||
import com.android.server.appsearch.visibilitystore.VisibilityStore;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -48,7 +53,7 @@ public class VisibilityStoreTest {
|
||||
private Context mContext;
|
||||
private AppSearchImpl mAppSearchImpl;
|
||||
private VisibilityStore mVisibilityStore;
|
||||
private int mGlobalQuerierUid;
|
||||
private int mUid;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@@ -67,11 +72,9 @@ public class VisibilityStoreTest {
|
||||
mTemporaryFolder.newFolder(),
|
||||
mContext,
|
||||
mContext.getUserId(),
|
||||
/*globalQuerierPackage=*/ mContext.getPackageName(),
|
||||
/*logger=*/ null);
|
||||
mGlobalQuerierUid =
|
||||
mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0);
|
||||
|
||||
mUid = mContext.getPackageManager().getPackageUid(mContext.getPackageName(), /*flags=*/ 0);
|
||||
mVisibilityStore = mAppSearchImpl.getVisibilityStoreLocked();
|
||||
}
|
||||
|
||||
@@ -99,83 +102,98 @@ public class VisibilityStoreTest {
|
||||
|
||||
@Test
|
||||
public void testSetVisibility_platformSurfaceable() throws Exception {
|
||||
// Make sure we have global query privileges
|
||||
mMockPackageManager.mockCheckPermission(
|
||||
READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName(), PERMISSION_GRANTED);
|
||||
|
||||
mVisibilityStore.setVisibility(
|
||||
"prefix",
|
||||
"package",
|
||||
"database",
|
||||
/*schemasNotPlatformSurfaceable=*/ ImmutableSet.of(
|
||||
"prefix/schema1", "prefix/schema2"),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap());
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schema1", mGlobalQuerierUid))
|
||||
"package",
|
||||
"database",
|
||||
"prefix/schema1",
|
||||
mContext.getPackageName(),
|
||||
mUid))
|
||||
.isFalse();
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schema2", mGlobalQuerierUid))
|
||||
"package",
|
||||
"database",
|
||||
"prefix/schema2",
|
||||
mContext.getPackageName(),
|
||||
mUid))
|
||||
.isFalse();
|
||||
|
||||
// New .setVisibility() call completely overrides previous visibility settings. So
|
||||
// "schema2" isn't preserved.
|
||||
// New .setVisibility() call completely overrides previous visibility settings.
|
||||
// So "schema2" isn't preserved.
|
||||
mVisibilityStore.setVisibility(
|
||||
"prefix",
|
||||
"package",
|
||||
"database",
|
||||
/*schemasNotPlatformSurfaceable=*/ ImmutableSet.of(
|
||||
"prefix/schema1", "prefix/schema3"),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap());
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schema1", mGlobalQuerierUid))
|
||||
"package",
|
||||
"database",
|
||||
"prefix/schema1",
|
||||
mContext.getPackageName(),
|
||||
mUid))
|
||||
.isFalse();
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schema2", mGlobalQuerierUid))
|
||||
"package",
|
||||
"database",
|
||||
"prefix/schema2",
|
||||
mContext.getPackageName(),
|
||||
mUid))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schema3", mGlobalQuerierUid))
|
||||
"package",
|
||||
"database",
|
||||
"prefix/schema3",
|
||||
mContext.getPackageName(),
|
||||
mUid))
|
||||
.isFalse();
|
||||
|
||||
// Everything defaults to visible again.
|
||||
mVisibilityStore.setVisibility(
|
||||
"prefix",
|
||||
"package",
|
||||
"database",
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap());
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schema1", mGlobalQuerierUid))
|
||||
"package",
|
||||
"database",
|
||||
"prefix/schema1",
|
||||
mContext.getPackageName(),
|
||||
mUid))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schema2", mGlobalQuerierUid))
|
||||
"package",
|
||||
"database",
|
||||
"prefix/schema2",
|
||||
mContext.getPackageName(),
|
||||
mUid))
|
||||
.isTrue();
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schema3", mGlobalQuerierUid))
|
||||
"package",
|
||||
"database",
|
||||
"prefix/schema3",
|
||||
mContext.getPackageName(),
|
||||
mUid))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsSchemaSearchableByCaller_platformQuerierHandlesNameNotFoundException()
|
||||
throws Exception {
|
||||
// Initialized the VisibilityStore with this context's package name as the global querier.
|
||||
mMockPackageManager.mockThrowsNameNotFoundException(mContext.getPackageName());
|
||||
|
||||
// Create a new VisibilityStore instance since we look up the UID on initialization
|
||||
AppSearchImpl appSearchImpl =
|
||||
AppSearchImpl.create(
|
||||
mTemporaryFolder.newFolder(),
|
||||
mContext,
|
||||
mContext.getUserId(),
|
||||
/*globalQuerierPackage=*/ mContext.getPackageName(),
|
||||
/*logger=*/ null);
|
||||
VisibilityStore visibilityStore = appSearchImpl.getVisibilityStoreLocked();
|
||||
|
||||
// Use some arbitrary callerUid. If we can't find the global querier's uid though,
|
||||
// nothing should be platform surfaceable.
|
||||
assertThat(
|
||||
visibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaFoo", /*callerUid=*/ 0))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetVisibility_packageAccessible() throws Exception {
|
||||
// Values for a "foo" client
|
||||
@@ -191,19 +209,26 @@ public class VisibilityStoreTest {
|
||||
// Can't be the same value as uidFoo nor uidBar
|
||||
int uidNotFooOrBar = 3;
|
||||
|
||||
// Make sure none of them have global query privileges
|
||||
mMockPackageManager.mockCheckPermission(
|
||||
READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo, PERMISSION_DENIED);
|
||||
mMockPackageManager.mockCheckPermission(
|
||||
READ_GLOBAL_APP_SEARCH_DATA, packageNameBar, PERMISSION_DENIED);
|
||||
|
||||
// By default, a schema isn't package accessible.
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaFoo", uidFoo))
|
||||
"package", "database", "prefix/schemaFoo", packageNameFoo, uidFoo))
|
||||
.isFalse();
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaBar", uidBar))
|
||||
"package", "database", "prefix/schemaBar", packageNameBar, uidBar))
|
||||
.isFalse();
|
||||
|
||||
// Grant package access
|
||||
mVisibilityStore.setVisibility(
|
||||
"prefix",
|
||||
"package",
|
||||
"database",
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
|
||||
/*schemasPackageAccessible=*/ ImmutableMap.of(
|
||||
"prefix/schemaFoo",
|
||||
@@ -216,7 +241,7 @@ public class VisibilityStoreTest {
|
||||
mMockPackageManager.mockRemoveSigningCertificate(packageNameFoo, sha256CertFoo);
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaFoo", uidFoo))
|
||||
"package", "database", "prefix/schemaFoo", packageNameFoo, uidFoo))
|
||||
.isFalse();
|
||||
|
||||
// Should fail if PackageManager doesn't think the package belongs to the uid
|
||||
@@ -225,7 +250,7 @@ public class VisibilityStoreTest {
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaFoo", uidFoo))
|
||||
"package", "database", "prefix/schemaFoo", packageNameFoo, uidFoo))
|
||||
.isFalse();
|
||||
|
||||
// But if uid and certificate match, then we should have access
|
||||
@@ -233,20 +258,21 @@ public class VisibilityStoreTest {
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaFoo", uidFoo))
|
||||
"package", "database", "prefix/schemaFoo", packageNameFoo, uidFoo))
|
||||
.isTrue();
|
||||
|
||||
mMockPackageManager.mockGetPackageUidAsUser(packageNameBar, mContext.getUserId(), uidBar);
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameBar, sha256CertBar);
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaBar", uidBar))
|
||||
"package", "database", "prefix/schemaBar", packageNameBar, uidBar))
|
||||
.isTrue();
|
||||
|
||||
// New .setVisibility() call completely overrides previous visibility settings. So
|
||||
// "schemaBar" settings aren't preserved.
|
||||
mVisibilityStore.setVisibility(
|
||||
"prefix",
|
||||
"package",
|
||||
"database",
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
|
||||
/*schemasPackageAccessible=*/ ImmutableMap.of(
|
||||
"prefix/schemaFoo",
|
||||
@@ -256,14 +282,14 @@ public class VisibilityStoreTest {
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaFoo", uidFoo))
|
||||
"package", "database", "prefix/schemaFoo", packageNameFoo, uidFoo))
|
||||
.isTrue();
|
||||
|
||||
mMockPackageManager.mockGetPackageUidAsUser(packageNameBar, mContext.getUserId(), uidBar);
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameBar, sha256CertBar);
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaBar", uidBar))
|
||||
"package", "database", "prefix/schemaBar", packageNameBar, uidBar))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@@ -278,9 +304,14 @@ public class VisibilityStoreTest {
|
||||
// Pretend we can't find the Foo package.
|
||||
mMockPackageManager.mockThrowsNameNotFoundException(packageNameFoo);
|
||||
|
||||
// Make sure "foo" doesn't have global query privileges
|
||||
mMockPackageManager.mockCheckPermission(
|
||||
READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo, PERMISSION_DENIED);
|
||||
|
||||
// Grant package access
|
||||
mVisibilityStore.setVisibility(
|
||||
"prefix",
|
||||
"package",
|
||||
"database",
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
|
||||
/*schemasPackageAccessible=*/ ImmutableMap.of(
|
||||
"prefix/schemaFoo",
|
||||
@@ -289,7 +320,7 @@ public class VisibilityStoreTest {
|
||||
// If we can't verify the Foo package that has access, assume it doesn't have access.
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
"prefix", "prefix/schemaFoo", uidFoo))
|
||||
"package", "database", "prefix/schemaFoo", packageNameFoo, uidFoo))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@@ -300,8 +331,15 @@ public class VisibilityStoreTest {
|
||||
byte[] sha256CertFoo = new byte[] {10};
|
||||
int uidFoo = 1;
|
||||
|
||||
// Set it up such that the test package has global query privileges, but "foo" doesn't.
|
||||
mMockPackageManager.mockCheckPermission(
|
||||
READ_GLOBAL_APP_SEARCH_DATA, mContext.getPackageName(), PERMISSION_GRANTED);
|
||||
mMockPackageManager.mockCheckPermission(
|
||||
READ_GLOBAL_APP_SEARCH_DATA, packageNameFoo, PERMISSION_DENIED);
|
||||
|
||||
mVisibilityStore.setVisibility(
|
||||
/*prefix=*/ "",
|
||||
/*packageName=*/ "",
|
||||
/*databaseName=*/ "",
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptySet(),
|
||||
/*schemasPackageAccessible=*/ ImmutableMap.of(
|
||||
"schema",
|
||||
@@ -309,12 +347,22 @@ public class VisibilityStoreTest {
|
||||
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
/*prefix=*/ "", "schema", mGlobalQuerierUid))
|
||||
/*packageName=*/ "",
|
||||
/*databaseName=*/ "",
|
||||
"schema",
|
||||
mContext.getPackageName(),
|
||||
mUid))
|
||||
.isTrue();
|
||||
|
||||
mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
|
||||
mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
|
||||
assertThat(mVisibilityStore.isSchemaSearchableByCaller(/*prefix=*/ "", "schema", uidFoo))
|
||||
assertThat(
|
||||
mVisibilityStore.isSchemaSearchableByCaller(
|
||||
/*packageName=*/ "",
|
||||
/*databaseName=*/ "",
|
||||
"schema",
|
||||
packageNameFoo,
|
||||
uidFoo))
|
||||
.isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ import com.android.server.appsearch.proto.SearchSpecProto;
|
||||
import com.android.server.appsearch.proto.StatusProto;
|
||||
import com.android.server.appsearch.proto.StringIndexingConfig;
|
||||
import com.android.server.appsearch.proto.TermMatchType;
|
||||
import com.android.server.appsearch.visibilitystore.VisibilityStore;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
@@ -86,7 +87,6 @@ public class AppSearchImplTest {
|
||||
mTemporaryFolder.newFolder(),
|
||||
context,
|
||||
VisibilityStore.NO_OP_USER_ID,
|
||||
/*globalQuerierPackage=*/ context.getPackageName(),
|
||||
/*logger=*/ null);
|
||||
}
|
||||
|
||||
@@ -496,11 +496,7 @@ public class AppSearchImplTest {
|
||||
File appsearchDir = mTemporaryFolder.newFolder();
|
||||
AppSearchImpl appSearchImpl =
|
||||
AppSearchImpl.create(
|
||||
appsearchDir,
|
||||
context,
|
||||
VisibilityStore.NO_OP_USER_ID,
|
||||
/*globalQuerierPackage=*/ "",
|
||||
/*logger=*/ null);
|
||||
appsearchDir, context, VisibilityStore.NO_OP_USER_ID, /*logger=*/ null);
|
||||
|
||||
// Insert schema
|
||||
List<AppSearchSchema> schemas =
|
||||
@@ -563,11 +559,7 @@ public class AppSearchImplTest {
|
||||
appSearchImpl.close();
|
||||
appSearchImpl =
|
||||
AppSearchImpl.create(
|
||||
appsearchDir,
|
||||
context,
|
||||
VisibilityStore.NO_OP_USER_ID,
|
||||
/*globalQuerierPackage=*/ context.getPackageName(),
|
||||
testLogger);
|
||||
appsearchDir, context, VisibilityStore.NO_OP_USER_ID, testLogger);
|
||||
|
||||
// Check recovery state
|
||||
InitializeStats initStats = testLogger.mInitializeStats;
|
||||
@@ -1163,6 +1155,7 @@ public class AppSearchImplTest {
|
||||
public void testClearPackageData() throws AppSearchException {
|
||||
List<SchemaTypeConfigProto> existingSchemas =
|
||||
mAppSearchImpl.getSchemaProtoLocked().getTypesList();
|
||||
Map<String, Set<String>> existingDatabases = mAppSearchImpl.getPackageToDatabases();
|
||||
|
||||
// Insert package schema
|
||||
List<AppSearchSchema> schema =
|
||||
@@ -1210,6 +1203,67 @@ public class AppSearchImplTest {
|
||||
// Verify the schema is cleared.
|
||||
assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
|
||||
.containsExactlyElementsIn(existingSchemas);
|
||||
assertThat(mAppSearchImpl.getPackageToDatabases())
|
||||
.containsExactlyEntriesIn(existingDatabases);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrunePackageData() throws AppSearchException {
|
||||
List<SchemaTypeConfigProto> existingSchemas =
|
||||
mAppSearchImpl.getSchemaProtoLocked().getTypesList();
|
||||
Map<String, Set<String>> existingDatabases = mAppSearchImpl.getPackageToDatabases();
|
||||
|
||||
Set<String> existingPackages = new ArraySet<>(existingSchemas.size());
|
||||
for (int i = 0; i < existingSchemas.size(); i++) {
|
||||
existingPackages.add(PrefixUtil.getPackageName(existingSchemas.get(i).getSchemaType()));
|
||||
}
|
||||
|
||||
// Insert schema for package A and B.
|
||||
List<AppSearchSchema> schema =
|
||||
ImmutableList.of(new AppSearchSchema.Builder("schema").build());
|
||||
mAppSearchImpl.setSchema(
|
||||
"packageA",
|
||||
"database",
|
||||
schema,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false,
|
||||
/*version=*/ 0);
|
||||
mAppSearchImpl.setSchema(
|
||||
"packageB",
|
||||
"database",
|
||||
schema,
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false,
|
||||
/*version=*/ 0);
|
||||
|
||||
// Verify these two packages is stored in AppSearch
|
||||
SchemaProto expectedProto =
|
||||
SchemaProto.newBuilder()
|
||||
.addTypes(
|
||||
SchemaTypeConfigProto.newBuilder()
|
||||
.setSchemaType("packageA$database/schema")
|
||||
.setVersion(0))
|
||||
.addTypes(
|
||||
SchemaTypeConfigProto.newBuilder()
|
||||
.setSchemaType("packageB$database/schema")
|
||||
.setVersion(0))
|
||||
.build();
|
||||
List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>();
|
||||
expectedTypes.addAll(existingSchemas);
|
||||
expectedTypes.addAll(expectedProto.getTypesList());
|
||||
assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
|
||||
.containsExactlyElementsIn(expectedTypes);
|
||||
|
||||
// Prune packages
|
||||
mAppSearchImpl.prunePackageData(existingPackages);
|
||||
|
||||
// Verify the schema is same as beginning.
|
||||
assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList())
|
||||
.containsExactlyElementsIn(existingSchemas);
|
||||
assertThat(mAppSearchImpl.getPackageToDatabases())
|
||||
.containsExactlyEntriesIn(existingDatabases);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1258,36 +1312,6 @@ public class AppSearchImplTest {
|
||||
.containsExactlyEntriesIn(expectedMapping);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPrefixes() throws Exception {
|
||||
Set<String> existingPrefixes = mAppSearchImpl.getPrefixesLocked();
|
||||
|
||||
// Has database1
|
||||
Set<String> expectedPrefixes = new ArraySet<>(existingPrefixes);
|
||||
expectedPrefixes.add(createPrefix("package", "database1"));
|
||||
mAppSearchImpl.setSchema(
|
||||
"package",
|
||||
"database1",
|
||||
Collections.singletonList(new AppSearchSchema.Builder("schema").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false,
|
||||
/*version=*/ 0);
|
||||
assertThat(mAppSearchImpl.getPrefixesLocked()).containsExactlyElementsIn(expectedPrefixes);
|
||||
|
||||
// Has both databases
|
||||
expectedPrefixes.add(createPrefix("package", "database2"));
|
||||
mAppSearchImpl.setSchema(
|
||||
"package",
|
||||
"database2",
|
||||
Collections.singletonList(new AppSearchSchema.Builder("schema").build()),
|
||||
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
|
||||
/*schemasPackageAccessible=*/ Collections.emptyMap(),
|
||||
/*forceOverride=*/ false,
|
||||
/*version=*/ 0);
|
||||
assertThat(mAppSearchImpl.getPrefixesLocked()).containsExactlyElementsIn(expectedPrefixes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRewriteSearchResultProto() throws Exception {
|
||||
final String prefix =
|
||||
@@ -1665,7 +1689,6 @@ public class AppSearchImplTest {
|
||||
mTemporaryFolder.newFolder(),
|
||||
context,
|
||||
VisibilityStore.NO_OP_USER_ID,
|
||||
/*globalQuerierPackage=*/ "",
|
||||
/*logger=*/ null);
|
||||
|
||||
// Initial check that we could do something at first.
|
||||
@@ -1815,11 +1838,7 @@ public class AppSearchImplTest {
|
||||
File appsearchDir = mTemporaryFolder.newFolder();
|
||||
AppSearchImpl appSearchImpl =
|
||||
AppSearchImpl.create(
|
||||
appsearchDir,
|
||||
context,
|
||||
VisibilityStore.NO_OP_USER_ID,
|
||||
/*globalQuerierPackage=*/ "",
|
||||
/*logger=*/ null);
|
||||
appsearchDir, context, VisibilityStore.NO_OP_USER_ID, /*logger=*/ null);
|
||||
|
||||
List<AppSearchSchema> schemas =
|
||||
Collections.singletonList(new AppSearchSchema.Builder("type").build());
|
||||
@@ -1846,11 +1865,7 @@ public class AppSearchImplTest {
|
||||
// That document should be visible even from another instance.
|
||||
AppSearchImpl appSearchImpl2 =
|
||||
AppSearchImpl.create(
|
||||
appsearchDir,
|
||||
context,
|
||||
VisibilityStore.NO_OP_USER_ID,
|
||||
/*globalQuerierPackage=*/ "",
|
||||
/*logger=*/ null);
|
||||
appsearchDir, context, VisibilityStore.NO_OP_USER_ID, /*logger=*/ null);
|
||||
getResult =
|
||||
appSearchImpl2.getDocument(
|
||||
"package", "database", "namespace1", "id1", Collections.emptyMap());
|
||||
@@ -1864,11 +1879,7 @@ public class AppSearchImplTest {
|
||||
File appsearchDir = mTemporaryFolder.newFolder();
|
||||
AppSearchImpl appSearchImpl =
|
||||
AppSearchImpl.create(
|
||||
appsearchDir,
|
||||
context,
|
||||
VisibilityStore.NO_OP_USER_ID,
|
||||
/*globalQuerierPackage=*/ "",
|
||||
/*logger=*/ null);
|
||||
appsearchDir, context, VisibilityStore.NO_OP_USER_ID, /*logger=*/ null);
|
||||
|
||||
List<AppSearchSchema> schemas =
|
||||
Collections.singletonList(new AppSearchSchema.Builder("type").build());
|
||||
@@ -1919,11 +1930,7 @@ public class AppSearchImplTest {
|
||||
// Only the second document should be retrievable from another instance.
|
||||
AppSearchImpl appSearchImpl2 =
|
||||
AppSearchImpl.create(
|
||||
appsearchDir,
|
||||
context,
|
||||
VisibilityStore.NO_OP_USER_ID,
|
||||
/*globalQuerierPackage=*/ "",
|
||||
/*logger=*/ null);
|
||||
appsearchDir, context, VisibilityStore.NO_OP_USER_ID, /*logger=*/ null);
|
||||
expectThrows(
|
||||
AppSearchException.class,
|
||||
() ->
|
||||
@@ -1946,11 +1953,7 @@ public class AppSearchImplTest {
|
||||
File appsearchDir = mTemporaryFolder.newFolder();
|
||||
AppSearchImpl appSearchImpl =
|
||||
AppSearchImpl.create(
|
||||
appsearchDir,
|
||||
context,
|
||||
VisibilityStore.NO_OP_USER_ID,
|
||||
/*globalQuerierPackage=*/ "",
|
||||
/*logger=*/ null);
|
||||
appsearchDir, context, VisibilityStore.NO_OP_USER_ID, /*logger=*/ null);
|
||||
|
||||
List<AppSearchSchema> schemas =
|
||||
Collections.singletonList(new AppSearchSchema.Builder("type").build());
|
||||
@@ -2009,11 +2012,7 @@ public class AppSearchImplTest {
|
||||
// Only the second document should be retrievable from another instance.
|
||||
AppSearchImpl appSearchImpl2 =
|
||||
AppSearchImpl.create(
|
||||
appsearchDir,
|
||||
context,
|
||||
VisibilityStore.NO_OP_USER_ID,
|
||||
/*globalQuerierPackage=*/ "",
|
||||
/*logger=*/ null);
|
||||
appsearchDir, context, VisibilityStore.NO_OP_USER_ID, /*logger=*/ null);
|
||||
expectThrows(
|
||||
AppSearchException.class,
|
||||
() ->
|
||||
|
||||
@@ -40,6 +40,7 @@ import com.android.server.appsearch.proto.PutDocumentStatsProto;
|
||||
import com.android.server.appsearch.proto.QueryStatsProto;
|
||||
import com.android.server.appsearch.proto.ScoringSpecProto;
|
||||
import com.android.server.appsearch.proto.TermMatchType;
|
||||
import com.android.server.appsearch.visibilitystore.VisibilityStore;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -64,7 +65,6 @@ public class AppSearchLoggerTest {
|
||||
mTemporaryFolder.newFolder(),
|
||||
context,
|
||||
VisibilityStore.NO_OP_USER_ID,
|
||||
/*globalQuerierPackage=*/ context.getPackageName(),
|
||||
/*logger=*/ null);
|
||||
mLogger = new TestLogger();
|
||||
}
|
||||
@@ -293,7 +293,6 @@ public class AppSearchLoggerTest {
|
||||
mTemporaryFolder.newFolder(),
|
||||
context,
|
||||
VisibilityStore.NO_OP_USER_ID,
|
||||
/*globalQuerierPackage=*/ context.getPackageName(),
|
||||
mLogger);
|
||||
|
||||
InitializeStats iStats = mLogger.mInitializeStats;
|
||||
|
||||
Reference in New Issue
Block a user